diff --git a/package-lock.json b/package-lock.json index 114762b90..406d54f9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -593,6 +593,166 @@ "dev": true, "license": "MIT OR Apache-2.0" }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", + "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.2.tgz", + "integrity": "sha512-JBP1+Z7ZSNG/d4mRP+y8VC5dka3tZVMLEZRvS+rzQ4DGV1EoxRFQckcJTTkXbHSQiTj0DtNI01Zwb/V2fX0mvQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": ">1.20260305.0 <2.0.0-0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/vitest-plugin": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@cloudflare/vitest-plugin/-/vitest-plugin-1.4.4.tgz", + "integrity": "sha512-nXNIlPiLvYFx/AH4/Bb/yntVkeloYcf1+tBi3l6Gp3TDCDtLxXAugrUtO7eTOWbHP6UIx5f9npy05OrACbZ5wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cjs-module-lexer": "1.2.3", + "esbuild": "0.28.2", + "miniflare": "5.20261012.1-beta", + "wrangler": "4.151.1", + "zod": "4.6.5" + }, + "peerDependencies": { + "vitest": "^4.1.0 || ^5.0.0" + } + }, + "node_modules/@cloudflare/vitest-plugin/node_modules/zod": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20261012.1.tgz", + "integrity": "sha512-dkTL9aY3V4D40b+jBpGPCistWUPFQ/M61wlWi6i1o8MvHFFgEw7EQkcaYEv6Yd3bkPDD/3TFT4hRsMb2xVZSwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20261012.1.tgz", + "integrity": "sha512-2/nVGcKXjNNI+DhxKSepTKvFng9H7aN2WXv6Zu67iqgI6JrxypNt5kTPv4ZHosxrcfyKMci/2RfN7GIrN0BitQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20261012.1.tgz", + "integrity": "sha512-uMe7ONHnLxStD1Ye0YFHBO36uj4cStv5P53rW+5GVWXFT8BRjqVixlH0IqCAHe1Pmcwyd1+7fVoGde89MZSCJg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20261012.1.tgz", + "integrity": "sha512-I0VMdcXk7ygG5vYDce0ePTBFdnBXC0i9WiBY5mWiv0iTinbfea6/rHqSQ7B1UMpjgrYVs9E0m4bZ9ufsk5GCFA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20261012.1.tgz", + "integrity": "sha512-c5hQvNx574UEoknaCFvMa/NdLrrYiALNbe6sx1okW8uOiRYWxrFuA8WpXFnBJ5XJypm0TEAHYaUTGv3rRLpY7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "5.20261016.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-5.20261016.1.tgz", + "integrity": "sha512-I8OJdPl3M6TIl9ksU3wtEyWVhSE8E8txJ/vGOv3SkhjcvN8uMjyFmMvRsNOZgzMe5xYFQ0LctmiGqTwR7DbZ6w==", + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "peer": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@cto.af/wtf8": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/@cto.af/wtf8/-/wtf8-0.0.5.tgz", @@ -709,6 +869,40 @@ "resolved": "packages/tui", "link": true }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", @@ -1175,6 +1369,543 @@ } } }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1182,6 +1913,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", @@ -1217,6 +1959,35 @@ "integrity": "sha512-bQY06wzzR8D2+vVCUoBsr5QS2U6UgPUQRmErNwtsuI6vLcyRKkafjkr3KxbtGFf9aBBIV2mcvlsKD1UYaIV+sg==", "license": "MIT" }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "dev": true, + "license": "MIT" + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1497,6 +2268,17 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", @@ -1550,6 +2332,19 @@ "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", "license": "Apache-2.0" }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@smithy/core": { "version": "3.33.3", "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.33.3.tgz", @@ -1631,6 +2426,13 @@ "node": ">=18.0.0" } }, + "node_modules/@speed-highlight/core": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.24.tgz", + "integrity": "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/@stablelib/base64": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", @@ -2386,6 +3188,13 @@ "readable-stream": "^3.4.0" } }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, "node_modules/bowser": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", @@ -2507,6 +3316,13 @@ "dev": true, "license": "ISC" }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true, + "license": "MIT" + }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -2562,6 +3378,20 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cpu-features": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", @@ -2680,6 +3510,16 @@ "once": "^1.4.0" } }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", @@ -3300,6 +4140,16 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -3629,6 +4479,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/miniflare": { + "version": "5.20261012.1-beta", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20261012.1-beta.tgz", + "integrity": "sha512-8Canhy6g9+vAhqMc0o80YphqYIwpiA3aAp8aX+uDXU7J/qbUeoZzcII9c2V1P4uT/eHD4SB93qet1o1bRY3BuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "0.35.5", + "undici": "7.29.1", + "workerd": "1.20261012.1", + "ws": "8.21.0", + "youch": "4.1.0-beta.10" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/miniflare/node_modules/undici": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz", + "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/minimatch": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", @@ -3936,6 +4814,13 @@ "dev": true, "license": "MIT" }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -4279,6 +5164,56 @@ "node": ">=10" } }, + "node_modules/sharp": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4478,6 +5413,19 @@ "node": ">=0.10.0" } }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -4683,6 +5631,16 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4966,6 +5924,63 @@ "node": ">=8" } }, + "node_modules/workerd": { + "version": "1.20261012.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20261012.1.tgz", + "integrity": "sha512-tEHoMPsMga0seVjxfsR8Pd3f8IRqYwkq++dTUKI5Gdgj6zuSDluYwisydq+dn90SsIACwBquJUQ1nBpcOqaWTA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20261012.1", + "@cloudflare/workerd-darwin-arm64": "1.20261012.1", + "@cloudflare/workerd-linux-64": "1.20261012.1", + "@cloudflare/workerd-linux-arm64": "1.20261012.1", + "@cloudflare/workerd-windows-64": "1.20261012.1" + } + }, + "node_modules/wrangler": { + "version": "4.151.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.151.1.tgz", + "integrity": "sha512-OAsa9UAHZshM6mq2PXcVoyxXgWThAanZMBc/IirtqEWd/BauRtmUKzAniOu/pU0pd3pxKuMZ2vsbF4eo6RiNDA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.5.0", + "@cloudflare/unenv-preset": "2.16.2", + "blake3-wasm": "2.1.5", + "esbuild": "0.28.2", + "miniflare": "5.20261012.1-beta", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20261012.1" + }, + "bin": { + "cf-wrangler": "bin/cf-wrangler.js", + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=22.0.0" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^5.20261006.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5009,6 +6024,31 @@ "url": "https://github.com/sponsors/eemeli" } }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", @@ -5308,6 +6348,7 @@ "typebox": "1.3.27" }, "devDependencies": { + "@cloudflare/vitest-plugin": "1.4.4", "shx": "0.3.4", "vitest": "4.1.11" }, diff --git a/packages/durable/CHANGELOG.md b/packages/durable/CHANGELOG.md index 20c287bf8..c2c44bfe4 100644 --- a/packages/durable/CHANGELOG.md +++ b/packages/durable/CHANGELOG.md @@ -12,6 +12,7 @@ - `settings.compaction.modelOverrides` sets compaction thresholds per model, keyed by exact `"provider/modelId"`, so one harness can serve small- and large-window models; the threshold check, overflow recovery, and the compaction task use the entry of the model they act for ([#10942](https://github.com/earendil-works/pi/issues/10942)). - Durable sleeps: a sleeping task holds no invocation, survives restarts, and resumes at its checkpoint once the Harness clock reaches `until`. Generation retry backoffs, deferred-response polls, and compaction retry backoffs sleep this way; a record that slept inside a running phase sleeps again when it resumes early. - `HarnessOptions.setTimer` arms every timer of the Session and Harness (progress throttles, idle-memory eviction, sleeps), and `ToolExecutionApi.now()` reads the Harness clock, which code mode now uses for its deadlines; with `now`, a Harness runs entirely on a host or fake clock. +- Tests that run in workerd (`npm run test:workers`): the storage conformance suite and the schema migration on real Durable Object SQLite, a sleep resumed by the object's alarm after an eviction, and a turn recovered through the recovery alarm after an eviction mid-stream. - `Harness.wake()` and `HarnessOptions.onWake` tell a host when the Session next needs a process: `active` while work can run, `at` for the earliest sleep. With `onWake`, the Harness keeps no timer for sleeps, so a Cloudflare Durable Object can hibernate and wake on its alarm, calling `resume()`. - `HarnessOptions.models` and `HarnessOptions.settings` also accept a function returning the current value, like `env`. Generation, compaction, hooks and tools call it at each use (`api.models` reads the current value at each access), so a host can replace its model configuration or settings without reopening the Harness. A plain object still works. @@ -23,6 +24,7 @@ ### Fixed +- In Cloudflare Workers with `nodejs_compat`, the timers that evict idle documents and contexts were armed, since timers there have an `unref()`; a pending one kept a Durable Object from hibernating and blocked its eviction. They are now declined in Workers, and memory is freed at later commits. - The README's Errors section now limits the `StorageRequestError` exemption to reads: a `StorageRequestError` thrown by `commit()` or `mintId()` fails the Harness like any other storage error ([#11014](https://github.com/earendil-works/pi/issues/11014)). ## [1.3.0] - 2026-10-15 diff --git a/packages/durable/README.md b/packages/durable/README.md index 797930eed..257a3b439 100644 --- a/packages/durable/README.md +++ b/packages/durable/README.md @@ -187,7 +187,7 @@ export class Session extends DurableObject { } ``` -The default `setTimer` declines the timers that only free memory where timers cannot be unreferenced, as in Workers, so no pending timer keeps the object from hibernating. +The default `setTimer` declines the timers that only free memory in Workers, so no pending timer keeps the object from hibernating or being evicted; memory is then freed at later commits. ## Extensions @@ -383,7 +383,7 @@ const harness = await Harness.open(storage, { compaction: { reserveTokens: 16384 }, progress: { partialIntervalMs: 100, outputIntervalMs: 100 }, toolExecution: "parallel", - contextRetentionMs: 600_000, // idle conversations keep their context in memory this long (checked lazily where timers cannot be unreferenced) + contextRetentionMs: 600_000, // idle conversations keep their context in memory this long (checked lazily where background timers are declined, as in Workers) documentRetentionMs: 600_000, // loaded documents nothing observes stay in memory this long after their last use; observed ones always get followUpMode() { return userSettings.followUpMode; diff --git a/packages/durable/docs/spec.md b/packages/durable/docs/spec.md index 5eb91c392..65d0350d4 100644 --- a/packages/durable/docs/spec.md +++ b/packages/durable/docs/spec.md @@ -379,7 +379,7 @@ type HarnessOptions = { /** * Arms every timer of the Session and Harness (section 5.6). `background` timers only free memory and may be * declined by returning `undefined`; the others pace or resume work. Default `setTimeout`, background timers - * unreferenced, or declined where they cannot be. + * unreferenced, or declined in Cloudflare Workers and where they cannot be unreferenced. */ readonly setTimer?: ( callback: () => void, @@ -2355,9 +2355,10 @@ poll, sleeps again with the same checkpoint (section 8.3). `HarnessOptions.setTimer` (default `setTimeout`). Background timers only free memory: document eviction (section 4) and kept contexts (section 5.1). A host may decline one by returning `undefined`; that memory is then freed at later -commits and task changes. The default declines them where timers cannot be -unreferenced, as in Cloudflare Workers, since a pending timer keeps a Durable -Object from hibernating. Foreground timers pace or resume work: throttled +commits and task changes. The default declines them in Cloudflare Workers, since +a pending timer keeps a Durable Object from hibernating or being evicted even +when `nodejs_compat` gives it an `unref()`, and wherever timers cannot be +unreferenced. Foreground timers pace or resume work: throttled progress commits through `TaskRuntime.setTimer()`, and, without `onWake`, the timer for the earliest sleep while scheduling runs. That one keeps a Node.js process alive, since a sleeping task is work left to do. A timer may fire early diff --git a/packages/durable/package.json b/packages/durable/package.json index c158d2d32..bb43f2fd8 100644 --- a/packages/durable/package.json +++ b/packages/durable/package.json @@ -113,7 +113,8 @@ "scripts": { "clean": "shx rm -rf dist", "build": "tsc -p tsconfig.build.json", - "test": "vitest --run && PI_DURABLE_DOCUMENT_RETENTION_MS=0 vitest --run", + "test": "vitest --run && PI_DURABLE_DOCUMENT_RETENTION_MS=0 vitest --run && npm run test:workers", + "test:workers": "vitest --run --config test-workers/vitest.config.ts", "bench:storage": "vitest bench --config vitest.benchmark.config.ts", "bench:storage:memory": "node --conditions=source --experimental-strip-types test/storage-memory.ts", "bench:tool-output": "node --conditions=source --experimental-strip-types --expose-gc test/tool-output-bench.ts", @@ -151,6 +152,7 @@ "typebox": "1.3.27" }, "devDependencies": { + "@cloudflare/vitest-plugin": "1.4.4", "shx": "0.3.4", "vitest": "4.1.11" } diff --git a/packages/durable/src/harness/types.ts b/packages/durable/src/harness/types.ts index 99d05983d..b17091ded 100644 --- a/packages/durable/src/harness/types.ts +++ b/packages/durable/src/harness/types.ts @@ -611,8 +611,8 @@ export type HarnessOptions = { /** * Arms every timer the Harness uses: pacing progress commits, freeing idle memory (`background`), and, without * `onWake`, resuming sleeping tasks; and the timers of `TaskRuntime.setTimer()`. Default `setTimeout`, with background - * timers unreferenced, or declined where they cannot be. Tests can pass fake timers; a host can keep timers off a - * process that should hibernate. + * timers unreferenced, or declined in Cloudflare Workers and where they cannot be unreferenced. Tests can pass fake + * timers; a host can keep timers off a process that should hibernate. */ readonly setTimer?: SetTimer; /** diff --git a/packages/durable/src/timers.ts b/packages/durable/src/timers.ts index de49d2284..e4a2e2c2c 100644 --- a/packages/durable/src/timers.ts +++ b/packages/durable/src/timers.ts @@ -3,12 +3,18 @@ import type { SetTimer } from "./types.ts"; /** Longest delay `setTimeout` supports; a longer one fires early, and its caller arms again. */ const MAX_TIMER_DELAY = 2_147_483_647; +/** + * Cloudflare Workers: a pending timer keeps a Durable Object from hibernating or being evicted, even an unreferenced + * one (`nodejs_compat` gives timers an `unref()` that changes nothing). + */ +const inWorkers = typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers"; + /** * The default `HarnessOptions.setTimer`: `setTimeout`. A background timer is unreferenced, so it never keeps the - * process alive; where timers cannot be unreferenced, as in Cloudflare Workers, it is declined, since a pending timer - * could keep a Durable Object from hibernating. + * process alive. It is declined in Cloudflare Workers and wherever timers cannot be unreferenced. */ export const nativeTimer: SetTimer = (callback, delayMs, options) => { + if (options.background && inWorkers) return undefined; const handle = setTimeout(callback, Math.min(Math.max(0, delayMs), MAX_TIMER_DELAY)); if (options.background) { const unref = (handle as { unref?: unknown }).unref; diff --git a/packages/durable/test-workers/cloudflare.d.ts b/packages/durable/test-workers/cloudflare.d.ts new file mode 100644 index 000000000..c3b6eb11e --- /dev/null +++ b/packages/durable/test-workers/cloudflare.d.ts @@ -0,0 +1,40 @@ +// The parts of the Workers runtime these tests use, typed for the repository's Node program. The tests run in workerd +// itself (`vitest.config.ts`); full Workers types would conflict with Node's. `DurableObject` itself is declared by +// packages/codemode/src/cloudflare-modules.d.ts, with an untyped `ctx`. + +declare module "cloudflare:workers" { + import type { DurableObjectSqliteStorage } from "@earendil-works/chord/sqlite/cloudflare"; + + export type DurableObjectStorage = DurableObjectSqliteStorage & { + getAlarm(): Promise; + setAlarm(scheduledTime: number): Promise; + deleteAlarm(): Promise; + /** Synchronous key-value storage of a SQLite-backed Durable Object. */ + readonly kv: { get(key: string): T | undefined; put(key: string, value: T): void }; + }; + + export type DurableObjectState = { + readonly storage: DurableObjectStorage; + blockConcurrencyWhile(callback: () => Promise): Promise; + }; + + export type DurableObjectId = { toString(): string }; + export type DurableObjectStub = { readonly id: DurableObjectId }; + export type DurableObjectNamespace = { newUniqueId(): DurableObjectId; get(id: DurableObjectId): DurableObjectStub }; + + export const env: { readonly SESSIONS: DurableObjectNamespace }; +} + +declare module "cloudflare:test" { + import type { DurableObjectState, DurableObjectStub } from "cloudflare:workers"; + + /** Run `callback` inside the object, with its instance and state. */ + export function runInDurableObject( + stub: DurableObjectStub, + callback: (instance: O, state: DurableObjectState) => R | Promise, + ): Promise; + /** Run the object's alarm now if one is set; whether one ran. */ + export function runDurableObjectAlarm(stub: DurableObjectStub): Promise; + /** Tear the object's instance down, keeping its storage. */ + export function evictDurableObject(stub: DurableObjectStub): Promise; +} diff --git a/packages/durable/test-workers/storage.test.ts b/packages/durable/test-workers/storage.test.ts new file mode 100644 index 000000000..9f45ec600 --- /dev/null +++ b/packages/durable/test-workers/storage.test.ts @@ -0,0 +1,54 @@ +import { runInDurableObject } from "cloudflare:test"; +import { env } from "cloudflare:workers"; +import { DurableObjectSqliteDatabase } from "@earendil-works/chord/sqlite/cloudflare"; +import { registerStorageConformance } from "@earendil-works/pi-durable/testing"; +import { describe, expect, it } from "vitest"; +import { openDurableObjectSqliteStorage } from "../src/storage/sqlite/cloudflare.ts"; +import { applySqliteMigrationsForSchema, createSqliteMigrations } from "../src/storage/sqlite/migrations.ts"; +import { createSqliteSchema } from "../src/storage/sqlite/schema.ts"; + +const fresh = () => env.SESSIONS.get(env.SESSIONS.newUniqueId()); + +let suite = 0; +registerStorageConformance({ describe, expect, it }, "SqliteStorage on Durable Object SQLite in workerd", (use) => + runInDurableObject(fresh(), async (_instance, state) => { + // Tables of their own, next to the ones the object's Harness opened. + await use(await openDurableObjectSqliteStorage(state.storage, { prefix: `conformance${suite++}_` })); + }), +); + +describe("Durable Object SQLite schema", () => { + it("migrates a version 1 schema to admit sleeping tasks, keeping tasks and indexes", async () => { + await runInDurableObject(fresh(), async (_instance, state) => { + const database = new DurableObjectSqliteDatabase(state.storage); + const schema = createSqliteSchema("legacy_"); + const tasks = schema.tables.tasks; + const insert = (id: number, status: string) => + database.run( + `INSERT INTO ${tasks} (id, conversation_id, kind, status, abort_requested, background, record) VALUES (?, 1, 'test', ?, 0, 0, '{}')`, + id, + status, + ); + // A database written by a version that knew schema version 1 only. + await applySqliteMigrationsForSchema(database, schema, createSqliteMigrations(schema).slice(0, 1)); + await insert(5, "waiting"); + await expect(insert(6, "sleeping")).rejects.toThrow(); + + await openDurableObjectSqliteStorage(state.storage, { prefix: "legacy_" }); + expect(await database.get(`SELECT version FROM ${schema.tables.durableSchema}`)).toEqual({ version: 2 }); + expect(await database.get(`SELECT status FROM ${tasks} WHERE id = 5`)).toEqual({ status: "waiting" }); + await insert(6, "sleeping"); + const indexes = await database.all<{ name: string }>( + `SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = ? ORDER BY name`, + tasks, + ); + expect(indexes.map(({ name }) => name)).toEqual([ + schema.indexes.tasksByAbortRequested, + schema.indexes.tasksByBackground, + schema.indexes.tasksByConversation, + schema.indexes.tasksByKind, + schema.indexes.tasksByStatus, + ]); + }); + }); +}); diff --git a/packages/durable/test-workers/vitest.config.ts b/packages/durable/test-workers/vitest.config.ts new file mode 100644 index 000000000..a2e12c917 --- /dev/null +++ b/packages/durable/test-workers/vitest.config.ts @@ -0,0 +1,24 @@ +import { fileURLToPath } from "node:url"; +import { cloudflareTest } from "@cloudflare/vitest-plugin"; +import { defineConfig } from "vitest/config"; + +const source = (path: string) => fileURLToPath(new URL(path, import.meta.url)); + +// Runs in workerd: the Durable Object storage, alarms, and eviction are Cloudflare's own. +export default defineConfig({ + root: source("."), + plugins: [cloudflareTest({ wrangler: { configPath: source("./wrangler.jsonc") } })], + test: { include: ["*.test.ts"] }, + resolve: { + conditions: ["source"], + alias: [ + { find: /^@earendil-works\/pi-durable$/, replacement: source("../src/index.ts") }, + { find: /^@earendil-works\/pi-durable\/testing$/, replacement: source("../src/testing/index.ts") }, + { + find: /^@earendil-works\/pi-durable\/storage\/sqlite\/cloudflare$/, + replacement: source("../src/storage/sqlite/cloudflare.ts"), + }, + ], + }, + ssr: { resolve: { conditions: ["source"] } }, +}); diff --git a/packages/durable/test-workers/wake.test.ts b/packages/durable/test-workers/wake.test.ts new file mode 100644 index 000000000..41c746bb3 --- /dev/null +++ b/packages/durable/test-workers/wake.test.ts @@ -0,0 +1,96 @@ +import { evictDurableObject, runDurableObjectAlarm, runInDurableObject } from "cloudflare:test"; +import { type DurableObjectState, type DurableObjectStub, env } from "cloudflare:workers"; +import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context"; +import { AssistantEntry, type TaskId } from "@earendil-works/pi-durable"; +import { describe, expect, it } from "vitest"; +import { nativeTimer } from "../src/timers.ts"; +import type { TestSession } from "./worker.ts"; + +const context = BACKGROUND_CONTEXT; +const fresh = () => env.SESSIONS.get(env.SESSIONS.newUniqueId()); + +/** Poll `check` inside the object until it holds. */ +async function eventually( + stub: DurableObjectStub, + check: (instance: TestSession, state: DurableObjectState) => Promise, +): Promise { + for (let attempt = 0; attempt < 200; attempt++) { + if (await runInDurableObject(stub, check)) return; + await new Promise((resolve) => setTimeout(resolve, 10)); + } + throw new Error("Condition not reached"); +} + +describe("a Harness woken by its Durable Object alarm", () => { + it("sleeps through an eviction and resumes the task from its alarm", async () => { + const stub = fresh(); + const sleep = 600_000; + const { id, at } = await runInDurableObject(stub, async (instance: TestSession) => { + const at = instance.now() + sleep; + return { id: await instance.remind(at), at }; + }); + await eventually(stub, async (instance, state) => { + const record = await instance.harness.getTask(id, context); + return record?.state.status === "sleeping" && (await state.storage.getAlarm()) === at; + }); + await runInDurableObject(stub, async (instance: TestSession) => { + expect(instance.harness.wake()).toEqual({ active: false, at }); + expect(instance.wakes.at(-1)).toEqual({ active: false, at }); + }); + + await evictDurableObject(stub); + // A new instance: the sleep is read from storage, and the alarm stays. + await runInDurableObject(stub, async (instance: TestSession, state) => { + expect(instance.harness.wake()).toEqual({ active: false, at }); + expect(await state.storage.getAlarm()).toBe(at); + // Time passes while the object sleeps. + instance.advance(sleep); + }); + await evictDurableObject(stub); + expect(await runDurableObjectAlarm(stub)).toBe(true); + + const woke = await runInDurableObject(stub, async (instance: TestSession) => { + const settled = await instance.harness.waitForTask(id as TaskId, context); + return settled.state.outcome; + }); + expect(woke).toMatchObject({ status: "completed" }); + expect(woke.status === "completed" && woke.result).toBeGreaterThanOrEqual(at); + // Nothing is left to do: the alarm is gone. + await eventually(stub, async (_instance, state) => (await state.storage.getAlarm()) === null); + }); + + it("recovers a turn an eviction cut off mid-stream through the heartbeat alarm", async () => { + const stub = fresh(); + await runInDurableObject(stub, async (instance: TestSession, state) => { + instance.script(["hang", "recovered"]); + await instance.submit("hi", "turn-1"); + await instance.hanging.promise; + expect(instance.harness.wake().active).toBe(true); + // The heartbeat alarm armed before the input was admitted. + expect(await state.storage.getAlarm()).not.toBeNull(); + }); + + await evictDurableObject(stub); + expect(await runDurableObjectAlarm(stub)).toBe(true); + + const answer = await runInDurableObject(stub, async (instance: TestSession) => { + const root = await instance.root(); + // The same request ID finds the submission admitted before the eviction. + const submission = await root.submit({ type: "input", content: "hi", requestId: "turn-1" }, context); + const settled = await submission.wait(context); + if (settled.status !== "done" || settled.type !== "input") throw new Error(`Unexpected ${settled.status}`); + const entry = await root.commit((tx) => tx.entry(AssistantEntry, settled.answer), context); + const message = entry?.model?.[0]; + return message?.role === "assistant" ? message.content : undefined; + }); + expect(answer).toEqual([{ type: "text", text: "recovered" }]); + await eventually(stub, async (_instance, state) => (await state.storage.getAlarm()) === null); + }); + + it("declines background timers by default, so no pending timer keeps an object from hibernating", () => { + expect(nativeTimer(() => {}, 10, { background: true })).toBeUndefined(); + const disarm = nativeTimer(() => {}, 10, { background: false }); + expect(typeof disarm).toBe("function"); + disarm?.(); + }); +}); diff --git a/packages/durable/test-workers/worker.ts b/packages/durable/test-workers/worker.ts new file mode 100644 index 000000000..7a94c0656 --- /dev/null +++ b/packages/durable/test-workers/worker.ts @@ -0,0 +1,148 @@ +// A Durable Object that hosts a Harness the way an application would: opened in the constructor, woken by its alarm +// through `onWake`. Tests drive it with `runInDurableObject()`, evict it, and run its alarm. +import { DurableObject, type DurableObjectState } from "cloudflare:workers"; +import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context"; +import { createModels, type FauxResponseStep, fauxAssistantMessage, fauxProvider } from "@earendil-works/pi-ai"; +import { + type Conversation, + createRegistry, + defineExtension, + defineTask, + Harness, + type TaskId, + type Wake, +} from "@earendil-works/pi-durable"; +import { openDurableObjectSqliteStorage } from "@earendil-works/pi-durable/storage/sqlite/cloudflare"; + +const context = BACKGROUND_CONTEXT; + +/** Sleeps until `at`, then completes with the Harness time it woke at. */ +export const Reminder = defineTask<{ at: number }, { phase: "wait" } | { phase: "deliver" }, number>({ + name: "test.reminder", + version: 1, + initial: () => ({ phase: "wait" }), + phases: { + wait: (task, runtime, ctx) => runtime.sleepUntil(task.input.at, { phase: "deliver" }, ctx), + deliver: async (_task, runtime, ctx) => { + const woke = runtime.now(); + await runtime.commit(() => ({ status: "terminal", outcome: { status: "completed", result: woke } }), ctx); + }, + }, + abort: (_task, runtime, ctx) => runtime.commit(() => ({ status: "terminal", outcome: { status: "aborted" } }), ctx), +}); + +/** Scripted model responses, kept in storage so a new instance continues the script: `hang` waits for an abort. */ +type Script = { responses: string[]; next: number }; + +export const HEARTBEAT_MS = 60_000; + +export class TestSession extends DurableObject { + readonly #state: DurableObjectState; + #harness!: Harness; + /** Added to `Date.now()`, kept in storage, so a test can move the Harness clock across evictions. */ + #offset = 0; + /** The wakes `onWake` received, in order. */ + readonly wakes: Wake[] = []; + /** Resolves when this instance's model request starts and hangs. */ + readonly hanging = Promise.withResolvers(); + + constructor(ctx: DurableObjectState, env: unknown) { + super(ctx, env); + this.#state = ctx; + void ctx.blockConcurrencyWhile(async () => { + this.#offset = ctx.storage.kv.get("offset") ?? 0; + const faux = fauxProvider(); + faux.setResponses(Array.from({ length: 8 }, () => this.#step())); + const models = createModels(); + models.setProvider(faux.provider); + const registry = createRegistry(); + registry.install(defineExtension({ name: "test", tasks: [Reminder] })); + this.#harness = await Harness.open( + await openDurableObjectSqliteStorage(ctx.storage), + { + models, + registry, + now: () => this.now(), + onWake: (wake) => { + this.wakes.push(wake); + void this.arm(wake); + }, + }, + context, + ); + this.#harness.resume(); + }); + } + + get harness(): Harness { + return this.#harness; + } + + now(): number { + return Date.now() + this.#offset; + } + + /** Move the Harness clock forward, for this instance and the ones after it. */ + advance(ms: number): void { + this.#offset += ms; + this.#state.storage.kv.put("offset", this.#offset); + } + + script(responses: string[]): void { + this.#state.storage.kv.put("script", { responses, next: 0 } satisfies Script); + } + + async alarm(): Promise { + this.#harness.resume(); + await this.arm(this.#harness.wake()); + } + + /** The alarm a host arms for `wake`: the earliest sleep, or a recovery heartbeat while work can run. */ + async arm(wake: Wake): Promise { + const heartbeat = wake.active ? Date.now() + HEARTBEAT_MS : undefined; + const sleep = wake.at === undefined ? undefined : wake.at - this.#offset; + const at = heartbeat === undefined || sleep === undefined ? (heartbeat ?? sleep) : Math.min(heartbeat, sleep); + if (at === undefined) await this.#state.storage.deleteAlarm(); + else await this.#state.storage.setAlarm(at); + } + + async root(): Promise { + return this.#harness.root(context, { agent: { model: { provider: "faux", modelId: "faux-1" } } }); + } + + async submit(text: string, requestId: string): Promise { + // Arm before admitting work: a process that dies after the commit is still woken. + await this.arm({ ...this.#harness.wake(), active: true }); + await (await this.root()).submit({ type: "input", content: text, requestId }, context); + } + + async remind(at: number): Promise> { + const root = await this.root(); + const id = await root.commit( + (tx) => tx.createTask(Reminder, { at }, { ownership: { kind: "conversation" } }), + context, + ); + this.#harness.resume(); + return id; + } + + /** The next scripted response; the script position is stored before the response is used. */ + #step(): FauxResponseStep { + return (_request, options) => { + const script = this.#state.storage.kv.get