Compare commits

..
6 Commits
Author SHA1 Message Date
polv a26d329d46 try load to local mongo 2026-08-30 21:57:33 +07:00
polv 91623834d6 working online D1 tutorial 2026-08-30 19:15:59 +07:00
polv 1079a1a481 create d1 tutorial 2026-08-30 12:58:49 +07:00
polv fc7831b04e bundle tokenizer 2026-08-30 12:52:08 +07:00
polv ac92453685 backend
TODO: sql loading/parsing from frontend
2026-03-29 19:01:38 +07:00
polv ecd5f6b34e theme with primevue 2026-03-29 12:47:56 +07:00
41 changed files with 18334 additions and 167 deletions

No files matched your search

+2
View File
@@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
tmp/
+9 -2
View File
@@ -1,19 +1,26 @@
{
"editor.tabSize": 2,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[vue]": {
"editor.formatOnSave": true
// "editor.defaultFormatter": "esbenp.prettier-vscode",
// "editor.formatOnSave": true
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[rust]": {
"editor.tabSize": 4,
"editor.formatOnSave": true
},
"files.associations": {
"tauri*.conf.json5": "jsonc"
},
"json.schemaDownload.trustedDomains": {
"https://schema.tauri.app": true
}
},
"vue3snippets.enable-compile-vue-file-on-did-save-code": true
}
+12
View File
@@ -0,0 +1,12 @@
# http://editorconfig.org
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.yml]
indent_style = space
+167
View File
@@ -0,0 +1,167 @@
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
# wrangler project
.dev.vars*
!.dev.vars.example
.env*
!.env.example
.wrangler/
+6
View File
@@ -0,0 +1,6 @@
{
"printWidth": 140,
"singleQuote": true,
"semi": true,
"useTabs": true
}
+5
View File
@@ -0,0 +1,5 @@
{
"files.associations": {
"wrangler.json": "jsonc"
}
}
+41
View File
@@ -0,0 +1,41 @@
# Cloudflare Workers
STOP. Your knowledge of Cloudflare Workers APIs and limits may be outdated. Always retrieve current documentation before any Workers, KV, R2, D1, Durable Objects, Queues, Vectorize, AI, or Agents SDK task.
## Docs
- https://developers.cloudflare.com/workers/
- MCP: `https://docs.mcp.cloudflare.com/mcp`
For all limits and quotas, retrieve from the product's `/platform/limits/` page. eg. `/workers/platform/limits`
## Commands
| Command | Purpose |
|---------|---------|
| `npx wrangler dev` | Local development |
| `npx wrangler deploy` | Deploy to Cloudflare |
| `npx wrangler types` | Generate TypeScript types |
Run `wrangler types` after changing bindings in wrangler.jsonc.
## Node.js Compatibility
https://developers.cloudflare.com/workers/runtime-apis/nodejs/
## Errors
- **Error 1102** (CPU/Memory exceeded): Retrieve limits from `/workers/platform/limits/`
- **All errors**: https://developers.cloudflare.com/workers/observability/errors/
## Product Docs
Retrieve API references and limits from:
`/kv/` · `/r2/` · `/d1/` · `/durable-objects/` · `/queues/` · `/vectorize/` · `/workers-ai/` · `/agents/`
## Best Practices (conditional)
If the application uses Durable Objects or Workflows, refer to the relevant best practices:
- Durable Objects: https://developers.cloudflare.com/durable-objects/best-practices/rules-of-durable-objects/
- Workflows: https://developers.cloudflare.com/workflows/build/rules-of-workflows/
+16
View File
@@ -0,0 +1,16 @@
{
"name": "d1-tutorial",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"@types/node": "^26.4.0",
"typescript": "^5.5.2",
"wrangler": "^4.127.1"
}
}
+913
View File
@@ -0,0 +1,913 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
'@types/node':
specifier: ^26.4.0
version: 26.4.0
typescript:
specifier: ^5.5.2
version: 5.9.3
wrangler:
specifier: ^4.127.1
version: 4.127.1
packages:
'@cloudflare/kv-asset-handler@0.5.0':
resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==}
engines: {node: '>=22.0.0'}
'@cloudflare/unenv-preset@2.16.1':
resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==}
peerDependencies:
unenv: 2.0.0-rc.24
workerd: '>1.20260305.0 <2.0.0-0'
peerDependenciesMeta:
workerd:
optional: true
'@cloudflare/workerd-darwin-64@1.20260828.1':
resolution: {integrity: sha512-CVd+xPhqUESg8Xhq09TZx0wl4FSirfJGOzvbPz2yHhBIvmNHFFQkSN3rkd7wEwnhQQk37Xi0/aD6ykPLJbmGiQ==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
'@cloudflare/workerd-darwin-arm64@1.20260828.1':
resolution: {integrity: sha512-5HDPXRM152vU5JveByGFk34X57TVyIsfp4cabepAf45DC0MKvm52ucJqAjW1h8bvW4X+zRw9GU35OHF9FEC9Ww==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
'@cloudflare/workerd-linux-64@1.20260828.1':
resolution: {integrity: sha512-MQ1Ll9P7F72HHUKizbb7BlDfbY8fRoNMpbIpZoU6uKsSkneFICWSKv6UlgU9EQZ+w0i7TMa12iUgJ8l29eRI9A==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
'@cloudflare/workerd-linux-arm64@1.20260828.1':
resolution: {integrity: sha512-FBTaUQ1xcU9jcp4OyBPcH8x0QiFvc1iuZL2GkD8zp2q1WyTVHYOptRDQUU+cuHjt0rQ2EIKVPBjahPxfa0joBw==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
'@cloudflare/workerd-windows-64@1.20260828.1':
resolution: {integrity: sha512-yvr77hC7dUbvK5K+SCg062kkPq3sx+drV1PcgHslzHDYcJBtT0V3X80qLE49LW1vq2svaeNmsVQS+vHsqWu8cQ==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@emnapi/runtime@1.11.3':
resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
'@esbuild/aix-ppc64@0.28.1':
resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.28.1':
resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.28.1':
resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.28.1':
resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.28.1':
resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.28.1':
resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.28.1':
resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.28.1':
resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.28.1':
resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.28.1':
resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.28.1':
resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.28.1':
resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.28.1':
resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.28.1':
resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.28.1':
resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.28.1':
resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.28.1':
resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.28.1':
resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.28.1':
resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.28.1':
resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.28.1':
resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.28.1':
resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.28.1':
resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.28.1':
resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.28.1':
resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.28.1':
resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.35.2':
resolution: {integrity: sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.35.2':
resolution: {integrity: sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [darwin]
'@img/sharp-freebsd-wasm32@0.35.2':
resolution: {integrity: sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==}
engines: {node: '>=20.9.0'}
os: [freebsd]
'@img/sharp-libvips-darwin-arm64@1.3.1':
resolution: {integrity: sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.3.1':
resolution: {integrity: sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.3.1':
resolution: {integrity: sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.3.1':
resolution: {integrity: sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.3.1':
resolution: {integrity: sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.3.1':
resolution: {integrity: sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.3.1':
resolution: {integrity: sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.3.1':
resolution: {integrity: sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.3.1':
resolution: {integrity: sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.3.1':
resolution: {integrity: sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.35.2':
resolution: {integrity: sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.35.2':
resolution: {integrity: sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==}
engines: {node: '>=20.9.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.35.2':
resolution: {integrity: sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==}
engines: {node: '>=20.9.0'}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.35.2':
resolution: {integrity: sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==}
engines: {node: '>=20.9.0'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.35.2':
resolution: {integrity: sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==}
engines: {node: '>=20.9.0'}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.35.2':
resolution: {integrity: sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.35.2':
resolution: {integrity: sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.35.2':
resolution: {integrity: sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.35.2':
resolution: {integrity: sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==}
engines: {node: '>=20.9.0'}
'@img/sharp-webcontainers-wasm32@0.35.2':
resolution: {integrity: sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==}
engines: {node: '>=20.9.0'}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.35.2':
resolution: {integrity: sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.35.2':
resolution: {integrity: sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==}
engines: {node: ^20.9.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.35.2':
resolution: {integrity: sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [win32]
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.6.0':
resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@poppinss/colors@4.1.6':
resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==}
'@poppinss/dumper@0.6.5':
resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==}
'@poppinss/exception@1.2.3':
resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==}
'@sindresorhus/is@7.2.0':
resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==}
engines: {node: '>=18'}
'@speed-highlight/core@1.2.24':
resolution: {integrity: sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==}
'@types/node@26.4.0':
resolution: {integrity: sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==}
blake3-wasm@2.1.5:
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
cookie@1.1.1:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
error-stack-parser-es@1.0.5:
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
esbuild@0.28.1:
resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==}
engines: {node: '>=18'}
hasBin: true
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
kleur@4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
miniflare@5.20260828.0-alpha:
resolution: {integrity: sha512-6nbxhZEcz/UET3Y1OnYPsrAUjUmuFoib3ynUqteRdn1YnDxsLg8cwgZJZCk9QmtOmGzXwzXzgE/d/C0dJAPtVw==}
engines: {node: '>=22.0.0'}
path-to-regexp@6.3.0:
resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
semver@7.8.5:
resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
engines: {node: '>=10'}
hasBin: true
sharp@0.35.2:
resolution: {integrity: sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==}
engines: {node: '>=20.9.0'}
supports-color@10.2.2:
resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
engines: {node: '>=18'}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
undici-types@8.3.0:
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
undici@7.29.0:
resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==}
engines: {node: '>=20.18.1'}
unenv@2.0.0-rc.24:
resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
workerd@1.20260828.1:
resolution: {integrity: sha512-pB9yvt0kkwZDAGZHmpY59r0o3hM0DzdW6BJERqwZOhunZ3ssOyDSgQxOQer2cSZW4YCFeOTIQYN1qwhK5wv/Cw==}
engines: {node: '>=16'}
hasBin: true
wrangler@4.127.1:
resolution: {integrity: sha512-OzsiNgaI8i681L/+KnAKc+uEZ5D57xK5JuNvCOpRKICF4/5Q3Cu1oTGuUiT/f3GDUqQb3gzXNT0tfOHGMEtknw==}
engines: {node: '>=22.0.0'}
hasBin: true
peerDependencies:
'@cloudflare/workers-types': ^5.20260828.1
peerDependenciesMeta:
'@cloudflare/workers-types':
optional: true
ws@8.21.0:
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
youch-core@0.3.3:
resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==}
youch@4.1.0-beta.10:
resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==}
snapshots:
'@cloudflare/kv-asset-handler@0.5.0': {}
'@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260828.1)':
dependencies:
unenv: 2.0.0-rc.24
optionalDependencies:
workerd: 1.20260828.1
'@cloudflare/workerd-darwin-64@1.20260828.1':
optional: true
'@cloudflare/workerd-darwin-arm64@1.20260828.1':
optional: true
'@cloudflare/workerd-linux-64@1.20260828.1':
optional: true
'@cloudflare/workerd-linux-arm64@1.20260828.1':
optional: true
'@cloudflare/workerd-windows-64@1.20260828.1':
optional: true
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@emnapi/runtime@1.11.3':
dependencies:
tslib: 2.8.1
optional: true
'@esbuild/aix-ppc64@0.28.1':
optional: true
'@esbuild/android-arm64@0.28.1':
optional: true
'@esbuild/android-arm@0.28.1':
optional: true
'@esbuild/android-x64@0.28.1':
optional: true
'@esbuild/darwin-arm64@0.28.1':
optional: true
'@esbuild/darwin-x64@0.28.1':
optional: true
'@esbuild/freebsd-arm64@0.28.1':
optional: true
'@esbuild/freebsd-x64@0.28.1':
optional: true
'@esbuild/linux-arm64@0.28.1':
optional: true
'@esbuild/linux-arm@0.28.1':
optional: true
'@esbuild/linux-ia32@0.28.1':
optional: true
'@esbuild/linux-loong64@0.28.1':
optional: true
'@esbuild/linux-mips64el@0.28.1':
optional: true
'@esbuild/linux-ppc64@0.28.1':
optional: true
'@esbuild/linux-riscv64@0.28.1':
optional: true
'@esbuild/linux-s390x@0.28.1':
optional: true
'@esbuild/linux-x64@0.28.1':
optional: true
'@esbuild/netbsd-arm64@0.28.1':
optional: true
'@esbuild/netbsd-x64@0.28.1':
optional: true
'@esbuild/openbsd-arm64@0.28.1':
optional: true
'@esbuild/openbsd-x64@0.28.1':
optional: true
'@esbuild/openharmony-arm64@0.28.1':
optional: true
'@esbuild/sunos-x64@0.28.1':
optional: true
'@esbuild/win32-arm64@0.28.1':
optional: true
'@esbuild/win32-ia32@0.28.1':
optional: true
'@esbuild/win32-x64@0.28.1':
optional: true
'@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.35.2':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.3.1
optional: true
'@img/sharp-darwin-x64@0.35.2':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.3.1
optional: true
'@img/sharp-freebsd-wasm32@0.35.2':
dependencies:
'@img/sharp-wasm32': 0.35.2
optional: true
'@img/sharp-libvips-darwin-arm64@1.3.1':
optional: true
'@img/sharp-libvips-darwin-x64@1.3.1':
optional: true
'@img/sharp-libvips-linux-arm64@1.3.1':
optional: true
'@img/sharp-libvips-linux-arm@1.3.1':
optional: true
'@img/sharp-libvips-linux-ppc64@1.3.1':
optional: true
'@img/sharp-libvips-linux-riscv64@1.3.1':
optional: true
'@img/sharp-libvips-linux-s390x@1.3.1':
optional: true
'@img/sharp-libvips-linux-x64@1.3.1':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.3.1':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.3.1':
optional: true
'@img/sharp-linux-arm64@0.35.2':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.3.1
optional: true
'@img/sharp-linux-arm@0.35.2':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.3.1
optional: true
'@img/sharp-linux-ppc64@0.35.2':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.3.1
optional: true
'@img/sharp-linux-riscv64@0.35.2':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.3.1
optional: true
'@img/sharp-linux-s390x@0.35.2':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.3.1
optional: true
'@img/sharp-linux-x64@0.35.2':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.3.1
optional: true
'@img/sharp-linuxmusl-arm64@0.35.2':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.3.1
optional: true
'@img/sharp-linuxmusl-x64@0.35.2':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.3.1
optional: true
'@img/sharp-wasm32@0.35.2':
dependencies:
'@emnapi/runtime': 1.11.3
optional: true
'@img/sharp-webcontainers-wasm32@0.35.2':
dependencies:
'@img/sharp-wasm32': 0.35.2
optional: true
'@img/sharp-win32-arm64@0.35.2':
optional: true
'@img/sharp-win32-ia32@0.35.2':
optional: true
'@img/sharp-win32-x64@0.35.2':
optional: true
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.6.0': {}
'@jridgewell/trace-mapping@0.3.9':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.6.0
'@poppinss/colors@4.1.6':
dependencies:
kleur: 4.1.5
'@poppinss/dumper@0.6.5':
dependencies:
'@poppinss/colors': 4.1.6
'@sindresorhus/is': 7.2.0
supports-color: 10.2.2
'@poppinss/exception@1.2.3': {}
'@sindresorhus/is@7.2.0': {}
'@speed-highlight/core@1.2.24': {}
'@types/node@26.4.0':
dependencies:
undici-types: 8.3.0
blake3-wasm@2.1.5: {}
cookie@1.1.1: {}
detect-libc@2.1.2: {}
error-stack-parser-es@1.0.5: {}
esbuild@0.28.1:
optionalDependencies:
'@esbuild/aix-ppc64': 0.28.1
'@esbuild/android-arm': 0.28.1
'@esbuild/android-arm64': 0.28.1
'@esbuild/android-x64': 0.28.1
'@esbuild/darwin-arm64': 0.28.1
'@esbuild/darwin-x64': 0.28.1
'@esbuild/freebsd-arm64': 0.28.1
'@esbuild/freebsd-x64': 0.28.1
'@esbuild/linux-arm': 0.28.1
'@esbuild/linux-arm64': 0.28.1
'@esbuild/linux-ia32': 0.28.1
'@esbuild/linux-loong64': 0.28.1
'@esbuild/linux-mips64el': 0.28.1
'@esbuild/linux-ppc64': 0.28.1
'@esbuild/linux-riscv64': 0.28.1
'@esbuild/linux-s390x': 0.28.1
'@esbuild/linux-x64': 0.28.1
'@esbuild/netbsd-arm64': 0.28.1
'@esbuild/netbsd-x64': 0.28.1
'@esbuild/openbsd-arm64': 0.28.1
'@esbuild/openbsd-x64': 0.28.1
'@esbuild/openharmony-arm64': 0.28.1
'@esbuild/sunos-x64': 0.28.1
'@esbuild/win32-arm64': 0.28.1
'@esbuild/win32-ia32': 0.28.1
'@esbuild/win32-x64': 0.28.1
fsevents@2.3.3:
optional: true
kleur@4.1.5: {}
miniflare@5.20260828.0-alpha:
dependencies:
'@cspotcode/source-map-support': 0.8.1
sharp: 0.35.2
undici: 7.29.0
workerd: 1.20260828.1
ws: 8.21.0
youch: 4.1.0-beta.10
transitivePeerDependencies:
- bufferutil
- utf-8-validate
path-to-regexp@6.3.0: {}
pathe@2.0.3: {}
semver@7.8.5: {}
sharp@0.35.2:
dependencies:
'@img/colour': 1.1.0
detect-libc: 2.1.2
semver: 7.8.5
optionalDependencies:
'@img/sharp-darwin-arm64': 0.35.2
'@img/sharp-darwin-x64': 0.35.2
'@img/sharp-freebsd-wasm32': 0.35.2
'@img/sharp-libvips-darwin-arm64': 1.3.1
'@img/sharp-libvips-darwin-x64': 1.3.1
'@img/sharp-libvips-linux-arm': 1.3.1
'@img/sharp-libvips-linux-arm64': 1.3.1
'@img/sharp-libvips-linux-ppc64': 1.3.1
'@img/sharp-libvips-linux-riscv64': 1.3.1
'@img/sharp-libvips-linux-s390x': 1.3.1
'@img/sharp-libvips-linux-x64': 1.3.1
'@img/sharp-libvips-linuxmusl-arm64': 1.3.1
'@img/sharp-libvips-linuxmusl-x64': 1.3.1
'@img/sharp-linux-arm': 0.35.2
'@img/sharp-linux-arm64': 0.35.2
'@img/sharp-linux-ppc64': 0.35.2
'@img/sharp-linux-riscv64': 0.35.2
'@img/sharp-linux-s390x': 0.35.2
'@img/sharp-linux-x64': 0.35.2
'@img/sharp-linuxmusl-arm64': 0.35.2
'@img/sharp-linuxmusl-x64': 0.35.2
'@img/sharp-webcontainers-wasm32': 0.35.2
'@img/sharp-win32-arm64': 0.35.2
'@img/sharp-win32-ia32': 0.35.2
'@img/sharp-win32-x64': 0.35.2
supports-color@10.2.2: {}
tslib@2.8.1:
optional: true
typescript@5.9.3: {}
undici-types@8.3.0: {}
undici@7.29.0: {}
unenv@2.0.0-rc.24:
dependencies:
pathe: 2.0.3
workerd@1.20260828.1:
optionalDependencies:
'@cloudflare/workerd-darwin-64': 1.20260828.1
'@cloudflare/workerd-darwin-arm64': 1.20260828.1
'@cloudflare/workerd-linux-64': 1.20260828.1
'@cloudflare/workerd-linux-arm64': 1.20260828.1
'@cloudflare/workerd-windows-64': 1.20260828.1
wrangler@4.127.1:
dependencies:
'@cloudflare/kv-asset-handler': 0.5.0
'@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260828.1)
blake3-wasm: 2.1.5
esbuild: 0.28.1
miniflare: 5.20260828.0-alpha
path-to-regexp: 6.3.0
unenv: 2.0.0-rc.24
workerd: 1.20260828.1
optionalDependencies:
fsevents: 2.3.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
ws@8.21.0: {}
youch-core@0.3.3:
dependencies:
'@poppinss/exception': 1.2.3
error-stack-parser-es: 1.0.5
youch@4.1.0-beta.10:
dependencies:
'@poppinss/colors': 4.1.6
'@poppinss/dumper': 0.6.5
'@speed-highlight/core': 1.2.24
cookie: 1.1.1
youch-core: 0.3.3
+7
View File
@@ -0,0 +1,7 @@
# Pre-approve build scripts for the packages C3 itself installs that need
# them: `workerd` downloads the platform binary and `esbuild` downloads/
# builds native bindings. Without these, pnpm 11+ aborts the install with
# ERR_PNPM_IGNORED_BUILDS.
allowBuilds:
esbuild: true
workerd: true
+20
View File
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hello, World!</title>
</head>
<body>
<h1 id="heading"></h1>
<p>This page comes from a static asset stored at `public/index.html` as configured in `wrangler.jsonc`.</p>
<script>
fetch('/message')
.then((resp) => resp.text())
.then((text) => {
const h1 = document.getElementById('heading');
h1.textContent = text;
});
</script>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
DROP TABLE IF EXISTS Customers;
CREATE TABLE IF NOT EXISTS Customers (CustomerId INTEGER PRIMARY KEY, CompanyName TEXT, ContactName TEXT);
INSERT INTO Customers (CustomerID, CompanyName, ContactName) VALUES (1, 'Alfreds Futterkiste', 'Maria Anders'), (4, 'Around the Horn', 'Thomas Hardy'), (11, 'Bs Beverages', 'Victoria Ashworth'), (13, 'Bs Beverages', 'Random Name');
+72
View File
@@ -0,0 +1,72 @@
import { DurableObject } from 'cloudflare:workers';
/**
* Welcome to Cloudflare Workers! This is your first Durable Objects application.
*
* - Run `npm run dev` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your Durable Object in action
* - Run `npm run deploy` to publish your application
*
* Bind resources to your worker in `wrangler.jsonc`. After adding bindings, a type definition for the
* `Env` object can be regenerated with `npm run cf-typegen`.
*
* Learn more at https://developers.cloudflare.com/durable-objects
*/
/** A Durable Object's behavior is defined in an exported Javascript class */
export class MyDurableObject extends DurableObject {
/**
* The constructor is invoked once upon creation of the Durable Object, i.e. the first call to
* `DurableObjectStub::get` for a given identifier (no-op constructors can be omitted)
*
* @param ctx - The interface for interacting with Durable Object state
* @param env - The interface to reference bindings declared in wrangler.jsonc
*/
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
}
/**
* The Durable Object exposes an RPC method sayHello which will be invoked when a Durable
* Object instance receives a request from a Worker via the same method invocation on the stub
*
* @param name - The name provided to a Durable Object instance from a Worker
* @returns The greeting to be sent back to the Worker
*/
async sayHello(name: string): Promise<string> {
return `Hello, ${name}!`;
}
}
export default {
/**
* This is the standard fetch handler for a Cloudflare Worker
*
* @param request - The request submitted to the Worker from the client
* @param env - The interface to reference bindings declared in wrangler.jsonc
* @param ctx - The execution context of the Worker
* @returns The response to be sent back to the client
*/
async fetch(request, env, ctx): Promise<Response> {
// Create a stub to open a communication channel with the Durable Object
// instance named "foo".
//
// Requests from all Workers to the Durable Object instance named "foo"
// will go to a single remote Durable Object instance.
const stub = env.MY_DURABLE_OBJECT.getByName('foo');
// Call the `sayHello()` RPC method on the stub to invoke the method on
// the remote Durable Object instance.
// const greeting = await stub.sayHello("world");
const { pathname } = new URL(request.url);
if (pathname === '/api/beverages') {
// If you did not use `DB` as your binding name, change it here
const { results } = await env.prod_d1_tutorial.prepare('SELECT * FROM Customers WHERE CompanyName = ?').bind('Bs Beverages').run();
return Response.json(results);
}
return new Response('Call /api/beverages to see everyone who works at Bs Beverages');
},
} satisfies ExportedHandler<Env>;
+42
View File
@@ -0,0 +1,42 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "es2024",
/* Specify a set of bundled library declaration files that describe the target runtime environment. */
"lib": ["es2024"],
/* Specify what JSX code is generated. */
"jsx": "react-jsx",
/* Specify what module code is generated. */
"module": "es2022",
/* Specify how TypeScript looks up a file from a given module specifier. */
"moduleResolution": "node",
/* Enable importing .json files */
"resolveJsonModule": true,
"ignoreDeprecations": "6.0",
/* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
"allowJs": true,
/* Enable error reporting in type-checked JavaScript files. */
"checkJs": false,
/* Disable emitting files from a compilation. */
"noEmit": true,
/* Ensure that each file can be safely transpiled without relying on other imports. */
"isolatedModules": true,
/* Allow 'import x from y' when a module doesn't have a default export. */
"allowSyntheticDefaultImports": true,
/* Ensure that casing is correct in imports. */
"forceConsistentCasingInFileNames": true,
/* Enable all strict type-checking options. */
"strict": true,
/* Skip type checking all .d.ts files. */
"skipLibCheck": true,
"types": ["./worker-configuration.d.ts", "node"]
}
}
File diff suppressed because it is too large. Load diff
+62
View File
@@ -0,0 +1,62 @@
/**
* For more details on how to configure Wrangler, refer to:
* https://developers.cloudflare.com/workers/wrangler/configuration/
*/
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "d1-tutorial",
"main": "src/index.ts",
"compatibility_date": "2026-08-25",
"migrations": [
{
"new_sqlite_classes": ["MyDurableObject"],
"tag": "v1",
},
],
"assets": {
// The path to the directory containing the `index.html` file to be served at `/`
"directory": "./public",
},
"durable_objects": {
"bindings": [
{
"class_name": "MyDurableObject",
"name": "MY_DURABLE_OBJECT",
},
],
},
"observability": {
"enabled": true,
},
"upload_source_maps": true,
"d1_databases": [
{
"binding": "prod_d1_tutorial",
"database_name": "prod-d1-tutorial",
"database_id": "1e595d69-ec95-4eef-a2ef-57ac6ada1973",
},
],
/**
* Smart Placement
* https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
*/
// "placement": { "mode": "smart" }
/**
* Bindings
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
* databases, object storage, AI inference, real-time communication and more.
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
*/
/**
* Environment Variables
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
* Note: Use secrets to store sensitive data.
* https://developers.cloudflare.com/workers/configuration/secrets/
*/
// "vars": { "MY_VARIABLE": "production_value" }
/**
* Service Bindings (communicate between multiple Workers)
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
*/
// "services": [ { "binding": "MY_SERVICE", "service": "my-service" } ]
}
+20
View File
@@ -0,0 +1,20 @@
services:
mongo:
image: mongo
restart: always
ports:
- 27017:27017
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
mongo-express:
image: mongo-express
restart: always
ports:
- 8081:8081
environment:
ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
ME_CONFIG_BASICAUTH_ENABLED: true
ME_CONFIG_BASICAUTH_USERNAME: mongoexpressuser
ME_CONFIG_BASICAUTH_PASSWORD: mongoexpresspass
+9 -1
View File
@@ -13,17 +13,25 @@
"tauri": "tauri"
},
"dependencies": {
"@primeuix/themes": "^2.0.3",
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-clipboard-manager": "~2.3.2",
"@tauri-apps/plugin-fs": "~2.4.5",
"@tauri-apps/plugin-http": "~2.5.7",
"@tauri-apps/plugin-opener": "^2.5.3",
"@tauri-apps/plugin-sql": "~2.3.2",
"vue": "^3.5.31"
"primevue": "^4.5.4",
"vue": "^3.5.31",
"vue-router": "^5.0.4"
},
"devDependencies": {
"@tauri-apps/cli": "^2.10.1",
"@types/better-sqlite3": "^9.6.0",
"@types/node": "^24.13.3",
"@vitejs/plugin-vue": "^5.2.4",
"better-sqlite3": "^13.0.3",
"mongodb": "^7.6.0",
"tsx": "^4.23.13",
"typescript": "~5.6.3",
"vite": "^6.4.1",
"vue-tsc": "^2.2.12"
+818 -5
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -1,2 +1,3 @@
onlyBuiltDependencies:
- better-sqlite3
- esbuild
+118
View File
@@ -0,0 +1,118 @@
import { MongoClient } from "mongodb";
import fs from "node:fs/promises";
import path from "node:path";
interface IYomitanIndex {
title: string;
sequenced: boolean;
format: number; // integer?
url: string;
revision: string;
attribution: string;
}
interface IYomitanTerm {
term: string;
reading: string;
def_tags: any | null;
rules: string;
score: number;
glossary: any; // structured?
sequence: number;
term_tags: string;
}
interface IYomitanTermMeta {
term: string;
mode: number;
data: {
reading: string;
} | null;
}
interface IYomitanTag {
name: string;
category: string;
sort_order: number;
notes: string;
score: number;
}
const client = new MongoClient("mongodb://root:example@localhost:27017");
await client.connect();
const dbYomitan = client.db("yomitan");
const colIndex = dbYomitan.collection<IYomitanIndex>("index");
// await loadFolderToYomitan("tmp/yomitan/jitenon-kotowaza");
await client.close();
async function loadFolderToYomitan(folderpath: string) {
const dbx = client.db(path.parse(folderpath).base);
for (const filename of await fs.readdir(folderpath)) {
if (!filename.endsWith(".json")) continue;
console.log(filename);
if (filename === "index.json") {
await colIndex.insertOne(
JSON.parse(await fs.readFile(path.join(folderpath, filename), "utf-8")),
);
} else if (filename.startsWith("term_bank_")) {
const terms: any[][] = JSON.parse(
await fs.readFile(path.join(folderpath, filename), "utf-8"),
);
const fields = [
"term",
"reading",
"def_tags",
"rules",
"score",
"glossary",
"structured",
"sequence",
"term_tags",
];
await dbx
.collection<IYomitanTerm>("term")
.insertMany(
terms.map((rs) =>
Object.fromEntries(rs.map((r, i) => [fields[i], r])),
) as IYomitanTerm[],
);
} else if (filename.startsWith("tag_bank_")) {
const terms: any[][] = JSON.parse(
await fs.readFile(path.join(folderpath, filename), "utf-8"),
);
const fields = [`name`, `category`, `sort_order`, `notes`, `score`];
await dbx
.collection<IYomitanTag>("tag")
.insertMany(
terms.map((rs) =>
Object.fromEntries(rs.map((r, i) => [fields[i], r])),
) as IYomitanTag[],
);
} else if (filename.startsWith("term_meta_bank_")) {
const terms: any[][] = JSON.parse(
await fs.readFile(path.join(folderpath, filename), "utf-8"),
);
const fields = ["term", "mode", "data"];
await dbx
.collection<IYomitanTermMeta>("term_meta")
.insertMany(
terms.map((rs) =>
Object.fromEntries(rs.map((r, i) => [fields[i], r])),
) as IYomitanTermMeta[],
);
} else {
console.error(filename);
}
}
}
+117
View File
@@ -0,0 +1,117 @@
import sqlite3 from "better-sqlite3";
import path from "node:path";
import fs from "node:fs/promises";
const mainDB = sqlite3("tmp/yomitan/index.db");
mainDB.exec(/* sql */ `
CREATE TABLE IF NOT EXISTS dictionaries (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
revision TEXT NOT NULL,
author TEXT,
[url] TEXT,
[description] TEXT,
sort_order INTEGER NOT NULL DEFAULT 0,
installed_at TEXT NOT NULL DEFAULT (datetime('now')),
lang TEXT NOT NULL, -- custom
[data] TEXT NOT NULL, -- JSON
UNIQUE (title, revision)
);
`);
async function loadFolder(folderpath: string, lang = "ja") {
const db = sqlite3(path.join(folderpath, "index.db"));
db.exec(/* sql */ `
CREATE TABLE IF NOT EXISTS def_tag_sets (id INTEGER PRIMARY KEY, tags TEXT NOT NULL UNIQUE);
CREATE TABLE IF NOT EXISTS term_tag_sets (id INTEGER PRIMARY KEY, tags TEXT NOT NULL UNIQUE);
CREATE TABLE IF NOT EXISTS rule_sets (id INTEGER PRIMARY KEY, rules TEXT NOT NULL UNIQUE);
CREATE TABLE IF NOT EXISTS terms (
id INTEGER PRIMARY KEY,
term TEXT NOT NULL,
reading TEXT NOT NULL,
def_tags_id INTEGER,
rules_id INTEGER,
score INTEGER,
glossary TEXT NOT NULL, -- JSON
glossary_id INTEGER, -- NOT NULL REFERENCES glossaries(id),
[sequence] INTEGER,
term_tags_id INTEGER
);
CREATE TABLE IF NOT EXISTS term_meta (
id INTEGER PRIMARY KEY,
term TEXT NOT NULL,
mode TEXT NOT NULL,
reading TEXT,
[data] TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY,
[name] TEXT NOT NULL,
category TEXT,
sort_order INTEGER NOT NULL DEFAULT 0,
notes TEXT,
score INTEGER NOT NULL DEFAULT 0,
UNIQUE ([name])
);
CREATE TABLE IF NOT EXISTS kanji (
id INTEGER PRIMARY KEY,
kanji TEXT NOT NULL,
onyomi TEXT,
kunyomi TEXT,
tags TEXT,
meanings TEXT NOT NULL DEFAULT '[]', -- JSON
[stats] TEXT NOT NULL DEFAULT '{}' -- JSON
);
`);
db.exec(/* sql */ `
CREATE INDEX IF NOT EXISTS terms_term_reading_score ON terms(term, reading, score DESC);
`);
const stmt = (() => {
let fields = ["title", "revision", "author", "url", "description", "lang"];
const index = mainDB.prepare(/* sql */ `
INSERT INTO dictionaries (${fields.map((f) => `[${f}]`).join(",")}, [data])
VALUES (${fields.map((f) => ":${f}").join(",")}, :data)
`);
fields = [
"term",
"reading",
"def_tags",
"rules",
"score",
"glossary",
"sequence",
"term_tags",
];
const term = db.prepare(/* sql */ `
INSERT INTO term (${fields.map((f) => `[${f}]`).join(",")})
VALUES (${fields.map(() => "?").join(",")})
`);
return {
index,
term,
};
})();
for (const filename of await fs.readdir(folderpath)) {
try {
if (!filename.endsWith(".json")) continue;
if (filename === "index.json") {
const obj = JSON.parse(
await fs.readFile(path.join(folderpath, filename), "utf-8"),
);
stmt.index.run({ lang, data: JSON.stringify(obj), ...obj });
} else if (filename.startsWith("term_bank_")) {
}
} catch (e) {
console.error(filename);
throw e;
}
}
}
+3
View File
@@ -673,8 +673,11 @@ dependencies = [
"tauri-plugin-http",
"tauri-plugin-opener",
"tauri-plugin-sql",
"thiserror 2.0.18",
"tokio",
"vibrato-rkyv",
"zip",
"zstd",
]
[[package]]
+3
View File
@@ -29,3 +29,6 @@ tauri-plugin-clipboard-manager = "2"
zip = "8.1.0"
blake3 = "1.8.3"
vibrato-rkyv = "0.7.5"
tokio = { version = "1.50.0", features = ["io-util"] }
thiserror = "2.0.18"
zstd = "0.13.3"
@@ -1,4 +1,4 @@
<resources>
<string name="app_name">cjdic2-next</string>
<string name="main_activity_title">cjdic2-next</string>
<string name="app_name">cjdic2</string>
<string name="main_activity_title">cjdic2</string>
</resources>
+110
View File
@@ -0,0 +1,110 @@
use serde::Serialize;
use std::{fs::remove_file, path::PathBuf};
use tauri::{AppHandle, Emitter, Manager};
use tauri_plugin_http::reqwest::Client;
use tokio::io::AsyncWriteExt;
use crate::{error::CJDicError, service::AppService};
#[tauri::command]
pub async fn tokenize(
text: String,
state: tauri::State<'_, AppService>,
) -> Result<Vec<String>, CJDicError> {
state.tokenize(text)
}
#[tauri::command]
pub async fn normalize(
text: String,
state: tauri::State<'_, AppService>,
) -> Result<Vec<String>, CJDicError> {
state.normalize(text)
}
#[derive(Serialize, Clone)]
struct DownloadProgress {
url: String,
filepath: String,
content_length: u64,
downloaded: u64,
}
#[tauri::command]
pub async fn download_url(app: AppHandle, url: &str, filepath: &str) -> Result<bool, CJDicError> {
download_url_local(url, &app.path().app_data_dir()?, filepath, |progress| {
app.emit("download-url-progress", progress).unwrap();
})
.await
}
async fn download_url_local(
url: &str,
rootdir: &PathBuf,
filepath: &str,
callback: impl Fn(DownloadProgress),
) -> Result<bool, CJDicError> {
let client = Client::new();
let file_pf = rootdir.join(filepath);
let temp_pf = rootdir.join(format!("{}.dl-tmp", filepath));
let temp_path = temp_pf.as_path();
// Check existing partial download size
let existing_size = tokio::fs::metadata(temp_path)
.await
.map(|m| m.len())
.unwrap_or(0);
let mut request = client.get(url);
// Request only the remaining bytes
if existing_size > 0 {
request = request.header("Range", format!("bytes={}-", existing_size));
}
// Making the asynchronous GET request
let mut response = request.send().await?;
// Check if the request was successful
if response.status().is_success() {
let content_length = response.content_length().unwrap_or(0);
let mut file = if existing_size > 0 {
tokio::fs::OpenOptions::new()
.append(true)
.open(temp_path)
.await?
} else {
tokio::fs::File::create(&file_pf).await?
};
// Downloading the file in chunks
let mut downloaded: u64 = 0;
while let Some(chunk) = response.chunk().await? {
file.write_all(&chunk).await?;
downloaded += chunk.len() as u64;
callback(DownloadProgress {
url: url.to_owned(),
filepath: filepath.to_owned(),
content_length,
downloaded,
});
}
// Verify download completeness
if content_length > 0 && downloaded < content_length {
// Delete incomplete file
drop(file); // ensure file handle is released before deleting
remove_file(&file_pf).ok();
return Err(CJDicError::Error(format!(
"Incomplete download: expected {} bytes, got {}",
content_length, downloaded
)));
}
println!("File downloaded successfully to {:?}", &file_pf);
} else {
eprintln!("Failed to download file: {:?}", response.status());
}
Ok(true)
}
-5
View File
@@ -1,5 +0,0 @@
use vibrato_rkyv::Tokenizer;
pub struct AppService {
tokenizer: Tokenizer,
}
+90
View File
@@ -0,0 +1,90 @@
use std::{backtrace::Backtrace, string::FromUtf8Error};
use serde::Serialize;
use thiserror::Error;
use vibrato_rkyv::errors::VibratoError;
use zip::result::ZipError;
#[derive(Debug, Error, Serialize)]
#[serde(tag = "kind", content = "message")]
pub enum CJDicError {
#[error("SerializationError: {0}")]
SerializationError(String),
#[error("IOError: {0}")]
IOError(String),
#[error("ZipError: {0}")]
ZipError(String),
#[error("VibratoError: {0}")]
VibratoError(String),
#[error("FromUtf8Error: {0}")]
FromUtf8Error(String),
#[error("TauriError: {0}")]
TauriError(String),
#[error("TauriReqwestError: {0}")]
TauriReqwestError(String),
#[error("Error: {0}")]
Error(String),
}
impl From<serde_json::Error> for CJDicError {
fn from(e: serde_json::Error) -> Self {
let bt = Backtrace::capture();
eprintln!("{e:#}\n{bt}");
CJDicError::SerializationError(e.to_string())
}
}
impl From<std::io::Error> for CJDicError {
fn from(e: std::io::Error) -> Self {
let bt = Backtrace::capture();
eprintln!("{e:#}\n{bt}");
CJDicError::IOError(e.to_string())
}
}
impl From<ZipError> for CJDicError {
fn from(e: ZipError) -> Self {
let bt = Backtrace::capture();
eprintln!("{e:#}\n{bt}");
CJDicError::ZipError(e.to_string())
}
}
impl From<VibratoError> for CJDicError {
fn from(e: VibratoError) -> Self {
let bt = Backtrace::capture();
eprintln!("{e:#}\n{bt}");
CJDicError::VibratoError(e.to_string())
}
}
impl From<FromUtf8Error> for CJDicError {
fn from(e: FromUtf8Error) -> Self {
let bt = Backtrace::capture();
eprintln!("{e:#}\n{bt}");
CJDicError::FromUtf8Error(e.to_string())
}
}
impl From<tauri::Error> for CJDicError {
fn from(e: tauri::Error) -> Self {
let bt = Backtrace::capture();
eprintln!("{e:#}\n{bt}");
CJDicError::TauriError(e.to_string())
}
}
impl From<tauri_plugin_http::reqwest::Error> for CJDicError {
fn from(e: tauri_plugin_http::reqwest::Error) -> Self {
let bt = Backtrace::capture();
eprintln!("{e:#}\n{bt}");
CJDicError::TauriReqwestError(e.to_string())
}
}
+57 -3
View File
@@ -1,4 +1,17 @@
mod core;
use std::fs::{create_dir_all, File};
use tauri::Manager;
use tauri_plugin_fs::FsExt;
use crate::command::*;
use crate::service::AppService;
use crate::yomitan::search::YomitanSearch;
mod tokenizer;
mod yomitan;
mod command;
mod error;
mod service;
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
@@ -11,10 +24,51 @@ pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_sql::Builder::new().build())
.plugin(
tauri_plugin_sql::Builder::default()
.add_migrations("sqlite:search.db", YomitanSearch::migrations())
.build(),
)
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet])
.setup(|app| {
let config_dir = app.path().app_config_dir()?;
create_dir_all(&config_dir)?;
let data_dir = app.path().app_data_dir()?;
create_dir_all(&data_dir.join("yomitan/ja"))?;
let cache_dir = app.path().app_cache_dir()?;
let vibrato_dic_dir = cache_dir.join("vibrato");
create_dir_all(&vibrato_dic_dir)?;
let vibrato_dic_path = vibrato_dic_dir.join("system.dic");
if !vibrato_dic_path.exists() {
let zst_path = app.path().resolve(
"resources/mecab-ipadic/system.dic.zst",
tauri::path::BaseDirectory::Resource,
)?;
// Use Tauri's fs abstraction — works on both Android and desktop
let bytes = app.fs().read(&zst_path)?;
let mut decoder = zstd::Decoder::new(bytes.as_slice())?;
let mut out = File::create(&vibrato_dic_path)?;
std::io::copy(&mut decoder, &mut out)?;
}
// db_path is at config_dir;
let service = AppService::new(&vibrato_dic_path)?;
app.manage(service);
Ok(())
})
.invoke_handler(tauri::generate_handler![
greet,
tokenize,
normalize,
download_url
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
+23
View File
@@ -0,0 +1,23 @@
use std::path::Path;
use crate::{error::CJDicError, tokenizer::ja::Tokenizer};
pub struct AppService {
pub ja_tokenizer: Tokenizer,
}
impl AppService {
pub fn new(vibrato_dic: impl AsRef<Path>) -> Result<Self, CJDicError> {
Ok(Self {
ja_tokenizer: Tokenizer::new(vibrato_dic)?,
})
}
pub fn tokenize(&self, text: String) -> Result<Vec<String>, CJDicError> {
self.ja_tokenizer.tokenize(text)
}
pub fn normalize(&self, text: String) -> Result<Vec<String>, CJDicError> {
self.ja_tokenizer.normalize(text)
}
}
+95
View File
@@ -0,0 +1,95 @@
use std::{collections::HashSet, fs::File, io::BufReader, path::Path};
use serde::Serialize;
use crate::error::CJDicError;
#[derive(Debug, Serialize)]
pub struct TokenizeSegment {
pub surface: String,
/// ```txt
/// 0 品詞 Part-of-speech
/// 1 品詞細分類1 Part-of-speech subcategory 1
/// 2 品詞細分類2 Part-of-speech subcategory 2
/// 3 品詞細分類3 Part-of-speech subcategory 3
/// 4 活用形 Conjugation form
/// 5 活用型 Conjugation type
/// 6 原形 Base form
/// 7 読み Reading
/// 8 発音 Pronunciation
/// ```
/// @see https://lindera.github.io/lindera/dictionaries/ipadic.html
pub details: Vec<String>,
}
#[derive(Clone)]
pub struct Tokenizer {
tokenizer: vibrato_rkyv::Tokenizer,
exclude_pos: HashSet<String>,
}
impl Tokenizer {
pub fn new(vibrato_dic: impl AsRef<Path>) -> Result<Self, CJDicError> {
let reader = BufReader::new(File::open(vibrato_dic)?);
let dictionary = vibrato_rkyv::Dictionary::read(reader)?;
Ok(Self {
tokenizer: vibrato_rkyv::Tokenizer::new(dictionary),
exclude_pos: HashSet::from(["助詞", "助動詞", "記号", "接続詞"].map(|p| p.to_string())),
})
}
pub fn tokenize(&self, text: String) -> Result<Vec<String>, CJDicError> {
let mut segmented: Vec<String> = vec![];
for t in self.ja_tokenize(text)? {
segmented.push(t.surface);
}
Ok(segmented)
}
fn ja_tokenize(&self, text: String) -> Result<Vec<TokenizeSegment>, CJDicError> {
let mut worker = self.tokenizer.new_worker();
worker.reset_sentence(text);
worker.tokenize();
let mut output: Vec<TokenizeSegment> = vec![];
for token in worker.token_iter() {
let surface = token.surface().to_string();
let details = token.feature().split(",").map(|s| s.to_string()).collect();
output.push(TokenizeSegment { surface, details });
}
Ok(output)
}
pub fn normalize(&self, text: String) -> Result<Vec<String>, CJDicError> {
let mut segmented: Vec<String> = vec![];
for t in self.ja_tokenize(text)? {
let mut surface = t.surface;
let mut pos = None;
for (i, dt) in t.details.iter().enumerate() {
match i {
0 => pos = Some(dt),
6 => {
let base = dt.to_string();
if base != "*" {
surface = base
}
}
_ => {}
}
}
if let Some(p) = pos {
if self.exclude_pos.contains(p.as_str()) {
continue;
}
}
segmented.push(surface);
}
Ok(segmented)
}
}
+1
View File
@@ -0,0 +1 @@
pub mod ja;
+2
View File
@@ -0,0 +1,2 @@
pub mod search;
pub mod unzip;
+75
View File
@@ -0,0 +1,75 @@
use tauri_plugin_sql::{Migration, MigrationKind};
pub struct YomitanSearch;
impl YomitanSearch {
pub fn migrations() -> Vec<Migration> {
vec![
Migration {
version: 1,
description: "create yomitan dict-version tables",
sql: "
CREATE TABLE dict (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
revision TEXT NOT NULL,
author TEXT,
url TEXT,
description TEXT,
sort_order INTEGER NOT NULL DEFAULT 0,
installed_at TEXT NOT NULL DEFAULT (datetime('now')),
bundle_name TEXT, -- custom
lang TEXT NOT NULL, -- custom
asset_count INTEGER, -- custom
source TEXT, -- custom
UNIQUE (title, revision)
);
",
kind: MigrationKind::Up,
},
Migration {
version: 1,
description: "create yomitan ja query tables",
sql: "
CREATE TABLE ja_search (
id INTEGER NOT NULL, -- yomitan.terms.id OR yomitan dups OR -user.terms.id
dict_id INTEGER NOT NULL REFERENCES dict(id) ON DELETE CASCADE,
term TEXT NOT NULL, -- normalized
term_seg TEXT, -- segmented and filtered for fillers
reading TEXT NOT NULL, -- normalized
frequency INTEGER,
PRIMARY KEY (id, dict_id)
);
CREATE INDEX ja_search_term ON ja_search (term);
CREATE INDEX ja_search_reading ON ja_search (reading);
CREATE INDEX ja_search_frequency ON ja_search (frequency DESC);
CREATE VIRTUAL TABLE IF NOT EXISTS ja_search_ft USING fts5 (
term_seg,
content='ja_search',
content_rowid='rowid',
tokenize=unicode61
);
CREATE TRIGGER t_ja_search_ft_after_insert
AFTER INSERT ON ja_search BEGIN
INSERT INTO ja_search_ft (rowid, term_seg) VALUES (NEW.rowid, NEW.term_seg);
END;
CREATE TRIGGER t_ja_search_ft_after_delete
AFTER DELETE ON ja_search BEGIN
INSERT INTO ja_search_ft (ja_search_ft, rowid, term_seg) VALUES ('delete', OLD.rowid, OLD.term_seg);
END;
CREATE TRIGGER t_ja_search_ft_after_update
AFTER UPDATE ON ja_search BEGIN
INSERT INTO ja_search_ft (ja_search_ft, rowid, term_seg) VALUES ('delete', OLD.rowid, OLD.term_seg);
INSERT INTO ja_search_ft (rowid, term_seg) VALUES (NEW.rowid, NEW.term_seg);
END;
",
kind: MigrationKind::Up
}
]
}
}
View File
Whitespace-only changes.
+4 -3
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "cjdic2-next",
"productName": "cjdic2",
"version": "0.1.0",
"identifier": "cc.polv.cjdic2.next",
"build": {
@@ -12,7 +12,7 @@
"app": {
"windows": [
{
"title": "cjdic2-next",
"title": "cjdic2",
"width": 1024,
"height": 768,
"maximized": true
@@ -31,6 +31,7 @@
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
],
"resources": ["resources/mecab-ipadic/system.dic.zst"]
}
}
+43 -142
View File
@@ -1,160 +1,61 @@
<script setup lang="ts">
import Button from "primevue/button";
import Dialog from "primevue/dialog";
import { ref } from "vue";
import { invoke } from "@tauri-apps/api/core";
const greetMsg = ref("");
const name = ref("");
async function greet() {
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
greetMsg.value = await invoke("greet", { name: name.value });
}
const isLoading = ref(true);
const isReady = ref(false);
</script>
<template>
<main class="container">
<h1>Welcome to Tauri + Vue</h1>
<div class="row">
<a href="https://vite.dev" target="_blank">
<img src="/vite.svg" class="logo vite" alt="Vite logo" />
</a>
<a href="https://tauri.app" target="_blank">
<img src="/tauri.svg" class="logo tauri" alt="Tauri logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<p>Click on the Tauri, Vite, and Vue logos to learn more.</p>
<form class="row" @submit.prevent="greet">
<input id="greet-input" v-model="name" placeholder="Enter a name..." />
<button type="submit">Greet</button>
</form>
<p>{{ greetMsg }}</p>
<main>
<RouterView />
<Dialog v-model:visible="isLoading" modal>
<template #container>
<aside class="loading-dialog">
<div class="container"></div>
<nav>
<Button
class="btn cancel"
label="Cancel"
variant="outlined"
severity="secondary"
@click="
isLoading = false;
isReady = true;
"
autofocus
/>
</nav>
</aside>
</template>
</Dialog>
</main>
</template>
<style scoped>
.logo.vite:hover {
filter: drop-shadow(0 0 2em #747bff);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #249b73);
}
</style>
<style>
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: #0f0f0f;
background-color: #f6f6f6;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
.container {
margin: 0;
padding-top: 10vh;
.loading-dialog {
width: 1000px;
max-width: 90vw;
min-height: 50vh;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
}
.container {
flex-grow: 1;
padding: 0.5em;
}
.logo.tauri:hover {
filter: drop-shadow(0 0 2em #24c8db);
}
.row {
nav {
display: flex;
justify-content: center;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
h1 {
text-align: center;
}
input,
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
color: #0f0f0f;
background-color: #ffffff;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
}
button {
cursor: pointer;
}
button:hover {
border-color: #396cd8;
}
button:active {
border-color: #396cd8;
background-color: #e8e8e8;
}
input,
button {
outline: none;
}
#greet-input {
margin-right: 5px;
}
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
a:hover {
color: #24c8db;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
button:active {
background-color: #0f0f0f69;
flex-direction: row-reverse;
padding: 0.5em;
}
}
</style>
<style>
:root {
font-family: sans-serif;
}
</style>
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

+11 -3
View File
@@ -1,4 +1,12 @@
import { createApp } from "vue";
import App from "./App.vue";
import Aura from "@primeuix/themes/aura";
import PrimeVue from "primevue/config";
createApp(App).mount("#app");
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App)
.use(router)
.use(PrimeVue, { theme: { preset: Aura } })
.mount("#app");
+63
View File
@@ -0,0 +1,63 @@
<template>
<div>
<h1>Welcome to Tauri + Vue</h1>
<div class="row">
<a href="https://vite.dev" target="_blank">
<img src="/vite.svg" class="logo vite" alt="Vite logo" />
</a>
<a href="https://tauri.app" target="_blank">
<img src="/tauri.svg" class="logo tauri" alt="Tauri logo" />
</a>
</div>
<p style="text-align: center">Click on the Tauri, and Vite logos to learn more.</p>
<form class="row" @submit.prevent="greet">
<InputText id="greet-input" v-model="name" placeholder="Enter a name..." />
<Button type="submit">Greet</Button>
</form>
</div>
</template>
<script setup lang="ts">
import { invoke } from "@tauri-apps/api/core";
import Button from "primevue/button";
import InputText from "primevue/inputtext";
import { ref } from "vue";
const greetMsg = ref("");
const name = ref("");
async function greet() {
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
greetMsg.value = await invoke("greet", { name: name.value });
}
</script>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
&.tauri:hover {
filter: drop-shadow(0 0 2em #24c8db);
}
&.vite:hover {
filter: drop-shadow(0 0 2em #747bff);
}
}
.row {
display: flex;
justify-content: center;
}
h1 {
text-align: center;
}
</style>
+10
View File
@@ -0,0 +1,10 @@
import { createRouter, createWebHashHistory } from "vue-router";
import SearchView from "./pages/SearchView.vue";
const routes = [{ path: "/", component: SearchView }];
export const router = createRouter({
history: createWebHashHistory(),
routes,
});