From 9179bdbf3870c2671372c9e98d5b2c5b10cb0fd7 Mon Sep 17 00:00:00 2001
From: Dan Connolly
Date: Thu, 8 Oct 2026 15:01:33 -0500
Subject: [PATCH 1/8] capsite/lib: MapStore and patterns work-alikes
Enough of @agoric/store's MapStore and @endo/patterns' M for the
credential-rotation design: makeMapStore(label, { keyShape, valueShape },
kv) keeps entries in a DO's sync KV under store::, and readOnly()
hands out the queries alone. Facets are workerd RpcTargets in place of
endo's Far.
Unlike endo, error messages never include keys or values: the values
here are credentials, and messages cross RPC into logs.
Generated-by: Claude Opus 5.5
---
lib/package.json | 16 +
lib/pnpm-lock.yaml | 2240 ++++++++++++++++++++++++++++++++++++++
lib/src/patterns.ts | 51 +
lib/src/store.ts | 133 +++
lib/tests/store.test.ts | 87 ++
lib/tests/test-worker.ts | 15 +
lib/tests/tsconfig.json | 7 +
lib/tsconfig.json | 15 +
lib/vitest.config.ts | 9 +
lib/wrangler.tests.jsonc | 13 +
10 files changed, 2586 insertions(+)
create mode 100644 lib/package.json
create mode 100644 lib/pnpm-lock.yaml
create mode 100644 lib/src/patterns.ts
create mode 100644 lib/src/store.ts
create mode 100644 lib/tests/store.test.ts
create mode 100644 lib/tests/test-worker.ts
create mode 100644 lib/tests/tsconfig.json
create mode 100644 lib/tsconfig.json
create mode 100644 lib/vitest.config.ts
create mode 100644 lib/wrangler.tests.jsonc
diff --git a/lib/package.json b/lib/package.json
new file mode 100644
index 0000000..ddb1171
--- /dev/null
+++ b/lib/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "capsite-lib",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "types": "wrangler types -c wrangler.tests.jsonc",
+ "lint": "wrangler types -c wrangler.tests.jsonc --check && tsc -p tsconfig.json && tsc -p tests/tsconfig.json",
+ "test": "vitest run"
+ },
+ "devDependencies": {
+ "@cloudflare/vitest-plugin": "1.3.6",
+ "typescript": "^5.9.0",
+ "vitest": "4.1.11",
+ "wrangler": "^4.148.0"
+ }
+}
diff --git a/lib/pnpm-lock.yaml b/lib/pnpm-lock.yaml
new file mode 100644
index 0000000..c9e4751
--- /dev/null
+++ b/lib/pnpm-lock.yaml
@@ -0,0 +1,2240 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ devDependencies:
+ '@cloudflare/vitest-plugin':
+ specifier: 1.3.6
+ version: 1.3.6(@vitest/runner@4.1.11)(@vitest/snapshot@4.1.11)(vitest@4.1.11(vite@8.3.4(esbuild@0.28.2)))
+ typescript:
+ specifier: ^5.9.0
+ version: 5.9.3
+ vitest:
+ specifier: 4.1.11
+ version: 4.1.11(vite@8.3.4(esbuild@0.28.2))
+ wrangler:
+ specifier: ^4.148.0
+ version: 4.149.0
+
+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.2':
+ resolution: {integrity: sha512-JBP1+Z7ZSNG/d4mRP+y8VC5dka3tZVMLEZRvS+rzQ4DGV1EoxRFQckcJTTkXbHSQiTj0DtNI01Zwb/V2fX0mvQ==}
+ peerDependencies:
+ unenv: 2.0.0-rc.24
+ workerd: '>1.20260305.0 <2.0.0-0'
+ peerDependenciesMeta:
+ workerd:
+ optional: true
+
+ '@cloudflare/vitest-plugin@1.3.6':
+ resolution: {integrity: sha512-vYhu5AwU0PErTM90wTtHBC6sR4nrcK3R401IPLCNfwJIKjVb208HQF8VveHPI5PxNU4FVgChG8i75I+f93MHrw==}
+ peerDependencies:
+ '@vitest/runner': ^4.1.0
+ '@vitest/snapshot': ^4.1.0
+ vitest: ^4.1.0
+
+ '@cloudflare/workerd-darwin-64@1.20261001.1':
+ resolution: {integrity: sha512-4cgSgDf28JSw/P5Dj5GCS59hzVqS5XnmGAWNkvYHLI6ODU9idGaMMNEuhJXDkEG/lsABmlVlnCgsdh2VbKWepw==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@cloudflare/workerd-darwin-64@1.20261006.1':
+ resolution: {integrity: sha512-lCX6avJO2It9AyDEYitjmOtJRVp/xmaW0TziyoLAnfxlzozyyRbSFfIIMMCiDx/sTP1++E4VLbV+7BHSD33f0Q==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@cloudflare/workerd-darwin-arm64@1.20261001.1':
+ resolution: {integrity: sha512-8ulAWruEVouNmEIsQsy9WSSCS9zkLu93W2MTwp5esiFyoPp05BNSVFIFsYSPi1pkFmBBd7fsnwMpbLkaJLaVPQ==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@cloudflare/workerd-darwin-arm64@1.20261006.1':
+ resolution: {integrity: sha512-PyFGjOaldtppnLuKsdkoYWXfLVpDjuf8u+uoykX+1Xvxo2EPmCVViq0iDKm+U7c7PRno85ehS3/MPVXyk2HwHw==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@cloudflare/workerd-linux-64@1.20261001.1':
+ resolution: {integrity: sha512-kZbTZJGrhsMOdqZ2BIybjaBRLZjYsRLWj7mcNw/6Y3hodOhp5MrNzcz4iWGwNVWwyIV+7Pl+/LX5VcgnRqdHOg==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [linux]
+
+ '@cloudflare/workerd-linux-64@1.20261006.1':
+ resolution: {integrity: sha512-Ntf9S8eesmVufdjaT2kzWlz6Ng9XGYTc6cHQVRKAg59lFufwsIabGeMT6U4KkrpEeSIxgHq8aPtuLbZXE19tCA==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [linux]
+
+ '@cloudflare/workerd-linux-arm64@1.20261001.1':
+ resolution: {integrity: sha512-oOk3Zj6k/8oP0FJgZBWDn7+BqbsqIMEMaV95pulHPVbwVu4wYoLfQq2hp0vkbCNsCFLqZb7cqixXdrwD54ZIow==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@cloudflare/workerd-linux-arm64@1.20261006.1':
+ resolution: {integrity: sha512-JyeDBymVWw5tVRebSsVE8ojDYxd6UlZrAseHU8G+NpIyd5wF61tXlF/axLeCUqu0pOhBFHb8I5QKi0jwrCK41Q==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@cloudflare/workerd-windows-64@1.20261001.1':
+ resolution: {integrity: sha512-uRxm5W4VyBkoSaoP1BfOuH0873tE+vxsknF4sab6/5YIRvIAufChq3QDp+zlAn67PuGPGcn7W8QraA0t4ucmOQ==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [win32]
+
+ '@cloudflare/workerd-windows-64@1.20261006.1':
+ resolution: {integrity: sha512-nWP0URNqOBN9fa/gZ7eh/w051DwjOXN8j4lDZDYVXyQPsux6sV3DfyYZT+CjR/rt9nKBSmJT9QE9/bWT18+84w==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [win32]
+
+ '@cspotcode/source-map-support@0.8.1':
+ resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+ engines: {node: '>=12'}
+
+ '@emnapi/runtime@1.11.3':
+ resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
+
+ '@esbuild/aix-ppc64@0.28.1':
+ resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/aix-ppc64@0.28.2':
+ resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==}
+ 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-arm@0.28.2':
+ resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==}
+ 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/android-x64@0.28.2':
+ resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==}
+ 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/darwin-x64@0.28.2':
+ resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==}
+ 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/freebsd-x64@0.28.2':
+ resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==}
+ 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-arm@0.28.2':
+ resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==}
+ 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-ia32@0.28.2':
+ resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==}
+ 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-loong64@0.28.2':
+ resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==}
+ 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-mips64el@0.28.2':
+ resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==}
+ 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-ppc64@0.28.2':
+ resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==}
+ 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-riscv64@0.28.2':
+ resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==}
+ 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-s390x@0.28.2':
+ resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==}
+ 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/linux-x64@0.28.2':
+ resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==}
+ 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/netbsd-x64@0.28.2':
+ resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==}
+ 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/openbsd-x64@0.28.2':
+ resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==}
+ 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/openharmony-arm64@0.28.2':
+ resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==}
+ 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/sunos-x64@0.28.2':
+ resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==}
+ 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-ia32@0.28.2':
+ resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==}
+ 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]
+
+ '@esbuild/win32-x64@0.28.2':
+ resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==}
+ 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.4':
+ resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-arm64@0.35.5':
+ resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.35.4':
+ resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.35.5':
+ resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-freebsd-wasm32@0.35.4':
+ resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==}
+ engines: {node: '>=20.9.0'}
+ os: [freebsd]
+
+ '@img/sharp-freebsd-wasm32@0.35.5':
+ resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==}
+ engines: {node: '>=20.9.0'}
+ os: [freebsd]
+
+ '@img/sharp-libvips-darwin-arm64@1.3.3':
+ resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.3.4':
+ resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.3.3':
+ resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.3.4':
+ resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.3.3':
+ resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm64@1.3.4':
+ resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.3.3':
+ resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.3.4':
+ resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.3.3':
+ resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.3.4':
+ resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-riscv64@1.3.3':
+ resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-riscv64@1.3.4':
+ resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.3.3':
+ resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.3.4':
+ resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.3.3':
+ resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.3.4':
+ resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.3':
+ resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.4':
+ resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.3':
+ resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.4':
+ resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.35.4':
+ resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.35.5':
+ resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.35.4':
+ resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.35.5':
+ resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-ppc64@0.35.4':
+ resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-linux-ppc64@0.35.5':
+ resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-linux-riscv64@0.35.4':
+ resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-linux-riscv64@0.35.5':
+ resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.35.4':
+ resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.35.5':
+ resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.35.4':
+ resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.35.5':
+ resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.35.4':
+ resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.35.5':
+ resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.35.4':
+ resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.35.5':
+ resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-wasm32@0.35.4':
+ resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==}
+ engines: {node: '>=20.9.0'}
+
+ '@img/sharp-wasm32@0.35.5':
+ resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==}
+ engines: {node: '>=20.9.0'}
+
+ '@img/sharp-webcontainers-wasm32@0.35.4':
+ resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [wasm32]
+
+ '@img/sharp-webcontainers-wasm32@0.35.5':
+ resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.35.4':
+ resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-arm64@0.35.5':
+ resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.35.4':
+ resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==}
+ engines: {node: ^20.9.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.35.5':
+ resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==}
+ engines: {node: ^20.9.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.35.4':
+ resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.35.5':
+ resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.6.0':
+ resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
+
+ '@jridgewell/trace-mapping@0.3.9':
+ resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
+ '@oxc-project/types@0.153.0':
+ resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==}
+
+ '@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==}
+
+ '@rolldown/binding-android-arm-eabi@1.2.13':
+ resolution: {integrity: sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [android]
+
+ '@rolldown/binding-android-arm64@1.2.13':
+ resolution: {integrity: sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
+
+ '@rolldown/binding-darwin-arm64@1.2.13':
+ resolution: {integrity: sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rolldown/binding-darwin-x64@1.2.13':
+ resolution: {integrity: sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rolldown/binding-freebsd-x64@1.2.13':
+ resolution: {integrity: sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.2.13':
+ resolution: {integrity: sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@rolldown/binding-linux-arm64-gnu@1.2.13':
+ resolution: {integrity: sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rolldown/binding-linux-arm64-musl@1.2.13':
+ resolution: {integrity: sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rolldown/binding-linux-ppc64-gnu@1.2.13':
+ resolution: {integrity: sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rolldown/binding-linux-s390x-gnu@1.2.13':
+ resolution: {integrity: sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rolldown/binding-linux-x64-gnu@1.2.13':
+ resolution: {integrity: sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@rolldown/binding-linux-x64-musl@1.2.13':
+ resolution: {integrity: sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@rolldown/binding-openharmony-arm64@1.2.13':
+ resolution: {integrity: sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rolldown/binding-win32-arm64-msvc@1.2.13':
+ resolution: {integrity: sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rolldown/binding-win32-x64-msvc@1.2.13':
+ resolution: {integrity: sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@rolldown/pluginutils@1.0.1':
+ resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
+
+ '@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==}
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@types/chai@5.2.3':
+ resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
+ '@types/estree@1.0.9':
+ resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+
+ '@vitest/expect@4.1.11':
+ resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==}
+
+ '@vitest/mocker@4.1.11':
+ resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@4.1.11':
+ resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==}
+
+ '@vitest/runner@4.1.11':
+ resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==}
+
+ '@vitest/snapshot@4.1.11':
+ resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==}
+
+ '@vitest/spy@4.1.11':
+ resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==}
+
+ '@vitest/utils@4.1.11':
+ resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==}
+
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
+ blake3-wasm@2.1.5:
+ resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
+
+ chai@6.3.0:
+ resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==}
+ engines: {node: '>=18'}
+
+ cjs-module-lexer@1.2.3:
+ resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ 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==}
+
+ es-module-lexer@2.3.2:
+ resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==}
+
+ esbuild@0.28.1:
+ resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ esbuild@0.28.2:
+ resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ expect-type@1.4.0:
+ resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
+ engines: {node: '>=12.0.0'}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ kleur@4.1.5:
+ resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+ engines: {node: '>=6'}
+
+ lightningcss-android-arm64@1.33.0:
+ resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.33.0:
+ resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.33.0:
+ resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.33.0:
+ resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.33.0:
+ resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.33.0:
+ resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.33.0:
+ resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.33.0:
+ resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.33.0:
+ resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.33.0:
+ resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.33.0:
+ resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.33.0:
+ resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
+ engines: {node: '>= 12.0.0'}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ miniflare@5.20261001.0-alpha:
+ resolution: {integrity: sha512-GaimS5mSIOMyvd16ga+e1/QkI8cmp3z35QPHFex0DktqNQf2RVZQwMPQXdyoUreUiFP/0Gpe2MoQInTyMAD5xA==}
+ engines: {node: '>=22.0.0'}
+
+ miniflare@5.20261006.1-alpha:
+ resolution: {integrity: sha512-6ylv8tamkrspDI2R/cAYfv+YWmfGNdXe76Pi9TauEUWsFVXn3LNF9Yk8oduzf1a+TYsSRYTqlOE7+nGGRsMHdw==}
+ engines: {node: '>=22.0.0'}
+
+ nanoid@3.3.20:
+ resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ obug@2.2.1:
+ resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==}
+ engines: {node: '>=12.20.0'}
+
+ path-to-regexp@6.3.0:
+ resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.7:
+ resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
+ engines: {node: '>=12'}
+
+ postcss@8.5.29:
+ resolution: {integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ rolldown@1.2.13:
+ resolution: {integrity: sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+
+ semver@7.8.5:
+ resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ sharp@0.35.4:
+ resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==}
+ engines: {node: '>=20.9.0'}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ sharp@0.35.5:
+ resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==}
+ engines: {node: '>=20.9.0'}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+ source-map-js@1.2.2:
+ resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
+ engines: {node: '>=0.10.0'}
+
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ std-env@4.3.0:
+ resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==}
+
+ supports-color@10.2.2:
+ resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
+ engines: {node: '>=18'}
+
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinyexec@1.3.1:
+ resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==}
+ engines: {node: '>=18'}
+
+ tinyglobby@0.2.17:
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+
+ tinyrainbow@3.2.0:
+ resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==}
+ engines: {node: '>=14.0.0'}
+
+ 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.1:
+ resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==}
+ engines: {node: '>=20.18.1'}
+
+ unenv@2.0.0-rc.24:
+ resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
+
+ vite@8.3.4:
+ resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ '@vitejs/devtools': ^0.7.1
+ esbuild: ^0.27.0 || ^0.28.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ '@vitejs/devtools':
+ optional: true
+ esbuild:
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ vitest@4.1.11:
+ resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==}
+ engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@opentelemetry/api': ^1.9.0
+ '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
+ '@vitest/browser-playwright': 4.1.11
+ '@vitest/browser-preview': 4.1.11
+ '@vitest/browser-webdriverio': 4.1.11
+ '@vitest/coverage-istanbul': 4.1.11
+ '@vitest/coverage-v8': 4.1.11
+ '@vitest/ui': 4.1.11
+ happy-dom: '*'
+ jsdom: '*'
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@opentelemetry/api':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser-playwright':
+ optional: true
+ '@vitest/browser-preview':
+ optional: true
+ '@vitest/browser-webdriverio':
+ optional: true
+ '@vitest/coverage-istanbul':
+ optional: true
+ '@vitest/coverage-v8':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ workerd@1.20261001.1:
+ resolution: {integrity: sha512-d/SIYHFO0PT/wiFZg8in4NpRIxYuFwslX1HdylOtWkBIIUmSpkGFhK820cV84XACFylwJ48xuRoWW/8DWDPsPQ==}
+ engines: {node: '>=16'}
+ hasBin: true
+
+ workerd@1.20261006.1:
+ resolution: {integrity: sha512-8MadBuffZ5NdpLwZjUmsqT5QlaKXq4gjgT6wzcKeynZTICNXyePhc+5QVoKiK7+MMhFTLNrRO2KXLlpk8Q6BkA==}
+ engines: {node: '>=16'}
+ hasBin: true
+
+ wrangler@4.147.0:
+ resolution: {integrity: sha512-pQYRoiq8PTAxphaG69z8+GC1DkSGd19EDZehQ8zxjo/Ko3mRB6Qs1mTrd8ZuKAarLklIjTqr1lUdCK9r4q2hUg==}
+ engines: {node: '>=22.0.0'}
+ hasBin: true
+ peerDependencies:
+ '@cloudflare/workers-types': ^5.20261001.1
+ peerDependenciesMeta:
+ '@cloudflare/workers-types':
+ optional: true
+
+ wrangler@4.149.0:
+ resolution: {integrity: sha512-OzK7xmB5r5iLKb3cIT3783g13fe6T7xyeKu9LEdUyl+DKAM2KP/jkjnE0OUhyw+mB/XJM9ycjgF3MWm8PR9amg==}
+ engines: {node: '>=22.0.0'}
+ hasBin: true
+ peerDependencies:
+ '@cloudflare/workers-types': ^5.20261006.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==}
+
+ zod@4.4.3:
+ resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
+
+snapshots:
+
+ '@cloudflare/kv-asset-handler@0.5.0': {}
+
+ '@cloudflare/unenv-preset@2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261001.1)':
+ dependencies:
+ unenv: 2.0.0-rc.24
+ optionalDependencies:
+ workerd: 1.20261001.1
+
+ '@cloudflare/unenv-preset@2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261006.1)':
+ dependencies:
+ unenv: 2.0.0-rc.24
+ optionalDependencies:
+ workerd: 1.20261006.1
+
+ '@cloudflare/vitest-plugin@1.3.6(@vitest/runner@4.1.11)(@vitest/snapshot@4.1.11)(vitest@4.1.11(vite@8.3.4(esbuild@0.28.2)))':
+ dependencies:
+ '@vitest/runner': 4.1.11
+ '@vitest/snapshot': 4.1.11
+ cjs-module-lexer: 1.2.3
+ esbuild: 0.28.1
+ miniflare: 5.20261001.0-alpha
+ vitest: 4.1.11(vite@8.3.4(esbuild@0.28.2))
+ wrangler: 4.147.0
+ zod: 4.4.3
+ transitivePeerDependencies:
+ - '@cloudflare/workers-types'
+ - '@types/node'
+ - bufferutil
+ - utf-8-validate
+
+ '@cloudflare/workerd-darwin-64@1.20261001.1':
+ optional: true
+
+ '@cloudflare/workerd-darwin-64@1.20261006.1':
+ optional: true
+
+ '@cloudflare/workerd-darwin-arm64@1.20261001.1':
+ optional: true
+
+ '@cloudflare/workerd-darwin-arm64@1.20261006.1':
+ optional: true
+
+ '@cloudflare/workerd-linux-64@1.20261001.1':
+ optional: true
+
+ '@cloudflare/workerd-linux-64@1.20261006.1':
+ optional: true
+
+ '@cloudflare/workerd-linux-arm64@1.20261001.1':
+ optional: true
+
+ '@cloudflare/workerd-linux-arm64@1.20261006.1':
+ optional: true
+
+ '@cloudflare/workerd-windows-64@1.20261001.1':
+ optional: true
+
+ '@cloudflare/workerd-windows-64@1.20261006.1':
+ optional: true
+
+ '@cspotcode/source-map-support@0.8.1':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.9
+
+ '@emnapi/runtime@1.11.3':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@esbuild/aix-ppc64@0.28.1':
+ optional: true
+
+ '@esbuild/aix-ppc64@0.28.2':
+ optional: true
+
+ '@esbuild/android-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/android-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/android-arm@0.28.1':
+ optional: true
+
+ '@esbuild/android-arm@0.28.2':
+ optional: true
+
+ '@esbuild/android-x64@0.28.1':
+ optional: true
+
+ '@esbuild/android-x64@0.28.2':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/darwin-x64@0.28.1':
+ optional: true
+
+ '@esbuild/darwin-x64@0.28.2':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.28.1':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-arm@0.28.1':
+ optional: true
+
+ '@esbuild/linux-arm@0.28.2':
+ optional: true
+
+ '@esbuild/linux-ia32@0.28.1':
+ optional: true
+
+ '@esbuild/linux-ia32@0.28.2':
+ optional: true
+
+ '@esbuild/linux-loong64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-loong64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.28.1':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.28.2':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-s390x@0.28.1':
+ optional: true
+
+ '@esbuild/linux-s390x@0.28.2':
+ optional: true
+
+ '@esbuild/linux-x64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-x64@0.28.2':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.28.1':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.28.2':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.28.1':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.28.2':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/sunos-x64@0.28.1':
+ optional: true
+
+ '@esbuild/sunos-x64@0.28.2':
+ optional: true
+
+ '@esbuild/win32-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/win32-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/win32-ia32@0.28.1':
+ optional: true
+
+ '@esbuild/win32-ia32@0.28.2':
+ optional: true
+
+ '@esbuild/win32-x64@0.28.1':
+ optional: true
+
+ '@esbuild/win32-x64@0.28.2':
+ optional: true
+
+ '@img/colour@1.1.0': {}
+
+ '@img/sharp-darwin-arm64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.3.3
+ optional: true
+
+ '@img/sharp-darwin-arm64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.3.4
+ optional: true
+
+ '@img/sharp-darwin-x64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.3.3
+ optional: true
+
+ '@img/sharp-darwin-x64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.3.4
+ optional: true
+
+ '@img/sharp-freebsd-wasm32@0.35.4':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.4
+ optional: true
+
+ '@img/sharp-freebsd-wasm32@0.35.5':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.5
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.4':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-arm64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-arm@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-arm@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-s390x@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-s390x@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-x64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-x64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.3.4
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.3.3
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.3.4
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.3.3
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.3.4
+ optional: true
+
+ '@img/sharp-wasm32@0.35.4':
+ dependencies:
+ '@emnapi/runtime': 1.11.3
+ optional: true
+
+ '@img/sharp-wasm32@0.35.5':
+ dependencies:
+ '@emnapi/runtime': 1.11.3
+ optional: true
+
+ '@img/sharp-webcontainers-wasm32@0.35.4':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.4
+ optional: true
+
+ '@img/sharp-webcontainers-wasm32@0.35.5':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.5
+ optional: true
+
+ '@img/sharp-win32-arm64@0.35.4':
+ optional: true
+
+ '@img/sharp-win32-arm64@0.35.5':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.35.4':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.35.5':
+ optional: true
+
+ '@img/sharp-win32-x64@0.35.4':
+ optional: true
+
+ '@img/sharp-win32-x64@0.35.5':
+ optional: true
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.6.0': {}
+
+ '@jridgewell/trace-mapping@0.3.9':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.6.0
+
+ '@oxc-project/types@0.153.0': {}
+
+ '@poppinss/colors@4.1.6':
+ dependencies:
+ kleur: 4.1.5
+
+ '@poppinss/dumper@0.6.5':
+ dependencies:
+ '@poppinss/colors': 4.1.6
+ '@sindresorhus/is': 7.2.0
+ supports-color: 10.2.2
+
+ '@poppinss/exception@1.2.3': {}
+
+ '@rolldown/binding-android-arm-eabi@1.2.13':
+ optional: true
+
+ '@rolldown/binding-android-arm64@1.2.13':
+ optional: true
+
+ '@rolldown/binding-darwin-arm64@1.2.13':
+ optional: true
+
+ '@rolldown/binding-darwin-x64@1.2.13':
+ optional: true
+
+ '@rolldown/binding-freebsd-x64@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-gnu@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-musl@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-ppc64-gnu@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-s390x-gnu@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-x64-gnu@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-x64-musl@1.2.13':
+ optional: true
+
+ '@rolldown/binding-openharmony-arm64@1.2.13':
+ optional: true
+
+ '@rolldown/binding-win32-arm64-msvc@1.2.13':
+ optional: true
+
+ '@rolldown/binding-win32-x64-msvc@1.2.13':
+ optional: true
+
+ '@rolldown/pluginutils@1.0.1': {}
+
+ '@sindresorhus/is@7.2.0': {}
+
+ '@speed-highlight/core@1.2.24': {}
+
+ '@standard-schema/spec@1.1.0': {}
+
+ '@types/chai@5.2.3':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+ assertion-error: 2.0.1
+
+ '@types/deep-eql@4.0.2': {}
+
+ '@types/estree@1.0.9': {}
+
+ '@vitest/expect@4.1.11':
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ '@types/chai': 5.2.3
+ '@vitest/spy': 4.1.11
+ '@vitest/utils': 4.1.11
+ chai: 6.3.0
+ tinyrainbow: 3.2.0
+
+ '@vitest/mocker@4.1.11(vite@8.3.4(esbuild@0.28.2))':
+ dependencies:
+ '@vitest/spy': 4.1.11
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 8.3.4(esbuild@0.28.2)
+
+ '@vitest/pretty-format@4.1.11':
+ dependencies:
+ tinyrainbow: 3.2.0
+
+ '@vitest/runner@4.1.11':
+ dependencies:
+ '@vitest/utils': 4.1.11
+ pathe: 2.0.3
+
+ '@vitest/snapshot@4.1.11':
+ dependencies:
+ '@vitest/pretty-format': 4.1.11
+ '@vitest/utils': 4.1.11
+ magic-string: 0.30.21
+ pathe: 2.0.3
+
+ '@vitest/spy@4.1.11': {}
+
+ '@vitest/utils@4.1.11':
+ dependencies:
+ '@vitest/pretty-format': 4.1.11
+ convert-source-map: 2.0.0
+ tinyrainbow: 3.2.0
+
+ assertion-error@2.0.1: {}
+
+ blake3-wasm@2.1.5: {}
+
+ chai@6.3.0: {}
+
+ cjs-module-lexer@1.2.3: {}
+
+ convert-source-map@2.0.0: {}
+
+ cookie@1.1.1: {}
+
+ detect-libc@2.1.2: {}
+
+ error-stack-parser-es@1.0.5: {}
+
+ es-module-lexer@2.3.2: {}
+
+ 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
+
+ esbuild@0.28.2:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.28.2
+ '@esbuild/android-arm': 0.28.2
+ '@esbuild/android-arm64': 0.28.2
+ '@esbuild/android-x64': 0.28.2
+ '@esbuild/darwin-arm64': 0.28.2
+ '@esbuild/darwin-x64': 0.28.2
+ '@esbuild/freebsd-arm64': 0.28.2
+ '@esbuild/freebsd-x64': 0.28.2
+ '@esbuild/linux-arm': 0.28.2
+ '@esbuild/linux-arm64': 0.28.2
+ '@esbuild/linux-ia32': 0.28.2
+ '@esbuild/linux-loong64': 0.28.2
+ '@esbuild/linux-mips64el': 0.28.2
+ '@esbuild/linux-ppc64': 0.28.2
+ '@esbuild/linux-riscv64': 0.28.2
+ '@esbuild/linux-s390x': 0.28.2
+ '@esbuild/linux-x64': 0.28.2
+ '@esbuild/netbsd-arm64': 0.28.2
+ '@esbuild/netbsd-x64': 0.28.2
+ '@esbuild/openbsd-arm64': 0.28.2
+ '@esbuild/openbsd-x64': 0.28.2
+ '@esbuild/openharmony-arm64': 0.28.2
+ '@esbuild/sunos-x64': 0.28.2
+ '@esbuild/win32-arm64': 0.28.2
+ '@esbuild/win32-ia32': 0.28.2
+ '@esbuild/win32-x64': 0.28.2
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.9
+
+ expect-type@1.4.0: {}
+
+ fdir@6.5.0(picomatch@4.0.7):
+ optionalDependencies:
+ picomatch: 4.0.7
+
+ fsevents@2.3.3:
+ optional: true
+
+ kleur@4.1.5: {}
+
+ lightningcss-android-arm64@1.33.0:
+ optional: true
+
+ lightningcss-darwin-arm64@1.33.0:
+ optional: true
+
+ lightningcss-darwin-x64@1.33.0:
+ optional: true
+
+ lightningcss-freebsd-x64@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.33.0:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.33.0:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.33.0:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.33.0:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.33.0:
+ optional: true
+
+ lightningcss@1.33.0:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.33.0
+ lightningcss-darwin-arm64: 1.33.0
+ lightningcss-darwin-x64: 1.33.0
+ lightningcss-freebsd-x64: 1.33.0
+ lightningcss-linux-arm-gnueabihf: 1.33.0
+ lightningcss-linux-arm64-gnu: 1.33.0
+ lightningcss-linux-arm64-musl: 1.33.0
+ lightningcss-linux-x64-gnu: 1.33.0
+ lightningcss-linux-x64-musl: 1.33.0
+ lightningcss-win32-arm64-msvc: 1.33.0
+ lightningcss-win32-x64-msvc: 1.33.0
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.6.0
+
+ miniflare@5.20261001.0-alpha:
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ sharp: 0.35.4
+ undici: 7.29.1
+ workerd: 1.20261001.1
+ ws: 8.21.0
+ youch: 4.1.0-beta.10
+ transitivePeerDependencies:
+ - '@types/node'
+ - bufferutil
+ - utf-8-validate
+
+ miniflare@5.20261006.1-alpha:
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ sharp: 0.35.5
+ undici: 7.29.1
+ workerd: 1.20261006.1
+ ws: 8.21.0
+ youch: 4.1.0-beta.10
+ transitivePeerDependencies:
+ - '@types/node'
+ - bufferutil
+ - utf-8-validate
+
+ nanoid@3.3.20: {}
+
+ obug@2.2.1: {}
+
+ path-to-regexp@6.3.0: {}
+
+ pathe@2.0.3: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.7: {}
+
+ postcss@8.5.29:
+ dependencies:
+ nanoid: 3.3.20
+ picocolors: 1.1.1
+ source-map-js: 1.2.2
+
+ rolldown@1.2.13:
+ dependencies:
+ '@oxc-project/types': 0.153.0
+ '@rolldown/pluginutils': 1.0.1
+ optionalDependencies:
+ '@rolldown/binding-android-arm-eabi': 1.2.13
+ '@rolldown/binding-android-arm64': 1.2.13
+ '@rolldown/binding-darwin-arm64': 1.2.13
+ '@rolldown/binding-darwin-x64': 1.2.13
+ '@rolldown/binding-freebsd-x64': 1.2.13
+ '@rolldown/binding-linux-arm-gnueabihf': 1.2.13
+ '@rolldown/binding-linux-arm64-gnu': 1.2.13
+ '@rolldown/binding-linux-arm64-musl': 1.2.13
+ '@rolldown/binding-linux-ppc64-gnu': 1.2.13
+ '@rolldown/binding-linux-s390x-gnu': 1.2.13
+ '@rolldown/binding-linux-x64-gnu': 1.2.13
+ '@rolldown/binding-linux-x64-musl': 1.2.13
+ '@rolldown/binding-openharmony-arm64': 1.2.13
+ '@rolldown/binding-win32-arm64-msvc': 1.2.13
+ '@rolldown/binding-win32-x64-msvc': 1.2.13
+
+ semver@7.8.5: {}
+
+ sharp@0.35.4:
+ dependencies:
+ '@img/colour': 1.1.0
+ detect-libc: 2.1.2
+ semver: 7.8.5
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.35.4
+ '@img/sharp-darwin-x64': 0.35.4
+ '@img/sharp-freebsd-wasm32': 0.35.4
+ '@img/sharp-libvips-darwin-arm64': 1.3.3
+ '@img/sharp-libvips-darwin-x64': 1.3.3
+ '@img/sharp-libvips-linux-arm': 1.3.3
+ '@img/sharp-libvips-linux-arm64': 1.3.3
+ '@img/sharp-libvips-linux-ppc64': 1.3.3
+ '@img/sharp-libvips-linux-riscv64': 1.3.3
+ '@img/sharp-libvips-linux-s390x': 1.3.3
+ '@img/sharp-libvips-linux-x64': 1.3.3
+ '@img/sharp-libvips-linuxmusl-arm64': 1.3.3
+ '@img/sharp-libvips-linuxmusl-x64': 1.3.3
+ '@img/sharp-linux-arm': 0.35.4
+ '@img/sharp-linux-arm64': 0.35.4
+ '@img/sharp-linux-ppc64': 0.35.4
+ '@img/sharp-linux-riscv64': 0.35.4
+ '@img/sharp-linux-s390x': 0.35.4
+ '@img/sharp-linux-x64': 0.35.4
+ '@img/sharp-linuxmusl-arm64': 0.35.4
+ '@img/sharp-linuxmusl-x64': 0.35.4
+ '@img/sharp-webcontainers-wasm32': 0.35.4
+ '@img/sharp-win32-arm64': 0.35.4
+ '@img/sharp-win32-ia32': 0.35.4
+ '@img/sharp-win32-x64': 0.35.4
+
+ sharp@0.35.5:
+ dependencies:
+ '@img/colour': 1.1.0
+ detect-libc: 2.1.2
+ semver: 7.8.5
+ 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
+
+ siginfo@2.0.0: {}
+
+ source-map-js@1.2.2: {}
+
+ stackback@0.0.2: {}
+
+ std-env@4.3.0: {}
+
+ supports-color@10.2.2: {}
+
+ tinybench@2.9.0: {}
+
+ tinyexec@1.3.1: {}
+
+ tinyglobby@0.2.17:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.7)
+ picomatch: 4.0.7
+
+ tinyrainbow@3.2.0: {}
+
+ tslib@2.8.1:
+ optional: true
+
+ typescript@5.9.3: {}
+
+ undici@7.29.1: {}
+
+ unenv@2.0.0-rc.24:
+ dependencies:
+ pathe: 2.0.3
+
+ vite@8.3.4(esbuild@0.28.2):
+ dependencies:
+ lightningcss: 1.33.0
+ picomatch: 4.0.7
+ postcss: 8.5.29
+ rolldown: 1.2.13
+ tinyglobby: 0.2.17
+ optionalDependencies:
+ esbuild: 0.28.2
+ fsevents: 2.3.3
+
+ vitest@4.1.11(vite@8.3.4(esbuild@0.28.2)):
+ dependencies:
+ '@vitest/expect': 4.1.11
+ '@vitest/mocker': 4.1.11(vite@8.3.4(esbuild@0.28.2))
+ '@vitest/pretty-format': 4.1.11
+ '@vitest/runner': 4.1.11
+ '@vitest/snapshot': 4.1.11
+ '@vitest/spy': 4.1.11
+ '@vitest/utils': 4.1.11
+ es-module-lexer: 2.3.2
+ expect-type: 1.4.0
+ magic-string: 0.30.21
+ obug: 2.2.1
+ pathe: 2.0.3
+ picomatch: 4.0.7
+ std-env: 4.3.0
+ tinybench: 2.9.0
+ tinyexec: 1.3.1
+ tinyglobby: 0.2.17
+ tinyrainbow: 3.2.0
+ vite: 8.3.4(esbuild@0.28.2)
+ why-is-node-running: 2.3.0
+ transitivePeerDependencies:
+ - msw
+
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
+ workerd@1.20261001.1:
+ optionalDependencies:
+ '@cloudflare/workerd-darwin-64': 1.20261001.1
+ '@cloudflare/workerd-darwin-arm64': 1.20261001.1
+ '@cloudflare/workerd-linux-64': 1.20261001.1
+ '@cloudflare/workerd-linux-arm64': 1.20261001.1
+ '@cloudflare/workerd-windows-64': 1.20261001.1
+
+ workerd@1.20261006.1:
+ optionalDependencies:
+ '@cloudflare/workerd-darwin-64': 1.20261006.1
+ '@cloudflare/workerd-darwin-arm64': 1.20261006.1
+ '@cloudflare/workerd-linux-64': 1.20261006.1
+ '@cloudflare/workerd-linux-arm64': 1.20261006.1
+ '@cloudflare/workerd-windows-64': 1.20261006.1
+
+ wrangler@4.147.0:
+ dependencies:
+ '@cloudflare/kv-asset-handler': 0.5.0
+ '@cloudflare/unenv-preset': 2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261001.1)
+ blake3-wasm: 2.1.5
+ esbuild: 0.28.1
+ miniflare: 5.20261001.0-alpha
+ path-to-regexp: 6.3.0
+ unenv: 2.0.0-rc.24
+ workerd: 1.20261001.1
+ optionalDependencies:
+ fsevents: 2.3.3
+ transitivePeerDependencies:
+ - '@types/node'
+ - bufferutil
+ - utf-8-validate
+
+ wrangler@4.149.0:
+ dependencies:
+ '@cloudflare/kv-asset-handler': 0.5.0
+ '@cloudflare/unenv-preset': 2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261006.1)
+ blake3-wasm: 2.1.5
+ esbuild: 0.28.2
+ miniflare: 5.20261006.1-alpha
+ path-to-regexp: 6.3.0
+ unenv: 2.0.0-rc.24
+ workerd: 1.20261006.1
+ optionalDependencies:
+ fsevents: 2.3.3
+ transitivePeerDependencies:
+ - '@types/node'
+ - 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
+
+ zod@4.4.3: {}
diff --git a/lib/src/patterns.ts b/lib/src/patterns.ts
new file mode 100644
index 0000000..5c9f844
--- /dev/null
+++ b/lib/src/patterns.ts
@@ -0,0 +1,51 @@
+/**
+ * @file a work-alike for enough of @endo/patterns: `M.string()`, `M.any()`,
+ * and literal patterns (a string, number, boolean, or null matches only
+ * itself, so `"cookie"` as a key shape admits exactly one key).
+ * @see https://github.com/endojs/endo/tree/master/packages/patterns
+ */
+
+type MatcherKind = "string" | "any";
+
+/** a matcher made by {@link M}; recognized by identity, so it is meaningful
+ * only in the realm that made it (it does not survive an RPC copy) */
+export type Matcher = Readonly<{ toString(): string }>;
+export type Pattern = Matcher | string | number | boolean | null;
+
+const kinds = new WeakMap();
+
+const makeMatcher = (kind: MatcherKind, label: string): Matcher => {
+ const matcher = Object.freeze({ toString: () => label });
+ kinds.set(matcher, kind);
+ return matcher;
+};
+
+const STRING = makeMatcher("string", "M.string()");
+const ANY = makeMatcher("any", "M.any()");
+
+export const M = Object.freeze({
+ string: (): Matcher => STRING,
+ any: (): Matcher => ANY,
+});
+
+export function matches(specimen: unknown, pattern: Pattern): boolean {
+ if (typeof pattern === "object" && pattern !== null) {
+ const kind = kinds.get(pattern);
+ if (kind === "string") return typeof specimen === "string";
+ if (kind === "any") return true;
+ throw new TypeError("not a pattern");
+ }
+ return Object.is(specimen, pattern);
+}
+
+/**
+ * throw unless `specimen` matches `pattern`. Unlike endo's, the message
+ * names the pattern and `label` but never the specimen: specimens here
+ * can be credentials, and messages cross RPC and land in logs.
+ */
+export function mustMatch(specimen: unknown, pattern: Pattern, label: string): void {
+ if (!matches(specimen, pattern)) {
+ const shape = typeof pattern === "object" && pattern !== null ? String(pattern) : JSON.stringify(pattern);
+ throw new TypeError(`${label}: must match ${shape}`);
+ }
+}
diff --git a/lib/src/store.ts b/lib/src/store.ts
new file mode 100644
index 0000000..212586d
--- /dev/null
+++ b/lib/src/store.ts
@@ -0,0 +1,133 @@
+/**
+ * @file a work-alike for enough of @agoric/store's MapStore: string keys,
+ * shapes checked with ./patterns.js, entries kept in a Durable Object's
+ * synchronous KV storage. The facets are RpcTargets (workerd's / capnweb's,
+ * in place of endo's `Far`), so a store or its read-only view can be
+ * handed out by reference.
+ * @see https://github.com/Agoric/agoric-sdk/tree/master/packages/store
+ */
+
+import { RpcTarget } from "cloudflare:workers";
+import { M, mustMatch, type Pattern } from "./patterns.js";
+
+/** the storage a store needs: a DO's `ctx.storage.kv`, or a stand-in */
+export type Kv = Pick;
+
+export type StoreShapes = { keyShape?: Pattern; valueShape?: Pattern };
+
+/**
+ * Entries live under `store::`, so the label also names the
+ * storage area (as a baggage key does for endo's durable stores): two
+ * stores with one label on one Kv are the same store.
+ *
+ * Error messages name the label and shapes, never keys or values.
+ */
+export function makeMapStore(label: string, { keyShape = M.string(), valueShape = M.any() }: StoreShapes, kv: Kv) {
+ if (label.includes(":")) throw new TypeError("store label must not contain ':'");
+ const prefix = `store:${label}:`;
+ const core: Core = {
+ label,
+ has: (key) => kv.get(prefix + key) !== undefined,
+ get: (key) => kv.get(prefix + key),
+ put: (key, value) => kv.put(prefix + key, value),
+ delete: (key) => {
+ kv.delete(prefix + key);
+ },
+ entries: () => [...kv.list({ prefix })].map(([k, v]): [string, V] => [k.slice(prefix.length), v]),
+ checkKey: (key) => mustMatch(key, keyShape, `${label}: key`),
+ checkValue: (value) => mustMatch(value, valueShape, `${label}: value`),
+ };
+ return new MapStore(core);
+}
+
+type Core = {
+ label: string;
+ has(key: string): boolean;
+ get(key: string): V | undefined;
+ put(key: string, value: V): void;
+ delete(key: string): void;
+ entries(): [string, V][];
+ checkKey(key: unknown): void;
+ checkValue(value: unknown): void;
+};
+
+/** the queries */
+function reads(core: Core) {
+ return {
+ has: (key: string): boolean => core.has(key),
+ get: (key: string): V => {
+ const value = core.get(key);
+ if (value === undefined) throw new RangeError(`${core.label}: key not found`);
+ return value;
+ },
+ keys: (): string[] => core.entries().map(([k]) => k),
+ values: (): V[] => core.entries().map(([, v]) => v),
+ entries: (): [string, V][] => core.entries(),
+ getSize: (): number => core.entries().length,
+ };
+}
+
+/** a store's queries only: what a reader of a rotating credential holds */
+export class ReadonlyMapStore extends RpcTarget {
+ #reads: ReturnType>;
+
+ constructor(core: Core) {
+ super();
+ this.#reads = reads(core);
+ }
+
+ has(key: string): boolean {
+ return this.#reads.has(key);
+ }
+ get(key: string): V {
+ return this.#reads.get(key);
+ }
+ keys(): string[] {
+ return this.#reads.keys();
+ }
+ values(): V[] {
+ return this.#reads.values();
+ }
+ entries(): [string, V][] {
+ return this.#reads.entries();
+ }
+ getSize(): number {
+ return this.#reads.getSize();
+ }
+}
+
+/** a store's full authority: the queries plus `init`/`set`/`delete`, and
+ * {@link MapStore.readOnly} to hand out the queries alone */
+export class MapStore extends ReadonlyMapStore {
+ #core: Core;
+
+ constructor(core: Core) {
+ super(core);
+ this.#core = core;
+ }
+
+ /** add a key that is not yet present */
+ init(key: string, value: V): void {
+ this.#core.checkKey(key);
+ this.#core.checkValue(value);
+ if (this.#core.has(key)) throw new RangeError(`${this.#core.label}: key already registered`);
+ this.#core.put(key, value);
+ }
+
+ /** replace the value of a key that is present */
+ set(key: string, value: V): void {
+ this.#core.checkKey(key);
+ this.#core.checkValue(value);
+ if (!this.#core.has(key)) throw new RangeError(`${this.#core.label}: key not found`);
+ this.#core.put(key, value);
+ }
+
+ delete(key: string): void {
+ if (!this.#core.has(key)) throw new RangeError(`${this.#core.label}: key not found`);
+ this.#core.delete(key);
+ }
+
+ readOnly(): ReadonlyMapStore {
+ return new ReadonlyMapStore(this.#core);
+ }
+}
diff --git a/lib/tests/store.test.ts b/lib/tests/store.test.ts
new file mode 100644
index 0000000..f058cfc
--- /dev/null
+++ b/lib/tests/store.test.ts
@@ -0,0 +1,87 @@
+import { env } from "cloudflare:workers";
+import { evictDurableObject } from "cloudflare:test";
+import { describe, expect, it } from "vitest";
+import { M, matches, mustMatch } from "../src/patterns.js";
+
+type TestEnv = { STORE_DO: DurableObjectNamespace };
+const testEnv = env as unknown as TestEnv;
+
+const SECRET = "session=s3kr1t";
+
+/** the message an RPC call fails with, or "" if it succeeds */
+async function errorOf(call: Promise): Promise {
+ try {
+ await call;
+ return "";
+ } catch (err) {
+ return String(err);
+ }
+}
+
+describe("patterns", () => {
+ it("M.string() matches strings only; a literal matches only itself", () => {
+ expect(matches("x", M.string())).toBe(true);
+ expect(matches(1, M.string())).toBe(false);
+ expect(matches(undefined, M.any())).toBe(true);
+ expect(matches("cookie", "cookie")).toBe(true);
+ expect(matches("authorization", "cookie")).toBe(false);
+ });
+
+ it("mustMatch names the shape but not the specimen", () => {
+ expect(() => mustMatch(SECRET, "cookie", "jar: key")).toThrow('jar: key: must match "cookie"');
+ expect(() => mustMatch(SECRET, "cookie", "jar: key")).not.toThrow(SECRET);
+ });
+});
+
+describe("MapStore over RPC", () => {
+ const fresh = () => testEnv.STORE_DO.get(testEnv.STORE_DO.newUniqueId());
+
+ it("rotates: the admin's set is what the read-only view sees next", async () => {
+ const store = await fresh().cookies();
+ const rd = await store.readOnly();
+ await store.init("cookie", "session=old");
+ expect(await rd.get("cookie")).toBe("session=old");
+ await store.set("cookie", "session=new");
+ expect(await rd.entries()).toEqual([["cookie", "session=new"]]);
+ });
+
+ it("the read-only view has no write methods", async () => {
+ const store = await fresh().cookies();
+ const rd = (await store.readOnly()) as unknown as { set(k: string, v: string): Promise };
+ expect(await errorOf(rd.set("cookie", SECRET))).not.toBe("");
+ expect(await store.has("cookie")).toBe(false);
+ });
+
+ it("the key shape admits only `cookie`; errors carry no values", async () => {
+ const store = await fresh().cookies();
+ const err = await errorOf(store.init("authorization", SECRET));
+ expect(err).toContain('cookie: key: must match "cookie"');
+ expect(err).not.toContain(SECRET);
+ expect(await errorOf(store.init("cookie", 42 as unknown as string))).toContain("cookie: value: must match M.string()");
+ });
+
+ it("init refuses a present key; set and get refuse a missing one", async () => {
+ const store = await fresh().cookies();
+ expect(await errorOf(store.set("cookie", SECRET))).toContain("cookie: key not found");
+ expect(await errorOf(store.get("cookie"))).toContain("cookie: key not found");
+ await store.init("cookie", SECRET);
+ const err = await errorOf(store.init("cookie", SECRET));
+ expect(err).toContain("cookie: key already registered");
+ expect(err).not.toContain(SECRET);
+ });
+
+ it("survives eviction: entries live in DO storage", async () => {
+ const stub = fresh();
+ {
+ // disposed before eviction: a live store stub holds the DO open
+ using store = await stub.cookies();
+ await store.init("cookie", "session=durable");
+ }
+ await evictDurableObject(stub);
+ using again = await stub.cookies();
+ expect(await again.get("cookie")).toBe("session=durable");
+ expect(await again.getSize()).toBe(1);
+ await again.delete("cookie");
+ expect(await again.keys()).toEqual([]);
+ });
+});
diff --git a/lib/tests/test-worker.ts b/lib/tests/test-worker.ts
new file mode 100644
index 0000000..631f997
--- /dev/null
+++ b/lib/tests/test-worker.ts
@@ -0,0 +1,15 @@
+/** @file the tests' main module: a DO that hosts stores on its own storage. */
+import { DurableObject } from "cloudflare:workers";
+import { M } from "../src/patterns.js";
+import { makeMapStore, type MapStore } from "../src/store.js";
+
+/** hosts the store from the credential-rotation sketch: one key, `cookie` */
+export class StoreHost extends DurableObject {
+ cookies(): MapStore {
+ return makeMapStore("cookie", { keyShape: "cookie", valueShape: M.string() }, this.ctx.storage.kv);
+ }
+}
+
+export default {
+ fetch: () => new Response("store tests", { status: 404 }),
+};
diff --git a/lib/tests/tsconfig.json b/lib/tests/tsconfig.json
new file mode 100644
index 0000000..631188f
--- /dev/null
+++ b/lib/tests/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "types": ["@cloudflare/vitest-plugin/types"]
+ },
+ "include": ["./**/*", "../src/**/*", "../worker-configuration.d.ts"]
+}
diff --git a/lib/tsconfig.json b/lib/tsconfig.json
new file mode 100644
index 0000000..cd40bd6
--- /dev/null
+++ b/lib/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "preserve",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "esModuleInterop": true,
+ "verbatimModuleSyntax": true,
+ "skipLibCheck": true,
+ "lib": ["ES2022", "ESNext.Disposable"],
+ "noEmit": true,
+ "types": []
+ },
+ "include": ["src/**/*", "worker-configuration.d.ts"]
+}
diff --git a/lib/vitest.config.ts b/lib/vitest.config.ts
new file mode 100644
index 0000000..61792f8
--- /dev/null
+++ b/lib/vitest.config.ts
@@ -0,0 +1,9 @@
+import { defineProject } from "vitest/config";
+import { cloudflareTest } from "@cloudflare/vitest-plugin";
+
+export default defineProject({
+ plugins: [cloudflareTest({ wrangler: { configPath: "./wrangler.tests.jsonc" } })],
+ test: {
+ include: ["tests/**/*.test.ts"],
+ },
+});
diff --git a/lib/wrangler.tests.jsonc b/lib/wrangler.tests.jsonc
new file mode 100644
index 0000000..53603b6
--- /dev/null
+++ b/lib/wrangler.tests.jsonc
@@ -0,0 +1,13 @@
+{
+ "$schema": "node_modules/wrangler/config-schema.json",
+ // a host DO for the stores under test (lib ships no worker of its own)
+ "name": "capsite-lib-tests",
+ "main": "./tests/test-worker.ts",
+ "compatibility_date": "2026-09-29",
+ "durable_objects": {
+ "bindings": [{ "name": "STORE_DO", "class_name": "StoreHost" }]
+ },
+ "exports": {
+ "StoreHost": { "type": "durable-object", "storage": "sqlite" }
+ }
+}
--
2.53.0
From 10414c2ce6c9883ccf5918091b5614f190c4b6e9 Mon Sep 17 00:00:00 2001
From: Dan Connolly
Date: Thu, 8 Oct 2026 15:03:57 -0500
Subject: [PATCH 2/8] capsite/webapp: rotate the cookie in place
The cookie moves out of the immutable init into a MapStore (key shape
"cookie", value M.string()) in the instance's DO storage. Handles get
its read-only view via the new WebApp.withHeaderMap, which reads at
every send, so a rotation reaches handles already out and the webapp
id, and with it the cache's wiring, stays put (checklist goal 6).
Whoever holds the id can rotate, by RPC (WebAppFactory.cookies(id)) or
POST /at//cookie; the diigo account owner revokes by logging out.
Durable state: instances minted earlier keep the cookie in
init.headers. The first handle() moves it to the store and rewrites
init without it, in one transaction. Code from before this commit
would then see no cookie, so rolling back past it needs the reverse
move.
webapp gains a package.json and a vitest workerd pool for the tests.
Generated-by: Claude Opus 5.5
---
capsite-checklist.md | 8 +-
webapp/README.md | 12 +
webapp/package.json | 16 +
webapp/pnpm-lock.yaml | 2240 +++++++++++++++++++++++++++++++++++
webapp/src/index.ts | 125 +-
webapp/tests/cookie.test.ts | 108 ++
webapp/tests/tsconfig.json | 7 +
webapp/tsconfig.json | 1 +
webapp/vitest.config.ts | 9 +
9 files changed, 2512 insertions(+), 14 deletions(-)
create mode 100644 webapp/package.json
create mode 100644 webapp/pnpm-lock.yaml
create mode 100644 webapp/tests/cookie.test.ts
create mode 100644 webapp/tests/tsconfig.json
create mode 100644 webapp/vitest.config.ts
diff --git a/capsite-checklist.md b/capsite-checklist.md
index 673e554..726511c 100644
--- a/capsite-checklist.md
+++ b/capsite-checklist.md
@@ -109,10 +109,10 @@ narrative.
weekly page renders against prod.
- [ ] **6. Cookie expiry**: when diigo's session ends, head refreshes
fail; the public page keeps serving what's stored (say so on the
- page, don't fail silently). Rotation is manual: re-mint via
- `/_connect`. Open question: can an existing cache be re-wired to
- a new webapp id (keeping its cache id and public username), or
- does re-mint mean a new cache? Prefer re-wire — durable ids.
+ page, don't fail silently). Rotation is in place, keeping the
+ webapp id (so the cache's wiring and ids hold): `POST
+ /at//cookie` with the new cookie (webapp README, "The cookie
+ store"). Still open: a rotate form in diigo-cache's UI.
- [ ] **7. bib sync #195**: repeat the pattern for the next
read-access need (webapp mints for any origin; the cache's
item shape is diigo-specific): [the zotero goal tree](zotero-checklist.md).
diff --git a/webapp/README.md b/webapp/README.md
index b75d2ad..b16be13 100644
--- a/webapp/README.md
+++ b/webapp/README.md
@@ -14,6 +14,7 @@ grows past a form or two.
- `GET /form?render=1&…` — the same form for a proxying worker (diigo-cache's `/_connect`): the action is left as an `ACTION` placeholder for the proxy to point at its own relay.
- `POST /` — mint. Form-encoded (that page) or JSON `{url, headers, msPerCall}` (service-style). Responds `{id}` (and `x-webapp-id`). The id is *not* logged: possession is authority.
- `POST /at/` — drive an instance by capability: JSON `{path?, query?, method?, body?}` → the fluent chain → `{status, text}` (or `{error}`; messages carry no header/param values).
+- `POST /at//cookie` — rotate the instance's cookie in place: JSON `{cookie}` → `{ok: true}`. The id that drives an instance can also replace its cookie; the account owner revokes by logging out.
## RPC surface (same-account workers; binding `entrypoint: "WebAppFactory"`)
@@ -22,9 +23,20 @@ grows past a form or two.
promise pipelining makes a whole `pathjoin().query().get()` chain a
single round trip
- `at(id)` — the fluent stub for a previously-minted instance
+- `cookies(id)` — the instance's cookie store (a MapStore from
+ [the capsite lib](../lib/src/store.ts) with key shape `"cookie"`):
+ `set("cookie", …)` rotates it under every handle already out
+
+## The cookie store
+
+An instance is fixed at creation except for its cookie, which lives in a
+store rather than in `init`; every send reads it. Instances minted before
+the store held their cookie in `init.headers`; the first `handle()` moves
+it over in one transaction.
## Run
+ pnpm test # cookie store + rotation (vitest, workerd pool)
npx wrangler dev # → its own ready line (usually :8787 with the
# two-config diigo-cache run; standalone, :8788)
diff --git a/webapp/package.json b/webapp/package.json
new file mode 100644
index 0000000..8f0b269
--- /dev/null
+++ b/webapp/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "webapp",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "types": "wrangler types",
+ "lint": "wrangler types --check && tsc -p tsconfig.json && tsc -p tests/tsconfig.json",
+ "test": "vitest run"
+ },
+ "devDependencies": {
+ "@cloudflare/vitest-plugin": "1.3.6",
+ "typescript": "^5.9.0",
+ "vitest": "4.1.11",
+ "wrangler": "^4.148.0"
+ }
+}
diff --git a/webapp/pnpm-lock.yaml b/webapp/pnpm-lock.yaml
new file mode 100644
index 0000000..c9e4751
--- /dev/null
+++ b/webapp/pnpm-lock.yaml
@@ -0,0 +1,2240 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ devDependencies:
+ '@cloudflare/vitest-plugin':
+ specifier: 1.3.6
+ version: 1.3.6(@vitest/runner@4.1.11)(@vitest/snapshot@4.1.11)(vitest@4.1.11(vite@8.3.4(esbuild@0.28.2)))
+ typescript:
+ specifier: ^5.9.0
+ version: 5.9.3
+ vitest:
+ specifier: 4.1.11
+ version: 4.1.11(vite@8.3.4(esbuild@0.28.2))
+ wrangler:
+ specifier: ^4.148.0
+ version: 4.149.0
+
+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.2':
+ resolution: {integrity: sha512-JBP1+Z7ZSNG/d4mRP+y8VC5dka3tZVMLEZRvS+rzQ4DGV1EoxRFQckcJTTkXbHSQiTj0DtNI01Zwb/V2fX0mvQ==}
+ peerDependencies:
+ unenv: 2.0.0-rc.24
+ workerd: '>1.20260305.0 <2.0.0-0'
+ peerDependenciesMeta:
+ workerd:
+ optional: true
+
+ '@cloudflare/vitest-plugin@1.3.6':
+ resolution: {integrity: sha512-vYhu5AwU0PErTM90wTtHBC6sR4nrcK3R401IPLCNfwJIKjVb208HQF8VveHPI5PxNU4FVgChG8i75I+f93MHrw==}
+ peerDependencies:
+ '@vitest/runner': ^4.1.0
+ '@vitest/snapshot': ^4.1.0
+ vitest: ^4.1.0
+
+ '@cloudflare/workerd-darwin-64@1.20261001.1':
+ resolution: {integrity: sha512-4cgSgDf28JSw/P5Dj5GCS59hzVqS5XnmGAWNkvYHLI6ODU9idGaMMNEuhJXDkEG/lsABmlVlnCgsdh2VbKWepw==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@cloudflare/workerd-darwin-64@1.20261006.1':
+ resolution: {integrity: sha512-lCX6avJO2It9AyDEYitjmOtJRVp/xmaW0TziyoLAnfxlzozyyRbSFfIIMMCiDx/sTP1++E4VLbV+7BHSD33f0Q==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@cloudflare/workerd-darwin-arm64@1.20261001.1':
+ resolution: {integrity: sha512-8ulAWruEVouNmEIsQsy9WSSCS9zkLu93W2MTwp5esiFyoPp05BNSVFIFsYSPi1pkFmBBd7fsnwMpbLkaJLaVPQ==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@cloudflare/workerd-darwin-arm64@1.20261006.1':
+ resolution: {integrity: sha512-PyFGjOaldtppnLuKsdkoYWXfLVpDjuf8u+uoykX+1Xvxo2EPmCVViq0iDKm+U7c7PRno85ehS3/MPVXyk2HwHw==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@cloudflare/workerd-linux-64@1.20261001.1':
+ resolution: {integrity: sha512-kZbTZJGrhsMOdqZ2BIybjaBRLZjYsRLWj7mcNw/6Y3hodOhp5MrNzcz4iWGwNVWwyIV+7Pl+/LX5VcgnRqdHOg==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [linux]
+
+ '@cloudflare/workerd-linux-64@1.20261006.1':
+ resolution: {integrity: sha512-Ntf9S8eesmVufdjaT2kzWlz6Ng9XGYTc6cHQVRKAg59lFufwsIabGeMT6U4KkrpEeSIxgHq8aPtuLbZXE19tCA==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [linux]
+
+ '@cloudflare/workerd-linux-arm64@1.20261001.1':
+ resolution: {integrity: sha512-oOk3Zj6k/8oP0FJgZBWDn7+BqbsqIMEMaV95pulHPVbwVu4wYoLfQq2hp0vkbCNsCFLqZb7cqixXdrwD54ZIow==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@cloudflare/workerd-linux-arm64@1.20261006.1':
+ resolution: {integrity: sha512-JyeDBymVWw5tVRebSsVE8ojDYxd6UlZrAseHU8G+NpIyd5wF61tXlF/axLeCUqu0pOhBFHb8I5QKi0jwrCK41Q==}
+ engines: {node: '>=16'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@cloudflare/workerd-windows-64@1.20261001.1':
+ resolution: {integrity: sha512-uRxm5W4VyBkoSaoP1BfOuH0873tE+vxsknF4sab6/5YIRvIAufChq3QDp+zlAn67PuGPGcn7W8QraA0t4ucmOQ==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [win32]
+
+ '@cloudflare/workerd-windows-64@1.20261006.1':
+ resolution: {integrity: sha512-nWP0URNqOBN9fa/gZ7eh/w051DwjOXN8j4lDZDYVXyQPsux6sV3DfyYZT+CjR/rt9nKBSmJT9QE9/bWT18+84w==}
+ engines: {node: '>=16'}
+ cpu: [x64]
+ os: [win32]
+
+ '@cspotcode/source-map-support@0.8.1':
+ resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+ engines: {node: '>=12'}
+
+ '@emnapi/runtime@1.11.3':
+ resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
+
+ '@esbuild/aix-ppc64@0.28.1':
+ resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/aix-ppc64@0.28.2':
+ resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==}
+ 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-arm@0.28.2':
+ resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==}
+ 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/android-x64@0.28.2':
+ resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==}
+ 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/darwin-x64@0.28.2':
+ resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==}
+ 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/freebsd-x64@0.28.2':
+ resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==}
+ 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-arm@0.28.2':
+ resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==}
+ 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-ia32@0.28.2':
+ resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==}
+ 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-loong64@0.28.2':
+ resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==}
+ 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-mips64el@0.28.2':
+ resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==}
+ 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-ppc64@0.28.2':
+ resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==}
+ 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-riscv64@0.28.2':
+ resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==}
+ 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-s390x@0.28.2':
+ resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==}
+ 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/linux-x64@0.28.2':
+ resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==}
+ 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/netbsd-x64@0.28.2':
+ resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==}
+ 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/openbsd-x64@0.28.2':
+ resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==}
+ 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/openharmony-arm64@0.28.2':
+ resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==}
+ 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/sunos-x64@0.28.2':
+ resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==}
+ 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-arm64@0.28.2':
+ resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==}
+ 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-ia32@0.28.2':
+ resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==}
+ 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]
+
+ '@esbuild/win32-x64@0.28.2':
+ resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==}
+ 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.4':
+ resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-arm64@0.35.5':
+ resolution: {integrity: sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.35.4':
+ resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.35.5':
+ resolution: {integrity: sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-freebsd-wasm32@0.35.4':
+ resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==}
+ engines: {node: '>=20.9.0'}
+ os: [freebsd]
+
+ '@img/sharp-freebsd-wasm32@0.35.5':
+ resolution: {integrity: sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==}
+ engines: {node: '>=20.9.0'}
+ os: [freebsd]
+
+ '@img/sharp-libvips-darwin-arm64@1.3.3':
+ resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.3.4':
+ resolution: {integrity: sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.3.3':
+ resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.3.4':
+ resolution: {integrity: sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.3.3':
+ resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm64@1.3.4':
+ resolution: {integrity: sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.3.3':
+ resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.3.4':
+ resolution: {integrity: sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.3.3':
+ resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.3.4':
+ resolution: {integrity: sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-riscv64@1.3.3':
+ resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-riscv64@1.3.4':
+ resolution: {integrity: sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.3.3':
+ resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.3.4':
+ resolution: {integrity: sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.3.3':
+ resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.3.4':
+ resolution: {integrity: sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.3':
+ resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.4':
+ resolution: {integrity: sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.3':
+ resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.4':
+ resolution: {integrity: sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.35.4':
+ resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.35.5':
+ resolution: {integrity: sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.35.4':
+ resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.35.5':
+ resolution: {integrity: sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-ppc64@0.35.4':
+ resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-linux-ppc64@0.35.5':
+ resolution: {integrity: sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-linux-riscv64@0.35.4':
+ resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-linux-riscv64@0.35.5':
+ resolution: {integrity: sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.35.4':
+ resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==}
+ engines: {node: '>=20.9.0'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.35.5':
+ resolution: {integrity: sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.35.4':
+ resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.35.5':
+ resolution: {integrity: sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.35.4':
+ resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.35.5':
+ resolution: {integrity: sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.35.4':
+ resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.35.5':
+ resolution: {integrity: sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-wasm32@0.35.4':
+ resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==}
+ engines: {node: '>=20.9.0'}
+
+ '@img/sharp-wasm32@0.35.5':
+ resolution: {integrity: sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==}
+ engines: {node: '>=20.9.0'}
+
+ '@img/sharp-webcontainers-wasm32@0.35.4':
+ resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [wasm32]
+
+ '@img/sharp-webcontainers-wasm32@0.35.5':
+ resolution: {integrity: sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==}
+ engines: {node: '>=20.9.0'}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.35.4':
+ resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-arm64@0.35.5':
+ resolution: {integrity: sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==}
+ engines: {node: '>=20.9.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.35.4':
+ resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==}
+ engines: {node: ^20.9.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.35.5':
+ resolution: {integrity: sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==}
+ engines: {node: ^20.9.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.35.4':
+ resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.35.5':
+ resolution: {integrity: sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==}
+ engines: {node: '>=20.9.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.6.0':
+ resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
+
+ '@jridgewell/trace-mapping@0.3.9':
+ resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
+ '@oxc-project/types@0.153.0':
+ resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==}
+
+ '@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==}
+
+ '@rolldown/binding-android-arm-eabi@1.2.13':
+ resolution: {integrity: sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [android]
+
+ '@rolldown/binding-android-arm64@1.2.13':
+ resolution: {integrity: sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
+
+ '@rolldown/binding-darwin-arm64@1.2.13':
+ resolution: {integrity: sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rolldown/binding-darwin-x64@1.2.13':
+ resolution: {integrity: sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rolldown/binding-freebsd-x64@1.2.13':
+ resolution: {integrity: sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.2.13':
+ resolution: {integrity: sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@rolldown/binding-linux-arm64-gnu@1.2.13':
+ resolution: {integrity: sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rolldown/binding-linux-arm64-musl@1.2.13':
+ resolution: {integrity: sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rolldown/binding-linux-ppc64-gnu@1.2.13':
+ resolution: {integrity: sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rolldown/binding-linux-s390x-gnu@1.2.13':
+ resolution: {integrity: sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rolldown/binding-linux-x64-gnu@1.2.13':
+ resolution: {integrity: sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@rolldown/binding-linux-x64-musl@1.2.13':
+ resolution: {integrity: sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@rolldown/binding-openharmony-arm64@1.2.13':
+ resolution: {integrity: sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rolldown/binding-win32-arm64-msvc@1.2.13':
+ resolution: {integrity: sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rolldown/binding-win32-x64-msvc@1.2.13':
+ resolution: {integrity: sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@rolldown/pluginutils@1.0.1':
+ resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
+
+ '@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==}
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@types/chai@5.2.3':
+ resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
+ '@types/estree@1.0.9':
+ resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+
+ '@vitest/expect@4.1.11':
+ resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==}
+
+ '@vitest/mocker@4.1.11':
+ resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@4.1.11':
+ resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==}
+
+ '@vitest/runner@4.1.11':
+ resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==}
+
+ '@vitest/snapshot@4.1.11':
+ resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==}
+
+ '@vitest/spy@4.1.11':
+ resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==}
+
+ '@vitest/utils@4.1.11':
+ resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==}
+
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
+ blake3-wasm@2.1.5:
+ resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
+
+ chai@6.3.0:
+ resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==}
+ engines: {node: '>=18'}
+
+ cjs-module-lexer@1.2.3:
+ resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ 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==}
+
+ es-module-lexer@2.3.2:
+ resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==}
+
+ esbuild@0.28.1:
+ resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ esbuild@0.28.2:
+ resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ expect-type@1.4.0:
+ resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
+ engines: {node: '>=12.0.0'}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ kleur@4.1.5:
+ resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+ engines: {node: '>=6'}
+
+ lightningcss-android-arm64@1.33.0:
+ resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.33.0:
+ resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.33.0:
+ resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.33.0:
+ resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.33.0:
+ resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.33.0:
+ resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.33.0:
+ resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.33.0:
+ resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.33.0:
+ resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.33.0:
+ resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.33.0:
+ resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.33.0:
+ resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
+ engines: {node: '>= 12.0.0'}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ miniflare@5.20261001.0-alpha:
+ resolution: {integrity: sha512-GaimS5mSIOMyvd16ga+e1/QkI8cmp3z35QPHFex0DktqNQf2RVZQwMPQXdyoUreUiFP/0Gpe2MoQInTyMAD5xA==}
+ engines: {node: '>=22.0.0'}
+
+ miniflare@5.20261006.1-alpha:
+ resolution: {integrity: sha512-6ylv8tamkrspDI2R/cAYfv+YWmfGNdXe76Pi9TauEUWsFVXn3LNF9Yk8oduzf1a+TYsSRYTqlOE7+nGGRsMHdw==}
+ engines: {node: '>=22.0.0'}
+
+ nanoid@3.3.20:
+ resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ obug@2.2.1:
+ resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==}
+ engines: {node: '>=12.20.0'}
+
+ path-to-regexp@6.3.0:
+ resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.7:
+ resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
+ engines: {node: '>=12'}
+
+ postcss@8.5.29:
+ resolution: {integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ rolldown@1.2.13:
+ resolution: {integrity: sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+
+ semver@7.8.5:
+ resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ sharp@0.35.4:
+ resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==}
+ engines: {node: '>=20.9.0'}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ sharp@0.35.5:
+ resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==}
+ engines: {node: '>=20.9.0'}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+ source-map-js@1.2.2:
+ resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==}
+ engines: {node: '>=0.10.0'}
+
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ std-env@4.3.0:
+ resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==}
+
+ supports-color@10.2.2:
+ resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==}
+ engines: {node: '>=18'}
+
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinyexec@1.3.1:
+ resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==}
+ engines: {node: '>=18'}
+
+ tinyglobby@0.2.17:
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+
+ tinyrainbow@3.2.0:
+ resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==}
+ engines: {node: '>=14.0.0'}
+
+ 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.1:
+ resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==}
+ engines: {node: '>=20.18.1'}
+
+ unenv@2.0.0-rc.24:
+ resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==}
+
+ vite@8.3.4:
+ resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ '@vitejs/devtools': ^0.7.1
+ esbuild: ^0.27.0 || ^0.28.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ '@vitejs/devtools':
+ optional: true
+ esbuild:
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ vitest@4.1.11:
+ resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==}
+ engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@opentelemetry/api': ^1.9.0
+ '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
+ '@vitest/browser-playwright': 4.1.11
+ '@vitest/browser-preview': 4.1.11
+ '@vitest/browser-webdriverio': 4.1.11
+ '@vitest/coverage-istanbul': 4.1.11
+ '@vitest/coverage-v8': 4.1.11
+ '@vitest/ui': 4.1.11
+ happy-dom: '*'
+ jsdom: '*'
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@opentelemetry/api':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser-playwright':
+ optional: true
+ '@vitest/browser-preview':
+ optional: true
+ '@vitest/browser-webdriverio':
+ optional: true
+ '@vitest/coverage-istanbul':
+ optional: true
+ '@vitest/coverage-v8':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ workerd@1.20261001.1:
+ resolution: {integrity: sha512-d/SIYHFO0PT/wiFZg8in4NpRIxYuFwslX1HdylOtWkBIIUmSpkGFhK820cV84XACFylwJ48xuRoWW/8DWDPsPQ==}
+ engines: {node: '>=16'}
+ hasBin: true
+
+ workerd@1.20261006.1:
+ resolution: {integrity: sha512-8MadBuffZ5NdpLwZjUmsqT5QlaKXq4gjgT6wzcKeynZTICNXyePhc+5QVoKiK7+MMhFTLNrRO2KXLlpk8Q6BkA==}
+ engines: {node: '>=16'}
+ hasBin: true
+
+ wrangler@4.147.0:
+ resolution: {integrity: sha512-pQYRoiq8PTAxphaG69z8+GC1DkSGd19EDZehQ8zxjo/Ko3mRB6Qs1mTrd8ZuKAarLklIjTqr1lUdCK9r4q2hUg==}
+ engines: {node: '>=22.0.0'}
+ hasBin: true
+ peerDependencies:
+ '@cloudflare/workers-types': ^5.20261001.1
+ peerDependenciesMeta:
+ '@cloudflare/workers-types':
+ optional: true
+
+ wrangler@4.149.0:
+ resolution: {integrity: sha512-OzK7xmB5r5iLKb3cIT3783g13fe6T7xyeKu9LEdUyl+DKAM2KP/jkjnE0OUhyw+mB/XJM9ycjgF3MWm8PR9amg==}
+ engines: {node: '>=22.0.0'}
+ hasBin: true
+ peerDependencies:
+ '@cloudflare/workers-types': ^5.20261006.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==}
+
+ zod@4.4.3:
+ resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
+
+snapshots:
+
+ '@cloudflare/kv-asset-handler@0.5.0': {}
+
+ '@cloudflare/unenv-preset@2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261001.1)':
+ dependencies:
+ unenv: 2.0.0-rc.24
+ optionalDependencies:
+ workerd: 1.20261001.1
+
+ '@cloudflare/unenv-preset@2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261006.1)':
+ dependencies:
+ unenv: 2.0.0-rc.24
+ optionalDependencies:
+ workerd: 1.20261006.1
+
+ '@cloudflare/vitest-plugin@1.3.6(@vitest/runner@4.1.11)(@vitest/snapshot@4.1.11)(vitest@4.1.11(vite@8.3.4(esbuild@0.28.2)))':
+ dependencies:
+ '@vitest/runner': 4.1.11
+ '@vitest/snapshot': 4.1.11
+ cjs-module-lexer: 1.2.3
+ esbuild: 0.28.1
+ miniflare: 5.20261001.0-alpha
+ vitest: 4.1.11(vite@8.3.4(esbuild@0.28.2))
+ wrangler: 4.147.0
+ zod: 4.4.3
+ transitivePeerDependencies:
+ - '@cloudflare/workers-types'
+ - '@types/node'
+ - bufferutil
+ - utf-8-validate
+
+ '@cloudflare/workerd-darwin-64@1.20261001.1':
+ optional: true
+
+ '@cloudflare/workerd-darwin-64@1.20261006.1':
+ optional: true
+
+ '@cloudflare/workerd-darwin-arm64@1.20261001.1':
+ optional: true
+
+ '@cloudflare/workerd-darwin-arm64@1.20261006.1':
+ optional: true
+
+ '@cloudflare/workerd-linux-64@1.20261001.1':
+ optional: true
+
+ '@cloudflare/workerd-linux-64@1.20261006.1':
+ optional: true
+
+ '@cloudflare/workerd-linux-arm64@1.20261001.1':
+ optional: true
+
+ '@cloudflare/workerd-linux-arm64@1.20261006.1':
+ optional: true
+
+ '@cloudflare/workerd-windows-64@1.20261001.1':
+ optional: true
+
+ '@cloudflare/workerd-windows-64@1.20261006.1':
+ optional: true
+
+ '@cspotcode/source-map-support@0.8.1':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.9
+
+ '@emnapi/runtime@1.11.3':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@esbuild/aix-ppc64@0.28.1':
+ optional: true
+
+ '@esbuild/aix-ppc64@0.28.2':
+ optional: true
+
+ '@esbuild/android-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/android-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/android-arm@0.28.1':
+ optional: true
+
+ '@esbuild/android-arm@0.28.2':
+ optional: true
+
+ '@esbuild/android-x64@0.28.1':
+ optional: true
+
+ '@esbuild/android-x64@0.28.2':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/darwin-x64@0.28.1':
+ optional: true
+
+ '@esbuild/darwin-x64@0.28.2':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.28.1':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-arm@0.28.1':
+ optional: true
+
+ '@esbuild/linux-arm@0.28.2':
+ optional: true
+
+ '@esbuild/linux-ia32@0.28.1':
+ optional: true
+
+ '@esbuild/linux-ia32@0.28.2':
+ optional: true
+
+ '@esbuild/linux-loong64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-loong64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.28.1':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.28.2':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.28.2':
+ optional: true
+
+ '@esbuild/linux-s390x@0.28.1':
+ optional: true
+
+ '@esbuild/linux-s390x@0.28.2':
+ optional: true
+
+ '@esbuild/linux-x64@0.28.1':
+ optional: true
+
+ '@esbuild/linux-x64@0.28.2':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.28.1':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.28.2':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.28.1':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.28.2':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/sunos-x64@0.28.1':
+ optional: true
+
+ '@esbuild/sunos-x64@0.28.2':
+ optional: true
+
+ '@esbuild/win32-arm64@0.28.1':
+ optional: true
+
+ '@esbuild/win32-arm64@0.28.2':
+ optional: true
+
+ '@esbuild/win32-ia32@0.28.1':
+ optional: true
+
+ '@esbuild/win32-ia32@0.28.2':
+ optional: true
+
+ '@esbuild/win32-x64@0.28.1':
+ optional: true
+
+ '@esbuild/win32-x64@0.28.2':
+ optional: true
+
+ '@img/colour@1.1.0': {}
+
+ '@img/sharp-darwin-arm64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.3.3
+ optional: true
+
+ '@img/sharp-darwin-arm64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.3.4
+ optional: true
+
+ '@img/sharp-darwin-x64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.3.3
+ optional: true
+
+ '@img/sharp-darwin-x64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.3.4
+ optional: true
+
+ '@img/sharp-freebsd-wasm32@0.35.4':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.4
+ optional: true
+
+ '@img/sharp-freebsd-wasm32@0.35.5':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.5
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.3.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.3':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.3.4':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-arm64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-arm@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-arm@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-s390x@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-s390x@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.3.4
+ optional: true
+
+ '@img/sharp-linux-x64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.3.3
+ optional: true
+
+ '@img/sharp-linux-x64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.3.4
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.3.3
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.3.4
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.35.4':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.3.3
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.35.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.3.4
+ optional: true
+
+ '@img/sharp-wasm32@0.35.4':
+ dependencies:
+ '@emnapi/runtime': 1.11.3
+ optional: true
+
+ '@img/sharp-wasm32@0.35.5':
+ dependencies:
+ '@emnapi/runtime': 1.11.3
+ optional: true
+
+ '@img/sharp-webcontainers-wasm32@0.35.4':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.4
+ optional: true
+
+ '@img/sharp-webcontainers-wasm32@0.35.5':
+ dependencies:
+ '@img/sharp-wasm32': 0.35.5
+ optional: true
+
+ '@img/sharp-win32-arm64@0.35.4':
+ optional: true
+
+ '@img/sharp-win32-arm64@0.35.5':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.35.4':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.35.5':
+ optional: true
+
+ '@img/sharp-win32-x64@0.35.4':
+ optional: true
+
+ '@img/sharp-win32-x64@0.35.5':
+ optional: true
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.6.0': {}
+
+ '@jridgewell/trace-mapping@0.3.9':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.6.0
+
+ '@oxc-project/types@0.153.0': {}
+
+ '@poppinss/colors@4.1.6':
+ dependencies:
+ kleur: 4.1.5
+
+ '@poppinss/dumper@0.6.5':
+ dependencies:
+ '@poppinss/colors': 4.1.6
+ '@sindresorhus/is': 7.2.0
+ supports-color: 10.2.2
+
+ '@poppinss/exception@1.2.3': {}
+
+ '@rolldown/binding-android-arm-eabi@1.2.13':
+ optional: true
+
+ '@rolldown/binding-android-arm64@1.2.13':
+ optional: true
+
+ '@rolldown/binding-darwin-arm64@1.2.13':
+ optional: true
+
+ '@rolldown/binding-darwin-x64@1.2.13':
+ optional: true
+
+ '@rolldown/binding-freebsd-x64@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-gnu@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-musl@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-ppc64-gnu@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-s390x-gnu@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-x64-gnu@1.2.13':
+ optional: true
+
+ '@rolldown/binding-linux-x64-musl@1.2.13':
+ optional: true
+
+ '@rolldown/binding-openharmony-arm64@1.2.13':
+ optional: true
+
+ '@rolldown/binding-win32-arm64-msvc@1.2.13':
+ optional: true
+
+ '@rolldown/binding-win32-x64-msvc@1.2.13':
+ optional: true
+
+ '@rolldown/pluginutils@1.0.1': {}
+
+ '@sindresorhus/is@7.2.0': {}
+
+ '@speed-highlight/core@1.2.24': {}
+
+ '@standard-schema/spec@1.1.0': {}
+
+ '@types/chai@5.2.3':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+ assertion-error: 2.0.1
+
+ '@types/deep-eql@4.0.2': {}
+
+ '@types/estree@1.0.9': {}
+
+ '@vitest/expect@4.1.11':
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ '@types/chai': 5.2.3
+ '@vitest/spy': 4.1.11
+ '@vitest/utils': 4.1.11
+ chai: 6.3.0
+ tinyrainbow: 3.2.0
+
+ '@vitest/mocker@4.1.11(vite@8.3.4(esbuild@0.28.2))':
+ dependencies:
+ '@vitest/spy': 4.1.11
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 8.3.4(esbuild@0.28.2)
+
+ '@vitest/pretty-format@4.1.11':
+ dependencies:
+ tinyrainbow: 3.2.0
+
+ '@vitest/runner@4.1.11':
+ dependencies:
+ '@vitest/utils': 4.1.11
+ pathe: 2.0.3
+
+ '@vitest/snapshot@4.1.11':
+ dependencies:
+ '@vitest/pretty-format': 4.1.11
+ '@vitest/utils': 4.1.11
+ magic-string: 0.30.21
+ pathe: 2.0.3
+
+ '@vitest/spy@4.1.11': {}
+
+ '@vitest/utils@4.1.11':
+ dependencies:
+ '@vitest/pretty-format': 4.1.11
+ convert-source-map: 2.0.0
+ tinyrainbow: 3.2.0
+
+ assertion-error@2.0.1: {}
+
+ blake3-wasm@2.1.5: {}
+
+ chai@6.3.0: {}
+
+ cjs-module-lexer@1.2.3: {}
+
+ convert-source-map@2.0.0: {}
+
+ cookie@1.1.1: {}
+
+ detect-libc@2.1.2: {}
+
+ error-stack-parser-es@1.0.5: {}
+
+ es-module-lexer@2.3.2: {}
+
+ 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
+
+ esbuild@0.28.2:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.28.2
+ '@esbuild/android-arm': 0.28.2
+ '@esbuild/android-arm64': 0.28.2
+ '@esbuild/android-x64': 0.28.2
+ '@esbuild/darwin-arm64': 0.28.2
+ '@esbuild/darwin-x64': 0.28.2
+ '@esbuild/freebsd-arm64': 0.28.2
+ '@esbuild/freebsd-x64': 0.28.2
+ '@esbuild/linux-arm': 0.28.2
+ '@esbuild/linux-arm64': 0.28.2
+ '@esbuild/linux-ia32': 0.28.2
+ '@esbuild/linux-loong64': 0.28.2
+ '@esbuild/linux-mips64el': 0.28.2
+ '@esbuild/linux-ppc64': 0.28.2
+ '@esbuild/linux-riscv64': 0.28.2
+ '@esbuild/linux-s390x': 0.28.2
+ '@esbuild/linux-x64': 0.28.2
+ '@esbuild/netbsd-arm64': 0.28.2
+ '@esbuild/netbsd-x64': 0.28.2
+ '@esbuild/openbsd-arm64': 0.28.2
+ '@esbuild/openbsd-x64': 0.28.2
+ '@esbuild/openharmony-arm64': 0.28.2
+ '@esbuild/sunos-x64': 0.28.2
+ '@esbuild/win32-arm64': 0.28.2
+ '@esbuild/win32-ia32': 0.28.2
+ '@esbuild/win32-x64': 0.28.2
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.9
+
+ expect-type@1.4.0: {}
+
+ fdir@6.5.0(picomatch@4.0.7):
+ optionalDependencies:
+ picomatch: 4.0.7
+
+ fsevents@2.3.3:
+ optional: true
+
+ kleur@4.1.5: {}
+
+ lightningcss-android-arm64@1.33.0:
+ optional: true
+
+ lightningcss-darwin-arm64@1.33.0:
+ optional: true
+
+ lightningcss-darwin-x64@1.33.0:
+ optional: true
+
+ lightningcss-freebsd-x64@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.33.0:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.33.0:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.33.0:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.33.0:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.33.0:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.33.0:
+ optional: true
+
+ lightningcss@1.33.0:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.33.0
+ lightningcss-darwin-arm64: 1.33.0
+ lightningcss-darwin-x64: 1.33.0
+ lightningcss-freebsd-x64: 1.33.0
+ lightningcss-linux-arm-gnueabihf: 1.33.0
+ lightningcss-linux-arm64-gnu: 1.33.0
+ lightningcss-linux-arm64-musl: 1.33.0
+ lightningcss-linux-x64-gnu: 1.33.0
+ lightningcss-linux-x64-musl: 1.33.0
+ lightningcss-win32-arm64-msvc: 1.33.0
+ lightningcss-win32-x64-msvc: 1.33.0
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.6.0
+
+ miniflare@5.20261001.0-alpha:
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ sharp: 0.35.4
+ undici: 7.29.1
+ workerd: 1.20261001.1
+ ws: 8.21.0
+ youch: 4.1.0-beta.10
+ transitivePeerDependencies:
+ - '@types/node'
+ - bufferutil
+ - utf-8-validate
+
+ miniflare@5.20261006.1-alpha:
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ sharp: 0.35.5
+ undici: 7.29.1
+ workerd: 1.20261006.1
+ ws: 8.21.0
+ youch: 4.1.0-beta.10
+ transitivePeerDependencies:
+ - '@types/node'
+ - bufferutil
+ - utf-8-validate
+
+ nanoid@3.3.20: {}
+
+ obug@2.2.1: {}
+
+ path-to-regexp@6.3.0: {}
+
+ pathe@2.0.3: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.7: {}
+
+ postcss@8.5.29:
+ dependencies:
+ nanoid: 3.3.20
+ picocolors: 1.1.1
+ source-map-js: 1.2.2
+
+ rolldown@1.2.13:
+ dependencies:
+ '@oxc-project/types': 0.153.0
+ '@rolldown/pluginutils': 1.0.1
+ optionalDependencies:
+ '@rolldown/binding-android-arm-eabi': 1.2.13
+ '@rolldown/binding-android-arm64': 1.2.13
+ '@rolldown/binding-darwin-arm64': 1.2.13
+ '@rolldown/binding-darwin-x64': 1.2.13
+ '@rolldown/binding-freebsd-x64': 1.2.13
+ '@rolldown/binding-linux-arm-gnueabihf': 1.2.13
+ '@rolldown/binding-linux-arm64-gnu': 1.2.13
+ '@rolldown/binding-linux-arm64-musl': 1.2.13
+ '@rolldown/binding-linux-ppc64-gnu': 1.2.13
+ '@rolldown/binding-linux-s390x-gnu': 1.2.13
+ '@rolldown/binding-linux-x64-gnu': 1.2.13
+ '@rolldown/binding-linux-x64-musl': 1.2.13
+ '@rolldown/binding-openharmony-arm64': 1.2.13
+ '@rolldown/binding-win32-arm64-msvc': 1.2.13
+ '@rolldown/binding-win32-x64-msvc': 1.2.13
+
+ semver@7.8.5: {}
+
+ sharp@0.35.4:
+ dependencies:
+ '@img/colour': 1.1.0
+ detect-libc: 2.1.2
+ semver: 7.8.5
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.35.4
+ '@img/sharp-darwin-x64': 0.35.4
+ '@img/sharp-freebsd-wasm32': 0.35.4
+ '@img/sharp-libvips-darwin-arm64': 1.3.3
+ '@img/sharp-libvips-darwin-x64': 1.3.3
+ '@img/sharp-libvips-linux-arm': 1.3.3
+ '@img/sharp-libvips-linux-arm64': 1.3.3
+ '@img/sharp-libvips-linux-ppc64': 1.3.3
+ '@img/sharp-libvips-linux-riscv64': 1.3.3
+ '@img/sharp-libvips-linux-s390x': 1.3.3
+ '@img/sharp-libvips-linux-x64': 1.3.3
+ '@img/sharp-libvips-linuxmusl-arm64': 1.3.3
+ '@img/sharp-libvips-linuxmusl-x64': 1.3.3
+ '@img/sharp-linux-arm': 0.35.4
+ '@img/sharp-linux-arm64': 0.35.4
+ '@img/sharp-linux-ppc64': 0.35.4
+ '@img/sharp-linux-riscv64': 0.35.4
+ '@img/sharp-linux-s390x': 0.35.4
+ '@img/sharp-linux-x64': 0.35.4
+ '@img/sharp-linuxmusl-arm64': 0.35.4
+ '@img/sharp-linuxmusl-x64': 0.35.4
+ '@img/sharp-webcontainers-wasm32': 0.35.4
+ '@img/sharp-win32-arm64': 0.35.4
+ '@img/sharp-win32-ia32': 0.35.4
+ '@img/sharp-win32-x64': 0.35.4
+
+ sharp@0.35.5:
+ dependencies:
+ '@img/colour': 1.1.0
+ detect-libc: 2.1.2
+ semver: 7.8.5
+ 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
+
+ siginfo@2.0.0: {}
+
+ source-map-js@1.2.2: {}
+
+ stackback@0.0.2: {}
+
+ std-env@4.3.0: {}
+
+ supports-color@10.2.2: {}
+
+ tinybench@2.9.0: {}
+
+ tinyexec@1.3.1: {}
+
+ tinyglobby@0.2.17:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.7)
+ picomatch: 4.0.7
+
+ tinyrainbow@3.2.0: {}
+
+ tslib@2.8.1:
+ optional: true
+
+ typescript@5.9.3: {}
+
+ undici@7.29.1: {}
+
+ unenv@2.0.0-rc.24:
+ dependencies:
+ pathe: 2.0.3
+
+ vite@8.3.4(esbuild@0.28.2):
+ dependencies:
+ lightningcss: 1.33.0
+ picomatch: 4.0.7
+ postcss: 8.5.29
+ rolldown: 1.2.13
+ tinyglobby: 0.2.17
+ optionalDependencies:
+ esbuild: 0.28.2
+ fsevents: 2.3.3
+
+ vitest@4.1.11(vite@8.3.4(esbuild@0.28.2)):
+ dependencies:
+ '@vitest/expect': 4.1.11
+ '@vitest/mocker': 4.1.11(vite@8.3.4(esbuild@0.28.2))
+ '@vitest/pretty-format': 4.1.11
+ '@vitest/runner': 4.1.11
+ '@vitest/snapshot': 4.1.11
+ '@vitest/spy': 4.1.11
+ '@vitest/utils': 4.1.11
+ es-module-lexer: 2.3.2
+ expect-type: 1.4.0
+ magic-string: 0.30.21
+ obug: 2.2.1
+ pathe: 2.0.3
+ picomatch: 4.0.7
+ std-env: 4.3.0
+ tinybench: 2.9.0
+ tinyexec: 1.3.1
+ tinyglobby: 0.2.17
+ tinyrainbow: 3.2.0
+ vite: 8.3.4(esbuild@0.28.2)
+ why-is-node-running: 2.3.0
+ transitivePeerDependencies:
+ - msw
+
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
+ workerd@1.20261001.1:
+ optionalDependencies:
+ '@cloudflare/workerd-darwin-64': 1.20261001.1
+ '@cloudflare/workerd-darwin-arm64': 1.20261001.1
+ '@cloudflare/workerd-linux-64': 1.20261001.1
+ '@cloudflare/workerd-linux-arm64': 1.20261001.1
+ '@cloudflare/workerd-windows-64': 1.20261001.1
+
+ workerd@1.20261006.1:
+ optionalDependencies:
+ '@cloudflare/workerd-darwin-64': 1.20261006.1
+ '@cloudflare/workerd-darwin-arm64': 1.20261006.1
+ '@cloudflare/workerd-linux-64': 1.20261006.1
+ '@cloudflare/workerd-linux-arm64': 1.20261006.1
+ '@cloudflare/workerd-windows-64': 1.20261006.1
+
+ wrangler@4.147.0:
+ dependencies:
+ '@cloudflare/kv-asset-handler': 0.5.0
+ '@cloudflare/unenv-preset': 2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261001.1)
+ blake3-wasm: 2.1.5
+ esbuild: 0.28.1
+ miniflare: 5.20261001.0-alpha
+ path-to-regexp: 6.3.0
+ unenv: 2.0.0-rc.24
+ workerd: 1.20261001.1
+ optionalDependencies:
+ fsevents: 2.3.3
+ transitivePeerDependencies:
+ - '@types/node'
+ - bufferutil
+ - utf-8-validate
+
+ wrangler@4.149.0:
+ dependencies:
+ '@cloudflare/kv-asset-handler': 0.5.0
+ '@cloudflare/unenv-preset': 2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261006.1)
+ blake3-wasm: 2.1.5
+ esbuild: 0.28.2
+ miniflare: 5.20261006.1-alpha
+ path-to-regexp: 6.3.0
+ unenv: 2.0.0-rc.24
+ workerd: 1.20261006.1
+ optionalDependencies:
+ fsevents: 2.3.3
+ transitivePeerDependencies:
+ - '@types/node'
+ - 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
+
+ zod@4.4.3: {}
diff --git a/webapp/src/index.ts b/webapp/src/index.ts
index ee10b10..58ac670 100644
--- a/webapp/src/index.ts
+++ b/webapp/src/index.ts
@@ -7,13 +7,21 @@
// DurableObjectNamespace/DurableObjectId/DurableObjectStub are ambient types
// in worker-configuration.d.ts (not exports of "cloudflare:workers")
import { RpcTarget, DurableObject, WorkerEntrypoint } from "cloudflare:workers";
+import { M } from "../../lib/src/patterns.js";
+import { makeMapStore, type MapStore } from "../../lib/src/store.js";
export type Query = Record;
type Headers = Record;
+/** headers looked up at each send, e.g. a store's read-only view: whoever
+ * holds the store can rotate a credential under handles already out */
+export type HeaderMap = { entries(): [string, unknown][] | Promise<[string, unknown][]> };
+
/**
- * init for a new instance — fixed at creation. An instance is immutable
- * authority: no re-config methods, only attenuation via the fluent chain.
+ * init for a new instance — fixed at creation, except that a `cookie`
+ * header goes to the instance's rotatable cookie store (see
+ * {@link WebAppDo}). Otherwise immutable authority: no re-config methods,
+ * only attenuation via the fluent chain.
*/
export interface WebAppInit {
url: string;
@@ -29,6 +37,7 @@ export interface FluentGet {
withHeaders(h: Headers): FluentGet;
withHeader(key: string, value: string): FluentGet;
cookies(cookie: string): FluentGet;
+ withHeaderMap(map: HeaderMap): FluentGet;
get(): Promise;
post(body: string): Promise;
}
@@ -57,16 +66,19 @@ export interface FluentGet {
export class WebApp extends RpcTarget implements FluentGet {
#url: string;
#headers: Headers;
+ #maps: readonly HeaderMap[];
#send: (url: string, headers: Headers, method: "GET" | "POST", body?: string) => Promise;
constructor(
url: string,
headers: Headers,
+ maps: readonly HeaderMap[],
send: (url: string, headers: Headers, method: "GET" | "POST", body?: string) => Promise,
) {
super();
this.#url = url;
this.#headers = headers;
+ this.#maps = maps;
this.#send = send;
}
@@ -85,13 +97,13 @@ export class WebApp extends RpcTarget implements FluentGet {
throw new Error(`pathjoin: ${ref} not within ${hereNoQuery.href}`);
}
const baseParams: Query = Object.fromEntries(here.searchParams);
- return new WebApp(unionQuery(there.href, baseParams), this.#headers, this.#send);
+ return new WebApp(unionQuery(there.href, baseParams), this.#headers, this.#maps, this.#send);
}
/** Union headers, flower-style (see {@link WebApp}): a shared key with a
* different value throws; the same value is fine. */
withHeaders(h: Headers): WebApp {
- return new WebApp(this.#url, unionHeaders(this.#headers, h), this.#send);
+ return new WebApp(this.#url, unionHeaders(this.#headers, h), this.#maps, this.#send);
}
/** one key, flower-style (see {@link WebApp}) */
@@ -105,15 +117,34 @@ export class WebApp extends RpcTarget implements FluentGet {
/** Union params into the existing query, flower-style (see {@link WebApp}). */
query(params: Query): WebApp {
- return new WebApp(unionQuery(this.#url, params), this.#headers, this.#send);
+ return new WebApp(unionQuery(this.#url, params), this.#headers, this.#maps, this.#send);
+ }
+
+ /** Add headers read from `map` at each send, unioned flower-style (see
+ * {@link WebApp}) with the rest — so a contradiction surfaces at send. */
+ withHeaderMap(map: HeaderMap): WebApp {
+ return new WebApp(this.#url, this.#headers, [...this.#maps, map], this.#send);
}
async get(): Promise {
- return this.#send(this.#url, this.#headers, "GET");
+ return this.#send(this.#url, await this.#headersNow(), "GET");
}
async post(body: string): Promise {
- return this.#send(this.#url, this.#headers, "POST", body);
+ return this.#send(this.#url, await this.#headersNow(), "POST", body);
+ }
+
+ async #headersNow(): Promise {
+ let headers = this.#headers;
+ for (const map of this.#maps) {
+ const entries = await map.entries();
+ for (const [key, value] of entries) {
+ // key name only: header values must not appear in Error messages
+ if (typeof value !== "string") throw new Error(`headers: ${key} is not a string`);
+ }
+ headers = unionHeaders(headers, Object.fromEntries(entries) as Headers);
+ }
+ return headers;
}
}
@@ -149,6 +180,12 @@ class Gate {
* storage, surviving eviction (instance memory evaporates when the DO naps;
* storage is where "this instance means this URL" durably lives).
*
+ * The cookie is the exception to "fixed at creation": it lives in a store
+ * (`store:cookie:cookie`) rather than in `init`, and every send reads it,
+ * so whoever holds the instance id can rotate it (see {@link cookies})
+ * without a re-mint. Instances minted before that kept it in
+ * `init.headers`; {@link handle} moves it over on first use.
+ *
* @see https://developers.cloudflare.com/durable-objects/ — newUniqueId is
* unforgeable (128-bit, inderivable); idFromName by contrast is ambient:
* any worker with the namespace binding can recompute it
@@ -172,11 +209,21 @@ export class WebAppDo extends DurableObject<{ WEBAPP?: never }> {
}
return; // idempotent for an identical re-configure
}
- await this.ctx.storage.put("init", init);
+ const { cookie, rest } = splitCookie(init.headers ?? {});
+ this.ctx.storage.transactionSync(() => {
+ this.ctx.storage.kv.put("init", { ...init, headers: rest });
+ if (cookie !== undefined) this.cookies().init("cookie", cookie);
+ });
})();
await this.#ready;
}
+ /** the instance's cookie, with full authority: `set` rotates it under
+ * every handle already out */
+ cookies(): MapStore {
+ return makeMapStore("cookie", { keyShape: "cookie", valueShape: M.string() }, this.ctx.storage.kv);
+ }
+
/** load init from storage; rejects while unconfigured */
async #load(): Promise {
const init = await this.ctx.storage.get("init");
@@ -189,7 +236,7 @@ export class WebAppDo extends DurableObject<{ WEBAPP?: never }> {
/** the fluent stub handed to callers, wired to a fresh gate per
* activation (handles within one activation share it) */
async handle(): Promise {
- const init = await this.#load();
+ const init = this.#migrateCookie(await this.#load());
const gate = new Gate(() => sleep(init.msPerCall ?? 0));
const send = async (
url: string,
@@ -207,8 +254,33 @@ export class WebAppDo extends DurableObject<{ WEBAPP?: never }> {
}
return res.text();
};
- return new WebApp(init.url, init.headers ?? {}, send);
+ return new WebApp(init.url, init.headers ?? {}, [this.cookies().readOnly()], send);
}
+
+ /** move a cookie stored in `init.headers` to the cookie store, in one
+ * transaction; returns init without it */
+ #migrateCookie(init: WebAppInit): WebAppInit {
+ const { cookie, rest } = splitCookie(init.headers ?? {});
+ if (cookie === undefined) return init;
+ const migrated = { ...init, headers: rest };
+ this.ctx.storage.transactionSync(() => {
+ const cookies = this.cookies();
+ if (!cookies.has("cookie")) cookies.init("cookie", cookie);
+ this.ctx.storage.kv.put("init", migrated);
+ });
+ return migrated;
+ }
+}
+
+/** split the cookie (any case of the header name) from the other headers */
+function splitCookie(headers: Headers): { cookie?: string; rest: Headers } {
+ const rest: Headers = {};
+ let cookie: string | undefined;
+ for (const [key, value] of Object.entries(headers)) {
+ if (key.toLowerCase() === "cookie") cookie = value;
+ else rest[key] = value;
+ }
+ return { cookie, rest };
}
/** the worker's whole HTTP surface (the default export and the
@@ -246,6 +318,13 @@ const defaultFetch = async (
return drive(await app, request);
}
+ // rotate: POST /at//cookie with JSON {cookie}; the id that drives
+ // an instance can also replace its cookie
+ const rotate = url.pathname.match(/^\/at\/([A-Za-z0-9-]+)\/cookie$/);
+ if (rotate && request.method === "POST") {
+ return rotateCookie(env.WEBAPP_DO.get(env.WEBAPP_DO.idFromString(rotate[1])), request);
+ }
+
return new Response("not found", { status: 404 });
};
@@ -368,6 +447,25 @@ async function drive(app: FluentGet, request: Request): Promise {
}
}
+/** the /at//cookie protocol: JSON {cookie} → {ok} (or {error}; no values) */
+async function rotateCookie(instance: DurableObjectStub, request: Request): Promise {
+ let cookie: unknown;
+ try {
+ ({ cookie } = (await request.json()) as { cookie?: unknown });
+ } catch (err) {
+ return Response.json({ error: `bad input: ${String(err)}` }, { status: 400 });
+ }
+ if (typeof cookie !== "string" || !cookie) {
+ return Response.json({ error: "bad input: cookie must be a non-empty string" }, { status: 400 });
+ }
+ using cookies = await instance.cookies();
+ if (await cookies.has("cookie")) await cookies.set("cookie", cookie);
+ else await cookies.init("cookie", cookie);
+ // note: neither id nor cookie logged
+ console.log({ event: "rotate" });
+ return Response.json({ ok: true });
+}
+
function unionHeaders(base: Headers, more: Headers): Headers {
const merged = { ...base };
for (const [key, value] of Object.entries(more)) {
@@ -434,6 +532,13 @@ export class WebAppFactory extends WorkerEntrypoint {
const ns = this.env.WEBAPP_DO;
return ns.get(ns.idFromString(id)).handle();
}
+
+ /** the cookie store of a previously-obtained id: holding the id is
+ * authority to rotate the cookie, not just to use it */
+ async cookies(id: string) {
+ const ns = this.env.WEBAPP_DO;
+ return ns.get(ns.idFromString(id)).cookies();
+ }
}
function sleep(ms: number): Promise {
return new Promise((r) => setTimeout(r, ms));
diff --git a/webapp/tests/cookie.test.ts b/webapp/tests/cookie.test.ts
new file mode 100644
index 0000000..9861892
--- /dev/null
+++ b/webapp/tests/cookie.test.ts
@@ -0,0 +1,108 @@
+import { env } from "cloudflare:workers";
+import { runInDurableObject } from "cloudflare:test";
+import { afterEach, describe, expect, it, vi } from "vitest";
+import worker, { type WebAppInit } from "../src/index.js";
+
+type TestEnv = { WEBAPP_DO: DurableObjectNamespace };
+const testEnv = env as unknown as TestEnv;
+
+const URL_ = "https://diigo.example/api/";
+
+/** the cookie header of each outbound fetch */
+function spyCookies(): (string | null)[] {
+ const seen: (string | null)[] = [];
+ vi.spyOn(globalThis, "fetch").mockImplementation(async (_input, init) => {
+ seen.push(new Headers(init?.headers).get("cookie"));
+ return new Response("ok");
+ });
+ return seen;
+}
+
+afterEach(() => {
+ vi.restoreAllMocks();
+});
+
+const fresh = () => testEnv.WEBAPP_DO.get(testEnv.WEBAPP_DO.newUniqueId());
+
+/** `init` as stored */
+const storedInit = (stub: DurableObjectStub) =>
+ runInDurableObject(stub, (_obj, state) => state.storage.kv.get("init"));
+
+describe("cookie rotation", () => {
+ it("mint keeps the cookie out of init; sends carry it", async () => {
+ const seen = spyCookies();
+ const stub = fresh();
+ await stub.configure({ url: URL_, headers: { Cookie: "session=a", accept: "application/json" } });
+ expect(await storedInit(stub)).toEqual({ url: URL_, headers: { accept: "application/json" } });
+ using app = await stub.handle();
+ await app.get();
+ expect(seen).toEqual(["session=a"]);
+ });
+
+ it("a rotation reaches handles already out", async () => {
+ const seen = spyCookies();
+ const stub = fresh();
+ await stub.configure({ url: URL_, headers: { cookie: "session=a" } });
+ using app = await stub.handle();
+ await app.pathjoin("x").get();
+ using cookies = await stub.cookies();
+ await cookies.set("cookie", "session=b");
+ await app.pathjoin("x").get();
+ expect(seen).toEqual(["session=a", "session=b"]);
+ });
+
+ it("an instance minted before rotation moves its cookie to the store on first use", async () => {
+ const seen = spyCookies();
+ const stub = fresh();
+ // the shape configure() stored until the cookie store
+ await runInDurableObject(stub, (_obj, state) =>
+ state.storage.put("init", { url: URL_, headers: { cookie: "session=old" }, msPerCall: 0 }),
+ );
+ using app = await stub.handle();
+ await app.get();
+ expect(await storedInit(stub)).toEqual({ url: URL_, headers: {}, msPerCall: 0 });
+ using cookies = await stub.cookies();
+ expect(await cookies.get("cookie")).toBe("session=old");
+ await cookies.set("cookie", "session=new");
+ using again = await stub.handle();
+ await again.get();
+ expect(seen).toEqual(["session=old", "session=new"]);
+ });
+
+ it("POST /at//cookie rotates, by the same id that drives", async () => {
+ const seen = spyCookies();
+ const id = testEnv.WEBAPP_DO.newUniqueId();
+ const stub = testEnv.WEBAPP_DO.get(id);
+ await stub.configure({ url: URL_ });
+ const rotate = (cookie: string) =>
+ worker.fetch(
+ new Request(`https://webapp.example/at/${id}/cookie`, {
+ method: "POST",
+ body: JSON.stringify({ cookie }),
+ headers: { "content-type": "application/json" },
+ }),
+ testEnv,
+ );
+ expect(await (await rotate("session=first")).json()).toEqual({ ok: true });
+ expect(await (await rotate("session=second")).json()).toEqual({ ok: true });
+ using app = await stub.handle();
+ await app.get();
+ expect(seen).toEqual(["session=second"]);
+ });
+
+ it("a fixed header contradicting the stored cookie fails at send, naming no values", async () => {
+ spyCookies();
+ const stub = fresh();
+ await stub.configure({ url: URL_, headers: { cookie: "session=a" } });
+ using app = await stub.handle();
+ const err = await app
+ .withHeader("cookie", "session=mine")
+ .get()
+ .then(
+ () => "",
+ (e: unknown) => String(e),
+ );
+ expect(err).toContain("headers: contradicts on cookie");
+ expect(err).not.toContain("session=");
+ });
+});
diff --git a/webapp/tests/tsconfig.json b/webapp/tests/tsconfig.json
new file mode 100644
index 0000000..4655b1d
--- /dev/null
+++ b/webapp/tests/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "types": ["@cloudflare/vitest-plugin/types"]
+ },
+ "include": ["./**/*", "../worker-configuration.d.ts"]
+}
diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json
index ca21ad5..cd40bd6 100644
--- a/webapp/tsconfig.json
+++ b/webapp/tsconfig.json
@@ -7,6 +7,7 @@
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
+ "lib": ["ES2022", "ESNext.Disposable"],
"noEmit": true,
"types": []
},
diff --git a/webapp/vitest.config.ts b/webapp/vitest.config.ts
new file mode 100644
index 0000000..47ae020
--- /dev/null
+++ b/webapp/vitest.config.ts
@@ -0,0 +1,9 @@
+import { defineProject } from "vitest/config";
+import { cloudflareTest } from "@cloudflare/vitest-plugin";
+
+export default defineProject({
+ plugins: [cloudflareTest({ wrangler: { configPath: "./wrangler.jsonc" } })],
+ test: {
+ include: ["tests/**/*.test.ts"],
+ },
+});
--
2.53.0
From 357e4561c4c5e54abd1586f71a5ac5600b1d58ed Mon Sep 17 00:00:00 2001
From: Dan Connolly
Date: Thu, 8 Oct 2026 15:45:21 -0500
Subject: [PATCH 3/8] capsite/lib: endo's own matcher data, smallcaps, and
MapStoreDo
Matchers are now exactly what @endo/patterns makes: tagged records as
from pass-style's makeTagged (M.string() is match:string with payload
[], M.any() is match:any with payload undefined). Structured clone drops
their symbol-keyed and non-enumerable properties, so they cross DO
storage and Workers RPC in marshal's smallcaps encoding (the subset
patterns need).
MapStoreDo is a MapStore as a DO of its own: its creator fixes label and
(encoded) shapes once; its id is full authority, and readOnly() hands
out the queries alone.
Generated-by: Claude Opus 5.5
---
lib/src/patterns.ts | 122 ++++++++++++++++++++++++++++++++-------
lib/src/store-do.ts | 60 +++++++++++++++++++
lib/tests/store.test.ts | 59 ++++++++++++++++++-
lib/tests/test-worker.ts | 2 +
lib/wrangler.tests.jsonc | 8 ++-
5 files changed, 225 insertions(+), 26 deletions(-)
create mode 100644 lib/src/store-do.ts
diff --git a/lib/src/patterns.ts b/lib/src/patterns.ts
index 5c9f844..4ae0d48 100644
--- a/lib/src/patterns.ts
+++ b/lib/src/patterns.ts
@@ -1,43 +1,67 @@
/**
* @file a work-alike for enough of @endo/patterns: `M.string()`, `M.any()`,
* and literal patterns (a string, number, boolean, or null matches only
- * itself, so `"cookie"` as a key shape admits exactly one key).
+ * itself, so `"cookie"` as a key shape admits exactly one key). Matchers
+ * are the same data endo makes: tagged records, as from @endo/pass-style's
+ * `makeTagged` (frozen here, where endo would harden).
+ *
+ * Structured clone (DO storage, Workers RPC) drops a tagged record's
+ * symbol-keyed and non-enumerable properties, so patterns cross those
+ * boundaries in endo's smallcaps encoding: see {@link toSmallcaps}.
* @see https://github.com/endojs/endo/tree/master/packages/patterns
+ * @see https://github.com/endojs/endo/blob/master/packages/marshal/src/encodeToSmallcaps.js
*/
-type MatcherKind = "string" | "any";
+const PASS_STYLE = Symbol.for("passStyle");
-/** a matcher made by {@link M}; recognized by identity, so it is meaningful
- * only in the realm that made it (it does not survive an RPC copy) */
-export type Matcher = Readonly<{ toString(): string }>;
-export type Pattern = Matcher | string | number | boolean | null;
+export type Tagged = Readonly<{ [Symbol.toStringTag]: string; payload: unknown }>;
+export type Pattern = Tagged | string | number | boolean | null;
-const kinds = new WeakMap();
+/** @endo/pass-style's `makeTagged` */
+export function makeTagged(tag: string, payload: unknown): Tagged {
+ return Object.freeze(
+ Object.create(Object.prototype, {
+ [PASS_STYLE]: { value: "tagged" },
+ [Symbol.toStringTag]: { value: tag },
+ payload: { value: payload, enumerable: true },
+ }),
+ ) as Tagged;
+}
-const makeMatcher = (kind: MatcherKind, label: string): Matcher => {
- const matcher = Object.freeze({ toString: () => label });
- kinds.set(matcher, kind);
- return matcher;
-};
+const isTagged = (x: unknown): x is Tagged =>
+ typeof x === "object" && x !== null && (x as Record)[PASS_STYLE] === "tagged";
-const STRING = makeMatcher("string", "M.string()");
-const ANY = makeMatcher("any", "M.any()");
+const StringShape = makeTagged("match:string", Object.freeze([]));
+const AnyShape = makeTagged("match:any", undefined);
export const M = Object.freeze({
- string: (): Matcher => STRING,
- any: (): Matcher => ANY,
+ string: (): Tagged => StringShape,
+ any: (): Tagged => AnyShape,
});
export function matches(specimen: unknown, pattern: Pattern): boolean {
if (typeof pattern === "object" && pattern !== null) {
- const kind = kinds.get(pattern);
- if (kind === "string") return typeof specimen === "string";
- if (kind === "any") return true;
- throw new TypeError("not a pattern");
+ const tag = isTagged(pattern) ? pattern[Symbol.toStringTag] : undefined;
+ switch (tag) {
+ case "match:string":
+ return typeof specimen === "string";
+ case "match:any":
+ return true;
+ default:
+ throw new TypeError("not a pattern");
+ }
}
return Object.is(specimen, pattern);
}
+/** the pattern as written in code, e.g. `M.string()` or `"cookie"` */
+export function describe(pattern: Pattern): string {
+ if (typeof pattern === "object" && pattern !== null) {
+ return `M.${pattern[Symbol.toStringTag].replace(/^match:/, "")}()`;
+ }
+ return JSON.stringify(pattern);
+}
+
/**
* throw unless `specimen` matches `pattern`. Unlike endo's, the message
* names the pattern and `label` but never the specimen: specimens here
@@ -45,7 +69,61 @@ export function matches(specimen: unknown, pattern: Pattern): boolean {
*/
export function mustMatch(specimen: unknown, pattern: Pattern, label: string): void {
if (!matches(specimen, pattern)) {
- const shape = typeof pattern === "object" && pattern !== null ? String(pattern) : JSON.stringify(pattern);
- throw new TypeError(`${label}: must match ${shape}`);
+ throw new TypeError(`${label}: must match ${describe(pattern)}`);
}
}
+
+/** a smallcaps-encoded passable, as JSON-able data */
+export type Smallcaps = null | boolean | number | string | Smallcaps[] | { "#tag": string; payload: Smallcaps };
+
+/** smallcaps quotes strings that start with one of these with `!` */
+const startsSpecial = (s: string) => /^[!-\-]/.test(s);
+
+/**
+ * the subset of @endo/marshal's smallcaps encoding that patterns here use:
+ * primitives, arrays, and tagged records (not a whole CapData: no `#`
+ * body prefix, no slots)
+ */
+export function toSmallcaps(passable: unknown): Smallcaps {
+ if (passable === undefined) return "#undefined";
+ if (passable === null || typeof passable === "boolean") return passable;
+ if (typeof passable === "number") {
+ if (Number.isNaN(passable)) return "#NaN";
+ if (passable === Infinity) return "#Infinity";
+ if (passable === -Infinity) return "#-Infinity";
+ return Object.is(passable, -0) ? 0 : passable;
+ }
+ if (typeof passable === "string") return startsSpecial(passable) ? `!${passable}` : passable;
+ if (Array.isArray(passable)) return passable.map(toSmallcaps);
+ if (isTagged(passable)) {
+ return { "#tag": toSmallcaps(passable[Symbol.toStringTag]) as string, payload: toSmallcaps(passable.payload) };
+ }
+ throw new TypeError("toSmallcaps: unsupported passable");
+}
+
+/** the inverse of {@link toSmallcaps} */
+export function fromSmallcaps(encoding: Smallcaps): unknown {
+ if (encoding === null || typeof encoding === "boolean" || typeof encoding === "number") return encoding;
+ if (typeof encoding === "string") {
+ if (encoding.startsWith("!")) return encoding.slice(1);
+ switch (encoding) {
+ case "#undefined":
+ return undefined;
+ case "#NaN":
+ return NaN;
+ case "#Infinity":
+ return Infinity;
+ case "#-Infinity":
+ return -Infinity;
+ }
+ if (startsSpecial(encoding)) throw new TypeError("fromSmallcaps: unsupported special string");
+ return encoding;
+ }
+ if (Array.isArray(encoding)) return Object.freeze(encoding.map(fromSmallcaps));
+ if (typeof encoding === "object" && "#tag" in encoding) {
+ const { "#tag": tag, payload, ...rest } = encoding;
+ if (Object.keys(rest).length > 0) throw new TypeError("fromSmallcaps: extra properties on a tagged record");
+ return makeTagged(fromSmallcaps(tag) as string, fromSmallcaps(payload));
+ }
+ throw new TypeError("fromSmallcaps: unsupported encoding");
+}
diff --git a/lib/src/store-do.ts b/lib/src/store-do.ts
new file mode 100644
index 0000000..100ffea
--- /dev/null
+++ b/lib/src/store-do.ts
@@ -0,0 +1,60 @@
+/**
+ * @file a MapStore as a Durable Object of its own, so a store can be held
+ * apart from whoever reads it: the DO id is the store's full authority
+ * (e.g. the account owner's, to rotate a credential), while a reader is
+ * handed only {@link MapStoreDo.readOnly}.
+ */
+
+import { DurableObject } from "cloudflare:workers";
+import { fromSmallcaps, matches, toSmallcaps, type Pattern, type Smallcaps } from "./patterns.js";
+import { makeMapStore, type MapStore, type ReadonlyMapStore, type StoreShapes } from "./store.js";
+
+/** store shapes as they cross RPC and sit in storage: smallcaps-encoded
+ * (structured clone would strip a matcher down to its payload) */
+export type EncodedShapes = { keyShape?: Smallcaps; valueShape?: Smallcaps };
+
+export const encodeShapes = ({ keyShape, valueShape }: StoreShapes): EncodedShapes => ({
+ ...(keyShape === undefined ? {} : { keyShape: toSmallcaps(keyShape) }),
+ ...(valueShape === undefined ? {} : { valueShape: toSmallcaps(valueShape) }),
+});
+
+const decodeShapes = ({ keyShape, valueShape }: EncodedShapes): StoreShapes => ({
+ ...(keyShape === undefined ? {} : { keyShape: fromSmallcaps(keyShape) as Pattern }),
+ ...(valueShape === undefined ? {} : { valueShape: fromSmallcaps(valueShape) as Pattern }),
+});
+
+type Config = { label: string; shapes: EncodedShapes };
+
+/** a store whose label and shapes its creator fixes, once */
+export class MapStoreDo extends DurableObject {
+ /** one-shot: fix the label and shapes (see {@link encodeShapes}); an
+ * identical re-configure is a no-op */
+ configure(label: string, shapes: EncodedShapes): void {
+ const decoded = decodeShapes(shapes);
+ for (const shape of [decoded.keyShape, decoded.valueShape]) {
+ if (shape !== undefined) matches(undefined, shape); // throws on a non-pattern
+ }
+ makeMapStore(label, decoded, this.ctx.storage.kv); // throws on a bad label
+ const config: Config = { label, shapes: encodeShapes(decoded) };
+ const existing = this.ctx.storage.kv.get("config");
+ if (existing) {
+ if (JSON.stringify(existing) !== JSON.stringify(config)) {
+ throw new Error("already configured with a different label or shapes");
+ }
+ return;
+ }
+ this.ctx.storage.kv.put("config", config);
+ }
+
+ /** the store, with full authority */
+ store(): MapStore {
+ const config = this.ctx.storage.kv.get("config");
+ if (!config) throw new Error("not configured: this store was never set up");
+ return makeMapStore(config.label, decodeShapes(config.shapes), this.ctx.storage.kv);
+ }
+
+ /** the store's queries alone */
+ readOnly(): ReadonlyMapStore {
+ return this.store().readOnly();
+ }
+}
diff --git a/lib/tests/store.test.ts b/lib/tests/store.test.ts
index f058cfc..5672b29 100644
--- a/lib/tests/store.test.ts
+++ b/lib/tests/store.test.ts
@@ -1,9 +1,13 @@
import { env } from "cloudflare:workers";
import { evictDurableObject } from "cloudflare:test";
import { describe, expect, it } from "vitest";
-import { M, matches, mustMatch } from "../src/patterns.js";
+import { M, fromSmallcaps, makeTagged, matches, mustMatch, toSmallcaps } from "../src/patterns.js";
+import { encodeShapes } from "../src/store-do.js";
-type TestEnv = { STORE_DO: DurableObjectNamespace };
+type TestEnv = {
+ STORE_DO: DurableObjectNamespace;
+ MAP_STORE_DO: DurableObjectNamespace;
+};
const testEnv = env as unknown as TestEnv;
const SECRET = "session=s3kr1t";
@@ -27,6 +31,28 @@ describe("patterns", () => {
expect(matches("authorization", "cookie")).toBe(false);
});
+ it("matchers are endo's tagged records", () => {
+ const s = M.string() as unknown as Record;
+ expect(s[Symbol.for("passStyle")]).toBe("tagged");
+ expect(s[Symbol.toStringTag]).toBe("match:string");
+ expect(Object.keys(s)).toEqual(["payload"]);
+ expect(s.payload).toEqual([]);
+ expect((M.any() as unknown as Record).payload).toBeUndefined();
+ expect(Object.isFrozen(s)).toBe(true);
+ });
+
+ it("smallcaps: endo's encoding, round-tripped", () => {
+ expect(toSmallcaps(M.string())).toEqual({ "#tag": "match:string", payload: [] });
+ expect(toSmallcaps(M.any())).toEqual({ "#tag": "match:any", payload: "#undefined" });
+ expect(toSmallcaps("#cookie")).toBe("!#cookie");
+ expect(toSmallcaps([NaN, -0, Infinity])).toEqual(["#NaN", 0, "#Infinity"]);
+ const back = fromSmallcaps(toSmallcaps(M.string())) as Parameters[1];
+ expect(matches("x", back)).toBe(true);
+ expect(matches(1, back)).toBe(false);
+ expect(fromSmallcaps("!#cookie")).toBe("#cookie");
+ expect(() => matches("x", makeTagged("match:nope", []))).toThrow("not a pattern");
+ });
+
it("mustMatch names the shape but not the specimen", () => {
expect(() => mustMatch(SECRET, "cookie", "jar: key")).toThrow('jar: key: must match "cookie"');
expect(() => mustMatch(SECRET, "cookie", "jar: key")).not.toThrow(SECRET);
@@ -85,3 +111,32 @@ describe("MapStore over RPC", () => {
expect(await again.keys()).toEqual([]);
});
});
+
+describe("MapStoreDo", () => {
+ const COOKIE_SHAPES = encodeShapes({ keyShape: "cookie", valueShape: M.string() });
+ const fresh = () => testEnv.MAP_STORE_DO.get(testEnv.MAP_STORE_DO.newUniqueId());
+
+ it("keeps the shapes its creator chose, across eviction", async () => {
+ const stub = fresh();
+ await stub.configure("cookie", COOKIE_SHAPES);
+ await evictDurableObject(stub);
+ {
+ using store = await stub.store();
+ expect(await errorOf(store.init("authorization", SECRET))).toContain('cookie: key: must match "cookie"');
+ expect(await errorOf(store.init("cookie", 42))).toContain("cookie: value: must match M.string()");
+ await store.init("cookie", "session=a");
+ }
+ using rd = await stub.readOnly();
+ expect(await rd.entries()).toEqual([["cookie", "session=a"]]);
+ });
+
+ it("configures once: the same shapes again is a no-op, others are refused", async () => {
+ const stub = fresh();
+ await stub.configure("cookie", COOKIE_SHAPES);
+ await stub.configure("cookie", encodeShapes({ keyShape: "cookie", valueShape: M.string() }));
+ expect(await errorOf(stub.configure("cookie", encodeShapes({ keyShape: M.string() })))).toContain(
+ "already configured",
+ );
+ expect(await errorOf(fresh().store())).toContain("not configured");
+ });
+});
diff --git a/lib/tests/test-worker.ts b/lib/tests/test-worker.ts
index 631f997..98fb5ae 100644
--- a/lib/tests/test-worker.ts
+++ b/lib/tests/test-worker.ts
@@ -3,6 +3,8 @@ import { DurableObject } from "cloudflare:workers";
import { M } from "../src/patterns.js";
import { makeMapStore, type MapStore } from "../src/store.js";
+export { MapStoreDo } from "../src/store-do.js";
+
/** hosts the store from the credential-rotation sketch: one key, `cookie` */
export class StoreHost extends DurableObject {
cookies(): MapStore {
diff --git a/lib/wrangler.tests.jsonc b/lib/wrangler.tests.jsonc
index 53603b6..6f28fe4 100644
--- a/lib/wrangler.tests.jsonc
+++ b/lib/wrangler.tests.jsonc
@@ -5,9 +5,13 @@
"main": "./tests/test-worker.ts",
"compatibility_date": "2026-09-29",
"durable_objects": {
- "bindings": [{ "name": "STORE_DO", "class_name": "StoreHost" }]
+ "bindings": [
+ { "name": "STORE_DO", "class_name": "StoreHost" },
+ { "name": "MAP_STORE_DO", "class_name": "MapStoreDo" }
+ ]
},
"exports": {
- "StoreHost": { "type": "durable-object", "storage": "sqlite" }
+ "StoreHost": { "type": "durable-object", "storage": "sqlite" },
+ "MapStoreDo": { "type": "durable-object", "storage": "sqlite" }
}
}
--
2.53.0
From 3f711a5f4b99f8dadf86ae50da1f0fc8d7ff5d53 Mon Sep 17 00:00:00 2001
From: Dan Connolly
Date: Thu, 8 Oct 2026 15:47:17 -0500
Subject: [PATCH 4/8] capsite/webapp: the cookie's store is the owner's, apart
from the instance
Letting the instance id rotate the cookie let its holder (the cache, or
whoever learns the id) swap in another account's session, which the
owner's logout would not stop. Now mint makes two things: the instance,
and a header store (lib's MapStoreDo, shaped ("cookie", { keyShape:
"cookie", valueShape: M.string() })). The instance id is authority to
use the cookie; the store id, for the account owner, to rotate (PUT
/store//cookie, or WebAppFactory.store) or revoke (DELETE).
Instances read the store's read-only view at every send, via init's new
headerStore.
This replaces the in-instance cookie store and its migration: instances
minted earlier keep their cookie in init.headers, untouched, and simply
cannot rotate. The MapStoreDo class arrives as wrangler migration v2
(additive).
Generated-by: Claude Opus 5.5
---
capsite-checklist.md | 7 +-
webapp/README.md | 38 ++++---
webapp/src/index.ts | 196 ++++++++++++++++++++----------------
webapp/tests/cookie.test.ts | 119 +++++++++++++---------
webapp/wrangler.jsonc | 10 +-
5 files changed, 219 insertions(+), 151 deletions(-)
diff --git a/capsite-checklist.md b/capsite-checklist.md
index 726511c..edc9f4a 100644
--- a/capsite-checklist.md
+++ b/capsite-checklist.md
@@ -110,9 +110,10 @@ narrative.
- [ ] **6. Cookie expiry**: when diigo's session ends, head refreshes
fail; the public page keeps serving what's stored (say so on the
page, don't fail silently). Rotation is in place, keeping the
- webapp id (so the cache's wiring and ids hold): `POST
- /at//cookie` with the new cookie (webapp README, "The cookie
- store"). Still open: a rotate form in diigo-cache's UI.
+ webapp id (so the cache's wiring and ids hold), by the store id
+ minted alongside it and kept by the account owner: `PUT
+ /store//cookie` (webapp README, "The header store").
+ Still open: a rotate form in diigo-cache's UI.
- [ ] **7. bib sync #195**: repeat the pattern for the next
read-access need (webapp mints for any origin; the cache's
item shape is diigo-specific): [the zotero goal tree](zotero-checklist.md).
diff --git a/webapp/README.md b/webapp/README.md
index b16be13..644a41d 100644
--- a/webapp/README.md
+++ b/webapp/README.md
@@ -12,31 +12,45 @@ grows past a form or two.
- `GET /` — the mint form. Prefill via query string: `?url=…&headers=&msPerCall=…&title=…`; secret header keys (cookie, authorization, proxy-authorization) render **blank** — paste them at mint time; never echoed back. A dedicated `cookie` field is sugar for the `cookie` header (if the JSON also has one, they must agree).
- `GET /form?render=1&…` — the same form for a proxying worker (diigo-cache's `/_connect`): the action is left as an `ACTION` placeholder for the proxy to point at its own relay.
-- `POST /` — mint. Form-encoded (that page) or JSON `{url, headers, msPerCall}` (service-style). Responds `{id}` (and `x-webapp-id`). The id is *not* logged: possession is authority.
+- `POST /` — mint. Form-encoded (that page) or JSON `{url, headers, msPerCall}` (service-style). Responds `{id, storeId}` (and `x-webapp-id`, `x-webapp-store-id`). Neither id is logged: possession is authority.
- `POST /at/` — drive an instance by capability: JSON `{path?, query?, method?, body?}` → the fluent chain → `{status, text}` (or `{error}`; messages carry no header/param values).
-- `POST /at//cookie` — rotate the instance's cookie in place: JSON `{cookie}` → `{ok: true}`. The id that drives an instance can also replace its cookie; the account owner revokes by logging out.
+- `PUT /store//` — set a header-store entry (JSON value body), e.g. `PUT /store//cookie` with `"session=…"` rotates the cookie; `DELETE` removes it. → `{ok: true}` (or `{error}`; messages carry no keys or values).
## RPC surface (same-account workers; binding `entrypoint: "WebAppFactory"`)
- `fetch(request)` — the HTTP surface above, over the binding
-- `newWebApp({url, headers?, msPerCall?})` → `{id, app}` — the fluent stub;
+- `newWebApp({url, headers?, msPerCall?})` → `{id, storeId, app}` — the fluent stub;
promise pipelining makes a whole `pathjoin().query().get()` chain a
single round trip
- `at(id)` — the fluent stub for a previously-minted instance
-- `cookies(id)` — the instance's cookie store (a MapStore from
- [the capsite lib](../lib/src/store.ts) with key shape `"cookie"`):
- `set("cookie", …)` rotates it under every handle already out
+- `store(storeId)` — a header store, with full authority:
+ `set("cookie", …)` rotates the cookie under every handle already out
-## The cookie store
+## The header store: two ids, two authorities
-An instance is fixed at creation except for its cookie, which lives in a
-store rather than in `init`; every send reads it. Instances minted before
-the store held their cookie in `init.headers`; the first `handle()` moves
-it over in one transaction.
+Mint makes two things: the instance, and a header store (a `MapStoreDo`
+from [the capsite lib](../lib/src/store-do.ts), shaped
+`("cookie", { keyShape: "cookie", valueShape: M.string() })`). A `cookie`
+header goes into the store, not the instance's `init`; the instance reads
+the store's read-only view at every send.
+
+- the **instance id** is authority to *use* the cookie: it goes to the
+ cache's wiring.
+- the **store id** is authority to *choose* it: it stays with the account
+ owner, who can rotate it (log in again, `PUT`) or revoke it (`DELETE`,
+ or log out).
+
+Kept apart because whoever can choose the cookie can swap in another
+account's session: if the cache's id could rotate, a leaked instance id
+would let Mallory fill the cache (and its public page) with her own
+bookmarks, and the owner's logout would not stop it.
+
+Instances minted before header stores keep their cookie in
+`init.headers`, unchanged; they work as before and cannot rotate.
## Run
- pnpm test # cookie store + rotation (vitest, workerd pool)
+ pnpm test # header store + rotation (vitest, workerd pool)
npx wrangler dev # → its own ready line (usually :8787 with the
# two-config diigo-cache run; standalone, :8788)
diff --git a/webapp/src/index.ts b/webapp/src/index.ts
index 58ac670..ab79ab9 100644
--- a/webapp/src/index.ts
+++ b/webapp/src/index.ts
@@ -8,7 +8,9 @@
// in worker-configuration.d.ts (not exports of "cloudflare:workers")
import { RpcTarget, DurableObject, WorkerEntrypoint } from "cloudflare:workers";
import { M } from "../../lib/src/patterns.js";
-import { makeMapStore, type MapStore } from "../../lib/src/store.js";
+import { encodeShapes, MapStoreDo } from "../../lib/src/store-do.js";
+
+export { MapStoreDo };
export type Query = Record;
type Headers = Record;
@@ -18,16 +20,18 @@ type Headers = Record;
export type HeaderMap = { entries(): [string, unknown][] | Promise<[string, unknown][]> };
/**
- * init for a new instance — fixed at creation, except that a `cookie`
- * header goes to the instance's rotatable cookie store (see
- * {@link WebAppDo}). Otherwise immutable authority: no re-config methods,
- * only attenuation via the fluent chain.
+ * init for a new instance — fixed at creation. An instance is immutable
+ * authority: no re-config methods, only attenuation via the fluent chain.
+ * The one thing that changes under it is its header store's contents.
*/
export interface WebAppInit {
url: string;
headers?: Headers;
/** min spacing between outbound calls, ms (0 = unthrottled) */
msPerCall?: number;
+ /** id of a {@link MapStoreDo} whose entries are added as headers at
+ * each send (see {@link mintInstance}); the instance only reads it */
+ headerStore?: string;
}
/** structural type of a stub handle: chain to narrow, then fire */
@@ -180,17 +184,16 @@ class Gate {
* storage, surviving eviction (instance memory evaporates when the DO naps;
* storage is where "this instance means this URL" durably lives).
*
- * The cookie is the exception to "fixed at creation": it lives in a store
- * (`store:cookie:cookie`) rather than in `init`, and every send reads it,
- * so whoever holds the instance id can rotate it (see {@link cookies})
- * without a re-mint. Instances minted before that kept it in
- * `init.headers`; {@link handle} moves it over on first use.
+ * Its cookie, when minted by {@link mintInstance}, is not in `init` but in
+ * the header store, whose id goes to the account owner and not to whoever
+ * holds the instance id: the owner can rotate it, and the holder cannot
+ * swap in another account's session.
*
* @see https://developers.cloudflare.com/durable-objects/ — newUniqueId is
* unforgeable (128-bit, inderivable); idFromName by contrast is ambient:
* any worker with the namespace binding can recompute it
*/
-export class WebAppDo extends DurableObject<{ WEBAPP?: never }> {
+export class WebAppDo extends DurableObject {
#ready?: Promise;
/** one-shot configuration; a different-url re-configure is refused */
@@ -209,21 +212,11 @@ export class WebAppDo extends DurableObject<{ WEBAPP?: never }> {
}
return; // idempotent for an identical re-configure
}
- const { cookie, rest } = splitCookie(init.headers ?? {});
- this.ctx.storage.transactionSync(() => {
- this.ctx.storage.kv.put("init", { ...init, headers: rest });
- if (cookie !== undefined) this.cookies().init("cookie", cookie);
- });
+ await this.ctx.storage.put("init", init);
})();
await this.#ready;
}
- /** the instance's cookie, with full authority: `set` rotates it under
- * every handle already out */
- cookies(): MapStore {
- return makeMapStore("cookie", { keyShape: "cookie", valueShape: M.string() }, this.ctx.storage.kv);
- }
-
/** load init from storage; rejects while unconfigured */
async #load(): Promise {
const init = await this.ctx.storage.get("init");
@@ -236,7 +229,7 @@ export class WebAppDo extends DurableObject<{ WEBAPP?: never }> {
/** the fluent stub handed to callers, wired to a fresh gate per
* activation (handles within one activation share it) */
async handle(): Promise {
- const init = this.#migrateCookie(await this.#load());
+ const init = await this.#load();
const gate = new Gate(() => sleep(init.msPerCall ?? 0));
const send = async (
url: string,
@@ -254,21 +247,20 @@ export class WebAppDo extends DurableObject<{ WEBAPP?: never }> {
}
return res.text();
};
- return new WebApp(init.url, init.headers ?? {}, [this.cookies().readOnly()], send);
+ const maps = init.headerStore === undefined ? [] : [this.#readHeaderStore(init.headerStore)];
+ return new WebApp(init.url, init.headers ?? {}, maps, send);
}
- /** move a cookie stored in `init.headers` to the cookie store, in one
- * transaction; returns init without it */
- #migrateCookie(init: WebAppInit): WebAppInit {
- const { cookie, rest } = splitCookie(init.headers ?? {});
- if (cookie === undefined) return init;
- const migrated = { ...init, headers: rest };
- this.ctx.storage.transactionSync(() => {
- const cookies = this.cookies();
- if (!cookies.has("cookie")) cookies.init("cookie", cookie);
- this.ctx.storage.kv.put("init", migrated);
- });
- return migrated;
+ /** a header map reading the store's read-only view, afresh at each send */
+ #readHeaderStore(storeId: string): HeaderMap {
+ const ns = this.env.MAP_STORE_DO;
+ const store = ns.get(ns.idFromString(storeId));
+ return {
+ async entries() {
+ using rd = await store.readOnly();
+ return await rd.entries();
+ },
+ };
}
}
@@ -283,12 +275,14 @@ function splitCookie(headers: Headers): { cookie?: string; rest: Headers } {
return { cookie, rest };
}
+type WebAppEnv = {
+ WEBAPP_DO: DurableObjectNamespace;
+ MAP_STORE_DO: DurableObjectNamespace;
+};
+
/** the worker's whole HTTP surface (the default export and the
* WebAppFactory entrypoint share it) */
-const defaultFetch = async (
- request: Request,
- env: { WEBAPP_DO: DurableObjectNamespace },
-): Promise => {
+const defaultFetch = async (request: Request, env: WebAppEnv): Promise => {
const url = new URL(request.url);
// mint form: site-agnostic; secret headers (cookie, authorization) are
@@ -299,7 +293,7 @@ const defaultFetch = async (
// mint: browser form POST (form-encoded) or JSON (service-style)
if (url.pathname === "/" && request.method === "POST") {
- return mint(request, env.WEBAPP_DO);
+ return mint(request, env);
}
// the mint form, renderable for a proxying worker: ?render=1 keeps the
@@ -318,11 +312,12 @@ const defaultFetch = async (
return drive(await app, request);
}
- // rotate: POST /at//cookie with JSON {cookie}; the id that drives
- // an instance can also replace its cookie
- const rotate = url.pathname.match(/^\/at\/([A-Za-z0-9-]+)\/cookie$/);
- if (rotate && request.method === "POST") {
- return rotateCookie(env.WEBAPP_DO.get(env.WEBAPP_DO.idFromString(rotate[1])), request);
+ // a header store, by its id (the owner's, not the instance's): PUT
+ // JSON value sets a key (e.g. rotates the cookie), DELETE removes it
+ const entry = url.pathname.match(/^\/store\/([A-Za-z0-9-]+)\/([^/]+)$/);
+ if (entry && (request.method === "PUT" || request.method === "DELETE")) {
+ const store = env.MAP_STORE_DO.get(env.MAP_STORE_DO.idFromString(entry[1]));
+ return storeEntry(store, decodeURIComponent(entry[2]), request);
}
return new Response("not found", { status: 404 });
@@ -384,10 +379,7 @@ function withCookie(init: WebAppInit, cookie: string): WebAppInit {
}
/** mint a fresh instance from form-encoded or JSON input */
-async function mint(
- request: Request,
- ns: DurableObjectNamespace,
-): Promise {
+async function mint(request: Request, env: WebAppEnv): Promise {
const ct = request.headers.get("content-type") ?? "";
let init: WebAppInit;
try {
@@ -414,13 +406,35 @@ async function mint(
return Response.json({ error: "bad url" }, { status: 400 });
}
- const id = ns.newUniqueId();
- const instance = ns.get(id);
- await instance.configure(init);
- const app = await instance.handle();
- // note: id intentionally NOT logged — it is a bearer capability
+ const { id, storeId } = await mintInstance(env, init);
+ // note: ids intentionally NOT logged — they are bearer capabilities
console.log({ event: "mint", msPerCall: init.msPerCall });
- return Response.json({ id: id.toString() }, { headers: { "x-webapp-id": id.toString() } });
+ return Response.json({ id, storeId }, { headers: { "x-webapp-id": id, "x-webapp-store-id": storeId } });
+}
+
+/**
+ * a configured instance plus its header store, shaped to hold just the
+ * cookie: the instance id is authority to use the cookie; the store id
+ * (for the account owner) is authority to rotate or remove it. Any
+ * `cookie` header in `init` goes to the store, not to the instance.
+ */
+async function mintInstance(env: WebAppEnv, init: WebAppInit): Promise<{ id: string; storeId: string }> {
+ const { cookie, rest } = splitCookie(init.headers ?? {});
+ const storeId = env.MAP_STORE_DO.newUniqueId();
+ const store = env.MAP_STORE_DO.get(storeId);
+ await store.configure("cookie", encodeShapes({ keyShape: "cookie", valueShape: M.string() }));
+ if (cookie !== undefined) {
+ using full = await store.store();
+ await full.init("cookie", cookie);
+ }
+ const id = env.WEBAPP_DO.newUniqueId();
+ await env.WEBAPP_DO.get(id).configure({
+ url: init.url,
+ headers: rest,
+ ...(init.msPerCall === undefined ? {} : { msPerCall: init.msPerCall }),
+ headerStore: storeId.toString(),
+ });
+ return { id: id.toString(), storeId: storeId.toString() };
}
/** drive a handle from the /at/ JSON protocol (also used by the
@@ -447,22 +461,32 @@ async function drive(app: FluentGet, request: Request): Promise {
}
}
-/** the /at//cookie protocol: JSON {cookie} → {ok} (or {error}; no values) */
-async function rotateCookie(instance: DurableObjectStub, request: Request): Promise {
- let cookie: unknown;
+/** the /store// protocol: PUT a JSON value (set, or init if
+ * absent), or DELETE → {ok} (or {error}; messages carry no values) */
+async function storeEntry(store: DurableObjectStub, key: string, request: Request): Promise {
+ let value: unknown;
+ if (request.method === "PUT") {
+ try {
+ value = await request.json();
+ } catch (err) {
+ return Response.json({ error: `bad input: ${String(err)}` }, { status: 400 });
+ }
+ }
try {
- ({ cookie } = (await request.json()) as { cookie?: unknown });
+ using full = await store.store();
+ if (request.method === "DELETE") {
+ if (await full.has(key)) await full.delete(key);
+ } else if (await full.has(key)) {
+ await full.set(key, value);
+ } else {
+ await full.init(key, value);
+ }
} catch (err) {
- return Response.json({ error: `bad input: ${String(err)}` }, { status: 400 });
+ // store errors name shapes, never keys or values (see lib/src/store.ts)
+ return Response.json({ error: String(err) }, { status: 400 });
}
- if (typeof cookie !== "string" || !cookie) {
- return Response.json({ error: "bad input: cookie must be a non-empty string" }, { status: 400 });
- }
- using cookies = await instance.cookies();
- if (await cookies.has("cookie")) await cookies.set("cookie", cookie);
- else await cookies.init("cookie", cookie);
- // note: neither id nor cookie logged
- console.log({ event: "rotate" });
+ // note: neither id nor value logged
+ console.log({ event: "store", method: request.method });
return Response.json({ ok: true });
}
@@ -494,37 +518,37 @@ function unionQuery(base: string, params: Query): string {
/**
* the RPC service of the webapp worker. Flow:
*
- * const { id, app } = await env.WEBAPP.newWebApp({
- * url: "https://api.github.com", headers: { authorization: `Bearer ${t}` },
+ * const { id, storeId, app } = await env.WEBAPP.newWebApp({
+ * url: "https://diigo.example/", headers: { cookie: c },
* msPerCall: 250,
* });
* // hold id (persist it, hand it to workers you trust), then e.g.:
* const text = await app.query({ per_page: 5 }).get();
* // later, from another worker (same account) holding the id:
* const app2 = env.WEBAPP.at(id);
+ * // the account owner, holding storeId, after logging in again:
+ * await (await env.WEBAPP.store(storeId)).set("cookie", c2);
*/
export class WebAppFactory extends WorkerEntrypoint {
/** the same HTTP surface as the default export, over the entrypoint
* (for service-binding callers that proxy pages, e.g. diigo-cache's
* /_connect → this form) */
async fetch(request: Request): Promise {
- const env = this.env as { WEBAPP_DO: DurableObjectNamespace };
- return defaultFetch(request, env);
+ return defaultFetch(request, this.env);
}
- /** mint a fresh instance: unforgeable uniqueId + configured fluent stub.
- * The caller decides who else ever learns the id. (If the caller only
+ /** mint a fresh instance: unforgeable uniqueId + configured fluent stub,
+ * plus the id of its header store (see {@link mintInstance}). The
+ * caller decides who else ever learns either id. (If the caller only
* wanted the id, it must dispose() the app stub — workerd warns about
* undisposed stubs; see the `at()` alternative + `/at/` route for
* id-only flows.) */
- async newWebApp(init: WebAppInit): Promise<{ id: string; app: FluentGet }> {
+ async newWebApp(init: WebAppInit): Promise<{ id: string; storeId: string; app: FluentGet }> {
new URL(init.url); // fail before minting on garbage
+ const { id, storeId } = await mintInstance(this.env, init);
const ns = this.env.WEBAPP_DO;
- const id = ns.newUniqueId();
- const instance = ns.get(id);
- await instance.configure(init);
- const app = await instance.handle();
- return { id: id.toString(), app };
+ const app = await ns.get(ns.idFromString(id)).handle();
+ return { id, storeId, app };
}
/** reconnect by a previously-obtained id */
@@ -533,11 +557,11 @@ export class WebAppFactory extends WorkerEntrypoint {
return ns.get(ns.idFromString(id)).handle();
}
- /** the cookie store of a previously-obtained id: holding the id is
- * authority to rotate the cookie, not just to use it */
- async cookies(id: string) {
- const ns = this.env.WEBAPP_DO;
- return ns.get(ns.idFromString(id)).cookies();
+ /** a header store by its id, with full authority (the instance id
+ * reaches none: see {@link mintInstance}) */
+ async store(storeId: string) {
+ const ns = this.env.MAP_STORE_DO;
+ return ns.get(ns.idFromString(storeId)).store();
}
}
function sleep(ms: number): Promise {
diff --git a/webapp/tests/cookie.test.ts b/webapp/tests/cookie.test.ts
index 9861892..b91ca5d 100644
--- a/webapp/tests/cookie.test.ts
+++ b/webapp/tests/cookie.test.ts
@@ -3,7 +3,10 @@ import { runInDurableObject } from "cloudflare:test";
import { afterEach, describe, expect, it, vi } from "vitest";
import worker, { type WebAppInit } from "../src/index.js";
-type TestEnv = { WEBAPP_DO: DurableObjectNamespace };
+type TestEnv = {
+ WEBAPP_DO: DurableObjectNamespace;
+ MAP_STORE_DO: DurableObjectNamespace;
+};
const testEnv = env as unknown as TestEnv;
const URL_ = "https://diigo.example/api/";
@@ -22,79 +25,99 @@ afterEach(() => {
vi.restoreAllMocks();
});
-const fresh = () => testEnv.WEBAPP_DO.get(testEnv.WEBAPP_DO.newUniqueId());
+/** the worker's HTTP surface, as a JSON client */
+const call = (path: string, method: string, body?: unknown) =>
+ worker.fetch(
+ new Request(`https://webapp.example${path}`, {
+ method,
+ body: body === undefined ? undefined : JSON.stringify(body),
+ headers: { "content-type": "application/json" },
+ }),
+ testEnv,
+ );
+
+const mint = async (init: WebAppInit) => {
+ const { id, storeId } = (await (await call("/", "POST", init)).json()) as { id: string; storeId: string };
+ const instance = testEnv.WEBAPP_DO.get(testEnv.WEBAPP_DO.idFromString(id));
+ return { id, storeId, instance };
+};
+
+/** the failure of a call: its error status, or what it threw; "" if it succeeds */
+async function errorOf(response: Promise): Promise {
+ try {
+ const res = await response;
+ return res.ok ? "" : `${res.status} ${await res.text()}`;
+ } catch (err) {
+ return String(err);
+ }
+}
/** `init` as stored */
const storedInit = (stub: DurableObjectStub) =>
runInDurableObject(stub, (_obj, state) => state.storage.kv.get("init"));
describe("cookie rotation", () => {
- it("mint keeps the cookie out of init; sends carry it", async () => {
+ it("mint puts the cookie in the header store, not init; sends carry it", async () => {
const seen = spyCookies();
- const stub = fresh();
- await stub.configure({ url: URL_, headers: { Cookie: "session=a", accept: "application/json" } });
- expect(await storedInit(stub)).toEqual({ url: URL_, headers: { accept: "application/json" } });
- using app = await stub.handle();
+ const { storeId, instance } = await mint({
+ url: URL_,
+ headers: { Cookie: "session=a", accept: "application/json" },
+ });
+ expect(await storedInit(instance)).toEqual({
+ url: URL_,
+ headers: { accept: "application/json" },
+ headerStore: storeId,
+ });
+ using app = await instance.handle();
await app.get();
expect(seen).toEqual(["session=a"]);
});
- it("a rotation reaches handles already out", async () => {
+ it("the store id rotates under handles already out, and revokes", async () => {
const seen = spyCookies();
- const stub = fresh();
- await stub.configure({ url: URL_, headers: { cookie: "session=a" } });
- using app = await stub.handle();
+ const { storeId, instance } = await mint({ url: URL_, headers: { cookie: "session=a" } });
+ using app = await instance.handle();
await app.pathjoin("x").get();
- using cookies = await stub.cookies();
- await cookies.set("cookie", "session=b");
+ expect(await (await call(`/store/${storeId}/cookie`, "PUT", "session=b")).json()).toEqual({ ok: true });
await app.pathjoin("x").get();
- expect(seen).toEqual(["session=a", "session=b"]);
+ expect(await (await call(`/store/${storeId}/cookie`, "DELETE")).json()).toEqual({ ok: true });
+ await app.get();
+ expect(seen).toEqual(["session=a", "session=b", null]);
});
- it("an instance minted before rotation moves its cookie to the store on first use", async () => {
+ it("the instance id reaches no store: its holder cannot swap in another session", async () => {
const seen = spyCookies();
- const stub = fresh();
- // the shape configure() stored until the cookie store
- await runInDurableObject(stub, (_obj, state) =>
- state.storage.put("init", { url: URL_, headers: { cookie: "session=old" }, msPerCall: 0 }),
- );
- using app = await stub.handle();
+ const { id, instance } = await mint({ url: URL_, headers: { cookie: "session=alice" } });
+ expect(await errorOf(call(`/store/${id}/cookie`, "PUT", "session=mallory"))).not.toBe("");
+ expect((await call(`/at/${id}/cookie`, "POST", { cookie: "session=mallory" })).status).toBe(404);
+ using app = await instance.handle();
await app.get();
- expect(await storedInit(stub)).toEqual({ url: URL_, headers: {}, msPerCall: 0 });
- using cookies = await stub.cookies();
- expect(await cookies.get("cookie")).toBe("session=old");
- await cookies.set("cookie", "session=new");
- using again = await stub.handle();
- await again.get();
- expect(seen).toEqual(["session=old", "session=new"]);
+ expect(seen).toEqual(["session=alice"]);
});
- it("POST /at//cookie rotates, by the same id that drives", async () => {
+ it("the store's shapes admit only the cookie; errors carry no values", async () => {
+ const { storeId } = await mint({ url: URL_ });
+ const err = await errorOf(call(`/store/${storeId}/authorization`, "PUT", "Bearer s3kr1t"));
+ expect(err).toContain('cookie: key: must match \\"cookie\\"');
+ expect(err).not.toContain("s3kr1t");
+ expect(await errorOf(call(`/store/${storeId}/cookie`, "PUT", 42))).toContain("must match M.string()");
+ });
+
+ it("an instance minted before header stores keeps its cookie in init, untouched", async () => {
const seen = spyCookies();
- const id = testEnv.WEBAPP_DO.newUniqueId();
- const stub = testEnv.WEBAPP_DO.get(id);
- await stub.configure({ url: URL_ });
- const rotate = (cookie: string) =>
- worker.fetch(
- new Request(`https://webapp.example/at/${id}/cookie`, {
- method: "POST",
- body: JSON.stringify({ cookie }),
- headers: { "content-type": "application/json" },
- }),
- testEnv,
- );
- expect(await (await rotate("session=first")).json()).toEqual({ ok: true });
- expect(await (await rotate("session=second")).json()).toEqual({ ok: true });
- using app = await stub.handle();
+ const instance = testEnv.WEBAPP_DO.get(testEnv.WEBAPP_DO.newUniqueId());
+ const old = { url: URL_, headers: { cookie: "session=old" }, msPerCall: 0 };
+ await runInDurableObject(instance, (_obj, state) => state.storage.put("init", old));
+ using app = await instance.handle();
await app.get();
- expect(seen).toEqual(["session=second"]);
+ expect(seen).toEqual(["session=old"]);
+ expect(await storedInit(instance)).toEqual(old);
});
it("a fixed header contradicting the stored cookie fails at send, naming no values", async () => {
spyCookies();
- const stub = fresh();
- await stub.configure({ url: URL_, headers: { cookie: "session=a" } });
- using app = await stub.handle();
+ const { instance } = await mint({ url: URL_, headers: { cookie: "session=a" } });
+ using app = await instance.handle();
const err = await app
.withHeader("cookie", "session=mine")
.get()
diff --git a/webapp/wrangler.jsonc b/webapp/wrangler.jsonc
index 82eee11..78d43d1 100644
--- a/webapp/wrangler.jsonc
+++ b/webapp/wrangler.jsonc
@@ -5,7 +5,13 @@
"compatibility_date": "2026-09-29",
"observability": { "enabled": true },
"durable_objects": {
- "bindings": [{ "name": "WEBAPP_DO", "class_name": "WebAppDo" }]
+ "bindings": [
+ { "name": "WEBAPP_DO", "class_name": "WebAppDo" },
+ { "name": "MAP_STORE_DO", "class_name": "MapStoreDo" }
+ ]
},
- "migrations": [{ "tag": "v1", "new_sqlite_classes": ["WebAppDo"] }]
+ "migrations": [
+ { "tag": "v1", "new_sqlite_classes": ["WebAppDo"] },
+ { "tag": "v2", "new_sqlite_classes": ["MapStoreDo"] }
+ ]
}
\ No newline at end of file
--
2.53.0
From 4dd3fe6bdeb4393550df9f7fee80d6f98c51111d Mon Sep 17 00:00:00 2001
From: Dan Connolly
Date: Thu, 8 Oct 2026 15:48:10 -0500
Subject: [PATCH 5/8] capsite/diigo-cache: the wire page hands the owner the
store id
webapp's mint now answers a store id (x-webapp-store-id) beside the
instance id: authority to rotate or revoke the diigo cookie. The wire
page shows it apart from the instance id, with its own copy button,
petname, and password-manager save; the cache never gets it. A webapp
that sends none gets a note on the page and a warning in the log.
Generated-by: Claude Opus 5.5
---
diigo-cache/src/index.ts | 53 +++++++++++++++++++++++++++-------------
1 file changed, 36 insertions(+), 17 deletions(-)
diff --git a/diigo-cache/src/index.ts b/diigo-cache/src/index.ts
index 7c01c0b..8ee5ea2 100644
--- a/diigo-cache/src/index.ts
+++ b/diigo-cache/src/index.ts
@@ -174,11 +174,13 @@ async function proxyMint(request: Request, env: Env): Promise {
const detail = (await res.text()).slice(0, 200);
return home(env, { error: `mint said ${res.status}: ${detail}` }, null);
}
- // webapp answers {id} (+ x-webapp-id); the id rides the header, the
- // wire page is ours
+ // webapp answers {id, storeId} (+ x-webapp-id, x-webapp-store-id);
+ // the ids ride the headers, the wire page is ours
const id = res.headers.get("x-webapp-id");
if (!id) return home(env, { error: "mint produced no id" }, null);
- return await wirePage(id);
+ const storeId = res.headers.get("x-webapp-store-id");
+ if (!storeId) console.warn({ event: "mint", outcome: "no store id: this session cannot be rotated" });
+ return await wirePage(id, storeId);
} catch (err) {
console.error({ event: "mint", error: String(err) });
return home(env, { error: `mint failed: ${String(err)}` }, null);
@@ -188,22 +190,37 @@ async function proxyMint(request: Request, env: Env): Promise {
/** the credential-save helper, once per page (see ./pm-save.ts) */
const PM_SAVE_JS = pmSave.toString();
-/** the suggested petname gets the holder's local hh:mm (the worker only
- * knows UTC), so two saves the same day are told apart at a glance */
-const PETNAME_TIME_JS = `{
- const p = document.getElementById("petname");
+/** the suggested petname (in the input with id `petnameId`) gets the
+ * holder's local hh:mm (the worker only knows UTC), so two saves the same
+ * day are told apart at a glance */
+function petnameTimeJs(petnameId: string): string {
+ return `{
+ const p = document.getElementById(${JSON.stringify(petnameId)});
const d = new Date();
p.value += " " + String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
}`;
-
-/** the onclick that hands pmSave its browser powers */
-function pmSaveClick(value: string): string {
- return `pmSave(this, ${JSON.stringify(value)}, { petname: document.getElementById("petname"), credentials: navigator.credentials, PasswordCredential: window.PasswordCredential, log: console })`;
}
-/** the wire step, with the freshly-minted webapp id pre-filled */
-async function wirePage(webappId: string): Promise {
+/** the onclick that hands pmSave its browser powers, saving under the
+ * petname in the input with id `petnameId` */
+function pmSaveClick(value: string, petnameId: string): string {
+ return `pmSave(this, ${JSON.stringify(value)}, { petname: document.getElementById(${JSON.stringify(petnameId)}), credentials: navigator.credentials, PasswordCredential: window.PasswordCredential, log: console })`;
+}
+
+/** the wire step, with the freshly-minted webapp id pre-filled, and the
+ * store id (rotating the cookie) for the account owner to keep */
+async function wirePage(webappId: string, storeId: string | null): Promise {
const fingerprint = await nick(webappId);
+ const storeSection = storeId
+ ? `keep: the cookie's store id
+This one is yours alone, not the cache's: with it you can rotate the
+diigo session (log in again, paste the new cookie) or revoke it. Fingerprint
+${esc(await nick(storeId))}
+copy
+your petname for it
+
+save to password manager
`
+ : `note: webapp sent no store id, so this session's cookie cannot be rotated; when it expires, mint again.
`;
const body = `
diigo cache: step 2 — wire the cache
@@ -212,8 +229,10 @@ async function wirePage(webappId: string): Promise {
copy
your petname for it
(private to you: the password-manager username; never sent to the server)
-
-save to password manager
+
+save to password manager
+${storeSection}
+wire
choose the cache mode, then wire — the next page shows the
cache id (copy that too), after which you land back on home: