void 0.25.0 → 0.26.0
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/{account-cmd-DcR1ywt8.mjs → account-cmd-CDry3B5V.mjs} +4 -4
- package/dist/{application-analysis-BVsVqc11.mjs → application-analysis-DC2yPvzY.mjs} +1 -1
- package/dist/{application-routing-B7QEjqSR.mjs → application-routing-C9khgLiS.mjs} +1 -1
- package/dist/{auth-CdgZ9uag.mjs → auth-B60QDbjg.mjs} +1 -1
- package/dist/{auth-7wuZ5ob8.mjs → auth-D52yG7hl.mjs} +3 -3
- package/dist/{auth-link-Z6E5DYvM.mjs → auth-link-DjeOptYi.mjs} +4 -4
- package/dist/{auth-router-BdM4avI8.mjs → auth-router-BRxIuqDi.mjs} +5 -5
- package/dist/{build-cmd-CEE7D-hM.mjs → build-cmd-Ce3C8huo.mjs} +4 -4
- package/dist/{cache-TcZnBLtH.mjs → cache-CS8Yxr5a.mjs} +4 -4
- package/dist/{cancel-deploy-CYoZmcmR.mjs → cancel-deploy-mZsD88Xa.mjs} +3 -3
- package/dist/{cf-access-AHKBzmuu.mjs → cf-access-YTlqvAsk.mjs} +1 -1
- package/dist/cli/cli.mjs +40 -40
- package/dist/cli/cloudflare-operation-process.mjs +1 -1
- package/dist/cli/env-schema-probe.d.mts +1 -1
- package/dist/client-BxVixJBI.mjs +2 -0
- package/dist/{client-RsPo8wsP.mjs → client-mC-dFGDD.mjs} +5 -5
- package/dist/{cloudflare-operations-CjaV4lZi.mjs → cloudflare-operations-DYa3coIk.mjs} +1 -1
- package/dist/{cloudflare-operations-BhbK6yTq.mjs → cloudflare-operations-DoQrVHAt.mjs} +1 -1
- package/dist/{connect-DCvUmPXY.mjs → connect-H6EvP62X.mjs} +6 -6
- package/dist/{create-project-eAPgYq35.mjs → create-project-B2kk9Q0o.mjs} +4 -4
- package/dist/{create-project-C734I-Mu.mjs → create-project-Dbb4uiN-.mjs} +1 -1
- package/dist/{db-C07GEdKF.mjs → db-DpCKZto0.mjs} +21 -21
- package/dist/{delete-pKMVsrpJ.mjs → delete-CxlSvsvW.mjs} +6 -6
- package/dist/{deploy-DfsAe-T0.mjs → deploy-CeEbnv4b.mjs} +38 -24
- package/dist/{deploy-BDu1vhSH.mjs → deploy-qmiLE1G1.mjs} +1 -1
- package/dist/{domain-D3uA9ize.mjs → domain-B_9VgOm6.mjs} +6 -6
- package/dist/{email-z3o2cwjH.mjs → email-BokHgjKj.mjs} +6 -6
- package/dist/{env-3Q8-_2IH.mjs → env-BdTc-K2L.mjs} +6 -5
- package/dist/{env-public-BX_r8HR6.d.mts → env-public-DR7ZM6d-.d.mts} +1 -1
- package/dist/{fetch-stream-IvCYKyQL.mjs → fetch-stream-Nm3hkZbT.mjs} +8 -1
- package/dist/{gen-NjwoG9FK.mjs → gen-BhDlTpwW.mjs} +1 -1
- package/dist/gen-qCBwH3fZ.mjs +2 -0
- package/dist/{github-cmd-DquUq-b1.mjs → github-cmd-hhkTzlGS.mjs} +4 -4
- package/dist/{handler-CZ4nAylQ.d.mts → handler-BeK3weD4.d.mts} +1 -1
- package/dist/{help-DRZXC4fj.mjs → help-Bv44e2tm.mjs} +13 -9
- package/dist/help-Dw5f9Xrg.mjs +2 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +30 -17
- package/dist/{info-BmA3_IFZ.mjs → info-DhtTKh65.mjs} +1 -1
- package/dist/{init-DT7yuuyl.mjs → init-BMf4T03q.mjs} +10 -9
- package/dist/{limits-Bq5LG8Id.d.mts → limits-CFsDKQvU.d.mts} +1 -10
- package/dist/{limits-Cjuk2VPm.mjs → limits-D0vD1M9A.mjs} +1 -22
- package/dist/{link-CXOlqh2F.mjs → link-BqYsSS_I.mjs} +5 -5
- package/dist/{list-eEWVJEPn.mjs → list-_kBToO-l.mjs} +7 -6
- package/dist/{live-Chw1eIMv.d.mts → live-iAuEH1hd.d.mts} +1 -1
- package/dist/{local-d1-Df4XpwhW.mjs → local-d1-fGxYrVD6.mjs} +1 -1
- package/dist/login-D7meQg_3.mjs +2 -0
- package/dist/{login-pWQHYVu2.mjs → login-DFuo-RWO.mjs} +4 -4
- package/dist/{logs-DnxVX7TR.mjs → logs-DYgnh6dH.mjs} +5 -5
- package/dist/{node-BkyRWRx8.mjs → node-C6Q6e1Es.mjs} +1 -1
- package/dist/{operator-args-CAVcmwUt.mjs → operator-args-DdY54Bgb.mjs} +1 -1
- package/dist/{operator-auth-CkmTE3rt.mjs → operator-auth-2dsGlTT5.mjs} +4 -8
- package/dist/{operator-client-LmwjxT6q.mjs → operator-client-BFqpp6z0.mjs} +3 -3
- package/dist/{operator-cmd-d-IEKgNk.mjs → operator-cmd-CUGHXrQf.mjs} +27 -9
- package/dist/operator-cmd-DRPvhpjM.mjs +2 -0
- package/dist/{output-BBsUwdyT.mjs → output-BvdaehtE.mjs} +26 -2
- package/dist/pages/client.d.mts +1 -1
- package/dist/pages/index.mjs +1 -1
- package/dist/pages/islands-plugin.mjs +1 -1
- package/dist/pages/protocol.d.mts +2 -2
- package/dist/{plan-NPpwZ_kc.mjs → plan-CJGEeTEI.mjs} +2 -2
- package/dist/plan-COtUvdMA.mjs +2 -0
- package/dist/{platform-args-Bzf8JPc2.mjs → platform-args-DSV2p4nY.mjs} +4 -2
- package/dist/platform-args-Dcy_5b_Y.mjs +2 -0
- package/dist/{platform-auth-config-0V0lA3pF.mjs → platform-auth-config-Bosx7U1h.mjs} +18 -17
- package/dist/{platform-auth-protection-DFeJ-JkK.mjs → platform-auth-protection-DcbL_LW_.mjs} +6 -6
- package/dist/{platform-auth-recovery-ICy4muq6.mjs → platform-auth-recovery-WfTZ8K6o.mjs} +5 -5
- package/dist/{platform-cmd-D7vSWMvV.mjs → platform-cmd-DbKtQb_4.mjs} +45 -20
- package/dist/{platform-cmd-BPySFOew.mjs → platform-cmd-dyFUof0t.mjs} +1 -1
- package/dist/{platform-discovery-DDDUvnqp.mjs → platform-discovery-BC0HCKYt.mjs} +18 -4
- package/dist/{platform-domain-CUdY064e.mjs → platform-domain-Bsq43AJp.mjs} +5 -4
- package/dist/{platform-lifecycle-BaUdTRPU.mjs → platform-lifecycle--5gWmDoI.mjs} +1 -1
- package/dist/{platform-lifecycle-Dm09JNkn.mjs → platform-lifecycle-Cfi0o6xM.mjs} +863 -35
- package/dist/{platform-management-Bp6aoH2C.mjs → platform-management-Bbwf6-Dq.mjs} +1 -1
- package/dist/{platform-management-BSEc_ZeG.mjs → platform-management-BrF9STZp.mjs} +173 -30
- package/dist/{platform-plans-config-B0pOVQyW.mjs → platform-plans-config-DU-8nC2Y.mjs} +2 -2
- package/dist/{platform-recovery-WOZ95Uze.mjs → platform-recovery-DQAqAQYu.mjs} +3 -3
- package/dist/{platform-registry-KfNEF84O.mjs → platform-registry-P387v42A.mjs} +17 -10
- package/dist/prepare-BBknoZI1.mjs +2 -0
- package/dist/{prepare-39EvOhI6.mjs → prepare-BMrRWLwP.mjs} +3 -2
- package/dist/{prepare-DnaKBi--.mjs → prepare-WoUubFKL.mjs} +1 -1
- package/dist/{project-IOjBU8R6.mjs → project-BiaPQ7ur.mjs} +1 -1
- package/dist/{project-BkbfDs0V.mjs → project-DQstYTxO.mjs} +1 -1
- package/dist/{project-cmd-Bs5A392E.mjs → project-cmd-BbH2LptN.mjs} +18 -18
- package/dist/{project-team-ohAWWaew.mjs → project-team-Yiww3-go.mjs} +5 -5
- package/dist/{project-token-CP01Jna6.mjs → project-token-BrtT2NkY.mjs} +4 -4
- package/dist/{project-zero-trust-BzkVgx35.mjs → project-zero-trust-nYJ2V2nc.mjs} +5 -5
- package/dist/{protocol-CjF_iI9X.d.mts → protocol-BX8t2jiw.d.mts} +1 -1
- package/dist/{provision-BEv0k-qz.mjs → provision-DMTPG8Rh.mjs} +1 -1
- package/dist/{provision-Btj1jppf.mjs → provision-VeqAC77A.mjs} +13 -1
- package/dist/{requests-B3PBuGlX.mjs → requests-TIz_jtSu.mjs} +4 -4
- package/dist/{rollback-DDub9GsO.mjs → rollback-dBqICPzj.mjs} +5 -5
- package/dist/{runner-Ol0TNjk6.mjs → runner--2KF6Kua.mjs} +1 -1
- package/dist/{runner-CXA9Fh8h.mjs → runner-voIltZrY.mjs} +1 -1
- package/dist/runtime/ai.d.mts +10 -11
- package/dist/runtime/ai.mjs +13 -18
- package/dist/runtime/client-react.mjs +1 -1
- package/dist/runtime/client-solid.mjs +1 -1
- package/dist/runtime/client-svelte.mjs +1 -1
- package/dist/runtime/client-vue.mjs +1 -1
- package/dist/runtime/client.mjs +1 -1
- package/dist/runtime/env-public.d.mts +1 -1
- package/dist/runtime/fetch-stream.mjs +1 -1
- package/dist/runtime/handler.d.mts +1 -1
- package/dist/runtime/limits.d.mts +2 -2
- package/dist/runtime/limits.mjs +2 -2
- package/dist/runtime/live-client.d.mts +1 -1
- package/dist/runtime/live.d.mts +1 -1
- package/dist/runtime/migration-handler.mjs +33 -7
- package/dist/runtime/routing.d.mts +1 -1
- package/dist/runtime/sandbox.d.mts +1 -1
- package/dist/runtime/sandbox.mjs +12 -22
- package/dist/runtime/validator.d.mts +1 -1
- package/dist/runtime/ws-server.d.mts +1 -1
- package/dist/runtime/ws.d.mts +1 -1
- package/dist/{sandbox-XZAqzFlG.d.mts → sandbox-C5LFoJMf.d.mts} +7 -7
- package/dist/{scan-C7okrLyM.mjs → scan-C8dA7iM4.mjs} +10 -6
- package/dist/{secret-3c72lY1U.mjs → secret-AKnNXNP5.mjs} +6 -6
- package/dist/{subcommand-prompt-Dhom39bd.mjs → subcommand-prompt-B6WS9Aoz.mjs} +1 -1
- package/dist/validate-pna7R3Pv.mjs +2 -0
- package/dist/{validate-ctOrgiS3.mjs → validate-ymhap6M_.mjs} +1 -1
- package/dist/{ws-CL1w7GXU.d.mts → ws-C79j4aED.d.mts} +1 -1
- package/package.json +7 -7
- package/skills/void/SKILL.md +5 -2
- package/skills/void/docs/guide/ai.md +65 -62
- package/skills/void/docs/guide/app-types.md +1 -1
- package/skills/void/docs/guide/database.md +15 -0
- package/skills/void/docs/guide/live.md +2 -0
- package/skills/void/docs/guide/pages-routing/actions-and-forms.md +1 -1
- package/skills/void/docs/guide/platform/installation/credentials.md +2 -2
- package/skills/void/docs/guide/platform/installation/first-deployment.md +5 -1
- package/skills/void/docs/guide/platform/installation/maintenance.md +3 -1
- package/skills/void/docs/guide/platform/installation/setup.md +1 -1
- package/skills/void/docs/guide/platform/installation/uninstall.md +44 -11
- package/skills/void/docs/guide/sandboxes.md +39 -20
- package/skills/void/docs/reference/api/resources.md +11 -14
- package/skills/void/docs/reference/api.md +35 -34
- package/skills/void/docs/reference/cli/auth.md +3 -1
- package/skills/void/docs/reference/cli/platform-config.md +2 -2
- package/skills/void/docs/reference/cli/platform-installation.md +14 -12
- package/skills/void/docs/reference/cli/platform.md +4 -2
- package/skills/void/docs/reference/cli.md +1 -1
- package/skills/void/docs/reference/resource-inference.md +1 -0
- package/dist/client-B4Me-9HE.mjs +0 -2
- package/dist/gen-ById9r2j.mjs +0 -2
- package/dist/help-DPGBwomV.mjs +0 -2
- package/dist/login-C98-sw79.mjs +0 -2
- package/dist/plan-D1Q5rf-r.mjs +0 -2
- package/dist/platform-args-Cq6EjQro.mjs +0 -2
- package/dist/prepare-CnjcTV2s.mjs +0 -2
- package/dist/validate-Dq_L3s0S.mjs +0 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { $ as wrapTextWithPrefix, G as promptTitle, M as getGuide, V as optionText, b as S_RADIO_ACTIVE, d as S_CHECKBOX_SELECTED, i as S_BAR_END, it as wrapAnsi, j as formatInstructionFooter, n as SELECT_INSTRUCTIONS, r as S_BAR, t as MULTISELECT_INSTRUCTIONS, u as S_CHECKBOX_INACTIVE, w as S_STEP_SUBMIT } from "./prompts-6OSVA7DJ.mjs";
|
|
2
|
-
import { c as import_picocolors } from "./output-
|
|
2
|
+
import { c as import_picocolors } from "./output-BvdaehtE.mjs";
|
|
3
3
|
//#region src/cli/subcommand-prompt.ts
|
|
4
4
|
function promptSubcommand(command, subcommands) {
|
|
5
5
|
return new Promise((resolve) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as join } from "./dist-BVqXeR_h.mjs";
|
|
2
2
|
import { n as readJournal, r as stripSqlExt } from "./collect-DiTn9yr6.mjs";
|
|
3
|
-
import { t as DESTRUCTIVE_PRAGMA } from "./plan-
|
|
3
|
+
import { t as DESTRUCTIVE_PRAGMA } from "./plan-CJGEeTEI.mjs";
|
|
4
4
|
import { existsSync, readdirSync } from "node:fs";
|
|
5
5
|
import { tokenizeSql } from "@void/deploy-core";
|
|
6
6
|
//#region src/migrations/validate.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as StandardSchemaV1 } from "./standard-schema-Fo_vCAZh.mjs";
|
|
2
2
|
import { r as AuthUser } from "./auth-DkcFflXV.mjs";
|
|
3
|
-
import { a as CloudEnv } from "./handler-
|
|
3
|
+
import { a as CloudEnv } from "./handler-BeK3weD4.mjs";
|
|
4
4
|
import { WebSocketRouteMap } from "void/routes";
|
|
5
5
|
//#region src/runtime/ws.d.ts
|
|
6
6
|
type MaybePromise<T> = T | Promise<T>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "void",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/voidzero-dev/void.git",
|
|
@@ -339,11 +339,11 @@
|
|
|
339
339
|
"@cloudflare/workers-auth": "0.12.0",
|
|
340
340
|
"@cloudflare/workers-types": "^4.20260702.1",
|
|
341
341
|
"@napi-rs/keyring": "2.1.0",
|
|
342
|
-
"@void/deploy-cloudflare": "0.
|
|
343
|
-
"@void/deploy-core": "0.
|
|
344
|
-
"@void/edge": "0.
|
|
345
|
-
"@void/isr": "0.
|
|
346
|
-
"@void/platform": "0.
|
|
342
|
+
"@void/deploy-cloudflare": "0.26.0",
|
|
343
|
+
"@void/deploy-core": "0.26.0",
|
|
344
|
+
"@void/edge": "0.26.0",
|
|
345
|
+
"@void/isr": "0.26.0",
|
|
346
|
+
"@void/platform": "0.26.0",
|
|
347
347
|
"acorn": "^8.18.0",
|
|
348
348
|
"better-auth": "^1.7.7",
|
|
349
349
|
"better-sqlite3": "^13.0.3",
|
|
@@ -393,7 +393,7 @@
|
|
|
393
393
|
"zod": "^4.6.5"
|
|
394
394
|
},
|
|
395
395
|
"peerDependencies": {
|
|
396
|
-
"@void/md": "0.
|
|
396
|
+
"@void/md": "0.26.0",
|
|
397
397
|
"arktype": ">=2.0.0",
|
|
398
398
|
"valibot": ">=1.0.0-beta.7",
|
|
399
399
|
"vite": "^8.0.0",
|
package/skills/void/SKILL.md
CHANGED
|
@@ -19,6 +19,8 @@ Use `void` and `@void/*` in imports, examples, and package manifests. Follow the
|
|
|
19
19
|
project's existing framework and deployment destination. Deploying to a Void
|
|
20
20
|
platform requires an explicit `void connect <url>` first; never infer a platform
|
|
21
21
|
from old examples, tokens, or project IDs.
|
|
22
|
+
Platform hostnames passed to `void connect` default to HTTPS; include `http://`
|
|
23
|
+
explicitly when connecting to a local HTTP platform.
|
|
22
24
|
For a one-time deployment elsewhere, connect to that platform, then set
|
|
23
25
|
`VOID_API_URL` for the deploy command and pass `--project <slug>`. This preserves
|
|
24
26
|
the app's existing saved destination, including when the target project is new.
|
|
@@ -78,7 +80,8 @@ the app's existing saved destination, including when the target project is new.
|
|
|
78
80
|
- Declare application environment keys in `env.ts` and access them through
|
|
79
81
|
`void/env`; follow `docs/guide/env-vars.md` for local and production values.
|
|
80
82
|
- For platform setup, follow the pages linked from
|
|
81
|
-
`docs/guide/self-hosted-platform.md`.
|
|
83
|
+
`docs/guide/self-hosted-platform.md`. Use `void platform list` to find the
|
|
84
|
+
installation slug for maintenance. For recovery, use its documented resume
|
|
82
85
|
or repair workflow and preserve the installation's credentials and resources.
|
|
83
86
|
- Use `--plan` to review platform changes. If an operation has an uncertain
|
|
84
87
|
outcome, inspect its status and events before retrying.
|
|
@@ -87,4 +90,4 @@ If the user invokes this skill without a task, inspect the app's local structure
|
|
|
87
90
|
configuration, and saved deployment destination, give a brief status, and ask
|
|
88
91
|
what they want to work on.
|
|
89
92
|
|
|
90
|
-
|
|
93
|
+
Await AI and Sandbox operations normally. Usage limits reject with `UsageLimit` from `void/limits`; Void routes and Pages actions automatically return HTTP 429. Optionally catch with `instanceof UsageLimit` for a fallback or custom response. In Pages, prefer the adapter’s `Form` with `useForm()` so action failures remain visible and entered data is preserved. See `docs/guide/ai.md`, `docs/guide/sandboxes.md`, and `docs/guide/pages-routing/actions-and-forms.md`.
|
|
@@ -34,15 +34,14 @@ import { ai } from 'void/ai';
|
|
|
34
34
|
export const POST = defineHandler(async (c) => {
|
|
35
35
|
const { prompt } = await c.req.json();
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
.match({ ok: (result) => c.json(result), limited: (limit) => limit.response() });
|
|
37
|
+
const result = await ai.run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
|
|
38
|
+
messages: [{ role: 'user', content: prompt }],
|
|
39
|
+
});
|
|
40
|
+
return c.json(result);
|
|
42
41
|
});
|
|
43
42
|
```
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
AI methods return promises. Await them to receive the result, or return the promise directly from a handler. Usage limits reject with [`UsageLimit`](#usage-limits); Void route handlers and Pages actions automatically send a structured HTTP 429 when it is left uncaught.
|
|
46
45
|
|
|
47
46
|
You can use any model available through Cloudflare's AI binding, including Workers AI models such as `@cf/meta/llama-3.3-70b-instruct-fp8-fast` and Cloudflare Gateway models such as `google/gemini-2.5-flash` or `openai/gpt-4.1-mini`. The input object must match the selected Cloudflare model's schema. Models that return binary data, such as generated images, are returned as a `Blob` from `ai.run()`.
|
|
48
47
|
|
|
@@ -57,29 +56,23 @@ import { ai } from 'void/ai';
|
|
|
57
56
|
export const POST = defineHandler(async (c) => {
|
|
58
57
|
const { prompt } = await c.req.json();
|
|
59
58
|
|
|
60
|
-
return ai
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})
|
|
64
|
-
.match({ ok: (result) => result, limited: (limit) => limit.response() });
|
|
59
|
+
return ai.stream('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
|
|
60
|
+
messages: [{ role: 'user', content: prompt }],
|
|
61
|
+
});
|
|
65
62
|
});
|
|
66
63
|
```
|
|
67
64
|
|
|
68
|
-
Streaming responses end with a `void:error` SSE event if generation is interrupted after the response starts
|
|
65
|
+
Streaming responses end with a `void:error` SSE event if generation is interrupted after the response starts, or `void:limit` for a usage limit. `fetchStream()` from `void/client` throws `UsageLimit` for limits before or during streaming and `StreamError` for other terminal errors. Catch the error in your UI to stop its generating state, retain partial output, and offer retry. This also applies to streaming provider responses and `ai.run()` with `stream: true`.
|
|
69
66
|
|
|
70
67
|
## Listing Models
|
|
71
68
|
|
|
72
69
|
Use `ai.models()` to list available models:
|
|
73
70
|
|
|
74
71
|
```ts
|
|
75
|
-
const models = await ai
|
|
76
|
-
.models()
|
|
77
|
-
.match({ ok: (result) => result, limited: (limit) => limit.response() });
|
|
72
|
+
const models = await ai.models();
|
|
78
73
|
|
|
79
74
|
// Filter by task
|
|
80
|
-
const textModels = await ai
|
|
81
|
-
.models({ task: 'Text Generation' })
|
|
82
|
-
.match({ ok: (result) => result, limited: (limit) => limit.response() });
|
|
75
|
+
const textModels = await ai.models({ task: 'Text Generation' });
|
|
83
76
|
```
|
|
84
77
|
|
|
85
78
|
## Markdown Conversion
|
|
@@ -87,9 +80,7 @@ const textModels = await ai
|
|
|
87
80
|
Use `ai.toMarkdown()` to convert documents to markdown:
|
|
88
81
|
|
|
89
82
|
```ts
|
|
90
|
-
const result = await ai
|
|
91
|
-
.toMarkdown([{ name: 'document.pdf', blob: pdfBytes }])
|
|
92
|
-
.match({ ok: (result) => result, limited: (limit) => limit.response() });
|
|
83
|
+
const result = await ai.toMarkdown([{ name: 'document.pdf', blob: pdfBytes }]);
|
|
93
84
|
```
|
|
94
85
|
|
|
95
86
|
## Usage Limits
|
|
@@ -100,21 +91,46 @@ requests also use your provider credentials and their billing terms.
|
|
|
100
91
|
|
|
101
92
|
A team platform uses its own Cloudflare account and may apply per-user limits. Ask your administrator about the available allowance.
|
|
102
93
|
|
|
94
|
+
Usage limits reject the operation's promise with `UsageLimit` from `void/limits`. It includes `resource`, `reason`, and an optional `retryAt` timestamp. In Void route handlers and Pages actions, an uncaught limit produces a structured HTTP 429 with retry information. In queues, scheduled jobs, and other server code, it remains a rejected promise.
|
|
95
|
+
|
|
96
|
+
Handling a limit is optional. Use `instanceof UsageLimit` when you want a fallback or a message tailored to your app:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { defineHandler } from 'void';
|
|
100
|
+
import { ai } from 'void/ai';
|
|
101
|
+
import { UsageLimit } from 'void/limits';
|
|
102
|
+
|
|
103
|
+
export const POST = defineHandler(async (c) => {
|
|
104
|
+
const { prompt } = await c.req.json();
|
|
105
|
+
try {
|
|
106
|
+
const result = await ai.run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
|
|
107
|
+
messages: [{ role: 'user', content: prompt }],
|
|
108
|
+
});
|
|
109
|
+
return c.json(result);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (error instanceof UsageLimit) {
|
|
112
|
+
return error.response({ message: 'Try again shortly. Your prompt has been kept.' });
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`error.response()` creates the structured HTTP 429 and accepts an optional custom `message`. Other failures reject normally. When using `fetchStream()`, the same `instanceof UsageLimit` check gives your client access to the message and retry information.
|
|
120
|
+
|
|
103
121
|
## Cloudflare Gateway Models
|
|
104
122
|
|
|
105
123
|
`ai.run()` mirrors Cloudflare's `env.AI.run()` model naming and input schemas. Third-party models use Cloudflare model IDs and Cloudflare-managed credentials.
|
|
106
124
|
|
|
107
125
|
```ts
|
|
108
|
-
const result = await ai
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
})
|
|
117
|
-
.match({ ok: (result) => result, limited: (limit) => limit.response() });
|
|
126
|
+
const result = await ai.run('google/gemini-2.5-flash', {
|
|
127
|
+
contents: [
|
|
128
|
+
{
|
|
129
|
+
role: 'user',
|
|
130
|
+
parts: [{ text: 'Explain Durable Objects in one paragraph.' }],
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
});
|
|
118
134
|
```
|
|
119
135
|
|
|
120
136
|
Pass AI Gateway options as the third argument: `ai.run(model, inputs, { gateway: { skipCache: true } })`.
|
|
@@ -137,16 +153,13 @@ import { ai } from 'void/ai';
|
|
|
137
153
|
export const POST = defineHandler(async (c) => {
|
|
138
154
|
const { prompt } = await c.req.json();
|
|
139
155
|
|
|
140
|
-
return ai
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
},
|
|
148
|
-
})
|
|
149
|
-
.match({ ok: (result) => result, limited: (limit) => limit.response() });
|
|
156
|
+
return ai.provider('openai').fetch('/chat/completions', {
|
|
157
|
+
body: {
|
|
158
|
+
model: 'gpt-4o',
|
|
159
|
+
messages: [{ role: 'user', content: prompt }],
|
|
160
|
+
max_tokens: 512,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
150
163
|
});
|
|
151
164
|
```
|
|
152
165
|
|
|
@@ -164,8 +177,7 @@ const response = await ai
|
|
|
164
177
|
},
|
|
165
178
|
],
|
|
166
179
|
},
|
|
167
|
-
})
|
|
168
|
-
.match({ ok: (result) => result, limited: (limit) => limit.response() });
|
|
180
|
+
});
|
|
169
181
|
```
|
|
170
182
|
|
|
171
183
|
### Custom Providers
|
|
@@ -181,8 +193,7 @@ const response = await ai
|
|
|
181
193
|
})
|
|
182
194
|
.fetch('/v1/respond', {
|
|
183
195
|
body: { prompt: 'Hello' },
|
|
184
|
-
})
|
|
185
|
-
.match({ ok: (result) => result, limited: (limit) => limit.response() });
|
|
196
|
+
});
|
|
186
197
|
```
|
|
187
198
|
|
|
188
199
|
### Image Generation
|
|
@@ -192,9 +203,7 @@ Use `ai.run()` or `ai.image()` for Cloudflare-native image models:
|
|
|
192
203
|
```ts
|
|
193
204
|
export const POST = defineHandler(async (c) => {
|
|
194
205
|
const { prompt } = await c.req.json();
|
|
195
|
-
return ai
|
|
196
|
-
.image('@cf/black-forest-labs/flux-1-schnell', { prompt })
|
|
197
|
-
.match({ ok: (result) => result, limited: (limit) => limit.response() });
|
|
206
|
+
return ai.image('@cf/black-forest-labs/flux-1-schnell', { prompt });
|
|
198
207
|
});
|
|
199
208
|
```
|
|
200
209
|
|
|
@@ -204,16 +213,13 @@ Use `ai.provider().fetch()` for provider-native image APIs. Match the [provider'
|
|
|
204
213
|
export const POST = defineHandler(async (c) => {
|
|
205
214
|
const { prompt } = await c.req.json();
|
|
206
215
|
|
|
207
|
-
return ai
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
},
|
|
215
|
-
})
|
|
216
|
-
.match({ ok: (result) => result, limited: (limit) => limit.response() });
|
|
216
|
+
return ai.provider('openai').fetch('/images/generations', {
|
|
217
|
+
body: {
|
|
218
|
+
model: 'gpt-image-2.5-sunburst',
|
|
219
|
+
prompt,
|
|
220
|
+
size: '1024x1024',
|
|
221
|
+
},
|
|
222
|
+
});
|
|
217
223
|
});
|
|
218
224
|
```
|
|
219
225
|
|
|
@@ -227,10 +233,7 @@ export const POST = defineHandler(async (c) => {
|
|
|
227
233
|
form.set('prompt', String(body.prompt));
|
|
228
234
|
form.set('image', body.image as Blob, 'source.png');
|
|
229
235
|
|
|
230
|
-
return ai
|
|
231
|
-
.provider('openai')
|
|
232
|
-
.fetch('/images/edits', { body: form })
|
|
233
|
-
.match({ ok: (result) => result, limited: (limit) => limit.response() });
|
|
236
|
+
return ai.provider('openai').fetch('/images/edits', { body: form });
|
|
234
237
|
});
|
|
235
238
|
```
|
|
236
239
|
|
|
@@ -45,7 +45,7 @@ Add `voidPlugin()` to the framework's Vite config to get binding inference, type
|
|
|
45
45
|
|
|
46
46
|
Any project that produces static files, whether that is an SPA, a static site, or a plain directory. Void serves the assets without requiring application code.
|
|
47
47
|
|
|
48
|
-
**SPAs** (client-side single-page apps)
|
|
48
|
+
**SPAs** (client-side single-page apps) serve `index.html` with a 200 status for paths that match no file, so client-side routing works out of the box. On a Void platform, missing files under the hashed assets folder (`/assets/`) return 404 instead. Detected when `vite` is a dependency and no backend files exist.
|
|
49
49
|
|
|
50
50
|
**Static sites** (static site generators like VitePress, Docusaurus, or any pre-built directory) produce per-page HTML files. Detected when a known SSG is a dependency, or when using the `--dir` flag.
|
|
51
51
|
|
|
@@ -361,6 +361,21 @@ npm install arktype
|
|
|
361
361
|
|
|
362
362
|
See the [CLI reference](../reference/cli/database.md#database) for connection setup, SQL execution, Studio, exports, and other commands.
|
|
363
363
|
|
|
364
|
+
## Hand-written migrations {#hand-written-migrations}
|
|
365
|
+
|
|
366
|
+
Deploy compares your Drizzle schema with the snapshots in `db/migrations/meta/`. If you have SQL migrations without this metadata, running `void db generate` would create another migration for the same tables.
|
|
367
|
+
|
|
368
|
+
To keep an existing migration history:
|
|
369
|
+
|
|
370
|
+
1. Make your Drizzle schema match the tables your SQL migrations create.
|
|
371
|
+
2. In a scratch copy of the project, remove `db/migrations/` and run `void db generate`.
|
|
372
|
+
3. Copy the generated `meta/` folder into your original migration directory. In `_journal.json`, replace `entries` with an entry for each existing SQL file in order. Use consecutive `idx` values starting at `0`, the file name without `.sql` as `tag`, increasing timestamps as `when`, and the generated entry's `version`. Set `breakpoints` to whether the file uses `--> statement-breakpoint` lines.
|
|
373
|
+
4. Rename the generated snapshot to `<prefix>_snapshot.json`, where `<prefix>` is the part of the last migration tag before its first `_`. For `001_create_users.sql`, use `001_snapshot.json`.
|
|
374
|
+
|
|
375
|
+
Review and commit the metadata, then run `void deploy` again. If no database has applied the SQL yet, you can instead replace it with migrations from `void db generate`.
|
|
376
|
+
|
|
377
|
+
For D1 migrations containing triggers, add a `--> statement-breakpoint` line after each complete SQL statement, including the trigger's closing `END;`. Keep the trigger body together.
|
|
378
|
+
|
|
364
379
|
## Scaffolding
|
|
365
380
|
|
|
366
381
|
The `void gen model` command generates dialect-appropriate `sqliteTable`, `pgTable`, or `mysqlTable` definitions.
|
|
@@ -22,6 +22,8 @@ export const live = defineLiveStream({
|
|
|
22
22
|
});
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
Void enables live streams when a [scanned file](../reference/resource-inference.md#scanned-directories) imports `void/live`, so keep this module in `src/` or another scanned directory. To keep it in an unscanned directory such as `lib/`, add that directory to [`inference.scanDirs`](../reference/resource-inference.md#custom-scan-directories) and list the default directories you still use, because `scanDirs` replaces them.
|
|
26
|
+
|
|
25
27
|
Expose it from a normal route:
|
|
26
28
|
|
|
27
29
|
```ts
|
|
@@ -269,7 +269,7 @@ export const action = defineHandler(async (c) => {
|
|
|
269
269
|
|
|
270
270
|
### Usage limits
|
|
271
271
|
|
|
272
|
-
For [AI](../ai.md) and [Sandbox](../sandboxes.md) actions, return `
|
|
272
|
+
For [AI](../ai.md#usage-limits) and [Sandbox](../sandboxes.md#usage-limits) actions, an uncaught `UsageLimit` automatically produces a structured HTTP 429. To customize the message, catch the error with `instanceof UsageLimit` and return `error.response({ message: 'Try again shortly. Your draft is still editable.' })`. `Form` displays the message and waits until a known retry deadline before allowing resubmission. It keeps the fields editable and never retries automatically.
|
|
273
273
|
|
|
274
274
|
## Authentication and origin checks
|
|
275
275
|
|
|
@@ -8,7 +8,7 @@ Keep one password-manager entry for this platform. Paste the saved values into t
|
|
|
8
8
|
|
|
9
9
|
## Cloudflare API Tokens {#runtime-token-permissions}
|
|
10
10
|
|
|
11
|
-
For a domain installation or Cloudflare Access setup, the installer guides you through creating separate management and runtime tokens using Cloudflare's [API token setup](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/). Its links preselect the permissions and suggest names based on your platform's display name
|
|
11
|
+
For a domain installation or Cloudflare Access setup, the installer guides you through creating separate management and runtime tokens using Cloudflare's [API token setup](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/). Its links preselect the permissions and suggest names based on your platform's display name, but Cloudflare's form cannot preselect a zone: for a domain installation, change the zone policy from **All Domains** to **Specified Domains** and select your zone. If Void will create the zone, keep **All Domains** on both tokens: the zone does not exist yet, and the runtime token needs it to purge cache. Save the tokens in your password manager and paste them into the masked CLI prompts.
|
|
12
12
|
|
|
13
13
|
For workers.dev testing with the default API hostname, browser login can authorize
|
|
14
14
|
infrastructure setup. Void also checks account-wide Access requirements; if the
|
|
@@ -21,7 +21,7 @@ Browser login does not grant AI Gateway access. Include **Account → AI Gateway
|
|
|
21
21
|
|
|
22
22
|
The management token lets your CLI install and maintain the platform. The runtime token is stored as a Worker secret so the platform can deploy apps after you close your terminal. They are separate credentials.
|
|
23
23
|
|
|
24
|
-
Compare each token's form with the permission checklist below
|
|
24
|
+
Compare each token's form with the permission checklist below. The installer saves a management token entered at the prompt in this installation’s encrypted local checkpoint and reuses it on resume. Upgrades, repairs, and other lifecycle maintenance also use that saved management token. During installation, a rejected token can be replaced at the prompt. An explicit `CLOUDFLARE_API_TOKEN` or `CF_API_TOKEN` takes precedence. Keep your password-manager copy for maintenance or another machine; scripts supply it through `CLOUDFLARE_API_TOKEN`.
|
|
25
25
|
|
|
26
26
|
::: details Permissions to select for each token
|
|
27
27
|
|
|
@@ -34,7 +34,11 @@ void project token create --name ci --expires-in 30
|
|
|
34
34
|
|
|
35
35
|
Store the printed `VOID_TOKEN` and `VOID_API_URL` in the CI secret manager, then
|
|
36
36
|
run `void connect "$VOID_API_URL" --no-login` before deploying in each fresh CI
|
|
37
|
-
environment. Project files do not replace this connection step.
|
|
37
|
+
environment. Project files do not replace this connection step. CI cannot answer
|
|
38
|
+
the project prompt, so choose the project before `void deploy`: set `VOID_PROJECT`,
|
|
39
|
+
pass `--project <slug>`, or commit `.void/project.json` from `void project link`.
|
|
40
|
+
New apps ignore `.void` in `.gitignore`; to commit the link, replace that line
|
|
41
|
+
with `.void/*` and `!.void/project.json`. Rotate with `void project token renew <id>` and revoke with
|
|
38
42
|
`void project token revoke <id>`. A human login token is not a CI credential.
|
|
39
43
|
|
|
40
44
|
If Cloudflare Access protects the platform, Access proof and the project
|
|
@@ -6,7 +6,7 @@ outline: deep
|
|
|
6
6
|
|
|
7
7
|
## Resume, repair, recover, and upgrade
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Run `void platform list` to find the installation slug beside its name. Lifecycle commands accept this slug, an installation ID, or a saved connection ID. You can omit the selector when only one installation is configured. With several installations, choose one interactively or pass its slug or ID in CI.
|
|
10
10
|
|
|
11
11
|
### Resume an installation
|
|
12
12
|
|
|
@@ -74,6 +74,8 @@ void platform upgrade [id]
|
|
|
74
74
|
|
|
75
75
|
An upgrade validates the runtime, applies supported pending migrations, deploys the Workers, and checks their health. Existing Worker secrets are preserved.
|
|
76
76
|
|
|
77
|
+
Before starting, Void checks access to the installation's Cloudflare account, Workers, and existing R2 buckets. If credentials are rejected, set `CLOUDFLARE_API_TOKEN` to a management token scoped to that account and retry. This overrides the installation's saved management token.
|
|
78
|
+
|
|
77
79
|
::: details Migration safety, credential rotation, and interrupted upgrades
|
|
78
80
|
|
|
79
81
|
Some upgrades briefly put the platform into maintenance mode. Traffic resumes after the new version passes health checks. If an upgrade is interrupted during maintenance, rerun the same command to complete it.
|
|
@@ -12,7 +12,7 @@ For workers.dev testing, start the interactive install and choose your login met
|
|
|
12
12
|
void platform install --workers-dev
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
For a domain installation or Access setup, Void guides you to create a management API token and paste it into a masked prompt. Browser login can preview DNS, but cannot change it or configure Access. The token-creation link preselects the permissions for your choices
|
|
15
|
+
For a domain installation or Access setup, Void guides you to create a management API token and paste it into a masked prompt. Browser login can preview DNS, but cannot change it or configure Access. The token-creation link preselects the permissions for your choices. When using an existing domain, change the zone policy from **All Domains** to **Specified Domains** and select your zone, as the CLI prompt describes. No shell environment variable is needed for interactive installation.
|
|
16
16
|
|
|
17
17
|
::: details Use an existing management token from the shell
|
|
18
18
|
|
|
@@ -11,7 +11,9 @@ void platform disable [id] --plan
|
|
|
11
11
|
void platform disable [id]
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Disabled platforms reject application traffic while retaining domains and routes. Repair and upgrade preserve that state.
|
|
14
|
+
Disabled platforms reject application traffic while retaining domains and routes. The platform API also rejects requests, so CLI commands that use it, including login and deploy, fail until you enable the platform again. Only the administrator status page at `<api-url>/admin/status` and administrator sign-in stay available. Repair and upgrade preserve that state.
|
|
15
|
+
|
|
16
|
+
Enabling redeploys the platform Workers from the selected runtime, verifies them, and then restores traffic. Pass `--runtime <path>` to enable with a custom platform build:
|
|
15
17
|
|
|
16
18
|
```sh
|
|
17
19
|
void platform enable [id] --plan
|
|
@@ -31,7 +33,9 @@ void platform uninstall [id] --plan
|
|
|
31
33
|
void platform uninstall [id]
|
|
32
34
|
```
|
|
33
35
|
|
|
34
|
-
Uninstall blocks platform traffic, removes Queues that no remaining platform Worker uses, and
|
|
36
|
+
Uninstall blocks platform traffic, removes Queues that no remaining platform Worker uses, and lists the resources left for you to review. Use `--plan` to review deletions and retained resources before making changes. By default, Workers, the Queues they use, KV, R2, Hyperdrive, the dispatch namespace, and AI Gateway remain in your account. Adopted resources, external PostgreSQL, and zones are always retained. Email Routing settings are retained; `--purge-resources` removes only the rules that send mail to the platform. DNS records, routes, custom domains, and Cloudflare Access resources are retained unless you use `--purge-resources`.
|
|
37
|
+
|
|
38
|
+
When uninstall finishes, the record of what was kept stays at `~/.void/platforms/<account-id>/<installation-id>.json`. Without `--purge-data`, the encrypted credentials file `<installation-id>.recovery.enc` next to it is kept too. Delete both once you have finished the manual cleanup below.
|
|
35
39
|
|
|
36
40
|
To also remove eligible data resources owned by the installer:
|
|
37
41
|
|
|
@@ -39,20 +43,49 @@ To also remove eligible data resources owned by the installer:
|
|
|
39
43
|
void platform uninstall [id] --purge-data
|
|
40
44
|
```
|
|
41
45
|
|
|
42
|
-
Even with `--purge-data`, Void retains Workers, the Queues they use, R2, AI Gateway, DNS records, routes, custom domains, Access resources, adopted resources, external PostgreSQL, and zones. Review those in the Cloudflare dashboard if you want to remove them
|
|
46
|
+
Even with `--purge-data`, Void retains Workers, the Queues they use, R2, AI Gateway, DNS records, routes, custom domains, Access resources, adopted resources, external PostgreSQL, and zones. Review those in the Cloudflare dashboard if you want to remove them, or use `--purge-resources`.
|
|
47
|
+
|
|
48
|
+
### Remove everything Void created
|
|
49
|
+
|
|
50
|
+
To delete every Cloudflare resource the installation created:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
void platform uninstall [id] --purge-resources --plan
|
|
54
|
+
void platform uninstall [id] --purge-resources
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`--purge-resources` includes `--purge-data`. It also deletes the platform Workers, Queues, the application Workers left in the dispatch namespace, the R2 bucket and its objects, the AI Gateway, the platform DNS record, Worker routes and custom domains, and the Access applications and service tokens the installer created. The plan lists each resource in deletion order, and everything that stays. If Cloudflare shows that the AI Gateway was created after installation, for example because it was deleted and created again under the same name, it is kept and listed.
|
|
58
|
+
|
|
59
|
+
If the platform sends email, it first deletes the Email Routing rules in the sender domain's zone that send mail to the platform email gateway Worker, and resets such a catch-all rule to drop mail. It leaves Email Routing enabled and keeps the sender domain's MX, SPF, and DKIM records.
|
|
60
|
+
|
|
61
|
+
It never removes zones, adopted resources, external PostgreSQL, other Email Routing settings, Analytics Engine datasets, Access policies or identity providers you chose, or API tokens. Revoke the API tokens you created for the platform afterward.
|
|
62
|
+
|
|
63
|
+
Before making changes, `--purge-resources` checks that no projects remain and that it can read the resources it plans to delete. Delete any projects with `void project delete <name>` first.
|
|
64
|
+
|
|
65
|
+
Set `CLOUDFLARE_API_TOKEN` to a token with the Edit permissions listed by `--plan`. Browser login cannot delete DNS records or Access resources. Access resources in a separate identity account need permissions in that account; email-enabled platforms also need **Zone → Email Routing Rules → Edit** for the sender domain's zone.
|
|
66
|
+
|
|
67
|
+
The read checks cannot verify Edit permissions. If a deletion is denied, uninstall may have already disabled traffic or removed resources. Update the token permissions and rerun the command to resume.
|
|
68
|
+
|
|
69
|
+
If a route, custom domain, DNS record, or Access resource no longer matches what the installation recorded, for example a route that now sends traffic to another Worker, it is kept and listed with the reason, and uninstall continues. Do not run `--purge-resources` while someone else is changing the platform's Workers, routes, or custom domains: a change made after Void checks a resource and before it deletes it is not detected.
|
|
70
|
+
|
|
71
|
+
If Cloudflare refuses a deletion, for example because a Worker you created yourself still sends logs to the platform tail Worker, uninstall stops and names the resource. Fix it, then rerun the same command to continue.
|
|
72
|
+
|
|
73
|
+
If a plain or `--purge-data` uninstall stopped before it finished, rerun it with `--purge-resources` to also delete what it kept. Once an uninstall finishes, use the manual steps below instead.
|
|
43
74
|
|
|
44
75
|
### Remove retained resources
|
|
45
76
|
|
|
46
|
-
Cloudflare refuses to delete a resource while a platform Worker still uses it. In the Cloudflare dashboard, remove what is left in this order, skipping anything already gone:
|
|
77
|
+
After an uninstall without `--purge-resources`, Cloudflare refuses to delete a resource while a platform Worker still uses it. In the Cloudflare dashboard, remove what is left in this order, skipping anything already gone:
|
|
47
78
|
|
|
48
79
|
1. Delete the platform DNS record, Worker routes, and custom domains.
|
|
49
|
-
2.
|
|
50
|
-
3.
|
|
51
|
-
4. Delete the platform
|
|
52
|
-
5. Delete the
|
|
53
|
-
6. Delete the
|
|
54
|
-
7.
|
|
55
|
-
8. Delete the
|
|
80
|
+
2. If the platform sends email, open **Email Routing** for the sender domain's zone. Delete the routing rules and any catch-all rule that send mail to the platform email gateway Worker. Then remove the sender subdomain from Email Routing, or disable Email Routing if you enabled it only for the platform.
|
|
81
|
+
3. Open each platform Queue and remove the platform API Worker from its consumers.
|
|
82
|
+
4. Delete the platform Workers, except the tail Worker (its name ends in `-tail`).
|
|
83
|
+
5. Delete the platform Queues.
|
|
84
|
+
6. Delete the application Workers in the dispatch namespace, then delete the namespace.
|
|
85
|
+
7. Delete the tail Worker. Cloudflare refuses while other Workers still send logs to it.
|
|
86
|
+
8. Delete the platform KV namespaces and Hyperdrive configuration.
|
|
87
|
+
9. Empty the R2 bucket, then delete it.
|
|
88
|
+
10. Delete the AI Gateway.
|
|
56
89
|
|
|
57
90
|
If installation configured Cloudflare Access, the uninstall plan lists its recorded applications and service tokens with their ownership and IDs. Review them in Zero Trust after uninstall. Remove installer-created resources only when nothing else uses them; resources connected from an existing company setup remain under their owner's control.
|
|
58
91
|
|
|
@@ -18,13 +18,10 @@ import { getSandbox } from 'void/sandbox';
|
|
|
18
18
|
|
|
19
19
|
export const POST = defineHandler(async (c) => {
|
|
20
20
|
const sandbox = await getSandbox('default');
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
stdout: new TextDecoder().decode(result.stdout),
|
|
26
|
-
}),
|
|
27
|
-
limited: (limit) => limit.response({ message: 'Execution is temporarily unavailable.' }),
|
|
21
|
+
const result = await sandbox.run(['node', '--version']);
|
|
22
|
+
return c.json({
|
|
23
|
+
exitCode: result.exitCode,
|
|
24
|
+
stdout: new TextDecoder().decode(result.stdout),
|
|
28
25
|
});
|
|
29
26
|
});
|
|
30
27
|
```
|
|
@@ -42,33 +39,55 @@ const sandbox = await getSandbox(`user-${user.id}`, {
|
|
|
42
39
|
inactivityTimeoutMs: 10 * 60 * 1000,
|
|
43
40
|
enableInternet: false,
|
|
44
41
|
});
|
|
45
|
-
const
|
|
46
|
-
.
|
|
47
|
-
|
|
48
|
-
})
|
|
49
|
-
.match({
|
|
50
|
-
ok: (result) => Response.json({ exitCode: result.exitCode }),
|
|
51
|
-
limited: (limit) => limit.response(),
|
|
52
|
-
});
|
|
42
|
+
const result = await sandbox.run(['node', '--version'], {
|
|
43
|
+
signal: AbortSignal.timeout(5_000),
|
|
44
|
+
});
|
|
53
45
|
```
|
|
54
46
|
|
|
55
47
|
Commands take an executable and arguments as an array. For shell syntax, explicitly run `['sh', '-c', command]`. Execution options include `cwd` (default `/workspace`), `env`, `user`, `signal`, `pty`, `stdin`, `stdout`, and `stderr`.
|
|
56
48
|
|
|
57
|
-
`run()`
|
|
49
|
+
Sandbox operations return promises. `run()` resolves with `stdout` and `stderr` as `ArrayBuffer`s and the command's exit code. Usage limits reject with [`UsageLimit`](#usage-limits); other failures reject normally.
|
|
58
50
|
|
|
59
|
-
`exec()`
|
|
51
|
+
`exec()` resolves with a process. Await `process.output()` to collect its output and exit code, or read the streams and await `process.exitCode`. Waiting for completion keeps a long command active during pauses in its output; unattended background commands can stop when the Sandbox becomes idle. A runtime limit can reject completion after a command has successfully started.
|
|
60
52
|
|
|
61
53
|
Use `stdin: 'pipe'` for a writable input stream. `kill(signal?)` stops a process, and `resize(cols, rows)` resizes its terminal.
|
|
62
54
|
|
|
63
|
-
File operations live under `sandbox.files`: `readFile`, `writeFile`, `stat`, `lstat`, `readDirectory`, `mkdir`, `rename`, and `remove`.
|
|
55
|
+
File operations live under `sandbox.files`: `readFile`, `writeFile`, `stat`, `lstat`, `readDirectory`, `mkdir`, `rename`, and `remove`. Await `readFile()` to receive a streaming `Response`, then use `.text()`, `.arrayBuffer()`, or `.body`. `writeFile()` accepts text, binary data, or a byte stream. Relative file paths require an explicit `cwd` option.
|
|
64
56
|
|
|
65
|
-
To reach a server inside the container,
|
|
57
|
+
To reach a server inside the container, await `sandbox.fetch(port, new Request(url))`. `sandbox.running()` checks whether the container is running; `sandbox.destroy()` stops it.
|
|
66
58
|
|
|
67
59
|
`getSandbox()` options include `inactivityTimeoutMs` (default ten minutes, maximum six hours), `enableInternet` (default `false`), and string `labels`. Internet access and labels take effect on the next container start. `binding` selects a custom native binding; managed platforms use the configured binding.
|
|
68
60
|
|
|
69
61
|
For code that runs on both deployment targets, use `getSandbox()`. Direct access through `c.env.SANDBOX` is available only on local and native Cloudflare deployments.
|
|
70
62
|
|
|
71
|
-
|
|
63
|
+
## Usage limits
|
|
64
|
+
|
|
65
|
+
Sandbox limits reject with `SandboxLimitError`, which extends `UsageLimit` from `void/limits`. The error includes `resource: 'sandbox'` and a `reason` of `concurrency` or `runtime_budget`. Void route handlers and Pages actions automatically send a structured HTTP 429 when the error is left uncaught.
|
|
66
|
+
|
|
67
|
+
Use `instanceof UsageLimit` to optionally handle both AI and Sandbox limits, or `instanceof SandboxLimitError` for Sandbox-specific handling:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { defineHandler } from 'void';
|
|
71
|
+
import { UsageLimit } from 'void/limits';
|
|
72
|
+
import { getSandbox } from 'void/sandbox';
|
|
73
|
+
|
|
74
|
+
export const POST = defineHandler(async (c) => {
|
|
75
|
+
const sandbox = await getSandbox('default');
|
|
76
|
+
try {
|
|
77
|
+
const result = await sandbox.run(['node', 'task.mjs']);
|
|
78
|
+
return c.json({ exitCode: result.exitCode });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error instanceof UsageLimit) {
|
|
81
|
+
return error.response({
|
|
82
|
+
message: 'Execution is temporarily unavailable. Try again shortly.',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Keep user input available so it can be retried. Checking `running()`, stopping a process, and `destroy()` remain available after a usage limit.
|
|
72
91
|
|
|
73
92
|
## State persistence
|
|
74
93
|
|