opencodekit 0.15.4 → 0.15.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/template/.opencode/command/cloudflare.md +70 -0
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/skill/cloudflare/SKILL.md +233 -0
- package/dist/template/.opencode/skill/cloudflare/references/agents-sdk/README.md +35 -0
- package/dist/template/.opencode/skill/cloudflare/references/agents-sdk/api.md +100 -0
- package/dist/template/.opencode/skill/cloudflare/references/agents-sdk/configuration.md +99 -0
- package/dist/template/.opencode/skill/cloudflare/references/agents-sdk/gotchas.md +59 -0
- package/dist/template/.opencode/skill/cloudflare/references/agents-sdk/patterns.md +89 -0
- package/dist/template/.opencode/skill/cloudflare/references/ai-gateway/README.md +695 -0
- package/dist/template/.opencode/skill/cloudflare/references/ai-search/README.md +14 -0
- package/dist/template/.opencode/skill/cloudflare/references/ai-search/api.md +38 -0
- package/dist/template/.opencode/skill/cloudflare/references/ai-search/configuration.md +52 -0
- package/dist/template/.opencode/skill/cloudflare/references/ai-search/gotchas.md +41 -0
- package/dist/template/.opencode/skill/cloudflare/references/ai-search/patterns.md +45 -0
- package/dist/template/.opencode/skill/cloudflare/references/analytics-engine/README.md +14 -0
- package/dist/template/.opencode/skill/cloudflare/references/analytics-engine/api.md +27 -0
- package/dist/template/.opencode/skill/cloudflare/references/analytics-engine/configuration.md +45 -0
- package/dist/template/.opencode/skill/cloudflare/references/analytics-engine/gotchas.md +3 -0
- package/dist/template/.opencode/skill/cloudflare/references/analytics-engine/patterns.md +36 -0
- package/dist/template/.opencode/skill/cloudflare/references/api/README.md +21 -0
- package/dist/template/.opencode/skill/cloudflare/references/api/api.md +31 -0
- package/dist/template/.opencode/skill/cloudflare/references/api/configuration.md +20 -0
- package/dist/template/.opencode/skill/cloudflare/references/api/gotchas.md +28 -0
- package/dist/template/.opencode/skill/cloudflare/references/api/patterns.md +47 -0
- package/dist/template/.opencode/skill/cloudflare/references/api-shield/README.md +20 -0
- package/dist/template/.opencode/skill/cloudflare/references/api-shield/api.md +78 -0
- package/dist/template/.opencode/skill/cloudflare/references/api-shield/configuration.md +128 -0
- package/dist/template/.opencode/skill/cloudflare/references/api-shield/gotchas.md +51 -0
- package/dist/template/.opencode/skill/cloudflare/references/api-shield/patterns.md +145 -0
- package/dist/template/.opencode/skill/cloudflare/references/argo-smart-routing/README.md +16 -0
- package/dist/template/.opencode/skill/cloudflare/references/argo-smart-routing/api.md +50 -0
- package/dist/template/.opencode/skill/cloudflare/references/argo-smart-routing/configuration.md +53 -0
- package/dist/template/.opencode/skill/cloudflare/references/argo-smart-routing/gotchas.md +16 -0
- package/dist/template/.opencode/skill/cloudflare/references/argo-smart-routing/patterns.md +45 -0
- package/dist/template/.opencode/skill/cloudflare/references/bindings/README.md +14 -0
- package/dist/template/.opencode/skill/cloudflare/references/bindings/api.md +3 -0
- package/dist/template/.opencode/skill/cloudflare/references/bindings/configuration.md +58 -0
- package/dist/template/.opencode/skill/cloudflare/references/bindings/gotchas.md +35 -0
- package/dist/template/.opencode/skill/cloudflare/references/bindings/patterns.md +37 -0
- package/dist/template/.opencode/skill/cloudflare/references/bot-management/README.md +71 -0
- package/dist/template/.opencode/skill/cloudflare/references/bot-management/api.md +168 -0
- package/dist/template/.opencode/skill/cloudflare/references/bot-management/configuration.md +114 -0
- package/dist/template/.opencode/skill/cloudflare/references/bot-management/gotchas.md +99 -0
- package/dist/template/.opencode/skill/cloudflare/references/bot-management/patterns.md +125 -0
- package/dist/template/.opencode/skill/cloudflare/references/browser-rendering/README.md +16 -0
- package/dist/template/.opencode/skill/cloudflare/references/browser-rendering/api.md +54 -0
- package/dist/template/.opencode/skill/cloudflare/references/browser-rendering/configuration.md +47 -0
- package/dist/template/.opencode/skill/cloudflare/references/browser-rendering/gotchas.md +29 -0
- package/dist/template/.opencode/skill/cloudflare/references/browser-rendering/patterns.md +29 -0
- package/dist/template/.opencode/skill/cloudflare/references/c3/README.md +264 -0
- package/dist/template/.opencode/skill/cloudflare/references/cache-reserve/README.md +93 -0
- package/dist/template/.opencode/skill/cloudflare/references/cache-reserve/api.md +176 -0
- package/dist/template/.opencode/skill/cloudflare/references/cache-reserve/configuration.md +164 -0
- package/dist/template/.opencode/skill/cloudflare/references/cache-reserve/gotchas.md +203 -0
- package/dist/template/.opencode/skill/cloudflare/references/cache-reserve/patterns.md +180 -0
- package/dist/template/.opencode/skill/cloudflare/references/containers/README.md +16 -0
- package/dist/template/.opencode/skill/cloudflare/references/containers/api.md +43 -0
- package/dist/template/.opencode/skill/cloudflare/references/containers/configuration.md +56 -0
- package/dist/template/.opencode/skill/cloudflare/references/containers/gotchas.md +21 -0
- package/dist/template/.opencode/skill/cloudflare/references/containers/patterns.md +40 -0
- package/dist/template/.opencode/skill/cloudflare/references/cron-triggers/README.md +85 -0
- package/dist/template/.opencode/skill/cloudflare/references/cron-triggers/api.md +198 -0
- package/dist/template/.opencode/skill/cloudflare/references/cron-triggers/configuration.md +151 -0
- package/dist/template/.opencode/skill/cloudflare/references/cron-triggers/gotchas.md +129 -0
- package/dist/template/.opencode/skill/cloudflare/references/cron-triggers/patterns.md +122 -0
- package/dist/template/.opencode/skill/cloudflare/references/d1/README.md +92 -0
- package/dist/template/.opencode/skill/cloudflare/references/d1/api.md +141 -0
- package/dist/template/.opencode/skill/cloudflare/references/d1/configuration.md +127 -0
- package/dist/template/.opencode/skill/cloudflare/references/d1/gotchas.md +70 -0
- package/dist/template/.opencode/skill/cloudflare/references/d1/patterns.md +144 -0
- package/dist/template/.opencode/skill/cloudflare/references/ddos/README.md +34 -0
- package/dist/template/.opencode/skill/cloudflare/references/ddos/api.md +136 -0
- package/dist/template/.opencode/skill/cloudflare/references/ddos/configuration.md +67 -0
- package/dist/template/.opencode/skill/cloudflare/references/ddos/gotchas.md +114 -0
- package/dist/template/.opencode/skill/cloudflare/references/ddos/patterns.md +158 -0
- package/dist/template/.opencode/skill/cloudflare/references/do-storage/README.md +62 -0
- package/dist/template/.opencode/skill/cloudflare/references/do-storage/api.md +89 -0
- package/dist/template/.opencode/skill/cloudflare/references/do-storage/configuration.md +116 -0
- package/dist/template/.opencode/skill/cloudflare/references/do-storage/gotchas.md +93 -0
- package/dist/template/.opencode/skill/cloudflare/references/do-storage/patterns.md +112 -0
- package/dist/template/.opencode/skill/cloudflare/references/durable-objects/README.md +125 -0
- package/dist/template/.opencode/skill/cloudflare/references/durable-objects/api.md +152 -0
- package/dist/template/.opencode/skill/cloudflare/references/durable-objects/configuration.md +148 -0
- package/dist/template/.opencode/skill/cloudflare/references/durable-objects/gotchas.md +158 -0
- package/dist/template/.opencode/skill/cloudflare/references/durable-objects/patterns.md +255 -0
- package/dist/template/.opencode/skill/cloudflare/references/email-routing/README.md +18 -0
- package/dist/template/.opencode/skill/cloudflare/references/email-routing/api.md +46 -0
- package/dist/template/.opencode/skill/cloudflare/references/email-routing/configuration.md +63 -0
- package/dist/template/.opencode/skill/cloudflare/references/email-routing/gotchas.md +16 -0
- package/dist/template/.opencode/skill/cloudflare/references/email-routing/patterns.md +46 -0
- package/dist/template/.opencode/skill/cloudflare/references/email-workers/README.md +598 -0
- package/dist/template/.opencode/skill/cloudflare/references/hyperdrive/README.md +62 -0
- package/dist/template/.opencode/skill/cloudflare/references/hyperdrive/api.md +137 -0
- package/dist/template/.opencode/skill/cloudflare/references/hyperdrive/configuration.md +133 -0
- package/dist/template/.opencode/skill/cloudflare/references/hyperdrive/gotchas.md +184 -0
- package/dist/template/.opencode/skill/cloudflare/references/hyperdrive/patterns.md +176 -0
- package/dist/template/.opencode/skill/cloudflare/references/images/README.md +14 -0
- package/dist/template/.opencode/skill/cloudflare/references/images/api.md +3 -0
- package/dist/template/.opencode/skill/cloudflare/references/images/configuration.md +45 -0
- package/dist/template/.opencode/skill/cloudflare/references/images/gotchas.md +23 -0
- package/dist/template/.opencode/skill/cloudflare/references/images/patterns.md +31 -0
- package/dist/template/.opencode/skill/cloudflare/references/kv/README.md +60 -0
- package/dist/template/.opencode/skill/cloudflare/references/kv/api.md +114 -0
- package/dist/template/.opencode/skill/cloudflare/references/kv/configuration.md +92 -0
- package/dist/template/.opencode/skill/cloudflare/references/kv/gotchas.md +117 -0
- package/dist/template/.opencode/skill/cloudflare/references/kv/patterns.md +139 -0
- package/dist/template/.opencode/skill/cloudflare/references/miniflare/README.md +64 -0
- package/dist/template/.opencode/skill/cloudflare/references/miniflare/api.md +144 -0
- package/dist/template/.opencode/skill/cloudflare/references/miniflare/configuration.md +203 -0
- package/dist/template/.opencode/skill/cloudflare/references/miniflare/gotchas.md +187 -0
- package/dist/template/.opencode/skill/cloudflare/references/miniflare/patterns.md +211 -0
- package/dist/template/.opencode/skill/cloudflare/references/network-interconnect/README.md +60 -0
- package/dist/template/.opencode/skill/cloudflare/references/network-interconnect/api.md +240 -0
- package/dist/template/.opencode/skill/cloudflare/references/network-interconnect/configuration.md +127 -0
- package/dist/template/.opencode/skill/cloudflare/references/network-interconnect/gotchas.md +171 -0
- package/dist/template/.opencode/skill/cloudflare/references/network-interconnect/patterns.md +171 -0
- package/dist/template/.opencode/skill/cloudflare/references/observability/README.md +18 -0
- package/dist/template/.opencode/skill/cloudflare/references/observability/api.md +51 -0
- package/dist/template/.opencode/skill/cloudflare/references/observability/configuration.md +60 -0
- package/dist/template/.opencode/skill/cloudflare/references/observability/gotchas.md +36 -0
- package/dist/template/.opencode/skill/cloudflare/references/observability/patterns.md +42 -0
- package/dist/template/.opencode/skill/cloudflare/references/pages/README.md +76 -0
- package/dist/template/.opencode/skill/cloudflare/references/pages/api.md +200 -0
- package/dist/template/.opencode/skill/cloudflare/references/pages/configuration.md +228 -0
- package/dist/template/.opencode/skill/cloudflare/references/pages/gotchas.md +161 -0
- package/dist/template/.opencode/skill/cloudflare/references/pages/patterns.md +145 -0
- package/dist/template/.opencode/skill/cloudflare/references/pages-functions/README.md +57 -0
- package/dist/template/.opencode/skill/cloudflare/references/pages-functions/api.md +201 -0
- package/dist/template/.opencode/skill/cloudflare/references/pages-functions/configuration.md +159 -0
- package/dist/template/.opencode/skill/cloudflare/references/pages-functions/gotchas.md +151 -0
- package/dist/template/.opencode/skill/cloudflare/references/pages-functions/patterns.md +190 -0
- package/dist/template/.opencode/skill/cloudflare/references/pipelines/README.md +664 -0
- package/dist/template/.opencode/skill/cloudflare/references/pulumi/README.md +107 -0
- package/dist/template/.opencode/skill/cloudflare/references/pulumi/api.md +194 -0
- package/dist/template/.opencode/skill/cloudflare/references/pulumi/configuration.md +216 -0
- package/dist/template/.opencode/skill/cloudflare/references/pulumi/gotchas.md +223 -0
- package/dist/template/.opencode/skill/cloudflare/references/pulumi/patterns.md +139 -0
- package/dist/template/.opencode/skill/cloudflare/references/queues/README.md +69 -0
- package/dist/template/.opencode/skill/cloudflare/references/queues/api.md +138 -0
- package/dist/template/.opencode/skill/cloudflare/references/queues/configuration.md +125 -0
- package/dist/template/.opencode/skill/cloudflare/references/queues/gotchas.md +112 -0
- package/dist/template/.opencode/skill/cloudflare/references/queues/patterns.md +155 -0
- package/dist/template/.opencode/skill/cloudflare/references/r2/README.md +61 -0
- package/dist/template/.opencode/skill/cloudflare/references/r2/api.md +127 -0
- package/dist/template/.opencode/skill/cloudflare/references/r2/configuration.md +76 -0
- package/dist/template/.opencode/skill/cloudflare/references/r2/gotchas.md +94 -0
- package/dist/template/.opencode/skill/cloudflare/references/r2/patterns.md +127 -0
- package/dist/template/.opencode/skill/cloudflare/references/r2-data-catalog/README.md +18 -0
- package/dist/template/.opencode/skill/cloudflare/references/r2-data-catalog/api.md +29 -0
- package/dist/template/.opencode/skill/cloudflare/references/r2-data-catalog/configuration.md +39 -0
- package/dist/template/.opencode/skill/cloudflare/references/r2-data-catalog/gotchas.md +20 -0
- package/dist/template/.opencode/skill/cloudflare/references/r2-data-catalog/patterns.md +46 -0
- package/dist/template/.opencode/skill/cloudflare/references/r2-sql/README.md +512 -0
- package/dist/template/.opencode/skill/cloudflare/references/realtime-sfu/README.md +21 -0
- package/dist/template/.opencode/skill/cloudflare/references/realtime-sfu/api.md +135 -0
- package/dist/template/.opencode/skill/cloudflare/references/realtime-sfu/configuration.md +63 -0
- package/dist/template/.opencode/skill/cloudflare/references/realtime-sfu/gotchas.md +75 -0
- package/dist/template/.opencode/skill/cloudflare/references/realtime-sfu/patterns.md +102 -0
- package/dist/template/.opencode/skill/cloudflare/references/realtimekit/README.md +81 -0
- package/dist/template/.opencode/skill/cloudflare/references/realtimekit/api.md +164 -0
- package/dist/template/.opencode/skill/cloudflare/references/realtimekit/configuration.md +147 -0
- package/dist/template/.opencode/skill/cloudflare/references/realtimekit/gotchas.md +172 -0
- package/dist/template/.opencode/skill/cloudflare/references/realtimekit/patterns.md +155 -0
- package/dist/template/.opencode/skill/cloudflare/references/sandbox/README.md +90 -0
- package/dist/template/.opencode/skill/cloudflare/references/sandbox/api.md +178 -0
- package/dist/template/.opencode/skill/cloudflare/references/sandbox/configuration.md +131 -0
- package/dist/template/.opencode/skill/cloudflare/references/sandbox/gotchas.md +156 -0
- package/dist/template/.opencode/skill/cloudflare/references/sandbox/patterns.md +203 -0
- package/dist/template/.opencode/skill/cloudflare/references/secrets-store/README.md +58 -0
- package/dist/template/.opencode/skill/cloudflare/references/secrets-store/api.md +182 -0
- package/dist/template/.opencode/skill/cloudflare/references/secrets-store/configuration.md +140 -0
- package/dist/template/.opencode/skill/cloudflare/references/secrets-store/gotchas.md +129 -0
- package/dist/template/.opencode/skill/cloudflare/references/secrets-store/patterns.md +218 -0
- package/dist/template/.opencode/skill/cloudflare/references/smart-placement/README.md +91 -0
- package/dist/template/.opencode/skill/cloudflare/references/smart-placement/api.md +139 -0
- package/dist/template/.opencode/skill/cloudflare/references/smart-placement/configuration.md +129 -0
- package/dist/template/.opencode/skill/cloudflare/references/smart-placement/gotchas.md +87 -0
- package/dist/template/.opencode/skill/cloudflare/references/smart-placement/patterns.md +135 -0
- package/dist/template/.opencode/skill/cloudflare/references/snippets/README.md +15 -0
- package/dist/template/.opencode/skill/cloudflare/references/snippets/api.md +47 -0
- package/dist/template/.opencode/skill/cloudflare/references/snippets/configuration.md +33 -0
- package/dist/template/.opencode/skill/cloudflare/references/snippets/gotchas.md +21 -0
- package/dist/template/.opencode/skill/cloudflare/references/snippets/patterns.md +34 -0
- package/dist/template/.opencode/skill/cloudflare/references/spectrum/README.md +16 -0
- package/dist/template/.opencode/skill/cloudflare/references/spectrum/api.md +24 -0
- package/dist/template/.opencode/skill/cloudflare/references/spectrum/configuration.md +43 -0
- package/dist/template/.opencode/skill/cloudflare/references/spectrum/gotchas.md +42 -0
- package/dist/template/.opencode/skill/cloudflare/references/spectrum/patterns.md +40 -0
- package/dist/template/.opencode/skill/cloudflare/references/static-assets/README.md +14 -0
- package/dist/template/.opencode/skill/cloudflare/references/static-assets/api.md +3 -0
- package/dist/template/.opencode/skill/cloudflare/references/static-assets/configuration.md +47 -0
- package/dist/template/.opencode/skill/cloudflare/references/static-assets/gotchas.md +44 -0
- package/dist/template/.opencode/skill/cloudflare/references/static-assets/patterns.md +42 -0
- package/dist/template/.opencode/skill/cloudflare/references/stream/README.md +103 -0
- package/dist/template/.opencode/skill/cloudflare/references/stream/api.md +204 -0
- package/dist/template/.opencode/skill/cloudflare/references/stream/configuration.md +127 -0
- package/dist/template/.opencode/skill/cloudflare/references/stream/gotchas.md +131 -0
- package/dist/template/.opencode/skill/cloudflare/references/stream/patterns.md +152 -0
- package/dist/template/.opencode/skill/cloudflare/references/tail-workers/README.md +640 -0
- package/dist/template/.opencode/skill/cloudflare/references/terraform/README.md +76 -0
- package/dist/template/.opencode/skill/cloudflare/references/terraform/api.md +159 -0
- package/dist/template/.opencode/skill/cloudflare/references/terraform/configuration.md +156 -0
- package/dist/template/.opencode/skill/cloudflare/references/terraform/gotchas.md +207 -0
- package/dist/template/.opencode/skill/cloudflare/references/terraform/patterns.md +135 -0
- package/dist/template/.opencode/skill/cloudflare/references/tunnel/README.md +82 -0
- package/dist/template/.opencode/skill/cloudflare/references/tunnel/api.md +105 -0
- package/dist/template/.opencode/skill/cloudflare/references/tunnel/configuration.md +113 -0
- package/dist/template/.opencode/skill/cloudflare/references/tunnel/gotchas.md +115 -0
- package/dist/template/.opencode/skill/cloudflare/references/tunnel/patterns.md +157 -0
- package/dist/template/.opencode/skill/cloudflare/references/turn/README.md +699 -0
- package/dist/template/.opencode/skill/cloudflare/references/turnstile/README.md +14 -0
- package/dist/template/.opencode/skill/cloudflare/references/turnstile/api.md +3 -0
- package/dist/template/.opencode/skill/cloudflare/references/turnstile/configuration.md +19 -0
- package/dist/template/.opencode/skill/cloudflare/references/turnstile/gotchas.md +27 -0
- package/dist/template/.opencode/skill/cloudflare/references/turnstile/patterns.md +41 -0
- package/dist/template/.opencode/skill/cloudflare/references/vectorize/README.md +682 -0
- package/dist/template/.opencode/skill/cloudflare/references/waf/README.md +14 -0
- package/dist/template/.opencode/skill/cloudflare/references/waf/api.md +3 -0
- package/dist/template/.opencode/skill/cloudflare/references/waf/configuration.md +44 -0
- package/dist/template/.opencode/skill/cloudflare/references/waf/gotchas.md +24 -0
- package/dist/template/.opencode/skill/cloudflare/references/waf/patterns.md +29 -0
- package/dist/template/.opencode/skill/cloudflare/references/web-analytics/README.md +19 -0
- package/dist/template/.opencode/skill/cloudflare/references/web-analytics/api.md +52 -0
- package/dist/template/.opencode/skill/cloudflare/references/web-analytics/configuration.md +31 -0
- package/dist/template/.opencode/skill/cloudflare/references/web-analytics/gotchas.md +28 -0
- package/dist/template/.opencode/skill/cloudflare/references/web-analytics/patterns.md +52 -0
- package/dist/template/.opencode/skill/cloudflare/references/workerd/README.md +47 -0
- package/dist/template/.opencode/skill/cloudflare/references/workerd/api.md +199 -0
- package/dist/template/.opencode/skill/cloudflare/references/workerd/configuration.md +185 -0
- package/dist/template/.opencode/skill/cloudflare/references/workerd/gotchas.md +203 -0
- package/dist/template/.opencode/skill/cloudflare/references/workerd/patterns.md +216 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers/README.md +96 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers/api.md +137 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers/configuration.md +147 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers/gotchas.md +99 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers/patterns.md +149 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-ai/README.md +116 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-for-platforms/README.md +48 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-for-platforms/api.md +169 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-for-platforms/configuration.md +136 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-for-platforms/gotchas.md +130 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-for-platforms/patterns.md +170 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-playground/README.md +16 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-playground/api.md +20 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-playground/configuration.md +3 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-playground/gotchas.md +35 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-playground/patterns.md +42 -0
- package/dist/template/.opencode/skill/cloudflare/references/workers-vpc/README.md +579 -0
- package/dist/template/.opencode/skill/cloudflare/references/workflows/README.md +62 -0
- package/dist/template/.opencode/skill/cloudflare/references/workflows/api.md +125 -0
- package/dist/template/.opencode/skill/cloudflare/references/workflows/configuration.md +177 -0
- package/dist/template/.opencode/skill/cloudflare/references/workflows/gotchas.md +136 -0
- package/dist/template/.opencode/skill/cloudflare/references/workflows/patterns.md +132 -0
- package/dist/template/.opencode/skill/cloudflare/references/wrangler/README.md +90 -0
- package/dist/template/.opencode/skill/cloudflare/references/wrangler/api.md +140 -0
- package/dist/template/.opencode/skill/cloudflare/references/wrangler/configuration.md +128 -0
- package/dist/template/.opencode/skill/cloudflare/references/wrangler/gotchas.md +93 -0
- package/dist/template/.opencode/skill/cloudflare/references/wrangler/patterns.md +150 -0
- package/dist/template/.opencode/skill/cloudflare/references/zaraz/README.md +360 -0
- package/dist/template/.opencode/skill/react-best-practices/AGENTS.md +2410 -0
- package/dist/template/.opencode/skill/react-best-practices/README.md +123 -0
- package/dist/template/.opencode/skill/react-best-practices/SKILL.md +125 -0
- package/dist/template/.opencode/skill/react-best-practices/metadata.json +15 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/_sections.md +46 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/_template.md +28 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/async-api-routes.md +38 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/async-defer-await.md +80 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/async-dependencies.md +36 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/async-parallel.md +28 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/bundle-conditional.md +31 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/bundle-preload.md +50 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/client-event-listeners.md +74 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-cache-storage.md +70 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-early-exit.md +50 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-index-maps.md +37 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-length-check-first.md +49 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rendering-activity.md +26 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rerender-memo.md +44 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/rerender-transitions.md +40 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/server-cache-lru.md +41 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/server-cache-react.md +76 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/dist/template/.opencode/skill/react-best-practices/rules/server-serialization.md +38 -0
- package/dist/template/.opencode/skill/supabase/SKILL.md +120 -0
- package/dist/template/.opencode/skill/supabase/mcp.json +27 -0
- package/dist/template/.opencode/skill/vercel-deploy-claimable/SKILL.md +112 -0
- package/dist/template/.opencode/skill/vercel-deploy-claimable/scripts/deploy.sh +249 -0
- package/dist/template/.opencode/skill/web-design-guidelines/SKILL.md +39 -0
- package/package.json +1 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# DO Storage Patterns & Best Practices
|
|
2
|
+
|
|
3
|
+
## Schema Migration
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export class MyDurableObject extends DurableObject {
|
|
7
|
+
constructor(ctx: DurableObjectState, env: Env) {
|
|
8
|
+
super(ctx, env);
|
|
9
|
+
this.sql = ctx.storage.sql;
|
|
10
|
+
this.sql.exec(`CREATE TABLE IF NOT EXISTS _meta(key TEXT PRIMARY KEY, value TEXT)`);
|
|
11
|
+
const ver = this.sql.exec("SELECT value FROM _meta WHERE key = 'schema_version'").toArray()[0]?.value || "0";
|
|
12
|
+
if (ver === "0") this.sql.exec(`CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT); INSERT OR REPLACE INTO _meta VALUES ('schema_version', '1')`);
|
|
13
|
+
if (ver === "1") this.sql.exec(`ALTER TABLE users ADD COLUMN email TEXT; UPDATE _meta SET value = '2' WHERE key = 'schema_version'`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## In-Memory Caching
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
export class UserCache extends DurableObject {
|
|
22
|
+
cache = new Map<string, User>();
|
|
23
|
+
async getUser(id: string): Promise<User> {
|
|
24
|
+
if (this.cache.has(id)) return this.cache.get(id)!;
|
|
25
|
+
const user = await this.ctx.storage.get<User>(`user:${id}`);
|
|
26
|
+
if (user) this.cache.set(id, user);
|
|
27
|
+
return user;
|
|
28
|
+
}
|
|
29
|
+
async updateUser(id: string, data: Partial<User>) {
|
|
30
|
+
const updated = { ...await this.getUser(id), ...data };
|
|
31
|
+
this.cache.set(id, updated);
|
|
32
|
+
await this.ctx.storage.put(`user:${id}`, updated);
|
|
33
|
+
return updated;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Rate Limiting
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
export class RateLimiter extends DurableObject {
|
|
42
|
+
async checkLimit(key: string, limit: number, window: number): Promise<boolean> {
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
this.sql.exec('DELETE FROM requests WHERE key = ? AND timestamp < ?', key, now - window);
|
|
45
|
+
const count = this.sql.exec('SELECT COUNT(*) as count FROM requests WHERE key = ?', key).one().count;
|
|
46
|
+
if (count >= limit) return false;
|
|
47
|
+
this.sql.exec('INSERT INTO requests (key, timestamp) VALUES (?, ?)', key, now);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Batch Processing with Alarms
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
export class BatchProcessor extends DurableObject {
|
|
57
|
+
pending: string[] = [];
|
|
58
|
+
async addItem(item: string) {
|
|
59
|
+
this.pending.push(item);
|
|
60
|
+
if (!await this.ctx.storage.getAlarm()) await this.ctx.storage.setAlarm(Date.now() + 5000);
|
|
61
|
+
}
|
|
62
|
+
async alarm() {
|
|
63
|
+
const items = [...this.pending];
|
|
64
|
+
this.pending = [];
|
|
65
|
+
this.sql.exec(`INSERT INTO processed_items (item, timestamp) VALUES ${items.map(() => "(?, ?)").join(", ")}`, ...items.flatMap(item => [item, Date.now()]));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Initialization Pattern
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
export class Counter extends DurableObject {
|
|
74
|
+
value: number;
|
|
75
|
+
constructor(ctx: DurableObjectState, env: Env) {
|
|
76
|
+
super(ctx, env);
|
|
77
|
+
ctx.blockConcurrencyWhile(async () => { this.value = (await ctx.storage.get("value")) || 0; });
|
|
78
|
+
}
|
|
79
|
+
async increment() {
|
|
80
|
+
this.value++;
|
|
81
|
+
this.ctx.storage.put("value", this.value); // Don't await (output gate protects)
|
|
82
|
+
return this.value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Safe Counter / Optimized Write
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// Input gate blocks other requests
|
|
91
|
+
async getUniqueNumber(): Promise<number> {
|
|
92
|
+
let val = await this.ctx.storage.get("counter");
|
|
93
|
+
await this.ctx.storage.put("counter", val + 1);
|
|
94
|
+
return val;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// No await on write - output gate delays response until write confirms
|
|
98
|
+
async increment(): Promise<Response> {
|
|
99
|
+
let val = await this.ctx.storage.get("counter");
|
|
100
|
+
this.ctx.storage.put("counter", val + 1);
|
|
101
|
+
return new Response(String(val));
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Cleanup
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
async cleanup() {
|
|
109
|
+
await this.ctx.storage.deleteAlarm(); // Separate from deleteAll
|
|
110
|
+
await this.ctx.storage.deleteAll();
|
|
111
|
+
}
|
|
112
|
+
```
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Cloudflare Durable Objects
|
|
2
|
+
|
|
3
|
+
Expert guidance for building stateful applications with Cloudflare Durable Objects.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Durable Objects combine compute with storage in globally-unique, strongly-consistent packages:
|
|
8
|
+
- **Globally unique instances**: Each DO has unique ID for multi-client coordination
|
|
9
|
+
- **Co-located storage**: Fast, strongly-consistent storage with compute
|
|
10
|
+
- **Automatic placement**: Objects spawn near first request location
|
|
11
|
+
- **Stateful serverless**: In-memory state + persistent storage
|
|
12
|
+
- **Single-threaded**: Serial request processing (no race conditions)
|
|
13
|
+
|
|
14
|
+
## When to Use DOs
|
|
15
|
+
|
|
16
|
+
Use DOs for **stateful coordination**, not stateless request handling:
|
|
17
|
+
- **Coordination**: Multiple clients interacting with shared state (chat rooms, multiplayer games)
|
|
18
|
+
- **Strong consistency**: Operations must serialize to avoid races (booking systems, inventory)
|
|
19
|
+
- **Per-entity storage**: Each user/tenant/resource needs isolated database (multi-tenant SaaS)
|
|
20
|
+
- **Persistent connections**: Long-lived WebSockets that survive across requests
|
|
21
|
+
- **Per-entity scheduled work**: Each entity needs its own timer (subscription renewals, game timeouts)
|
|
22
|
+
|
|
23
|
+
## When NOT to Use DOs
|
|
24
|
+
|
|
25
|
+
| Scenario | Use Instead |
|
|
26
|
+
|----------|-------------|
|
|
27
|
+
| Stateless request handling | Workers |
|
|
28
|
+
| Maximum global distribution | Workers |
|
|
29
|
+
| High fan-out (independent requests) | Workers |
|
|
30
|
+
| Global singleton handling all traffic | Shard across multiple DOs |
|
|
31
|
+
| High-frequency pub/sub | Queues |
|
|
32
|
+
| Long-running continuous processes | Workers + Alarms |
|
|
33
|
+
| Chatty microservice (every request) | Reconsider architecture |
|
|
34
|
+
| Eventual consistency OK, read-heavy | KV |
|
|
35
|
+
| Relational queries across entities | D1 |
|
|
36
|
+
|
|
37
|
+
## Design Heuristics
|
|
38
|
+
|
|
39
|
+
Model each DO around your **atom of coordination**—the logical unit needing serialized access (user, room, document, session).
|
|
40
|
+
|
|
41
|
+
| Characteristic | Feels Right | Question It | Reconsider |
|
|
42
|
+
|----------------|-------------|-------------|------------|
|
|
43
|
+
| Requests/sec (sustained) | < 100 | 100-500 | > 500 |
|
|
44
|
+
| Storage keys | < 100 | 100-1000 | > 1000 |
|
|
45
|
+
| Total state size | < 10MB | 10MB-100MB | > 1GB |
|
|
46
|
+
| Alarm frequency | Minutes-hours | Every 30s | Every few seconds |
|
|
47
|
+
| WebSocket duration | Short bursts | Hours (hibernating) | Days always-on |
|
|
48
|
+
| Fan-out from this DO | Never/rarely | To < 10 DOs | To 100+ DOs |
|
|
49
|
+
|
|
50
|
+
## Core Concepts
|
|
51
|
+
|
|
52
|
+
### Class Structure
|
|
53
|
+
All DOs extend `DurableObject` base class with constructor receiving `DurableObjectState` (storage, WebSockets, alarms) and `Env` (bindings).
|
|
54
|
+
|
|
55
|
+
### Accessing from Workers
|
|
56
|
+
Workers use bindings to get stubs, then call RPC methods directly (recommended) or use fetch handler (legacy).
|
|
57
|
+
|
|
58
|
+
### ID Generation
|
|
59
|
+
- `idFromName()`: Deterministic, named coordination
|
|
60
|
+
- `newUniqueId()`: Random IDs for sharding
|
|
61
|
+
- `idFromString()`: Derive from existing IDs
|
|
62
|
+
- Jurisdiction option: Data locality
|
|
63
|
+
|
|
64
|
+
### Storage Options
|
|
65
|
+
- **SQLite** (recommended): Structured data, transactions, 10GB/DO
|
|
66
|
+
- **Synchronous KV API**: Simple key-value on SQLite objects
|
|
67
|
+
- **Asynchronous KV API**: Legacy/advanced use cases
|
|
68
|
+
|
|
69
|
+
### Special Features
|
|
70
|
+
- **Alarms**: Schedule future execution per-DO
|
|
71
|
+
- **WebSocket Hibernation**: Zero-cost idle connections
|
|
72
|
+
- **Point-in-Time Recovery**: Restore to any point in 30 days
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { DurableObject } from "cloudflare:workers";
|
|
78
|
+
|
|
79
|
+
export class Counter extends DurableObject<Env> {
|
|
80
|
+
async increment(): Promise<number> {
|
|
81
|
+
const result = this.ctx.storage.sql.exec(
|
|
82
|
+
`INSERT INTO counters (id, value) VALUES (1, 1)
|
|
83
|
+
ON CONFLICT(id) DO UPDATE SET value = value + 1
|
|
84
|
+
RETURNING value`
|
|
85
|
+
).one();
|
|
86
|
+
return result.value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Worker access
|
|
91
|
+
export default {
|
|
92
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
93
|
+
const id = env.COUNTER.idFromName("global");
|
|
94
|
+
const stub = env.COUNTER.get(id);
|
|
95
|
+
const count = await stub.increment();
|
|
96
|
+
return new Response(`Count: ${count}`);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Essential Commands
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx wrangler dev # Local dev with DOs
|
|
105
|
+
npx wrangler dev --remote # Test against prod DOs
|
|
106
|
+
npx wrangler deploy # Deploy + auto-apply migrations
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Resources
|
|
110
|
+
|
|
111
|
+
**Docs**: https://developers.cloudflare.com/durable-objects/
|
|
112
|
+
**API Reference**: https://developers.cloudflare.com/durable-objects/api/
|
|
113
|
+
**Examples**: https://developers.cloudflare.com/durable-objects/examples/
|
|
114
|
+
|
|
115
|
+
## In This Reference
|
|
116
|
+
|
|
117
|
+
- [Configuration](./configuration.md) - wrangler.jsonc setup, migrations, bindings
|
|
118
|
+
- [API](./api.md) - Class structure, storage APIs, alarms, WebSockets
|
|
119
|
+
- [Patterns](./patterns.md) - Rate limiting, locks, real-time collab, sessions
|
|
120
|
+
- [Gotchas](./gotchas.md) - Limits, common issues, troubleshooting
|
|
121
|
+
|
|
122
|
+
## See Also
|
|
123
|
+
|
|
124
|
+
- [Workers](../workers/README.md) - Core Workers runtime
|
|
125
|
+
- [DO Storage](../do-storage/README.md) - Deep dive on storage APIs
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Durable Objects API
|
|
2
|
+
|
|
3
|
+
## Class Structure
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { DurableObject } from "cloudflare:workers";
|
|
7
|
+
|
|
8
|
+
export class MyDO extends DurableObject<Env> {
|
|
9
|
+
constructor(ctx: DurableObjectState, env: Env) {
|
|
10
|
+
super(ctx, env);
|
|
11
|
+
// Initialize storage/run migrations before any requests
|
|
12
|
+
ctx.blockConcurrencyWhile(async () => {
|
|
13
|
+
await this.migrate();
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async myMethod(arg: string): Promise<string> { return arg; }
|
|
17
|
+
async alarm() { }
|
|
18
|
+
async webSocketMessage(ws: WebSocket, msg: string | ArrayBuffer) { }
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Concurrency Model
|
|
23
|
+
|
|
24
|
+
### Input/Output Gates
|
|
25
|
+
|
|
26
|
+
DOs are single-threaded but async/await allows request interleaving. The runtime uses **gates** to prevent data races:
|
|
27
|
+
|
|
28
|
+
**Input gates** block new events while synchronous JS executes. Awaiting async ops opens the gate, allowing interleaving. Storage operations provide special protection.
|
|
29
|
+
|
|
30
|
+
**Output gates** hold outgoing network messages until pending storage writes complete—clients never see confirmation of unpersisted data.
|
|
31
|
+
|
|
32
|
+
### Write Coalescing
|
|
33
|
+
|
|
34
|
+
Multiple storage writes without intervening `await` are automatically batched into a single atomic transaction:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
async transfer(fromId: string, toId: string, amount: number) {
|
|
38
|
+
// All three writes commit together atomically
|
|
39
|
+
this.ctx.storage.sql.exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromId);
|
|
40
|
+
this.ctx.storage.sql.exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toId);
|
|
41
|
+
this.ctx.storage.sql.exec("INSERT INTO transfers (from_id, to_id, amount) VALUES (?, ?, ?)", fromId, toId, amount);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### blockConcurrencyWhile()
|
|
46
|
+
|
|
47
|
+
Guarantees no other events process until callback completes. **Use sparingly**—only for initialization/migrations.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
constructor(ctx: DurableObjectState, env: Env) {
|
|
51
|
+
super(ctx, env);
|
|
52
|
+
ctx.blockConcurrencyWhile(async () => {
|
|
53
|
+
const version = this.ctx.storage.sql.exec<{ version: number }>("PRAGMA user_version").one()?.version ?? 0;
|
|
54
|
+
if (version < 1) {
|
|
55
|
+
this.ctx.storage.sql.exec(`CREATE TABLE IF NOT EXISTS data (...); PRAGMA user_version = 1;`);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Anti-pattern**: Using `blockConcurrencyWhile()` on every request or across I/O (fetch, KV, R2) severely degrades throughput. For regular requests, rely on input/output gates and write coalescing.
|
|
62
|
+
|
|
63
|
+
### Optimistic Locking (Non-Storage I/O)
|
|
64
|
+
|
|
65
|
+
Input gates only protect during storage ops. External I/O like `fetch()` allows interleaving. Use check-and-set:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
async updateFromExternal(key: string) {
|
|
69
|
+
const version = this.ctx.storage.sql.exec<{ v: number }>("SELECT version as v FROM data WHERE key = ?", key).one()?.v;
|
|
70
|
+
const externalData = await fetch("https://api.example.com/data"); // Other requests can interleave here
|
|
71
|
+
const newVersion = this.ctx.storage.sql.exec<{ v: number }>("SELECT version as v FROM data WHERE key = ?", key).one()?.v;
|
|
72
|
+
|
|
73
|
+
if (version !== newVersion) throw new Error("Concurrent modification");
|
|
74
|
+
this.ctx.storage.sql.exec("UPDATE data SET value = ?, version = version + 1 WHERE key = ?", await externalData.text(), key);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## SQLite Storage
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// Query
|
|
82
|
+
const cursor = this.ctx.storage.sql.exec("SELECT * FROM users WHERE age > ?", 18);
|
|
83
|
+
cursor.toArray() // All rows
|
|
84
|
+
cursor.one() // 1 row or throw
|
|
85
|
+
cursor.raw().toArray() // [[1, 'Alice']]
|
|
86
|
+
cursor.rowsRead, cursor.rowsWritten, cursor.columnNames
|
|
87
|
+
this.ctx.storage.sql.databaseSize
|
|
88
|
+
|
|
89
|
+
// Schema
|
|
90
|
+
this.ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)");
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## KV Storage
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Sync (SQLite only)
|
|
97
|
+
this.ctx.storage.kv.get/put/delete("key", value)
|
|
98
|
+
this.ctx.storage.kv.list({ prefix: "user:" })
|
|
99
|
+
|
|
100
|
+
// Async
|
|
101
|
+
await this.ctx.storage.get/put/delete("key", value)
|
|
102
|
+
await this.ctx.storage.put({ k1: "a", k2: "b" }) // Batch 128 max
|
|
103
|
+
await this.ctx.storage.list({ prefix: "user:", start: "user:100", limit: 50 })
|
|
104
|
+
await this.ctx.storage.deleteAll()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Transactions
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// Sync
|
|
111
|
+
this.ctx.storage.transactionSync(() => { /* SQL ops */ });
|
|
112
|
+
|
|
113
|
+
// Async
|
|
114
|
+
await this.ctx.storage.transaction(async (txn) => {
|
|
115
|
+
await txn.get/put/delete("key", value); // Throw to rollback
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Point-in-Time Recovery
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
await this.ctx.storage.getCurrentBookmark()
|
|
123
|
+
await this.ctx.storage.getBookmarkForTime(Date.now() - 86400000)
|
|
124
|
+
await this.ctx.storage.onNextSessionRestoreBookmark(bookmark) // Call ctx.abort() after
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Alarms
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
await this.ctx.storage.setAlarm(Date.now() + 3600000)
|
|
131
|
+
await this.ctx.storage.getAlarm()
|
|
132
|
+
await this.ctx.storage.deleteAlarm()
|
|
133
|
+
async alarm() { /* runs when fires */ }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## WebSockets
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
async fetch(req: Request): Promise<Response> {
|
|
140
|
+
const [client, server] = Object.values(new WebSocketPair());
|
|
141
|
+
this.ctx.acceptWebSocket(server, ["room:123"]);
|
|
142
|
+
server.serializeAttachment({ userId: "abc" });
|
|
143
|
+
return new Response(null, { status: 101, webSocket: client });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async webSocketMessage(ws: WebSocket, msg: string | ArrayBuffer) {
|
|
147
|
+
const data = ws.deserializeAttachment();
|
|
148
|
+
for (const c of this.ctx.getWebSockets()) c.send(msg);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Management: getWebSockets(), getTags(ws), ws.send/close()
|
|
152
|
+
```
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Durable Objects Configuration
|
|
2
|
+
|
|
3
|
+
## Basic Setup
|
|
4
|
+
|
|
5
|
+
```jsonc
|
|
6
|
+
{
|
|
7
|
+
"name": "my-worker",
|
|
8
|
+
"main": "src/index.ts",
|
|
9
|
+
"compatibility_date": "2024-04-03",
|
|
10
|
+
"durable_objects": {
|
|
11
|
+
"bindings": [
|
|
12
|
+
{ "name": "MY_DO", "class_name": "MyDO" },
|
|
13
|
+
{ "name": "EXTERNAL", "class_name": "ExternalDO", "script_name": "other-worker" }
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"migrations": [
|
|
17
|
+
{ "tag": "v1", "new_sqlite_classes": ["MyDO"] }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Migrations
|
|
23
|
+
|
|
24
|
+
```jsonc
|
|
25
|
+
{
|
|
26
|
+
"migrations": [
|
|
27
|
+
// Create new SQLite-backed class (recommended for new classes)
|
|
28
|
+
{ "tag": "v1", "new_sqlite_classes": ["MyDO"] },
|
|
29
|
+
|
|
30
|
+
// Create new KV-backed class (legacy, paid only)
|
|
31
|
+
// { "tag": "v1", "new_classes": ["MyDO"] },
|
|
32
|
+
|
|
33
|
+
// Rename class - preserves all data and object IDs
|
|
34
|
+
{ "tag": "v2", "renamed_classes": [{ "from": "OldName", "to": "NewName" }] },
|
|
35
|
+
|
|
36
|
+
// Transfer between scripts - requires coordination
|
|
37
|
+
{ "tag": "v3", "transferred_classes": [{ "from": "Src", "from_script": "old-worker", "to": "Dest" }] },
|
|
38
|
+
|
|
39
|
+
// DELETE - DESTROYS ALL DATA PERMANENTLY, NO RECOVERY
|
|
40
|
+
{ "tag": "v4", "deleted_classes": ["Obsolete"] }
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Migration rules:**
|
|
46
|
+
- Tags must be unique and sequential
|
|
47
|
+
- No rollback mechanism—test with `--dry-run` first
|
|
48
|
+
- Auto-applied on deploy
|
|
49
|
+
- `renamed_classes` preserves data and IDs
|
|
50
|
+
- `deleted_classes` is irreversible—all storage gone
|
|
51
|
+
- Transfers between scripts require both scripts deployed with coordinated migrations
|
|
52
|
+
|
|
53
|
+
## Advanced
|
|
54
|
+
|
|
55
|
+
```jsonc
|
|
56
|
+
{
|
|
57
|
+
"limits": { "cpu_ms": 300000 }, // Default 30s, max 300s
|
|
58
|
+
"env": {
|
|
59
|
+
"production": {
|
|
60
|
+
"durable_objects": {
|
|
61
|
+
"bindings": [{ "name": "MY_DO", "class_name": "MyDO", "environment": "production" }]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Types
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { DurableObject } from "cloudflare:workers";
|
|
72
|
+
|
|
73
|
+
interface Env {
|
|
74
|
+
MY_DO: DurableObjectNamespace<MyDO>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class MyDO extends DurableObject<Env> {}
|
|
78
|
+
|
|
79
|
+
type DurableObjectNamespace<T> = {
|
|
80
|
+
newUniqueId(options?: { jurisdiction?: string }): DurableObjectId;
|
|
81
|
+
idFromName(name: string): DurableObjectId;
|
|
82
|
+
idFromString(id: string): DurableObjectId;
|
|
83
|
+
get(id: DurableObjectId): DurableObjectStub<T>;
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Testing with Vitest
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// vitest.config.ts
|
|
91
|
+
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
|
|
92
|
+
|
|
93
|
+
export default defineWorkersConfig({
|
|
94
|
+
test: {
|
|
95
|
+
poolOptions: {
|
|
96
|
+
workers: { wrangler: { configPath: "./wrangler.toml" } },
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// test/my-do.test.ts
|
|
104
|
+
import { env, runInDurableObject, runDurableObjectAlarm } from "cloudflare:test";
|
|
105
|
+
import { describe, it, expect } from "vitest";
|
|
106
|
+
|
|
107
|
+
describe("MyDO", () => {
|
|
108
|
+
it("handles RPC methods", async () => {
|
|
109
|
+
const id = env.MY_DO.idFromName("test");
|
|
110
|
+
const stub = env.MY_DO.get(id);
|
|
111
|
+
|
|
112
|
+
const result = await stub.myMethod("test-arg");
|
|
113
|
+
expect(result).toBe("test-arg");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("can access storage directly", async () => {
|
|
117
|
+
const id = env.MY_DO.idFromName("test");
|
|
118
|
+
const stub = env.MY_DO.get(id);
|
|
119
|
+
|
|
120
|
+
await runInDurableObject(stub, async (instance, state) => {
|
|
121
|
+
const count = state.storage.sql
|
|
122
|
+
.exec<{ count: number }>("SELECT COUNT(*) as count FROM data")
|
|
123
|
+
.one();
|
|
124
|
+
expect(count.count).toBe(0);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("can trigger alarms", async () => {
|
|
129
|
+
const id = env.MY_DO.idFromName("test");
|
|
130
|
+
const stub = env.MY_DO.get(id);
|
|
131
|
+
|
|
132
|
+
const alarmRan = await runDurableObjectAlarm(stub);
|
|
133
|
+
expect(alarmRan).toBe(false); // No alarm scheduled
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Commands
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npx wrangler dev # Local dev
|
|
142
|
+
npx wrangler dev --remote # Test prod DOs
|
|
143
|
+
npx wrangler deploy # Deploy + migrations
|
|
144
|
+
npx wrangler deploy --dry-run # Validate only
|
|
145
|
+
npx wrangler durable-objects list # List namespaces
|
|
146
|
+
npx wrangler durable-objects info <namespace> <id> # Object info
|
|
147
|
+
npx wrangler durable-objects delete <namespace> <id> # Delete object
|
|
148
|
+
```
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Durable Objects Gotchas
|
|
2
|
+
|
|
3
|
+
## Limits
|
|
4
|
+
|
|
5
|
+
| Resource | Free | Paid |
|
|
6
|
+
|----------|------|------|
|
|
7
|
+
| Storage per DO | 10GB (SQLite) | 10GB (SQLite) |
|
|
8
|
+
| Total storage | 5GB | Unlimited |
|
|
9
|
+
| DO classes | 100 | 500 |
|
|
10
|
+
| Requests/sec/DO | ~1000 | ~1000 |
|
|
11
|
+
| CPU time | 30s default, 300s max | 30s default, 300s max |
|
|
12
|
+
| WebSocket message | 32MiB | 32MiB |
|
|
13
|
+
| SQL columns | 100 | 100 |
|
|
14
|
+
| SQL statement | 100KB | 100KB |
|
|
15
|
+
| Key+value size | 2MB | 2MB |
|
|
16
|
+
|
|
17
|
+
## Billing Gotchas
|
|
18
|
+
|
|
19
|
+
### Duration Billing Trap
|
|
20
|
+
DOs bill for **wall-clock time** while active, not CPU time. WebSocket open 8 hours = 8 hours duration billing, even if DO processed 50 small messages.
|
|
21
|
+
|
|
22
|
+
**Fix**: Use Hibernatable WebSockets API. DO sleeps while maintaining connections, only wakes (and bills) when messages arrive.
|
|
23
|
+
|
|
24
|
+
### storage.list() on Every Request
|
|
25
|
+
Storage reads are cheap but not free. Calling `storage.list()` or multiple `storage.get()` on every request adds up.
|
|
26
|
+
|
|
27
|
+
**Fix**: Profile actual usage. Options:
|
|
28
|
+
- `storage.get(['key1', 'key2', 'key3'])` - cheapest if only need specific keys
|
|
29
|
+
- `storage.list()` once on wake, cache in memory - cheapest if serving many requests per wake cycle
|
|
30
|
+
- Single `storage.get('allData')` with combined object - cheapest if often need multiple keys together
|
|
31
|
+
|
|
32
|
+
### Alarm Recursion
|
|
33
|
+
Scheduling `setAlarm()` every 5 minutes = 288 wake-ups/day × minimum billable duration. Across thousands of DOs, you're waking them all whether work exists or not.
|
|
34
|
+
|
|
35
|
+
**Fix**: Only schedule alarms when actual work is pending. Check if alarm is needed before setting.
|
|
36
|
+
|
|
37
|
+
### WebSocket Never Closes
|
|
38
|
+
If users close browser tabs without proper disconnect and you don't handle it, connection stays "open" from DO's perspective, preventing hibernation.
|
|
39
|
+
|
|
40
|
+
**Fix**:
|
|
41
|
+
1. Handle `webSocketClose` and `webSocketError` events
|
|
42
|
+
2. Implement heartbeat/ping-pong to detect dead connections
|
|
43
|
+
3. Use Hibernatable WebSockets API properly
|
|
44
|
+
|
|
45
|
+
### Singleton vs Sharding
|
|
46
|
+
Global singleton DO handling all traffic = bottleneck + continuous duration billing (never hibernates).
|
|
47
|
+
|
|
48
|
+
| Design | Cost Pattern |
|
|
49
|
+
|--------|--------------|
|
|
50
|
+
| One global DO | Never hibernates, continuous billing |
|
|
51
|
+
| Per-user DO | Each only wakes for their requests, most hibernate |
|
|
52
|
+
| Per-user-per-hour | Many cold starts, many minimum durations |
|
|
53
|
+
|
|
54
|
+
**Fix**: Use per-entity DOs (per-user, per-room, per-document). They hibernate between activity.
|
|
55
|
+
|
|
56
|
+
### Batching Reads
|
|
57
|
+
Five separate `storage.get()` calls > one `storage.get(['k1','k2','k3','k4','k5'])`. Each operation has overhead.
|
|
58
|
+
|
|
59
|
+
**Fix**: Batch reads/writes. Writes without intervening `await` are automatically coalesced into single atomic transaction.
|
|
60
|
+
|
|
61
|
+
### Hibernation State Loss
|
|
62
|
+
In-memory state is **lost** when DO hibernates or evicts. Waking DO reconstructs from storage.
|
|
63
|
+
|
|
64
|
+
**Fix**:
|
|
65
|
+
1. Store all important state in SQLite storage
|
|
66
|
+
2. Use `blockConcurrencyWhile()` in constructor to load state on wake
|
|
67
|
+
3. Cache in memory for current wake cycle only
|
|
68
|
+
4. Accept every wake is potentially "cold"
|
|
69
|
+
|
|
70
|
+
### Fan-Out Tax
|
|
71
|
+
Event notifying 1,000 DOs = 1,000 DO invocations billed immediately. Queue pattern doesn't reduce invocations but provides retries and batching.
|
|
72
|
+
|
|
73
|
+
**Fix**: For time-sensitive, accept cost. For deferrable, use Queues for retry/dead-letter handling.
|
|
74
|
+
|
|
75
|
+
### Idempotency Key Explosion
|
|
76
|
+
Creating one DO per idempotency key (used once) = millions of single-use DOs that persist until deleted.
|
|
77
|
+
|
|
78
|
+
**Fix**:
|
|
79
|
+
1. Hash idempotency keys into N sharded buckets
|
|
80
|
+
2. Store records as rows in single DO's SQLite table
|
|
81
|
+
3. Implement TTL cleanup via alarms
|
|
82
|
+
4. Consider if KV is sufficient (if strong consistency not needed)
|
|
83
|
+
|
|
84
|
+
### Storage Compaction
|
|
85
|
+
Individual writes billed per-operation. Writing 100 events individually = 100× the write operations vs batching.
|
|
86
|
+
|
|
87
|
+
**Fix**: Batch writes. Multiple `INSERT` statements without intervening `await` coalesce into single transaction.
|
|
88
|
+
|
|
89
|
+
### waitUntil() Behavior
|
|
90
|
+
`ctx.waitUntil()` keeps DO alive (billed) until promises resolve. Waiting for slow external calls = paying for wait time.
|
|
91
|
+
|
|
92
|
+
**Fix**: For true background work, use alarms or Queues instead of `waitUntil()`.
|
|
93
|
+
|
|
94
|
+
### KV vs DO Storage
|
|
95
|
+
For read-heavy, write-rare, eventually-consistent-OK data: **KV is cheaper**.
|
|
96
|
+
|
|
97
|
+
| | KV | DO Storage |
|
|
98
|
+
|-|----|----|
|
|
99
|
+
| Reads | Global edge cache, cheap | Every read hits DO compute |
|
|
100
|
+
| Writes | ~60s propagation | Immediate consistency |
|
|
101
|
+
| Use case | Config, sessions, cache | Read-modify-write, coordination |
|
|
102
|
+
|
|
103
|
+
## Common Issues
|
|
104
|
+
|
|
105
|
+
| Issue | Cause | Fix |
|
|
106
|
+
|-------|-------|-----|
|
|
107
|
+
| DO overloaded (503) | Single DO bottleneck | Shard across DOs with random/deterministic IDs |
|
|
108
|
+
| Storage quota exceeded | Write failures | Upgrade plan or cleanup via alarms |
|
|
109
|
+
| CPU exceeded | Terminated mid-request | Increase `limits.cpu_ms` or chunk work |
|
|
110
|
+
| WebSockets disconnect | Eviction | Use hibernation + reconnection logic |
|
|
111
|
+
| Migration failed | Deploy error | Check tag uniqueness, class names, use `--dry-run` |
|
|
112
|
+
| RPC not found | Old compatibility_date | Update to >= 2024-04-03 or use fetch |
|
|
113
|
+
| One alarm limit | Need multiple timers | Use event queue pattern (store events, single alarm) |
|
|
114
|
+
| Constructor expensive | Slow cold starts | Lazy load in methods, cache after first load |
|
|
115
|
+
|
|
116
|
+
## RPC vs Fetch
|
|
117
|
+
|
|
118
|
+
| | RPC | Fetch |
|
|
119
|
+
|-|-----|-------|
|
|
120
|
+
| Type safety | Full TypeScript support | Manual parsing |
|
|
121
|
+
| Simplicity | Direct method calls | HTTP request/response |
|
|
122
|
+
| Performance | Slightly faster | HTTP overhead |
|
|
123
|
+
| Requirement | compatibility_date >= 2024-04-03 | Always works |
|
|
124
|
+
| Use case | **Default choice** | Legacy, proxying |
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// RPC (recommended)
|
|
128
|
+
const result = await stub.myMethod(arg);
|
|
129
|
+
|
|
130
|
+
// Fetch (legacy)
|
|
131
|
+
const response = await stub.fetch(new Request("http://do/endpoint"));
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Migration Gotchas
|
|
135
|
+
|
|
136
|
+
- Tags must be unique and sequential
|
|
137
|
+
- No rollback mechanism
|
|
138
|
+
- `deleted_classes` **destroys ALL data** permanently
|
|
139
|
+
- Test with `--dry-run` before production deploy
|
|
140
|
+
- Transfers between scripts need coordination
|
|
141
|
+
- Renames preserve data and IDs
|
|
142
|
+
|
|
143
|
+
## Debugging
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npx wrangler dev # Local development
|
|
147
|
+
npx wrangler dev --remote # Test against production DOs
|
|
148
|
+
npx wrangler tail # Stream logs
|
|
149
|
+
npx wrangler durable-objects list
|
|
150
|
+
npx wrangler durable-objects info <namespace> <id>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// Storage diagnostics
|
|
155
|
+
this.ctx.storage.sql.databaseSize // Current storage usage
|
|
156
|
+
cursor.rowsRead // Rows scanned
|
|
157
|
+
cursor.rowsWritten // Rows modified
|
|
158
|
+
```
|