void 0.10.8 → 0.10.10
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/{auth-cmd-DlgNwByu.mjs → auth-cmd-Cw1edJdZ.mjs} +2 -2
- package/dist/{build-cmd-Br8qL0rA.mjs → build-cmd-C6kNDJBv.mjs} +1 -1
- package/dist/{cache-wH-mP8UE.mjs → cache-DIUSnIjJ.mjs} +1 -1
- package/dist/{cancel-deploy-BEBOEgtu.mjs → cancel-deploy-BosnzEHC.mjs} +1 -1
- package/dist/cli/cli.mjs +77 -28
- package/dist/{client-Cj96iiBH.mjs → client-C9FG6Vzc.mjs} +8 -0
- package/dist/{config-1twldYCW.mjs → config-BcAeIPe3.mjs} +6 -0
- package/dist/{create-project-DD9n8Ho-.mjs → create-project-CjYX23M_.mjs} +1 -1
- package/dist/{db-DjKE2-A-.mjs → db-BvKV34IK.mjs} +4 -4
- package/dist/{delete-D2kr3Kmk.mjs → delete-CThKLXfi.mjs} +1 -1
- package/dist/{deploy-C4PbkFyE.mjs → deploy-B02JyKP9.mjs} +59 -16
- package/dist/{domain-_luIsM_B.mjs → domain-DMrBabBQ.mjs} +1 -1
- package/dist/{env-CO5XAS9t.mjs → env-BfG7O71F.mjs} +1 -1
- package/dist/env-raw-Cx8ElDdj.mjs +58 -0
- package/dist/{gen-Cf79J4aw.mjs → gen-Bee261SV.mjs} +1 -1
- package/dist/{github-cmd-B6OX9c7d.mjs → github-cmd-DfeFEasr.mjs} +101 -2
- package/dist/{headers-BwvFGhkx.mjs → headers-CTAjX-UO.mjs} +1 -1
- package/dist/index.d.mts +13 -1
- package/dist/index.mjs +70 -31
- package/dist/{init-KirOzVDs.mjs → init-xfwW7cLp.mjs} +4 -4
- package/dist/{link-CUmiosyb.mjs → link-Co136MEs.mjs} +2 -2
- package/dist/{list-3GEw7b6m.mjs → list-BKDjA3M5.mjs} +1 -1
- package/dist/{login-DJReaT_Q.mjs → login-CrOTnR_6.mjs} +1 -1
- package/dist/{logs-D-rQ56Lq.mjs → logs-DlAg25ww.mjs} +1 -1
- package/dist/{mcp-D7yc0dXY.mjs → mcp-BNjMGMB0.mjs} +1 -1
- package/dist/{node-yFFk626c.mjs → node-CnE-SZCk.mjs} +1 -1
- package/dist/pages/index.mjs +2 -2
- package/dist/{prepare-C_cVurhP.mjs → prepare-BRyC4TCC.mjs} +1 -1
- package/dist/{project-cmd-DnU7u9QF.mjs → project-cmd-B2bcKefD.mjs} +8 -8
- package/dist/{rollback-Yh7bCKob.mjs → rollback-9dfmB-YZ.mjs} +1 -1
- package/dist/runtime/ai.mjs +113 -15
- package/dist/runtime/env-public.mjs +9 -4
- package/dist/runtime/env.d.mts +14 -1
- package/dist/runtime/env.mjs +24 -10
- package/dist/runtime/isr-cache.d.mts +207 -0
- package/dist/runtime/isr-cache.mjs +523 -0
- package/dist/runtime/isr.d.mts +4 -3
- package/dist/runtime/isr.mjs +44 -5
- package/dist/runtime/live.mjs +1 -1
- package/dist/runtime/sandbox.mjs +1 -1
- package/dist/{secret-u7FRvg8d.mjs → secret-DeMnMcV0.mjs} +1 -1
- package/package.json +3 -2
- package/schema.json +11 -0
- package/skills/void/docs/integrations/cloudflare.md +107 -0
- package/skills/void/docs/node_modules/void/{AGENTS.md → CLAUDE.md} +14 -0
- package/skills/void/docs/reference/cli.md +23 -0
- package/dist/env-raw-CoS20LHP.mjs +0 -32
|
@@ -285,3 +285,110 @@ That's it. The Cloudflare Vite plugin produces a complete build output with a me
|
|
|
285
285
|
### Local development
|
|
286
286
|
|
|
287
287
|
`pnpm dev` continues to work as before -- Miniflare creates local instances of all bindings regardless of the IDs in your `wrangler.jsonc`. Your real resource IDs are only used when you run `wrangler deploy`.
|
|
288
|
+
|
|
289
|
+
### AI (self-host)
|
|
290
|
+
|
|
291
|
+
`void/ai` works on your own Cloudflare account, along two paths:
|
|
292
|
+
|
|
293
|
+
- **Workers AI** (`ai.run`, `ai.stream`, `ai.image`) works out of the box. When your app imports `void/ai`, `vite build` infers that you need AI and adds a Workers AI binding (`env.AI`) to the generated `wrangler.json` automatically -- you do **not** add it to `wrangler.jsonc`.
|
|
294
|
+
- **Provider models** (`ai.provider("openai").fetch(...)`) route through _your own_ Cloudflare AI Gateway. Set its id in `void.json` and add the provider's API key as a Worker secret.
|
|
295
|
+
|
|
296
|
+
```jsonc
|
|
297
|
+
// void.json
|
|
298
|
+
{
|
|
299
|
+
"ai": {
|
|
300
|
+
"gateway": "my-gateway", // an AI Gateway in YOUR Cloudflare account
|
|
301
|
+
},
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# provider API key, stored as a Worker secret (never committed)
|
|
307
|
+
wrangler secret put OPENAI_API_KEY
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
// routes/chat.ts
|
|
312
|
+
import { defineHandler } from 'void';
|
|
313
|
+
import { ai } from 'void/ai';
|
|
314
|
+
|
|
315
|
+
export const POST = defineHandler(async (c) => {
|
|
316
|
+
// Workers AI -- uses env.AI directly
|
|
317
|
+
const summary = await ai.run('@cf/meta/llama-3.1-8b-instruct', {
|
|
318
|
+
prompt: 'Summarize the changelog.',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Provider model -- routes through your "my-gateway" AI Gateway,
|
|
322
|
+
// authed with the OPENAI_API_KEY secret above
|
|
323
|
+
const res = await ai.provider('openai').fetch('chat/completions', {
|
|
324
|
+
method: 'POST',
|
|
325
|
+
body: JSON.stringify({ model: 'gpt-4o-mini', messages: [] }),
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return c.json({ summary, provider: await res.json() });
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`ai.gateway` is required for `ai.provider().fetch()` -- without it, that call returns a `501`. No AI traffic or provider secret passes through Void's shared proxy: requests go directly to _your own_ Cloudflare AI Gateway, authenticated with provider secrets from _your_ Worker's environment. (The request and that secret are of course still sent onward to the AI Gateway and the upstream provider you call.)
|
|
333
|
+
|
|
334
|
+
Notes:
|
|
335
|
+
|
|
336
|
+
- **No cross-tenant usage metering.** On the Void platform, AI calls are metered and billed through a shared proxy. Self-hosted, there is no metering -- you get your own [Cloudflare AI Gateway analytics](https://developers.cloudflare.com/ai-gateway/) instead.
|
|
337
|
+
- **The runtime reads the `env.AI` binding by name** -- a custom-named root AI binding is not supported.
|
|
338
|
+
- **`void dev` has no local Workers AI emulation.** In development, AI still routes through Void, so `void dev` AI requires `void auth login` even when you deploy self-hosted.
|
|
339
|
+
|
|
340
|
+
### ISR (self-host)
|
|
341
|
+
|
|
342
|
+
[Revalidation (ISR)](../guide/edge/revalidation.md) works self-hosted, but the cache KV is **not** auto-injected (a KV binding needs a real namespace id). Create a namespace and bind it as `ISR_CACHE`:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
wrangler kv namespace create ISR_CACHE
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
```jsonc
|
|
349
|
+
// wrangler.jsonc
|
|
350
|
+
{
|
|
351
|
+
"kv_namespaces": [
|
|
352
|
+
{
|
|
353
|
+
"binding": "ISR_CACHE",
|
|
354
|
+
"id": "<your-namespace-id>",
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Configure revalidation exactly as on the platform -- globally or per-path in `void.json`:
|
|
361
|
+
|
|
362
|
+
```jsonc
|
|
363
|
+
// void.json
|
|
364
|
+
{
|
|
365
|
+
"routing": {
|
|
366
|
+
"revalidate": { "/blog/*": 3600, "*": 60 },
|
|
367
|
+
},
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
...or per page with an exported `revalidate` literal in a `.server.ts` companion (Pages mode):
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
// pages/blog/[slug].server.ts
|
|
375
|
+
export const revalidate = 3600; // seconds
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
On-demand purges work through `revalidate()`:
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
import { revalidate } from 'void/isr';
|
|
382
|
+
|
|
383
|
+
await revalidate({ paths: ['/blog/hello'] });
|
|
384
|
+
// or purge every ISR page:
|
|
385
|
+
await revalidate({ all: true });
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Self-hosted, `revalidate()` purges this worker's own KV entries (global, authoritative) plus the current colo's edge cache.
|
|
389
|
+
|
|
390
|
+
Limits (the single-worker cache ladder can't do everything the platform's dispatch layer does):
|
|
391
|
+
|
|
392
|
+
- **No fleet-wide / cross-colo edge purge.** `revalidate()` clears KV globally and the _local_ colo's edge cache; other colos keep serving their edge copy until it expires (bounded by the response's `s-maxage`), then re-render on the next edge miss.
|
|
393
|
+
- **The pages-protocol JSON variant is not served from a cold colo's KV.** A colo that hasn't rendered the HTML yet re-renders the JSON live; the JSON edge cache is warmed only as a side effect of the HTML render path.
|
|
394
|
+
- **The warm cache is dropped on every redeploy.** Each `vite build` bakes a fresh deployment id into the cache keys, so cached HTML never outlives the hashed assets it references -- the first request after a deploy is a cold render.
|
|
@@ -89,6 +89,14 @@ src/
|
|
|
89
89
|
- **Drizzle-first database**: Schema is defined in `db/schema.ts` (Drizzle table definitions). `void/db` exports a Drizzle D1 instance. `@schema` is an auto-configured alias for `db/schema.ts`. Migrations live in `db/migrations/`. CLI commands: `void db push` (prototype), `void db generate` (create migration files), `void db migrate` (apply locally). `drizzle-orm` and `drizzle-kit` ship with void.
|
|
90
90
|
- **Journal coherence invariant**: `db/migrations/*.sql` and `db/migrations/meta/_journal.json` must stay in sync — every `.sql` file has a matching journal entry (by `tag`) and vice versa. `assertJournalCoherence` in `src/migrations/validate.ts` is the gate, called from `deploy`, `db status`, `db migrate`, and `db reset`. The two CLI commands that mutate migration state — `void gen migration` and `void db rename-migrations` — are responsible for keeping the journal coherent themselves: the first appends a new entry when writing a file (failing loud if the journal is missing — run `drizzle-kit generate` first), the second rewrites `tag` values alongside the file renames and also renames the matching `meta/<prefix>_snapshot.json` (with byte-level rollback on failure). `gen migration` additionally copies the previous `meta/<idx:04d>_snapshot.json` forward to the new idx so drizzle-kit's drop/rollback tooling keeps working on mixed hand-written + drizzle projects; projects without prior snapshots are skipped silently. Drift will fail loudly before touching any remote DB. Motivated by [void-sdk/void#6](https://github.com/void-sdk/void/issues/6), where an orphan scaffolded `.sql` file half-applied against D1 and wedged the deploy.
|
|
91
91
|
|
|
92
|
+
### Self-Host AI + ISR
|
|
93
|
+
|
|
94
|
+
Two features need extra wiring on a self-hosted `vite build && wrangler deploy` (no Void platform proxy/dispatch in front of the worker). Both are inert on managed `void deploy`.
|
|
95
|
+
|
|
96
|
+
- **`ai.gateway` config + `__VOID_AI_GATEWAY` var** — `void.json`'s `ai.gateway` (validated in `config.ts` as `ai?: { gateway?: string }`; non-empty string, `additionalProperties: false`) names a Cloudflare AI Gateway in the user's own account. `mergeBindings` (`index.ts`): (1) injects `{ ai: { binding: 'AI' } }`, but only when `shouldBindWorkersAi()` is true — the app infers `needsAI`, `command === 'build'`, and neither the resolved root wrangler config nor the result already declares an `ai` binding (so it never clobbers a user's own `ai` binding); (2) emits `vars.__VOID_AI_GATEWAY = config.ai.gateway` whenever `config.ai?.gateway` is set. At runtime, `runtime/ai.ts` `resolveBackend()` selects the direct backend (Priority 3) when `env.AI` is present and no `__VOID_PROXY`/`__VOID_TOKEN` exists. `ai.run/stream/image` hit Workers AI directly; `ai.provider().fetch()` routes through the user's own gateway via `directAi.gateway(__VOID_AI_GATEWAY).getUrl(provider)` with the provider key from the user's own secret (`resolveProviderKey` → `env[PROVIDER_KEY_MAP[provider]]`), returning HTTP 501 if `__VOID_AI_GATEWAY` is unset. No cross-tenant metering on this path. The runtime reads `env.AI` **by name** — a custom-named root AI binding is not honored.
|
|
97
|
+
- **Internal-binding leak check permits exactly `__VOID_AI_GATEWAY`** — the integration guard (`test/integration/kitchen-sink-build.test.ts`) that asserts no `__VOID_*` var reaches `dist/ssr/wrangler.json` explicitly excludes `__VOID_AI_GATEWAY`, because it is a legitimate, non-credential runtime var (unlike the credential keys in `INTERNAL_ENV_KEYS`, which are stripped).
|
|
98
|
+
- **`ISR_CACHE` self-host requirement** — self-host ISR (`runtime/isr-cache.ts`, `runtime/isr.ts`) reads `env.ISR_CACHE` (a `KVNamespace`) + `env.__VOID_ISR_DEPLOYMENT_ID`. Unlike D1/KV/R2, `ISR_CACHE` is **not** auto-injected (a KV binding needs a real namespace id), so the user must create the namespace and declare it as `ISR_CACHE` in their root wrangler config. `routing.revalidate` (void.json) and per-page `export const revalidate` are merged by `collectRevalidate` (`isr-config.ts`) and baked as `__VOID_ISR_REVALIDATE`. On-demand `revalidate()` on self-host purges the worker's own KV (global) plus the local-colo `caches.default` edge variants only.
|
|
99
|
+
|
|
92
100
|
### Package Exports
|
|
93
101
|
|
|
94
102
|
| Subpath | What |
|
|
@@ -203,3 +211,9 @@ Agent instructions use versioned markers (`<!--injected-by-void-v0.0.1-->` / `<!
|
|
|
203
211
|
| `test/unit/plugin-node-target.test.ts` | Node target plugin — binding guard, plugin creation (5 tests) |
|
|
204
212
|
| `test/integration/node-target.test.ts` | Node app playground — dev server + production build (5 tests) |
|
|
205
213
|
| `test/integration/queue-playground.test.ts` | Queue send → consumer → KV round-trip (1 test) |
|
|
214
|
+
|
|
215
|
+
## Pitfalls / Things That Broke
|
|
216
|
+
|
|
217
|
+
- **`__VOID_ISR_DEPLOYMENT_ID` must be a compile-time literal.** `router/compile.ts` stringifies `crypto.randomUUID()` once at build time (`export const __VOID_ISR_DEPLOYMENT_ID = "..."`, exactly like `__VOID_BUILD_KEY`) so every isolate of a deployment shares one id. A bare runtime `crypto.randomUUID()` per-isolate in the emitted source would mint a fresh id per isolate, so the edge-cache key (`handleIsr`'s `deploymentId` option) and the KV key (`env.__VOID_ISR_DEPLOYMENT_ID`, also used by the `revalidate()` purge path) would disagree — breaking cache reads and on-demand purge.
|
|
218
|
+
- **Self-host ISR requires a user-declared `ISR_CACHE` KV.** There is no `id: 'local'` injection for it: a placeholder id is fine for miniflare-backed dev but breaks a real `wrangler deploy`, which rejects an unknown namespace id. The user creates the namespace and binds it as `ISR_CACHE`; `readIsrCache`/`writeIsrCache` fail open to a live render when it is absent.
|
|
219
|
+
- **Workers AI binding injection is `command === 'build'`-gated.** `shouldBindWorkersAi()` only injects `{ ai: { binding: 'AI' } }` on build, because dev/miniflare has no local Workers AI emulation — `void dev` AI still routes through the Void proxy (P2 `__VOID_TOKEN` path) and needs `void auth login`.
|
|
@@ -700,6 +700,29 @@ void github status my-app
|
|
|
700
700
|
|
|
701
701
|
**Project resolution** follows the same order as deploy: positional / `--project`, `VOID_PROJECT`, linked project (`.void/project.json`).
|
|
702
702
|
|
|
703
|
+
### `void github disconnect`
|
|
704
|
+
|
|
705
|
+
```
|
|
706
|
+
void github disconnect [project]
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
Disconnect a project from its GitHub repository, stopping automatic deploys. Any in-flight builds for the project are cancelled (their deploy tokens are revoked) before the connection is removed. If the project has no connection, it reports that and exits successfully. To point a project at a different repository, disconnect first, then run `void github connect`.
|
|
710
|
+
|
|
711
|
+
You are asked to confirm before anything is removed. Pass `--yes` to skip the prompt; `--yes` is **required** in a non-interactive shell (CI), where there is no prompt to answer.
|
|
712
|
+
|
|
713
|
+
**Options**
|
|
714
|
+
|
|
715
|
+
| Flag | Description |
|
|
716
|
+
| ------------------ | ----------------------------------------------------------------- |
|
|
717
|
+
| `--project <name>` | Project name (alias for the positional argument) |
|
|
718
|
+
| `--yes` | Skip the confirmation prompt (required in non-interactive shells) |
|
|
719
|
+
|
|
720
|
+
```
|
|
721
|
+
void github disconnect my-app --yes
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
**Project resolution** follows the same order as deploy: positional / `--project`, `VOID_PROJECT`, linked project (`.void/project.json`).
|
|
725
|
+
|
|
703
726
|
## Build
|
|
704
727
|
|
|
705
728
|
Inspect Deploy-on-GitHub builds.
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
//#region src/runtime/env-raw.ts
|
|
3
|
-
/**
|
|
4
|
-
* Raw runtime env resolution — framework-internal only.
|
|
5
|
-
*
|
|
6
|
-
* This module is NOT exported in package.json, so it cannot be imported
|
|
7
|
-
* via "void/..." subpaths. Only framework modules using relative imports
|
|
8
|
-
* (isr.ts, ai.ts, env.ts) can access it.
|
|
9
|
-
*/
|
|
10
|
-
/** Prefixes for platform-internal bindings that must be hidden from user code. */
|
|
11
|
-
const INTERNAL_BINDING_PREFIXES = ["__VOID_", "__PROJECT_"];
|
|
12
|
-
const envContext = new AsyncLocalStorage();
|
|
13
|
-
let cloudflareEnv;
|
|
14
|
-
if (typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers") import("cloudflare:workers").then((mod) => {
|
|
15
|
-
cloudflareEnv = asEnv(mod.env) ?? void 0;
|
|
16
|
-
}).catch(() => {});
|
|
17
|
-
function asEnv(value) {
|
|
18
|
-
if (typeof value !== "object" || value === null) return null;
|
|
19
|
-
if ("then" in value && typeof value.then === "function") return null;
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
function getNuxtEnv() {
|
|
23
|
-
return asEnv(globalThis.__env__);
|
|
24
|
-
}
|
|
25
|
-
/** Resolve raw env from AsyncLocalStorage, Nuxt globals, or CF module env. */
|
|
26
|
-
function getRawRuntimeEnv() {
|
|
27
|
-
return envContext.getStore() ?? getNuxtEnv() ?? cloudflareEnv ?? (() => {
|
|
28
|
-
throw new Error("env: Cloudflare env is unavailable. Use void runtime bindings inside Nuxt/SvelteKit/Analog server request handlers or Worker handlers.");
|
|
29
|
-
})();
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
32
|
-
export { getRawRuntimeEnv as i, asEnv as n, envContext as r, INTERNAL_BINDING_PREFIXES as t };
|