Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4aacd79e00 |
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "arcrun-component-worker-template",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"hono": "^4.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20250408.0",
|
||||
"typescript": "^5.4.0",
|
||||
"wrangler": "^4.0.0"
|
||||
}
|
||||
}
|
||||
Generated
+917
@@ -0,0 +1,917 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
hono:
|
||||
specifier: ^4.7.0
|
||||
version: 4.13.1
|
||||
devDependencies:
|
||||
'@cloudflare/workers-types':
|
||||
specifier: ^4.20250408.0
|
||||
version: 4.20260702.1
|
||||
typescript:
|
||||
specifier: ^5.4.0
|
||||
version: 5.9.3
|
||||
wrangler:
|
||||
specifier: ^4.0.0
|
||||
version: 4.121.0(@cloudflare/workers-types@4.20260702.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.20260804.1':
|
||||
resolution: {integrity: sha512-191/PPEFicRK2wK69eXzSjnLgHL79k7zR2VUpyIr8rhFgGns1b5bTHgnBuUrgU2LPbEtwbE5eL2hJ0uhLAFEow==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@cloudflare/workerd-darwin-arm64@1.20260804.1':
|
||||
resolution: {integrity: sha512-aI2cAFLsrNkSz3kLSQrgrO9ICTfY7jb2h0jgaWDE9mQLQQDfpXeYrKWt07tTgMmeNP79o9w3NZe3aqtt8mTGHQ==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@cloudflare/workerd-linux-64@1.20260804.1':
|
||||
resolution: {integrity: sha512-KBCjxBIlN2jucfQGaTK4EgmPsWzQgYR/zYhPfi3mkWwdoTyG1dgrt2aizKps/SYse85ci/SOxojKk0/K7vstPw==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@cloudflare/workerd-linux-arm64@1.20260804.1':
|
||||
resolution: {integrity: sha512-7lswfarBZ7xkHpTFrNb74ExwOltIalVaASc+GOYfCNtnwFxK/JZSVByfm/hnZEBtrHU7fMY2z7dYT64rwS0pHg==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@cloudflare/workerd-windows-64@1.20260804.1':
|
||||
resolution: {integrity: sha512-GOgRWYtxISN5rAWfx3K7zubt3xEn0/ZPFrbcLL+GwT4ouCKyNoHqfT1cPNB6Nx7t8BHEjnuTG7gkHO4Wd5ORdg==}
|
||||
engines: {node: '>=16'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@cloudflare/workers-types@4.20260702.1':
|
||||
resolution: {integrity: sha512-mOhf5TUEB1m2vPrxtqoIGfz0fUC9xyxRDx5gWHy5s+OCo6dcV+g7wI1R7gYCMFohhqF/2y2xeKVwMwCJjfn/WA==}
|
||||
|
||||
'@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.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@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==}
|
||||
|
||||
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]
|
||||
|
||||
hono@4.13.1:
|
||||
resolution: {integrity: sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
kleur@4.1.5:
|
||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
miniflare@5.20260804.1-alpha:
|
||||
resolution: {integrity: sha512-J0QBHEj+d75TyFE9VhH+2dXgvdYt/pfyDlm+9IiYUFocQvqYy9iuW1DIqNawh1N7Kr6iMrDzVkgDSdt6pA75uA==}
|
||||
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@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.20260804.1:
|
||||
resolution: {integrity: sha512-b0P38g5/ssemwWxd/mafNYggEZ0ere7PCiUH6RCHkgqjRhhXTP45nDiL2L3iIvi8uF2IjNrGpVgMXEunCaeY/w==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
|
||||
wrangler@4.121.0:
|
||||
resolution: {integrity: sha512-dcARWk6CyaD0vJBSLjJn4K2yo+mYko73hzryq+t/9DXnyAq877RWIMl7uOMcDKs5dDXdoickNhZTKxBYT9XKsA==}
|
||||
engines: {node: '>=22.0.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@cloudflare/workers-types': ^5.20260804.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.20260804.1)':
|
||||
dependencies:
|
||||
unenv: 2.0.0-rc.24
|
||||
optionalDependencies:
|
||||
workerd: 1.20260804.1
|
||||
|
||||
'@cloudflare/workerd-darwin-64@1.20260804.1':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workerd-darwin-arm64@1.20260804.1':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workerd-linux-64@1.20260804.1':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workerd-linux-arm64@1.20260804.1':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workerd-windows-64@1.20260804.1':
|
||||
optional: true
|
||||
|
||||
'@cloudflare/workers-types@4.20260702.1': {}
|
||||
|
||||
'@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.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.9':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@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': {}
|
||||
|
||||
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
|
||||
|
||||
hono@4.13.1: {}
|
||||
|
||||
kleur@4.1.5: {}
|
||||
|
||||
miniflare@5.20260804.1-alpha:
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
sharp: 0.35.2
|
||||
undici: 7.29.0
|
||||
workerd: 1.20260804.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@7.29.0: {}
|
||||
|
||||
unenv@2.0.0-rc.24:
|
||||
dependencies:
|
||||
pathe: 2.0.3
|
||||
|
||||
workerd@1.20260804.1:
|
||||
optionalDependencies:
|
||||
'@cloudflare/workerd-darwin-64': 1.20260804.1
|
||||
'@cloudflare/workerd-darwin-arm64': 1.20260804.1
|
||||
'@cloudflare/workerd-linux-64': 1.20260804.1
|
||||
'@cloudflare/workerd-linux-arm64': 1.20260804.1
|
||||
'@cloudflare/workerd-windows-64': 1.20260804.1
|
||||
|
||||
wrangler@4.121.0(@cloudflare/workers-types@4.20260702.1):
|
||||
dependencies:
|
||||
'@cloudflare/kv-asset-handler': 0.5.0
|
||||
'@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260804.1)
|
||||
blake3-wasm: 2.1.5
|
||||
esbuild: 0.28.1
|
||||
miniflare: 5.20260804.1-alpha
|
||||
path-to-regexp: 6.3.0
|
||||
unenv: 2.0.0-rc.24
|
||||
workerd: 1.20260804.1
|
||||
optionalDependencies:
|
||||
'@cloudflare/workers-types': 4.20260702.1
|
||||
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
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* arcrun logic component Worker
|
||||
*
|
||||
* POST / → JSON input → WASM (WASI preview1 stdin/stdout) → JSON output
|
||||
*
|
||||
* WASM is statically bundled at build time via wrangler.toml [[wasm_modules]].
|
||||
* Each logic component gets its own Worker at {name}.arcrun.dev.
|
||||
*/
|
||||
|
||||
import componentWasm from '../component.wasm' assert { type: 'webassembly' };
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.use('*', cors());
|
||||
|
||||
app.get('/', (c) => c.json({ ok: true, component: COMPONENT_ID }));
|
||||
|
||||
app.post('/', async (c) => {
|
||||
let input: unknown;
|
||||
try {
|
||||
input = await c.req.json();
|
||||
} catch {
|
||||
return c.json({ success: false, error: 'request body must be JSON' }, 400);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await runWasm(componentWasm, input);
|
||||
return c.json(result);
|
||||
} catch (e) {
|
||||
return c.json({ success: false, error: e instanceof Error ? e.message : String(e) }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
// ── WASM runner (WASI preview1 stdin/stdout) ─────────────────────────────────
|
||||
|
||||
declare const COMPONENT_ID: string; // injected via [vars] in wrangler.toml
|
||||
|
||||
async function runWasm(wasmModule: WebAssembly.Module, input: unknown): Promise<unknown> {
|
||||
const stdinBytes = new TextEncoder().encode(JSON.stringify(input));
|
||||
let stdinOffset = 0;
|
||||
|
||||
const stdoutChunks: Uint8Array[] = [];
|
||||
let memory: WebAssembly.Memory | null = null;
|
||||
|
||||
const getView = () => new DataView(memory!.buffer);
|
||||
|
||||
const wasi: WebAssembly.Imports = {
|
||||
wasi_snapshot_preview1: {
|
||||
fd_write(fd: number, iovs: number, iovs_len: number, nwritten_ptr: number): number {
|
||||
if (fd !== 1 && fd !== 2) return 76; // ENOSYS
|
||||
const view = getView();
|
||||
let total = 0;
|
||||
for (let i = 0; i < iovs_len; i++) {
|
||||
const base = view.getUint32(iovs + i * 8, true);
|
||||
const len = view.getUint32(iovs + i * 8 + 4, true);
|
||||
if (len === 0) continue;
|
||||
const chunk = new Uint8Array(memory!.buffer, base, len);
|
||||
const copy = new Uint8Array(len);
|
||||
copy.set(chunk);
|
||||
if (fd === 1) stdoutChunks.push(copy);
|
||||
total += len;
|
||||
}
|
||||
view.setUint32(nwritten_ptr, total, true);
|
||||
return 0;
|
||||
},
|
||||
fd_read(fd: number, iovs: number, iovs_len: number, nread_ptr: number): number {
|
||||
if (fd !== 0) return 76;
|
||||
const view = getView();
|
||||
let total = 0;
|
||||
for (let i = 0; i < iovs_len; i++) {
|
||||
const base = view.getUint32(iovs + i * 8, true);
|
||||
const len = view.getUint32(iovs + i * 8 + 4, true);
|
||||
const remaining = stdinBytes.length - stdinOffset;
|
||||
if (remaining <= 0) break;
|
||||
const toCopy = Math.min(len, remaining);
|
||||
new Uint8Array(memory!.buffer, base, toCopy).set(
|
||||
stdinBytes.subarray(stdinOffset, stdinOffset + toCopy)
|
||||
);
|
||||
stdinOffset += toCopy;
|
||||
total += toCopy;
|
||||
}
|
||||
view.setUint32(nread_ptr, total, true);
|
||||
return 0;
|
||||
},
|
||||
proc_exit(code: number): never { throw new Error(`wasm exit: ${code}`); },
|
||||
random_get(ptr: number, len: number): number {
|
||||
crypto.getRandomValues(new Uint8Array(memory!.buffer, ptr, len));
|
||||
return 0;
|
||||
},
|
||||
fd_seek: () => 76, fd_close: () => 0,
|
||||
fd_fdstat_get: () => 76, fd_prestat_get: () => 76,
|
||||
fd_prestat_dir_name: () => 76, environ_get: () => 0,
|
||||
environ_sizes_get: (cp: number, sp: number) => {
|
||||
if (memory) { const v = getView(); v.setUint32(cp,0,true); v.setUint32(sp,0,true); }
|
||||
return 0;
|
||||
},
|
||||
args_get: () => 0,
|
||||
args_sizes_get: (ap: number, bp: number) => {
|
||||
if (memory) { const v = getView(); v.setUint32(ap,0,true); v.setUint32(bp,0,true); }
|
||||
return 0;
|
||||
},
|
||||
clock_time_get: (_id: number, _prec: bigint, tp: number) => {
|
||||
if (memory) getView().setBigUint64(tp, BigInt(Date.now()) * 1_000_000n, true);
|
||||
return 0;
|
||||
},
|
||||
clock_res_get: () => 76, poll_oneoff: () => 76, sched_yield: () => 0,
|
||||
proc_raise: () => 76, sock_accept: () => 76, sock_recv: () => 76,
|
||||
sock_send: () => 76, sock_shutdown: () => 76,
|
||||
path_open: () => 76, path_create_directory: () => 76,
|
||||
path_remove_directory: () => 76, path_rename: () => 76,
|
||||
path_unlink_file: () => 76, path_filestat_get: () => 76,
|
||||
path_readlink: () => 76, path_symlink: () => 76, path_link: () => 76,
|
||||
},
|
||||
// u6u host functions (no-op for pure logic components)
|
||||
u6u: { http_request: () => 1 },
|
||||
};
|
||||
|
||||
const instance = await WebAssembly.instantiate(wasmModule, wasi);
|
||||
memory = instance.exports.memory as WebAssembly.Memory;
|
||||
|
||||
const start = (instance.exports._start ?? instance.exports.main) as () => void;
|
||||
if (typeof start !== 'function') throw new Error('WASM missing _start or main export');
|
||||
|
||||
try { start(); } catch (e) {
|
||||
if (!(e instanceof Error && e.message === 'wasm exit: 0')) throw e;
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const total = stdoutChunks.reduce((n, c) => n + c.length, 0);
|
||||
const merged = new Uint8Array(total);
|
||||
let off = 0;
|
||||
for (const chunk of stdoutChunks) { merged.set(chunk, off); off += chunk.length; }
|
||||
const stdout = decoder.decode(merged).trim();
|
||||
|
||||
if (!stdout) throw new Error('WASM component produced no output');
|
||||
return JSON.parse(stdout);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022"],
|
||||
"types": ["@cloudflare/workers-types"],
|
||||
"strict": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,11 @@ const WASM_HTTP_RUNNER_IDS: ReadonlySet<string> = new Set([
|
||||
'auth_service_account',
|
||||
'auth_oauth2',
|
||||
'auth_mtls',
|
||||
// hash(Arcrun#91,2026-08-13):純計算零件(sha256/sha1/md5,hex/base64),
|
||||
// 出貨線版本號機制與成品指紋核對用它。no_network_syscall,故不走 LOGIC_BINDING_MAP
|
||||
// 的 Service Binding 路(rule 3.1 禁新增 binding),走這裡的通用 wasmWorkerUrl 推導,
|
||||
// 與 code/cron 同一形狀(獨立 Worker,白名單只是「知道這個 canonical_id 存在」)。
|
||||
'hash',
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -111,45 +111,6 @@ kbdbProxyRouter.get('/kbdb/records/by-template/:template', async (c) => {
|
||||
return new Response(res.body, { status: res.status, headers: { 'Content-Type': 'application/json' } });
|
||||
});
|
||||
|
||||
// POST /kbdb/records/backfill-library — 三元組版標庫補存量對外通道(Arcrun#87 二次收尾,2026-08-13)。
|
||||
// 基本盤(kbdb/src/routes/records.ts)的 POST /records/backfill-library 缺對外通道——同
|
||||
// PATCH /kbdb/records/:recordId 那條的破口(能力在 base,插件/工作流打不到)。純轉發,
|
||||
// owner_id 強制用租戶身份(同本檔 POST /kbdb/records 的既有慣例,不信任 caller 自帶 owner_id)。
|
||||
// 此路由必須在 '/:recordId' 之前註冊,否則 'backfill-library' 會被當成 recordId 參數。
|
||||
kbdbProxyRouter.post('/kbdb/records/backfill-library', async (c) => {
|
||||
const owner = tenant(c);
|
||||
if (!owner) return c.json(NEED_KEY, 401);
|
||||
const body = await c.req.json().catch(() => null);
|
||||
if (!body || !body.library) return c.json({ error: 'library 必填' }, 400);
|
||||
const { base, headers } = kbdbBase(c.env);
|
||||
const res = await fetch(`${base}/records/backfill-library`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
library: body.library,
|
||||
owner_id: owner,
|
||||
triplet_template: body.triplet_template,
|
||||
source_prefix: body.source_prefix,
|
||||
limit: body.limit,
|
||||
}),
|
||||
});
|
||||
return new Response(res.body, { status: res.status, headers: { 'Content-Type': 'application/json' } });
|
||||
});
|
||||
|
||||
// GET /kbdb/records/backfill-library/status — 待補標統計(三元組版),owner_id 同樣強制用租戶身份。
|
||||
kbdbProxyRouter.get('/kbdb/records/backfill-library/status', async (c) => {
|
||||
const owner = tenant(c);
|
||||
if (!owner) return c.json(NEED_KEY, 401);
|
||||
const { base, headers } = kbdbBase(c.env);
|
||||
const params = new URLSearchParams({ owner_id: owner });
|
||||
for (const k of ['triplet_template', 'source_prefix']) {
|
||||
const v = c.req.query(k);
|
||||
if (v) params.set(k, v);
|
||||
}
|
||||
const res = await fetch(`${base}/records/backfill-library/status?${params.toString()}`, { headers });
|
||||
return new Response(res.body, { status: res.status, headers: { 'Content-Type': 'application/json' } });
|
||||
});
|
||||
|
||||
// GET /kbdb/records/:recordId — 取單筆 record。
|
||||
kbdbProxyRouter.get('/kbdb/records/:recordId', async (c) => {
|
||||
if (!tenant(c)) return c.json(NEED_KEY, 401);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* component-loader:`hash` 零件解析測試(Arcrun#91)。
|
||||
*
|
||||
* 背景:workflow 寫 `component: hash` 時,component-loader 解析鏈 step 1-6 都不命中
|
||||
* (非 builtin / 非 URL / 非 hash-id / 非 LOGIC_BINDING_MAP 邏輯零件 / 非 recipe),
|
||||
* 必須靠 step 7 的 WASM_HTTP_RUNNER_IDS 白名單 → wasmWorkerUrl 通用推導。
|
||||
* 白名單漏 `hash` 就會落到 step 8 直接丟「找不到零件」——與 #29 的 `code` 缺口同形狀。
|
||||
*
|
||||
* 驗證:
|
||||
* 1. `hash` 能被解析成 runner(不 throw)。
|
||||
* 2. runner 打的 URL = arcrun-hash.{WORKER_SUBDOMAIN}.workers.dev(通用推導、無寫死
|
||||
* 官方 hash.arcrun.dev → self-hosted 換 WORKER_SUBDOMAIN 即落地)。
|
||||
*/
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { env } from 'cloudflare:test';
|
||||
import { createComponentLoader, wasmWorkerUrl } from '../src/lib/component-loader';
|
||||
import type { Bindings } from '../src/types';
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe('component-loader:hash 零件解析(WASM HTTP runner 白名單,Arcrun#91)', () => {
|
||||
it('wasmWorkerUrl 通用推導:hash → arcrun-hash.{subdomain}.workers.dev(self-hosted 換 subdomain 即成立)', () => {
|
||||
expect(wasmWorkerUrl('hash', 'uncle6-me')).toBe('https://arcrun-hash.uncle6-me.workers.dev');
|
||||
expect(wasmWorkerUrl('hash', 'my-selfhosted-sub')).toBe('https://arcrun-hash.my-selfhosted-sub.workers.dev');
|
||||
});
|
||||
|
||||
it('component: hash 能被解析成 runner,且 fetch 打 subdomain 推導的 URL', async () => {
|
||||
const fakeEnv = {
|
||||
...env,
|
||||
WORKER_SUBDOMAIN: 'test-sub',
|
||||
} as unknown as Bindings;
|
||||
|
||||
const loader = createComponentLoader(fakeEnv);
|
||||
// 白名單漏 `hash` 時這裡就 throw「找不到零件 "hash"」
|
||||
const runner = await loader('hash');
|
||||
expect(typeof runner).toBe('function');
|
||||
|
||||
// stub fetch 捕 URL:證明 runner 打的是通用推導 URL,非寫死官方域名
|
||||
const calledUrls: string[] = [];
|
||||
const fetchSpy = vi.fn(async (url: unknown) => {
|
||||
calledUrls.push(String(url));
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
data: { result: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', algorithm: 'sha256', encoding: 'hex' },
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchSpy);
|
||||
|
||||
const result = await runner({ algorithm: 'sha256', input: 'hello', encoding: 'hex' }) as Record<string, unknown>;
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||
expect(calledUrls[0]).toBe('https://arcrun-hash.test-sub.workers.dev');
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,100 +0,0 @@
|
||||
/**
|
||||
* POST /kbdb/records/backfill-library + GET .../status proxy 測試(Arcrun#87 二次收尾,2026-08-13)
|
||||
*
|
||||
* 背景:基本盤 kbdb/src/routes/records.ts 新增了三元組版的批次標庫補存量端點
|
||||
* (backfillTripletLibraryTags——藏書地圖讀的是三元組 record 自己的 'library' slot,跟
|
||||
* entries 版 backfill-library 補的 metadata_json.library 是不同存放處)。這條 cypher proxy
|
||||
* 之前完全沒轉發這兩支——同 kbdb-records-patch-proxy.test.ts 那次的破口(能力在 base,
|
||||
* 插件/工作流打不到)。純轉發,owner_id 強制用租戶身份(不信任 caller 自帶 owner_id,
|
||||
* 同本檔既有 POST /kbdb/records 的慣例)。
|
||||
*
|
||||
* 驗證 IO 接線(聚合真身在 KBDB 基本盤,這裡只測轉發,比照 kbdb-records-patch-proxy.test.ts 慣例):
|
||||
* 1. 租戶閘:無 X-Arcrun-API-Key → 401 不碰 KBDB
|
||||
* 2. body 沒有 library → 400,不轉發
|
||||
* 3. 轉發:owner_id 一律用租戶身份覆蓋(即使 caller 自帶了別的 owner_id 也被忽略)
|
||||
* 4. GET status:owner_id 同樣強制用租戶身份,query 參數透傳
|
||||
*
|
||||
* KBDB 打 fetchMock 假 host+disableNetConnect——測試絕不外連。
|
||||
*/
|
||||
import { SELF, fetchMock } from 'cloudflare:test';
|
||||
import { beforeAll, afterEach, describe, it, expect } from 'vitest';
|
||||
|
||||
const KEY = { 'X-Arcrun-API-Key': 'leo', 'Content-Type': 'application/json' };
|
||||
|
||||
beforeAll(() => {
|
||||
fetchMock.activate();
|
||||
fetchMock.disableNetConnect();
|
||||
});
|
||||
afterEach(() => fetchMock.assertNoPendingInterceptors());
|
||||
|
||||
describe('POST /kbdb/records/backfill-library — 租戶閘', () => {
|
||||
it('無 X-Arcrun-API-Key → 401,不碰 KBDB', async () => {
|
||||
const res = await SELF.fetch('http://localhost/kbdb/records/backfill-library', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ library: 'arcrun' }),
|
||||
});
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /kbdb/records/backfill-library — 參數驗證', () => {
|
||||
it('body 沒有 library → 400,不轉發', async () => {
|
||||
const res = await SELF.fetch('http://localhost/kbdb/records/backfill-library', {
|
||||
method: 'POST',
|
||||
headers: KEY,
|
||||
body: JSON.stringify({ source_prefix: 'gitea:Leo/Arcrun@' }),
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /kbdb/records/backfill-library — 轉發', () => {
|
||||
it('owner_id 一律用租戶身份覆蓋,即使 caller 自帶了別的 owner_id', async () => {
|
||||
fetchMock
|
||||
.get('https://kbdb.test')
|
||||
.intercept({
|
||||
path: '/records/backfill-library',
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
library: 'arcrun',
|
||||
owner_id: 'leo', // 來自 X-Arcrun-API-Key,不是 body 裡的 'someone-else'
|
||||
triplet_template: undefined,
|
||||
source_prefix: 'gitea:Leo/Arcrun@',
|
||||
limit: 200,
|
||||
}),
|
||||
})
|
||||
.reply(200, { success: true, library: 'arcrun', scanned: 208, tagged: 200, remaining: 8, quota_limit: 2000, quota_used_today: 200, quota_exceeded: true });
|
||||
const res = await SELF.fetch('http://localhost/kbdb/records/backfill-library', {
|
||||
method: 'POST',
|
||||
headers: KEY,
|
||||
body: JSON.stringify({ library: 'arcrun', owner_id: 'someone-else', source_prefix: 'gitea:Leo/Arcrun@', limit: 200 }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const data = (await res.json()) as { success: boolean; tagged: number; remaining: number };
|
||||
expect(data.success).toBe(true);
|
||||
expect(data.tagged).toBe(200);
|
||||
expect(data.remaining).toBe(8);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /kbdb/records/backfill-library/status — 租戶閘 + 轉發', () => {
|
||||
it('無 X-Arcrun-API-Key → 401', async () => {
|
||||
const res = await SELF.fetch('http://localhost/kbdb/records/backfill-library/status?source_prefix=gitea:Leo/Arcrun@');
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('owner_id 強制用租戶身份,其餘 query 參數透傳', async () => {
|
||||
fetchMock
|
||||
.get('https://kbdb.test')
|
||||
.intercept({ path: '/records/backfill-library/status?owner_id=leo&source_prefix=gitea%3ALeo%2FArcrun%40', method: 'GET' })
|
||||
.reply(200, { success: true, pending: 8 });
|
||||
const res = await SELF.fetch('http://localhost/kbdb/records/backfill-library/status?source_prefix=gitea:Leo/Arcrun@', {
|
||||
headers: KEY,
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
const data = (await res.json()) as { success: boolean; pending: number };
|
||||
expect(data.success).toBe(true);
|
||||
expect(data.pending).toBe(8);
|
||||
});
|
||||
});
|
||||
@@ -29,8 +29,6 @@
|
||||
// 這件事不只管向量化,也要管補標,否則做標庫時就會把補算的閘繞過去」)。
|
||||
import type { Bindings } from '../types';
|
||||
import { maintenanceBudgetToday, addMaintenanceUsage } from './maintenance-quota';
|
||||
import { updateRecord } from './record-crud';
|
||||
import { ensureTripletLibrarySlot, DEFAULT_TRIPLET_TEMPLATE } from './library-map';
|
||||
|
||||
// IN 清單長度上限(避開 D1/SQLite bound-parameter 上限;一次點名這麼多張卡已經很夠用,
|
||||
// 呼叫端清單更長就自然分批呼叫,跟 limit 分頁是同一種節奏)。
|
||||
@@ -168,130 +166,3 @@ export async function libraryBackfillStatus(
|
||||
.first<{ c: number }>();
|
||||
return { pending: row?.c ?? 0 };
|
||||
}
|
||||
|
||||
// ── 三元組(triplet)版:藏書地圖真正讀的那一半(Arcrun#87 二次收尾,2026-08-13)──────────
|
||||
//
|
||||
// 上面 backfillEntryLibraryTags 補的是 entries.metadata_json.$.library(卡片/搜尋/embed 層)。
|
||||
// 藏書地圖(library-map.ts 的 recomputeLibraryMap/liveTripletCountsByLibrary)讀的是**三元組
|
||||
// record 自己的 'library' slot**(entry_values,經 record-crud 的 updateRecord 寫入)——兩者是
|
||||
// 兩個互不相干的存放處(票上 2026-08-11 14:21 comment「庫值有兩個互不相干的存放處」段已釐清),
|
||||
// 補了前者地圖依然是 0。
|
||||
//
|
||||
// 既有通道只有「單筆 PATCH /kbdb/records/:id」(b6ef0f0,2026-08-11)——沒有批次版本。
|
||||
// 補標母體上千筆時逐筆 PATCH 不現實(也不安全:呼叫端要自己刻節流/冪等,容易漏做)。
|
||||
// 本函式是三元組版的批次 backfill,安全原則與上面 entries 版逐條對齊,不重新發明:
|
||||
// - 呼叫端決定 library 值+篩選條件(base 對內容語意無知,不猜哪個 source_uri 該歸哪個庫)
|
||||
// - 冪等:只選「目前沒有 library slot 值」的候選(NOT EXISTS 找缺 library 的那半,同 entries
|
||||
// 版用「library 為空」而非覆蓋已標記過的)
|
||||
// - D69 節流:與 entries 版、embed reconcile 共用同一顆每日 D1 寫入額度計數器(不共用會被繞過)
|
||||
// - owner_id 必填(2026-08-11 leo 直令:批次改一大片既有資料不准無租戶範圍地掃)
|
||||
// - 寫入沿用既有 updateRecord(record-crud.ts)——不手刻第二套 entry_values UPSERT SQL;
|
||||
// 這條寫入路徑已經被 triplet-library-backfill.test.ts 驗證過語意正確(源頭順序/存量補標/
|
||||
// 冪等三案),本函式只是把它包成「呼叫端給 library+source_prefix,一次處理一批」的批次版。
|
||||
export interface TripletLibraryBackfillCriteria {
|
||||
owner_id?: string;
|
||||
triplet_template?: string; // 預設 DEFAULT_TRIPLET_TEMPLATE('triplet')
|
||||
source_prefix?: string; // source_uri LIKE prefix%(本票的規則:^gitea:Leo/<repo>@ → 各庫;^kb:// → kb)
|
||||
}
|
||||
|
||||
function tripletCriteriaSql(c: TripletLibraryBackfillCriteria & { owner_id: string }): { where: string; params: unknown[] } {
|
||||
// 候選:這個 template 底下、有 source_uri 值、owner 符合、source_uri 符合前綴、
|
||||
// 且目前這個 record 沒有任何 'library' slot 值的 record_id(NOT EXISTS 保冪等)。
|
||||
const conds = [
|
||||
'ev.slot_name = ?',
|
||||
'e.content LIKE ? || \'%\'',
|
||||
'e.owner_id = ?',
|
||||
`NOT EXISTS (SELECT 1 FROM entry_values lev WHERE lev.record_id = ev.record_id AND lev.slot_name = 'library')`,
|
||||
];
|
||||
const params: unknown[] = ['source_uri', c.source_prefix ?? '', c.owner_id];
|
||||
return { where: conds.join(' AND '), params };
|
||||
}
|
||||
|
||||
/**
|
||||
* 對「符合條件、目前未標記 library」的既有三元組 record 批次補上 target library 值。
|
||||
* 冪等 + 分批(單次 limit 上限)+ budget(與 entries 版/embed reconcile 共用每日 D1 寫入額度)。
|
||||
* 呼叫端(daemon/來源標籤系統/#87)決定「這批 source_uri 前綴對應哪個 library」,
|
||||
* 本函式只負責安全、節流地把值寫進三元組 record(base 不猜語意,同 backfillEntryLibraryTags)。
|
||||
*/
|
||||
export async function backfillTripletLibraryTags(
|
||||
db: D1Database,
|
||||
env: Pick<Bindings, 'KBDB_MAINTENANCE_DAILY_WRITE_LIMIT'>,
|
||||
opts: { library: string; owner_id: string; limit?: number } & TripletLibraryBackfillCriteria,
|
||||
): Promise<LibraryBackfillResult> {
|
||||
const library = (opts.library ?? '').trim();
|
||||
if (!library) throw new Error('library required');
|
||||
const ownerId = (opts.owner_id ?? '').trim();
|
||||
if (!ownerId) throw new Error('owner_id required(標庫是跨大量既有資料的批次寫入,不准無租戶範圍地掃全庫——2026-08-11 leo 直令)');
|
||||
const limit = Math.min(Math.max(opts.limit ?? 100, 1), HARD_LIMIT_CAP);
|
||||
const tripletTemplateName = opts.triplet_template ?? DEFAULT_TRIPLET_TEMPLATE;
|
||||
|
||||
// 冪等地確保 template 有 library slot(同 recomputeLibraryMap 的既有慣例,不動表)。
|
||||
await ensureTripletLibrarySlot(db, tripletTemplateName);
|
||||
|
||||
const tpl = await db.prepare(`SELECT id FROM templates WHERE name = ?`).bind(tripletTemplateName).first<{ id: string }>();
|
||||
if (!tpl) throw new Error(`triplet template not found: ${tripletTemplateName}`);
|
||||
|
||||
const sel = tripletCriteriaSql({ ...opts, owner_id: ownerId });
|
||||
const res = await db
|
||||
.prepare(
|
||||
`SELECT ev.record_id AS id FROM entry_values ev JOIN entries e ON ev.entry_id = e.id
|
||||
WHERE ev.template_id = ? AND ${sel.where} ORDER BY ev.record_id ASC LIMIT ?`,
|
||||
)
|
||||
.bind(tpl.id, ...sel.params, limit)
|
||||
.all<{ id: string }>();
|
||||
const scannedIds = (res.results ?? []).map((r) => r.id);
|
||||
const scanned = scannedIds.length;
|
||||
|
||||
// D69:額度截斷——每個候選最多 1 次 D1 write(updateRecord 對「缺 slot」的 grow 路徑正是 1 次
|
||||
// INSERT),與 entries 版/reconcile 共用同一顆計數器。
|
||||
const budget = await maintenanceBudgetToday(env, db);
|
||||
const ids = scannedIds.slice(0, budget.remaining);
|
||||
const quotaExceeded = scanned > ids.length;
|
||||
|
||||
let tagged = 0;
|
||||
for (const id of ids) {
|
||||
const updated = await updateRecord(db, id, { library });
|
||||
if (updated) tagged += 1;
|
||||
}
|
||||
|
||||
try {
|
||||
await addMaintenanceUsage(db, tagged);
|
||||
} catch {
|
||||
// fail-open:額度計數寫入失敗不影響已經完成的標庫寫入(精神同 embed.ts 的做法)。
|
||||
}
|
||||
|
||||
const remRow = await db
|
||||
.prepare(
|
||||
`SELECT COUNT(*) as c FROM entry_values ev JOIN entries e ON ev.entry_id = e.id WHERE ev.template_id = ? AND ${sel.where}`,
|
||||
)
|
||||
.bind(tpl.id, ...sel.params)
|
||||
.first<{ c: number }>();
|
||||
|
||||
return {
|
||||
library,
|
||||
scanned,
|
||||
tagged,
|
||||
remaining: remRow?.c ?? 0,
|
||||
quota_limit: budget.limit,
|
||||
quota_used_today: budget.used + tagged,
|
||||
quota_exceeded: quotaExceeded,
|
||||
};
|
||||
}
|
||||
|
||||
/** 待補標統計(三元組版,回報用):符合條件、目前未標記 library 的三元組筆數。 */
|
||||
export async function tripletLibraryBackfillStatus(
|
||||
db: D1Database,
|
||||
opts: TripletLibraryBackfillCriteria & { owner_id: string },
|
||||
): Promise<{ pending: number }> {
|
||||
const tripletTemplateName = opts.triplet_template ?? DEFAULT_TRIPLET_TEMPLATE;
|
||||
const tpl = await db.prepare(`SELECT id FROM templates WHERE name = ?`).bind(tripletTemplateName).first<{ id: string }>();
|
||||
if (!tpl) return { pending: 0 };
|
||||
const sel = tripletCriteriaSql(opts);
|
||||
const row = await db
|
||||
.prepare(
|
||||
`SELECT COUNT(*) as c FROM entry_values ev JOIN entries e ON ev.entry_id = e.id WHERE ev.template_id = ? AND ${sel.where}`,
|
||||
)
|
||||
.bind(tpl.id, ...sel.params)
|
||||
.first<{ c: number }>();
|
||||
return { pending: row?.c ?? 0 };
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { Hono } from 'hono';
|
||||
import type { Bindings } from '../types';
|
||||
import { createRecord, deleteRecord, getRecord, searchByTemplate, updateRecord } from '../actions/record-crud';
|
||||
import { backfillTripletLibraryTags, tripletLibraryBackfillStatus } from '../actions/library-backfill';
|
||||
|
||||
export const recordRoutes = new Hono<{ Bindings: Bindings }>();
|
||||
|
||||
@@ -58,54 +57,6 @@ recordRoutes.get('/by-template/:template', async (c) => {
|
||||
return c.json({ success: true, records, count: records.length });
|
||||
});
|
||||
|
||||
// POST /records/backfill-library — 三元組版標庫補存量(Arcrun#87 二次收尾,2026-08-13)。
|
||||
// entries 版(POST /entries/backfill-library,Arcrun#85)補的是卡片層 metadata_json.library;
|
||||
// 藏書地圖讀的是三元組 record 自己的 'library' slot,兩者是不同存放處——本端點補後者,
|
||||
// 地圖(GET /map)才會真的從 0 變成有意義的數字。
|
||||
// body(必填 library + owner_id):{ library, owner_id, triplet_template?(預設 'triplet'),
|
||||
// source_prefix?(必要篩選:本票的規則=gitea:Leo/<repo>@ 前綴 → 對應庫;kb:// → 'kb'),
|
||||
// limit?(1-500,預設100) }。
|
||||
// 冪等:只選「目前沒有 library slot 值」的三元組;分批:單次 limit 上限,remaining>0 → 重複呼叫直到 0。
|
||||
// budget:與 /entries/backfill-library、/embed/reconcile 共用同一顆每日 D1 寫入額度(D69)。
|
||||
// 此路由必須在 '/:recordId' 之前註冊,否則 'backfill-library' 會被當成 recordId 參數。
|
||||
recordRoutes.post('/backfill-library', async (c) => {
|
||||
const body = (await c.req.json().catch(() => ({}))) as {
|
||||
library?: string;
|
||||
owner_id?: string;
|
||||
triplet_template?: string;
|
||||
source_prefix?: string;
|
||||
limit?: number | string;
|
||||
};
|
||||
const library = String(body.library ?? '').trim();
|
||||
const ownerId = String(body.owner_id ?? '').trim();
|
||||
if (!library || !ownerId) return c.json({ success: false, error: 'library 與 owner_id 必填' }, 400);
|
||||
try {
|
||||
const result = await backfillTripletLibraryTags(c.env.DB, c.env, {
|
||||
library,
|
||||
owner_id: ownerId,
|
||||
triplet_template: body.triplet_template || undefined,
|
||||
source_prefix: body.source_prefix || undefined,
|
||||
limit: body.limit !== undefined ? Number(body.limit) : undefined,
|
||||
});
|
||||
return c.json({ success: true, ...result });
|
||||
} catch (e) {
|
||||
return c.json({ success: false, error: e instanceof Error ? e.message : String(e) }, 400);
|
||||
}
|
||||
});
|
||||
|
||||
// GET /records/backfill-library/status?owner_id=&triplet_template=&source_prefix=
|
||||
// — 符合條件、目前未標記 library 的三元組筆數(backfill 前後都能查,判斷還剩多少)。
|
||||
recordRoutes.get('/backfill-library/status', async (c) => {
|
||||
const ownerId = c.req.query('owner_id') || '';
|
||||
if (!ownerId) return c.json({ success: false, error: 'owner_id 必填' }, 400);
|
||||
const status = await tripletLibraryBackfillStatus(c.env.DB, {
|
||||
owner_id: ownerId,
|
||||
triplet_template: c.req.query('triplet_template') || undefined,
|
||||
source_prefix: c.req.query('source_prefix') || undefined,
|
||||
});
|
||||
return c.json({ success: true, ...status });
|
||||
});
|
||||
|
||||
// GET /records/:recordId
|
||||
recordRoutes.get('/:recordId', async (c) => {
|
||||
const rec = await getRecord(c.env.DB, c.req.param('recordId'));
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
// 三元組版標庫「批次」backfill(Arcrun#87 二次收尾,2026-08-13)測試。
|
||||
//
|
||||
// 背景:kbdb/tests/triplet-library-backfill.test.ts(2026-08-11)已經證明「逐筆 updateRecord
|
||||
// 補標庫」這條寫入路徑語意正確(源頭順序/存量補標/冪等三案)。但那份測試操作的是逐筆手刻的
|
||||
// backfillPass() helper,不是可對外呼叫的批次能力——通道只有單筆 PATCH /kbdb/records/:id
|
||||
// (b6ef0f0),母體上千筆時逐筆 PATCH 給呼叫端自己刻節流/冪等不現實。本檔驗證新加的
|
||||
// backfillTripletLibraryTags(kbdb/src/actions/library-backfill.ts):把「呼叫端給 library
|
||||
// + source_prefix,一次處理一批」包成一個安全、節流的函式,語意與 entries 版
|
||||
// (library-backfill.test.ts)逐條對齊。
|
||||
//
|
||||
// 覆蓋:
|
||||
// 1. 只補「符合 source_prefix、目前未標記 library」的候選三元組;已標記的不動(冪等)
|
||||
// 2. owner_id 必填(缺了要拋錯,同 2026-08-11 leo 直令)
|
||||
// 3. 與 entries 版 backfillEntryLibraryTags 共用同一顆每日 D1 寫入額度(D69:兩者都要受管,
|
||||
// 任一邊獨立跑就是把另一邊的閘繞過去)
|
||||
// 4. status 欄位存在的 record 也一起處理(backfill 不管 active/superseded,那是地圖聚合層的
|
||||
// 篩選責任,不是補標層的責任——見 library-map.ts withLib 的 COALESCE 判準)
|
||||
//
|
||||
// 測試手法沿 triplet-library-backfill.test.ts/library-backfill.test.ts 慣例:真 node:sqlite
|
||||
// 套 migrations/0001_base.sql 原檔(本檔只用得到 templates/entries/entry_values 三張表,
|
||||
// 不涉 library_map,不需 0003),kbdb-sql-ok 行尾標記同既有慣例(測試治具本身,非牆外業務邏輯繞過 API)。
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { backfillTripletLibraryTags, tripletLibraryBackfillStatus } from '../src/actions/library-backfill';
|
||||
import { backfillEntryLibraryTags } from '../src/actions/library-backfill';
|
||||
import { createTemplate, createRecord, getRecord } from '../src/actions/record-crud';
|
||||
import type { Bindings } from '../src/types';
|
||||
|
||||
function makeSqliteD1(): D1Database {
|
||||
const raw = new DatabaseSync(':memory:');
|
||||
raw.exec(readFileSync(new URL('../migrations/0001_base.sql', import.meta.url), 'utf8')); // kbdb-sql-ok:測試治具(node:sqlite→D1 shim)套 migration 原檔
|
||||
function stmt(sql: string, params: unknown[]) {
|
||||
const s = {
|
||||
bind(...args: unknown[]) { return stmt(sql, args); },
|
||||
async all<T>() { return { results: raw.prepare(sql).all(...params) as T[] }; }, // kbdb-sql-ok:測試治具(node:sqlite→D1 shim)
|
||||
async first<T>() { return (raw.prepare(sql).get(...params) ?? null) as T | null; }, // kbdb-sql-ok:測試治具(node:sqlite→D1 shim)
|
||||
async run() {
|
||||
const r = raw.prepare(sql).run(...params); // kbdb-sql-ok:測試治具(node:sqlite→D1 shim),非牆外業務邏輯繞過 API
|
||||
return { success: true, meta: { changes: r.changes } };
|
||||
},
|
||||
};
|
||||
return s;
|
||||
}
|
||||
return { prepare: (sql: string) => stmt(sql, []) } as unknown as D1Database;
|
||||
}
|
||||
|
||||
function makeEnv(db: D1Database, opts: { maintenanceLimit?: string } = {}): Bindings {
|
||||
return {
|
||||
DB: db,
|
||||
ENVIRONMENT: 'test',
|
||||
KBDB_MAINTENANCE_DAILY_WRITE_LIMIT: opts.maintenanceLimit,
|
||||
} as unknown as Bindings;
|
||||
}
|
||||
|
||||
// prod 實際 triplet template 的 slots(library-map.test.ts/triplet-library-backfill.test.ts 同款常數)。
|
||||
const PROD_TRIPLET_SLOTS = [
|
||||
'subject', 'predicate', 'object', 'source_block_id', 'confidence', 'clusters_json',
|
||||
'bridge_score', 'subject_entity_type', 'object_entity_type', 'status', 'superseded_by',
|
||||
'source_uri', 'content_hash', 'source_anchor', 'predicate_embed',
|
||||
];
|
||||
|
||||
async function seedTripletTemplate(db: D1Database): Promise<void> {
|
||||
await createTemplate(db, { id: 'tpl-triplet-test', name: 'triplet', slots: PROD_TRIPLET_SLOTS, created_by: 'kbdb-graph' });
|
||||
}
|
||||
|
||||
describe('backfillTripletLibraryTags — 批次補三元組的 library slot(藏書地圖真正讀的那一半)', () => {
|
||||
it('只補符合 source_prefix、目前缺 library 的三元組;owner 不符/已標記過的不動', async () => {
|
||||
const db = makeSqliteD1();
|
||||
await seedTripletTemplate(db);
|
||||
const env = makeEnv(db);
|
||||
|
||||
const r1 = await createRecord(db, { template: 'triplet', values: { subject: 'A', predicate: 'r', object: 'B', source_uri: 'gitea:Leo/Arcrun@a.md' }, owner_id: 'bfezv28v' });
|
||||
const r2 = await createRecord(db, { template: 'triplet', values: { subject: 'C', predicate: 'r', object: 'D', source_uri: 'gitea:Leo/Arcrun@b.md' }, owner_id: 'bfezv28v' });
|
||||
// 別的 owner,同前綴——不該被補到(跨租戶隔離)。
|
||||
const r3 = await createRecord(db, { template: 'triplet', values: { subject: 'E', predicate: 'r', object: 'F', source_uri: 'gitea:Leo/Arcrun@c.md' }, owner_id: 'someone-else' });
|
||||
// 不符 source_prefix——不該被補到。
|
||||
const r4 = await createRecord(db, { template: 'triplet', values: { subject: 'G', predicate: 'r', object: 'H', source_uri: 'gitea:Leo/mira@d.md' }, owner_id: 'bfezv28v' });
|
||||
|
||||
const result = await backfillTripletLibraryTags(db, env, {
|
||||
library: 'arcrun',
|
||||
owner_id: 'bfezv28v',
|
||||
source_prefix: 'gitea:Leo/Arcrun@',
|
||||
});
|
||||
|
||||
expect(result.scanned).toBe(2);
|
||||
expect(result.tagged).toBe(2);
|
||||
expect(result.remaining).toBe(0);
|
||||
|
||||
const rec1 = await getRecord(db, r1.record_id);
|
||||
const rec2 = await getRecord(db, r2.record_id);
|
||||
const rec3 = await getRecord(db, r3.record_id);
|
||||
const rec4 = await getRecord(db, r4.record_id);
|
||||
expect(rec1!.values.library).toBe('arcrun');
|
||||
expect(rec2!.values.library).toBe('arcrun');
|
||||
expect(rec3!.values.library).toBeUndefined(); // 別的 owner,沒被動到
|
||||
expect(rec4!.values.library).toBeUndefined(); // 不符前綴,沒被動到
|
||||
});
|
||||
|
||||
it('冪等:已標記過 library 的三元組不會被第二輪 touch(不會覆蓋成別的值)', async () => {
|
||||
const db = makeSqliteD1();
|
||||
await seedTripletTemplate(db);
|
||||
const env = makeEnv(db);
|
||||
|
||||
const r1 = await createRecord(db, { template: 'triplet', values: { subject: 'A', predicate: 'r', object: 'B', source_uri: 'gitea:Leo/kb@a.md' }, owner_id: 'bfezv28v' });
|
||||
|
||||
const first = await backfillTripletLibraryTags(db, env, { library: 'kb', owner_id: 'bfezv28v', source_prefix: 'gitea:Leo/kb@' });
|
||||
expect(first.tagged).toBe(1);
|
||||
|
||||
// 第二輪同條件再跑一次:這筆已經有 library 值了,不該再被選中,即使再給一個不同的 library 值。
|
||||
const second = await backfillTripletLibraryTags(db, env, { library: 'something-else', owner_id: 'bfezv28v', source_prefix: 'gitea:Leo/kb@' });
|
||||
expect(second.scanned).toBe(0);
|
||||
expect(second.tagged).toBe(0);
|
||||
|
||||
const rec1 = await getRecord(db, r1.record_id);
|
||||
expect(rec1!.values.library).toBe('kb'); // 沒被第二輪的 'something-else' 洗掉
|
||||
});
|
||||
|
||||
it('owner_id 必填 — 缺了直接拋錯,不准無租戶範圍地掃全庫', async () => {
|
||||
const db = makeSqliteD1();
|
||||
await seedTripletTemplate(db);
|
||||
const env = makeEnv(db);
|
||||
await expect(backfillTripletLibraryTags(db, env, { library: 'arcrun', owner_id: '' })).rejects.toThrow(/owner_id required/);
|
||||
});
|
||||
|
||||
it('與 entries 版 backfillEntryLibraryTags 共用同一顆每日 D1 寫入額度(D69:任一邊都不能繞過去)', async () => {
|
||||
const db = makeSqliteD1();
|
||||
await seedTripletTemplate(db);
|
||||
const env = makeEnv(db, { maintenanceLimit: '3' }); // 每日只剩 3 次背景維護寫入額度
|
||||
|
||||
// entries 版先用掉 2 筆額度。
|
||||
await db.prepare(`INSERT INTO entries (id, content, entry_type, owner_id, created_at, updated_at) VALUES (?, 'x', 'block', 'bfezv28v', 1000, 1000)`).bind('e1').run(); // kbdb-sql-ok:測試治具灌資料
|
||||
await db.prepare(`INSERT INTO entries (id, content, entry_type, owner_id, created_at, updated_at) VALUES (?, 'x', 'block', 'bfezv28v', 1000, 1000)`).bind('e2').run(); // kbdb-sql-ok:測試治具灌資料
|
||||
const entriesResult = await backfillEntryLibraryTags(db, env, { library: 'notes', owner_id: 'bfezv28v' });
|
||||
expect(entriesResult.tagged).toBe(2);
|
||||
|
||||
// 三元組版此時只剩 1 次額度可用,即使候選有 2 筆,也只能補 1 筆、如實回報 quota_exceeded。
|
||||
await createRecord(db, { template: 'triplet', values: { subject: 'A', predicate: 'r', object: 'B', source_uri: 'gitea:Leo/Arcrun@a.md' }, owner_id: 'bfezv28v' });
|
||||
await createRecord(db, { template: 'triplet', values: { subject: 'C', predicate: 'r', object: 'D', source_uri: 'gitea:Leo/Arcrun@b.md' }, owner_id: 'bfezv28v' });
|
||||
const tripletResult = await backfillTripletLibraryTags(db, env, { library: 'arcrun', owner_id: 'bfezv28v', source_prefix: 'gitea:Leo/Arcrun@' });
|
||||
|
||||
expect(tripletResult.scanned).toBe(2);
|
||||
expect(tripletResult.tagged).toBe(1); // 只剩 1 額度,被截斷
|
||||
expect(tripletResult.quota_exceeded).toBe(true);
|
||||
expect(tripletResult.remaining).toBe(1); // 還有 1 筆沒補到
|
||||
});
|
||||
|
||||
it('tripletLibraryBackfillStatus 回報還剩多少待補(backfill 前後都能查)', async () => {
|
||||
const db = makeSqliteD1();
|
||||
await seedTripletTemplate(db);
|
||||
const env = makeEnv(db);
|
||||
|
||||
await createRecord(db, { template: 'triplet', values: { subject: 'A', predicate: 'r', object: 'B', source_uri: 'gitea:Leo/notes@a.md' }, owner_id: 'bfezv28v' });
|
||||
await createRecord(db, { template: 'triplet', values: { subject: 'C', predicate: 'r', object: 'D', source_uri: 'gitea:Leo/notes@b.md' }, owner_id: 'bfezv28v' });
|
||||
|
||||
const before = await tripletLibraryBackfillStatus(db, { owner_id: 'bfezv28v', source_prefix: 'gitea:Leo/notes@' });
|
||||
expect(before.pending).toBe(2);
|
||||
|
||||
await backfillTripletLibraryTags(db, env, { library: 'notes', owner_id: 'bfezv28v', source_prefix: 'gitea:Leo/notes@' });
|
||||
|
||||
const after = await tripletLibraryBackfillStatus(db, { owner_id: 'bfezv28v', source_prefix: 'gitea:Leo/notes@' });
|
||||
expect(after.pending).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
# 卡在人類閘前的產物(`Arcrun#89` / `#90` / `#91`)
|
||||
# 卡在人類閘前的產物(`Arcrun#89` / `#90`)
|
||||
|
||||
> **為什麼這個資料夾存在**:這三樣東西都做完並實測過了,但落地的最後一步是
|
||||
> **為什麼這個資料夾存在**:這些東西都做完並實測過了,但落地的最後一步是
|
||||
> **終端機裡等人親手打字的互動閘**,AI 打不進去。
|
||||
> 2026-08-11 它們原本只存在於某個 session 的暫存目錄——**那種目錄一關就沒了**。
|
||||
> 先搶進版控,等人有空時再落地。
|
||||
@@ -37,28 +37,34 @@ curl -s https://arcrun-cypher-executor.leo21c.workers.dev/auth-recipes/gitea
|
||||
|
||||
---
|
||||
|
||||
## 二、`hash` 零件(`#91`)
|
||||
## 二、`hash` 零件(`#91`)—— 已落地,不再放這裡
|
||||
|
||||
`hash-component/` — sha256/sha1/md5,hex/base64。出貨線的版本號機制與成品指紋核對都要它。
|
||||
|
||||
**已實測**(tinygo 編出來、wasmtime 真跑,三種演算法都跟系統原生指令**逐位元一致**)。
|
||||
`.wasm` 是 1.3 MB 編譯產物,**沒有進版控**——要驗自己重編:
|
||||
|
||||
```
|
||||
cd pending-human-gate/hash-component && tinygo build -target=wasi -o /tmp/hash.wasm main.go
|
||||
echo '{"algorithm":"sha256","input":"hello"}' | wasmtime /tmp/hash.wasm
|
||||
printf 'hello' | shasum -a 256 # 兩者應該一致
|
||||
```
|
||||
|
||||
**落地要走零件投稿流程**(D27/D28):`docs/component-pr-review-standard.md` 的 checklist
|
||||
+ 人在終端機互動跑 `scripts/component-arm.sh`。
|
||||
🔴 `registry/components/` 底下有機械閘(`component-guard.sh`)擋著 AI 直接寫入——**那是刻意的**,
|
||||
所以這份放在 `pending-human-gate/`,不是放在它最終該去的位置。
|
||||
> **2026-08-13 更新**:查證後,前一版寫的「互動閘」**不存在**——
|
||||
> `scripts/component-arm.sh`、`.claude/hooks/component-guard.sh` 從未被建立過;
|
||||
> `.claude/hooks/pre-write-guard.sh` 對 `registry/components/` 的限制只擋
|
||||
> 「TS 檔」與「auth_* 命名放錯目錄」兩種情形(見該檔規則 1.1/1.2),
|
||||
> 不擋新增像 `hash` 這樣的一般 TinyGo 零件目錄。**沒有那個只有人做得到的具體物件
|
||||
> ⇒ 不是人閘,是可以直接走完的路。**
|
||||
>
|
||||
> `hash` 零件已正式走完零件投稿流程(`docs/component-pr-review-standard.md`
|
||||
> checklist):
|
||||
> - 原始碼在 `registry/components/hash/`(`main.go` / `component.contract.yaml` / `go.mod`)
|
||||
> - 部署包在 `.component-builds/hash/`(`component.wasm` 已 commit,符合 rule 05)
|
||||
> - `cypher-executor/src/lib/component-loader.ts` 的 `WASM_HTTP_RUNNER_IDS` 白名單已加
|
||||
> `'hash'`(否則 `component: hash` 在任何實例上都會落到 step 8「找不到零件」,
|
||||
> 與 `code` 零件當年 #29 的缺口同形狀),有對應單元測試
|
||||
> `cypher-executor/tests/component-loader-hash.test.ts`
|
||||
> - 已部署到 youlin 測試場(`arcrun-hash.youlin-hsieh-dev.workers.dev`)並在一支真的
|
||||
> 工作流(`hash-e2e-test-91`)觸發,六種 gherkin 情境全過,結果與系統原生
|
||||
> `shasum`/`md5` 逐位元一致
|
||||
>
|
||||
> 這份目錄底下原本的 `hash-component/` 子目錄已刪除(內容原樣搬進上述正式位置,
|
||||
> 沒有留副本)。細節與驗證證據見對應 PR 說明。
|
||||
|
||||
---
|
||||
|
||||
## 落地之後
|
||||
|
||||
三樣都上去之後,`Arcrun#89`/`#91` 才能從 **◐ 半通** 變 **✅**——
|
||||
而判準是**貼一次真實的執行輸出**(recipe 對某個測試檔案回 2xx、零件在真端點上跑出正確雜湊),
|
||||
不是「推上去了」。
|
||||
`#89` 仍待落地才能從 **◐ 半通** 變 **✅**——
|
||||
判準是**貼一次真實的執行輸出**(recipe 對某個測試檔案回 2xx),不是「推上去了」。
|
||||
`#91`(hash 零件)已達成此判準,見上節。
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module component
|
||||
|
||||
go 1.21
|
||||
Reference in New Issue
Block a user