void 0.10.7 → 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.
Files changed (47) hide show
  1. package/dist/{auth-cmd-DlgNwByu.mjs → auth-cmd-Cw1edJdZ.mjs} +2 -2
  2. package/dist/{build-cmd-Br8qL0rA.mjs → build-cmd-C6kNDJBv.mjs} +1 -1
  3. package/dist/{cache-wH-mP8UE.mjs → cache-DIUSnIjJ.mjs} +1 -1
  4. package/dist/{cancel-deploy-BEBOEgtu.mjs → cancel-deploy-BosnzEHC.mjs} +1 -1
  5. package/dist/cli/cli.mjs +77 -28
  6. package/dist/{client-Cj96iiBH.mjs → client-C9FG6Vzc.mjs} +8 -0
  7. package/dist/{config-1twldYCW.mjs → config-BcAeIPe3.mjs} +6 -0
  8. package/dist/{create-project-DD9n8Ho-.mjs → create-project-CjYX23M_.mjs} +1 -1
  9. package/dist/{db-DjKE2-A-.mjs → db-BvKV34IK.mjs} +4 -4
  10. package/dist/{delete-D2kr3Kmk.mjs → delete-CThKLXfi.mjs} +1 -1
  11. package/dist/{deploy-C4PbkFyE.mjs → deploy-B02JyKP9.mjs} +59 -16
  12. package/dist/{domain-_luIsM_B.mjs → domain-DMrBabBQ.mjs} +1 -1
  13. package/dist/{env-CO5XAS9t.mjs → env-BfG7O71F.mjs} +1 -1
  14. package/dist/env-raw-Cx8ElDdj.mjs +58 -0
  15. package/dist/{gen-Cf79J4aw.mjs → gen-Bee261SV.mjs} +1 -1
  16. package/dist/{github-cmd-DnqxyOsb.mjs → github-cmd-DfeFEasr.mjs} +178 -45
  17. package/dist/{headers-BwvFGhkx.mjs → headers-CTAjX-UO.mjs} +1 -1
  18. package/dist/index.d.mts +13 -1
  19. package/dist/index.mjs +70 -31
  20. package/dist/{init-KirOzVDs.mjs → init-xfwW7cLp.mjs} +4 -4
  21. package/dist/{link-CUmiosyb.mjs → link-Co136MEs.mjs} +2 -2
  22. package/dist/{list-3GEw7b6m.mjs → list-BKDjA3M5.mjs} +1 -1
  23. package/dist/{login-DJReaT_Q.mjs → login-CrOTnR_6.mjs} +1 -1
  24. package/dist/{logs-D-rQ56Lq.mjs → logs-DlAg25ww.mjs} +1 -1
  25. package/dist/{mcp-D7yc0dXY.mjs → mcp-BNjMGMB0.mjs} +1 -1
  26. package/dist/{node-yFFk626c.mjs → node-CnE-SZCk.mjs} +1 -1
  27. package/dist/pages/index.mjs +2 -2
  28. package/dist/{prepare-C_cVurhP.mjs → prepare-BRyC4TCC.mjs} +1 -1
  29. package/dist/{project-cmd-DnU7u9QF.mjs → project-cmd-B2bcKefD.mjs} +8 -8
  30. package/dist/{rollback-Yh7bCKob.mjs → rollback-9dfmB-YZ.mjs} +1 -1
  31. package/dist/runtime/ai.mjs +113 -15
  32. package/dist/runtime/env-public.mjs +9 -4
  33. package/dist/runtime/env.d.mts +14 -1
  34. package/dist/runtime/env.mjs +24 -10
  35. package/dist/runtime/isr-cache.d.mts +207 -0
  36. package/dist/runtime/isr-cache.mjs +523 -0
  37. package/dist/runtime/isr.d.mts +4 -3
  38. package/dist/runtime/isr.mjs +44 -5
  39. package/dist/runtime/live.mjs +1 -1
  40. package/dist/runtime/sandbox.mjs +1 -1
  41. package/dist/{secret-u7FRvg8d.mjs → secret-DeMnMcV0.mjs} +1 -1
  42. package/package.json +3 -2
  43. package/schema.json +11 -0
  44. package/skills/void/docs/integrations/cloudflare.md +107 -0
  45. package/skills/void/docs/node_modules/void/{AGENTS.md → CLAUDE.md} +14 -0
  46. package/skills/void/docs/reference/cli.md +29 -2
  47. package/dist/env-raw-CoS20LHP.mjs +0 -32
@@ -1,5 +1,5 @@
1
1
  import { a as join, n as dirname } from "./pathe.M-eThtNZ-CQzLbt4c.mjs";
2
- import { S as writeProjectConfig, f as getToken, n as PlatformClient } from "./client-Cj96iiBH.mjs";
2
+ import { S as writeProjectConfig, f as getToken, n as PlatformClient } from "./client-C9FG6Vzc.mjs";
3
3
  import { existsSync, readFileSync, readdirSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { createInterface } from "node:readline";
@@ -1,6 +1,6 @@
1
1
  import { a as join } from "./pathe.M-eThtNZ-CQzLbt4c.mjs";
2
2
  import { c as loadVoidAuthConfig, s as findVoidAuthConfig } from "./scan-i7Yz54fv.mjs";
3
- import { f as readConfig } from "./config-1twldYCW.mjs";
3
+ import { f as readConfig } from "./config-BcAeIPe3.mjs";
4
4
  import { a as resolveVoidAuthMigrationSchema } from "./route-types-CfKfhbIg.mjs";
5
5
  import { n as applySqliteAuthMigrations, t as applyPgAuthMigrations } from "./auth-migrations-BTZ-ATvQ.mjs";
6
6
  import { getMiniflareDatabasePath } from "./runner-h272wcPj.mjs";
@@ -1,8 +1,8 @@
1
1
  import { a as join } from "../pathe.M-eThtNZ-CQzLbt4c.mjs";
2
2
  import { r as resolvePagesDir } from "../project-paths-tpdR1mJR.mjs";
3
- import { f as readConfig } from "../config-1twldYCW.mjs";
3
+ import { f as readConfig } from "../config-BcAeIPe3.mjs";
4
4
  import { a as resolveNamedLayout, i as resolveLayoutIds, n as resolveEffectiveLayoutChain, r as resolveLayoutChain, t as scanPages } from "../scan-Dp_Gyzs3.mjs";
5
- import { i as parseRedirectsFile } from "../headers-BwvFGhkx.mjs";
5
+ import { i as parseRedirectsFile } from "../headers-CTAjX-UO.mjs";
6
6
  import { renderBodyAttrs, renderHeadToString, renderHtmlAttrs, resolveHead } from "./head.mjs";
7
7
  import { serializePageData } from "./serialize.mjs";
8
8
  //#region src/pages/manifest.ts
@@ -3,7 +3,7 @@ import { D as outro, E as note, T as log, a as cliTitle, w as intro } from "./ag
3
3
  import { i as resolveProjectPaths } from "./project-paths-tpdR1mJR.mjs";
4
4
  import { c as loadVoidAuthConfig, r as scanRoutes, s as findVoidAuthConfig } from "./scan-i7Yz54fv.mjs";
5
5
  import { n as ensureProjectTsconfig, r as writeFileIfChanged } from "./project-tsconfig-D9uSVVpA.mjs";
6
- import { c as getDatabaseDialect, f as readConfig } from "./config-1twldYCW.mjs";
6
+ import { c as getDatabaseDialect, f as readConfig } from "./config-BcAeIPe3.mjs";
7
7
  import { i as inferProjectBindings, n as FRAMEWORK_SCAN_DIRS, r as detectFramework } from "./plugin-inference-CJxi_fWI.mjs";
8
8
  import { i as generateVoidAuthDrizzleSchema, n as generateQueueTypes, r as generateVoidAuthDatabaseModule, s as generateDbDts, t as generateRouteTypes } from "./route-types-CfKfhbIg.mjs";
9
9
  import { t as discoverSchema } from "./discover-BuVVSAum.mjs";
@@ -1,5 +1,5 @@
1
1
  import { D as outro, T as log, a as cliTitle, f as tablePrefix, s as dim, u as import_picocolors, w as intro } from "./agents-Bmr5tFFb.mjs";
2
- import { d as getSiteDomain, f as getToken, n as PlatformClient } from "./client-Cj96iiBH.mjs";
2
+ import { d as getSiteDomain, f as getToken, n as PlatformClient } from "./client-C9FG6Vzc.mjs";
3
3
  //#region src/cli/projects.ts
4
4
  async function runProjectsCommand(root) {
5
5
  console.log();
@@ -33,32 +33,32 @@ async function runProjectsCommand(root) {
33
33
  //#region src/cli/project-cmd.ts
34
34
  async function runProjectCommand(root, args) {
35
35
  if (args.subcommand === "status") {
36
- const { runListCommand } = await import("./list-3GEw7b6m.mjs");
36
+ const { runListCommand } = await import("./list-BKDjA3M5.mjs");
37
37
  return runListCommand(root, args.name || void 0);
38
38
  }
39
39
  if (args.subcommand === "link") {
40
- const { runLink } = await import("./link-CUmiosyb.mjs");
40
+ const { runLink } = await import("./link-Co136MEs.mjs");
41
41
  return runLink(root, args.name || void 0);
42
42
  }
43
43
  if (args.subcommand === "list") return runProjectsCommand(root);
44
44
  if (args.subcommand === "logs") {
45
- const { runLogsCommand } = await import("./logs-D-rQ56Lq.mjs");
45
+ const { runLogsCommand } = await import("./logs-DlAg25ww.mjs");
46
46
  return runLogsCommand(root, args.filter, args.range, args.deployment || void 0, args.level || void 0);
47
47
  }
48
48
  if (args.subcommand === "rollback") {
49
- const { runRollbackCommand } = await import("./rollback-Yh7bCKob.mjs");
49
+ const { runRollbackCommand } = await import("./rollback-9dfmB-YZ.mjs");
50
50
  return runRollbackCommand(root, args.deployId || void 0);
51
51
  }
52
52
  if (args.subcommand === "cancel") {
53
- const { runCancelDeployCommand } = await import("./cancel-deploy-BEBOEgtu.mjs");
53
+ const { runCancelDeployCommand } = await import("./cancel-deploy-BosnzEHC.mjs");
54
54
  return runCancelDeployCommand(root, args.deployId || void 0);
55
55
  }
56
56
  if (args.subcommand === "delete") {
57
- const { runDeleteCommand } = await import("./delete-D2kr3Kmk.mjs");
57
+ const { runDeleteCommand } = await import("./delete-CThKLXfi.mjs");
58
58
  return runDeleteCommand(root, args.name || void 0, { force: args.force });
59
59
  }
60
60
  if (args.subcommand === "purge-cache") {
61
- const { runPurgeCacheCommand } = await import("./cache-wH-mP8UE.mjs");
61
+ const { runPurgeCacheCommand } = await import("./cache-DIUSnIjJ.mjs");
62
62
  return runPurgeCacheCommand(root, args.projectSlug || void 0);
63
63
  }
64
64
  }
@@ -1,5 +1,5 @@
1
1
  import { D as outro, S as confirm, T as log, a as cliTitle, c as ensureUtc, j as isCancel, k as select, o as createSpinner, s as dim, u as import_picocolors, w as intro, x as cancel } from "./agents-Bmr5tFFb.mjs";
2
- import { b as readProjectConfig, f as getToken, n as PlatformClient } from "./client-Cj96iiBH.mjs";
2
+ import { b as readProjectConfig, f as getToken, n as PlatformClient } from "./client-C9FG6Vzc.mjs";
3
3
  //#region src/cli/rollback.ts
4
4
  async function runRollbackCommand(root, deployId) {
5
5
  console.log();
@@ -1,5 +1,5 @@
1
1
  import { t as cfAccessHeaders } from "../cf-access-Bqw81xAf.mjs";
2
- import { i as getRawRuntimeEnv } from "../env-raw-CoS20LHP.mjs";
2
+ import { a as getRawRuntimeEnv } from "../env-raw-Cx8ElDdj.mjs";
3
3
  //#region src/runtime/ai-types.ts
4
4
  /** Maps provider prefix to expected env var name for the API key */
5
5
  const PROVIDER_KEY_MAP = {
@@ -29,6 +29,44 @@ const PROVIDER_KEY_MAP = {
29
29
  const PROXY_URL = "https://proxy.void.cloud/ai";
30
30
  const STAGING_PROXY_URL = "https://proxy.staging.void.cloud/ai";
31
31
  const VOID_FILE_MARKER = "__voidFile";
32
+ const PROVIDER_AUTH_DEFAULTS = {
33
+ "google-ai-studio": {
34
+ header: "x-goog-api-key",
35
+ prefix: ""
36
+ },
37
+ anthropic: {
38
+ header: "x-api-key",
39
+ prefix: ""
40
+ },
41
+ elevenlabs: {
42
+ header: "xi-api-key",
43
+ prefix: ""
44
+ },
45
+ deepgram: {
46
+ header: "authorization",
47
+ prefix: "Token "
48
+ },
49
+ fal: {
50
+ header: "authorization",
51
+ prefix: "Key "
52
+ },
53
+ cartesia: {
54
+ header: "x-api-key",
55
+ prefix: ""
56
+ },
57
+ baseten: {
58
+ header: "authorization",
59
+ prefix: "Api-Key "
60
+ },
61
+ ideogram: {
62
+ header: "Api-Key",
63
+ prefix: ""
64
+ },
65
+ parallel: {
66
+ header: "x-api-key",
67
+ prefix: ""
68
+ }
69
+ };
32
70
  function hasStreamFlag(value) {
33
71
  return typeof value === "object" && value !== null && "stream" in value;
34
72
  }
@@ -203,6 +241,41 @@ function getProxyUrl(runtimeEnv) {
203
241
  if (typeof custom === "string" && custom.length > 0) return custom + "/ai";
204
242
  return runtimeEnv.__VOID_STAGING ? STAGING_PROXY_URL : PROXY_URL;
205
243
  }
244
+ function providerAuthDefaults(provider) {
245
+ return PROVIDER_AUTH_DEFAULTS[provider] ?? {
246
+ header: "authorization",
247
+ prefix: "Bearer "
248
+ };
249
+ }
250
+ function gatewayEndpointUrl(baseUrl, endpoint) {
251
+ return `${baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl}/${endpoint.startsWith("/") ? endpoint.slice(1) : endpoint}`;
252
+ }
253
+ function isUnsafeProviderPath(path) {
254
+ if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(path) || path.includes("\\")) return true;
255
+ const pathname = path.split(/[?#]/, 1)[0] ?? "";
256
+ for (const segment of pathname.split("/")) try {
257
+ if (decodeURIComponent(segment) === "..") return true;
258
+ } catch {
259
+ return true;
260
+ }
261
+ return false;
262
+ }
263
+ /**
264
+ * Build a real outgoing HTTP body for the user's own AI Gateway (P3 self-host).
265
+ *
266
+ * Unlike `serializeProviderBody`/`providerRequestBody` (which build the proxy
267
+ * envelope decoded on the proxy side), this emits an actual `BodyInit` +
268
+ * content-type for a direct `fetch` to the gateway.
269
+ */
270
+ function toDirectBody(body) {
271
+ if (body === void 0 || body === null) return { body: void 0 };
272
+ if (typeof body === "string") return { body };
273
+ if (body instanceof ReadableStream || body instanceof FormData || body instanceof URLSearchParams || body instanceof Blob || body instanceof ArrayBuffer || isBinaryView(body)) return { body };
274
+ return {
275
+ body: JSON.stringify(body),
276
+ contentType: "application/json"
277
+ };
278
+ }
206
279
  function resolveBackend() {
207
280
  const runtimeEnv = getRawRuntimeEnv();
208
281
  const proxy = runtimeEnv.__VOID_PROXY;
@@ -211,26 +284,14 @@ function resolveBackend() {
211
284
  const projectId = runtimeEnv.__VOID_PROJECT_ID;
212
285
  if (typeof token === "string" && typeof projectId === "string") return makeHttpBackend(token, projectId, runtimeEnv);
213
286
  const directAi = runtimeEnv.AI;
214
- if (directAi) return {
215
- run: directAi.run.bind(directAi),
216
- async image(model, inputs, options) {
217
- const { runOptions } = splitImageOptions(options);
218
- return responseFromResult(await directAi.run(model, inputs, runOptions));
219
- },
220
- providerFetch: unsupportedProviderFetch,
221
- models: directAi.models.bind(directAi),
222
- toMarkdown: directAi.toMarkdown?.bind(directAi) ?? unsupported("toMarkdown")
223
- };
224
- throw new Error("ai: Not available. Run `void auth login` and `void project link` first.");
287
+ if (directAi) return makeDirectBackend(directAi, runtimeEnv, runtimeEnv.__VOID_AI_GATEWAY);
288
+ throw new Error("ai: Not available. Run `void auth login` and `void project link` first, or bind Workers AI (env.AI) for self-hosted deploys.");
225
289
  }
226
290
  function unsupported(method) {
227
291
  return () => {
228
292
  throw new Error(`ai: ai.${method}() is not supported in this environment.`);
229
293
  };
230
294
  }
231
- function unsupportedProviderFetch() {
232
- throw new Error("ai: ai.provider().fetch() is not supported in this environment.");
233
- }
234
295
  async function providerRequestBody(provider, path, init, options, runtimeEnv) {
235
296
  return {
236
297
  provider,
@@ -370,6 +431,43 @@ function makeHttpBackend(token, projectId, runtimeEnv) {
370
431
  }
371
432
  };
372
433
  }
434
+ function makeDirectBackend(directAi, runtimeEnv, gatewayId) {
435
+ return {
436
+ run: directAi.run.bind(directAi),
437
+ async image(model, inputs, options) {
438
+ const { runOptions } = splitImageOptions(options);
439
+ return responseFromResult(await directAi.run(model, inputs, runOptions));
440
+ },
441
+ async providerFetch(provider, path, init, options) {
442
+ if (!gatewayId) return Response.json({ error: "ai: ai.provider().fetch() needs an AI Gateway when self-hosting. Set \"ai\": { \"gateway\": \"<id>\" } in void.json." }, { status: 501 });
443
+ if (isUnsafeProviderPath(path)) return Response.json({ error: `ai: provider path '${path}' must be relative.` }, { status: 400 });
444
+ let providerKey;
445
+ try {
446
+ providerKey = resolveProviderKey(provider, runtimeEnv, options);
447
+ } catch (e) {
448
+ return Response.json({ error: e.message }, { status: 400 });
449
+ }
450
+ let baseUrl;
451
+ try {
452
+ baseUrl = await directAi.gateway(gatewayId).getUrl(provider);
453
+ } catch (e) {
454
+ return Response.json({ error: `ai: gateway '${gatewayId}': ${e.message}` }, { status: 502 });
455
+ }
456
+ const { body, contentType } = toDirectBody(init?.body);
457
+ const headers = new Headers(init?.headers);
458
+ const defaults = providerAuthDefaults(provider);
459
+ headers.set(options?.apiKeyHeader ?? defaults.header, `${options?.apiKeyPrefix ?? defaults.prefix}${providerKey}`);
460
+ if (contentType && !headers.has("content-type")) headers.set("content-type", contentType);
461
+ return fetch(gatewayEndpointUrl(baseUrl, path), {
462
+ method: init?.method ?? "POST",
463
+ headers,
464
+ body
465
+ });
466
+ },
467
+ models: directAi.models.bind(directAi),
468
+ toMarkdown: directAi.toMarkdown?.bind(directAi) ?? unsupported("toMarkdown")
469
+ };
470
+ }
373
471
  let _backend;
374
472
  function backend() {
375
473
  return _backend ??= resolveBackend();
@@ -1,5 +1,5 @@
1
- import { i as getRawRuntimeEnv } from "../env-raw-CoS20LHP.mjs";
2
- import { getRuntimeEnv } from "./env.mjs";
1
+ import { a as getRawRuntimeEnv } from "../env-raw-Cx8ElDdj.mjs";
2
+ import { getRuntimeEnv, isInternalBinding } from "./env.mjs";
3
3
  import { boolean, email, getSecretOverride, json, number, oneOf, string, url } from "./env-helpers.mjs";
4
4
  //#region src/shared/env-mask.ts
5
5
  /**
@@ -177,7 +177,10 @@ async function _validateEnvOnce(envValues) {
177
177
  validatedCache.set(cacheKey, cache);
178
178
  cacheList.push(cache);
179
179
  }
180
- for (const [key, value] of report.values) if (!cache.has(key)) cache.set(key, value);
180
+ for (const [key, value] of report.values) {
181
+ if (isInternalBinding(key, cacheKey)) continue;
182
+ if (!cache.has(key)) cache.set(key, value);
183
+ }
181
184
  } catch {}
182
185
  bootValidationState = { ok: true };
183
186
  return;
@@ -254,11 +257,13 @@ var EnvValidationError = class extends Error {
254
257
  }
255
258
  };
256
259
  function readKey(key) {
260
+ const rawEnv = getRawRuntimeEnv();
261
+ if (isInternalBinding(key, rawEnv)) return;
257
262
  const rawValue = getRuntimeEnv()[key];
258
263
  if (!registeredSchema) return rawValue;
259
264
  const validator = registeredSchema[key];
260
265
  if (!validator) return rawValue;
261
- const cacheKey = getRawRuntimeEnv();
266
+ const cacheKey = rawEnv;
262
267
  let cache = validatedCache.get(cacheKey);
263
268
  if (!cache) {
264
269
  cache = /* @__PURE__ */ new Map();
@@ -5,9 +5,22 @@ type RuntimeEnv = Record<string, unknown>;
5
5
  //#endregion
6
6
  //#region src/runtime/env.d.ts
7
7
  declare function withRuntimeEnv<T>(env: unknown, fn: () => T): T;
8
+ /**
9
+ * Shared internal-binding predicate. A binding is framework-internal — and must
10
+ * never be exposed to user code — when it carries an internal prefix
11
+ * (`__VOID_`, `__PROJECT_`; always hidden) or is an exact-name internal (e.g.
12
+ * `ISR_CACHE`; hidden ONLY while ISR is active for the isolate). `rawEnv` is the
13
+ * unfiltered runtime env, consulted via {@link isIsrActive} to gate the
14
+ * exact-name check so a non-ISR app's own `ISR_CACHE` binding stays visible —
15
+ * matching the build-time `isr` gate on the generated `c.env` filter.
16
+ *
17
+ * Single source of truth for the runtime `env` filter here and the public
18
+ * `void/env` proxy (`env-public.ts`).
19
+ */
20
+ declare function isInternalBinding(name: string, rawEnv: RuntimeEnv): boolean;
8
21
  /** Returns the runtime env with internal platform bindings hidden. */
9
22
  declare function getRuntimeEnv(): RuntimeEnv;
10
23
  declare function getRuntimeBinding<T = unknown>(name: string): T | undefined;
11
24
  declare function requireRuntimeBinding<T = unknown>(name: string): T;
12
25
  //#endregion
13
- export { type RuntimeEnv, getRuntimeBinding, getRuntimeEnv, requireRuntimeBinding, withRuntimeEnv };
26
+ export { type RuntimeEnv, getRuntimeBinding, getRuntimeEnv, isInternalBinding, requireRuntimeBinding, withRuntimeEnv };
@@ -1,4 +1,4 @@
1
- import { i as getRawRuntimeEnv, n as asEnv, r as envContext, t as INTERNAL_BINDING_PREFIXES } from "../env-raw-CoS20LHP.mjs";
1
+ import { a as getRawRuntimeEnv, i as envContext, n as INTERNAL_BINDING_PREFIXES, o as isIsrActive, r as asEnv, t as INTERNAL_BINDING_EXACT_NAMES } from "../env-raw-Cx8ElDdj.mjs";
2
2
  //#region src/runtime/env.ts
3
3
  /**
4
4
  * Public runtime env API — exposed via "void/_env".
@@ -12,24 +12,37 @@ function withRuntimeEnv(env, fn) {
12
12
  if (!resolved) return fn();
13
13
  return envContext.run(resolved, fn);
14
14
  }
15
- function isInternalBinding(name) {
16
- return INTERNAL_BINDING_PREFIXES.some((p) => name.startsWith(p));
15
+ /**
16
+ * Shared internal-binding predicate. A binding is framework-internal — and must
17
+ * never be exposed to user code — when it carries an internal prefix
18
+ * (`__VOID_`, `__PROJECT_`; always hidden) or is an exact-name internal (e.g.
19
+ * `ISR_CACHE`; hidden ONLY while ISR is active for the isolate). `rawEnv` is the
20
+ * unfiltered runtime env, consulted via {@link isIsrActive} to gate the
21
+ * exact-name check so a non-ISR app's own `ISR_CACHE` binding stays visible —
22
+ * matching the build-time `isr` gate on the generated `c.env` filter.
23
+ *
24
+ * Single source of truth for the runtime `env` filter here and the public
25
+ * `void/env` proxy (`env-public.ts`).
26
+ */
27
+ function isInternalBinding(name, rawEnv) {
28
+ if (INTERNAL_BINDING_PREFIXES.some((p) => name.startsWith(p))) return true;
29
+ return isIsrActive(rawEnv) && INTERNAL_BINDING_EXACT_NAMES.includes(name);
17
30
  }
18
31
  function filterEnvProxy(env) {
19
32
  return new Proxy(env, {
20
33
  get(target, prop, receiver) {
21
- if (typeof prop === "string" && isInternalBinding(prop)) return;
34
+ if (typeof prop === "string" && isInternalBinding(prop, target)) return;
22
35
  return Reflect.get(target, prop, receiver);
23
36
  },
24
37
  has(target, prop) {
25
- if (typeof prop === "string" && isInternalBinding(prop)) return false;
38
+ if (typeof prop === "string" && isInternalBinding(prop, target)) return false;
26
39
  return Reflect.has(target, prop);
27
40
  },
28
41
  ownKeys(target) {
29
- return Reflect.ownKeys(target).filter((key) => typeof key !== "string" || !isInternalBinding(key));
42
+ return Reflect.ownKeys(target).filter((key) => typeof key !== "string" || !isInternalBinding(key, target));
30
43
  },
31
44
  getOwnPropertyDescriptor(target, prop) {
32
- if (typeof prop === "string" && isInternalBinding(prop)) return;
45
+ if (typeof prop === "string" && isInternalBinding(prop, target)) return;
33
46
  return Reflect.getOwnPropertyDescriptor(target, prop);
34
47
  }
35
48
  });
@@ -39,8 +52,9 @@ function getRuntimeEnv() {
39
52
  return filterEnvProxy(getRawRuntimeEnv());
40
53
  }
41
54
  function getRuntimeBinding(name) {
42
- if (isInternalBinding(name)) return;
43
- return getRawRuntimeEnv()[name];
55
+ const raw = getRawRuntimeEnv();
56
+ if (isInternalBinding(name, raw)) return;
57
+ return raw[name];
44
58
  }
45
59
  function requireRuntimeBinding(name) {
46
60
  const value = getRuntimeBinding(name);
@@ -48,4 +62,4 @@ function requireRuntimeBinding(name) {
48
62
  throw new Error(`env: Missing Cloudflare binding '${name}'.`);
49
63
  }
50
64
  //#endregion
51
- export { getRuntimeBinding, getRuntimeEnv, requireRuntimeBinding, withRuntimeEnv };
65
+ export { getRuntimeBinding, getRuntimeEnv, isInternalBinding, requireRuntimeBinding, withRuntimeEnv };
@@ -0,0 +1,207 @@
1
+ //#region src/runtime/isr-cache.d.ts
2
+ /**
3
+ * Single-tenant ISR (incremental static regeneration) cache ladder for the
4
+ * self-hosted Void worker (`vite build && wrangler deploy`).
5
+ *
6
+ * On managed Void, ISR lives in the dispatch worker that sits IN FRONT of the
7
+ * user worker. Self-host has no dispatch worker, so this module ports that
8
+ * caching ladder INTO the user worker, collapsed from multi-tenant to a single
9
+ * deployment:
10
+ *
11
+ * - No `projectId`, no cross-tenant metering.
12
+ * - No `#rw=` rewrite variant, no query-string variant fanout, no variant cap.
13
+ * - The deployment id is baked at build time as a compile-time literal and
14
+ * surfaced on `env.__VOID_ISR_DEPLOYMENT_ID` (Task 7). The KV helpers read
15
+ * it from `env` (matching the `revalidate()` purge path in `runtime/isr.ts`),
16
+ * while `handleIsr` uses the `deploymentId` option for the edge-cache keys —
17
+ * the two are the same value.
18
+ *
19
+ * Fail-open contract: every `env.ISR_CACHE` (KV) and `caches.default` operation
20
+ * is wrapped so a misconfigured or erroring cache degrades to a live render,
21
+ * never a 500. Read/hit-path failures bail (caller renders live); write-path
22
+ * failures are swallowed.
23
+ *
24
+ * Worker-only: no `lib.dom` types — globals come from `@cloudflare/workers-types`.
25
+ *
26
+ * Ported from `void-platform/packages/dispatch` (`isr.ts`, `index.ts`,
27
+ * `pattern.ts`) and `void-platform/packages/isr-shared`.
28
+ */
29
+ /**
30
+ * Minimal env surface this module needs. Both fields are optional so a worker
31
+ * with no ISR binding (or a caller that only knows the deployment id) still
32
+ * typechecks and fails open at runtime.
33
+ */
34
+ interface IsrCacheEnv {
35
+ ISR_CACHE?: KVNamespace;
36
+ __VOID_ISR_DEPLOYMENT_ID?: string;
37
+ }
38
+ type IsrCacheEntry = Readonly<{
39
+ body: ReadableStream;
40
+ status: number;
41
+ headers: Record<string, string>;
42
+ createdAt: number;
43
+ ttl: number;
44
+ }>;
45
+ type IsrCacheMetadata = Readonly<{
46
+ /** HTTP status code */s: number; /** createdAt (ms) */
47
+ c: number; /** TTL (seconds) */
48
+ t: number;
49
+ }>;
50
+ /** Renders a live response for a request (worker dispatch + asset serving). */
51
+ type RenderFn = (request: Request) => Promise<Response>;
52
+ type HandleIsrOptions = Readonly<{
53
+ revalidate: number | Record<string, number> | undefined;
54
+ deploymentId: string;
55
+ }>;
56
+ /**
57
+ * Build the normalized path segment for an ISR-cached page.
58
+ * Root path `/` maps to `__index__` to avoid trailing-slash ambiguity.
59
+ */
60
+ declare function normalizeIsrPath(pathname: string): string;
61
+ /**
62
+ * Inverse of `normalizeIsrPath`: turn a normalized segment back into a
63
+ * pathname starting with `/`.
64
+ */
65
+ declare function denormalizeIsrPath(normalized: string): string;
66
+ /**
67
+ * Canonicalize an inbound pathname to the form used inside edge cache URLs.
68
+ * Equivalent to `denormalizeIsrPath(normalizeIsrPath(pathname))` — strips any
69
+ * trailing slash except for root.
70
+ */
71
+ declare function canonicalIsrPathname(pathname: string): string;
72
+ /**
73
+ * KV cache key for an ISR-cached page. Deployment- AND host-scoped: cached HTML
74
+ * cannot outlive the hashed assets of the deployment that produced it (a fresh
75
+ * `wrangler deploy` mints a new deployment id → cold cache), and one host's
76
+ * cached content never bleeds into another host served by the same worker
77
+ * (custom domain + workers.dev, apex + www, etc.).
78
+ */
79
+ declare function isrKey(deploymentId: string, host: string, pathname: string): string;
80
+ /**
81
+ * Host-registry key. Every host that has written cached content registers itself
82
+ * under `void-isr-hosts/<id>/<host>` (value `''`) so a purge with no request host
83
+ * (cron / queue `revalidate({ paths })`) can enumerate the hosts to purge.
84
+ * Scoped by deployment id so registrations from an old deploy do not leak.
85
+ */
86
+ declare function hostRegistryKey(deploymentId: string, host: string): string;
87
+ /**
88
+ * Edge-cache (`caches.default`) key for the HTML variant. The public URL stays
89
+ * clean; this internal key carries the deployment id so a new deploy never
90
+ * reads HTML produced by an older deploy.
91
+ */
92
+ declare function htmlEdgeKey(host: string, pathname: string, id: string): string;
93
+ /**
94
+ * Edge-cache key for the JSON (pages-protocol) variant. Separated from HTML by
95
+ * the `&__void=json` marker.
96
+ */
97
+ declare function jsonEdgeKey(host: string, pathname: string, id: string): string;
98
+ /**
99
+ * Match a pattern against a pathname using LITERAL semantics — no named
100
+ * captures. Two wildcards are special: `*` matches any characters INCLUDING `/`
101
+ * (a splat), and a WHOLE-segment `:name` (preceded by `/` or start, followed by
102
+ * `/` or end) matches exactly ONE path segment (`[^/]+`). A mid-segment literal
103
+ * `:` (e.g. `/api/v1:health`) is left alone. This is a strict SUPERSET of the
104
+ * old `*`-only matcher (old `*`-only manifests match identically). Malformed
105
+ * patterns yield `false` rather than throwing.
106
+ */
107
+ declare function matchLiteralPattern(pattern: string, pathname: string): boolean;
108
+ /**
109
+ * Pack response headers + body into a single ArrayBuffer.
110
+ * Format: [4 bytes uint32 LE header length][UTF-8 header JSON][raw body]
111
+ */
112
+ declare function packIsrValue(headers: Record<string, string>, body: ArrayBuffer): ArrayBuffer;
113
+ /**
114
+ * Unpack headers + body from a packed ArrayBuffer.
115
+ * Returns null if the buffer is too small or malformed.
116
+ */
117
+ declare function unpackIsrValue(buffer: ArrayBuffer): {
118
+ headers: Record<string, string>;
119
+ body: ArrayBuffer;
120
+ } | null;
121
+ /**
122
+ * Read an ISR cache entry from KV. Returns null on cache miss, malformed
123
+ * metadata, or ANY KV error (fail-open → caller renders live). The KV key is
124
+ * deployment-scoped via `env.__VOID_ISR_DEPLOYMENT_ID` (see module header) and
125
+ * host-scoped via `host` so one host never reads another host's cached bytes.
126
+ */
127
+ declare function readIsrCache(env: IsrCacheEnv, host: string, pathname: string): Promise<IsrCacheEntry | null>;
128
+ /**
129
+ * Write an ISR cache entry to KV. The `response.clone()` is taken SYNCHRONOUSLY
130
+ * before any `await` so a caller that consumes `response.body` for the client
131
+ * response (after kicking us off via `ctx.waitUntil`) cannot race the body
132
+ * away. Any KV error is swallowed (fail-open on the write path).
133
+ */
134
+ declare function writeIsrCache(env: IsrCacheEnv, host: string, pathname: string, response: Response, ttl: number): Promise<void>;
135
+ /**
136
+ * List every host that has registered cached content for a deployment. Used by
137
+ * the on-demand `revalidate({ paths })` purge when there is no request host
138
+ * (cron / queue). Fail-open: a registry read error yields the hosts collected so
139
+ * far (or none) rather than throwing — degraded purge coverage, never a 500.
140
+ */
141
+ declare function listIsrHosts(kv: KVNamespace, deploymentId: string): Promise<Array<string>>;
142
+ /**
143
+ * Delete an ISR cache entry from KV. Used when a worker permanently opts out
144
+ * via `x-revalidate: 0`. Any KV error is swallowed (fail-open).
145
+ */
146
+ declare function deleteIsrCache(env: IsrCacheEnv, host: string, pathname: string): Promise<void>;
147
+ /** Check if a cached entry is stale. */
148
+ declare function isStale(entry: IsrCacheEntry): boolean;
149
+ /** Build a Response from an ISR cache entry. */
150
+ declare function isrCacheResponse(entry: IsrCacheEntry, stale: boolean): Response;
151
+ /**
152
+ * Read the `x-revalidate` header from a worker response.
153
+ * Returns the override TTL, or the default. A value of 0 means "do not cache".
154
+ */
155
+ declare function resolveRevalidateTtl(response: Response, defaultTtl: number): number;
156
+ /**
157
+ * ISR should be bypassed for requests with credentials and EventSource
158
+ * requests. Pages protocol navigation (X-VoidPages) uses a separate JSON edge
159
+ * cache.
160
+ */
161
+ declare function shouldBypassIsr(request: Request): boolean;
162
+ /**
163
+ * Resolve the ISR TTL for a given pathname.
164
+ * Accepts the `revalidate` config (number, object, or undefined). For the
165
+ * object form, the first matching pattern wins.
166
+ */
167
+ declare function resolvePathTtl(revalidate: number | Record<string, number> | undefined, pathname: string): number;
168
+ declare function serializeHeaders(response: Response): Record<string, string>;
169
+ declare function normalizePageDataForJsonCache(raw: string): string | null;
170
+ declare function extractPageDataFromHtml(html: string): string | null;
171
+ declare function clientRevalidateResponse(response: Response): Response;
172
+ declare function isEventStreamResponse(response: Response): boolean;
173
+ /**
174
+ * Populate the JSON edge cache from a response's embedded page data (the
175
+ * `x-void-page-data` header or the `__VOID_PAGE_DATA__` inline script). No-ops
176
+ * when there is no extractable page data. Any cache error is swallowed
177
+ * (fail-open) so this is always safe to fire inside `ctx.waitUntil`.
178
+ */
179
+ declare function populateJsonEdgeCache(cache: Cache | undefined, cacheKey: string, response: Response, ttl: number, state: "HIT" | "MISS"): Promise<void>;
180
+ /**
181
+ * Build a new Request with the `X-VoidPages` header removed. Headers on an
182
+ * existing Request are immutable, so we must clone into a fresh Request. ISR
183
+ * only runs for GET, so there is no body to preserve.
184
+ */
185
+ declare function cloneWithoutVoidPages(request: Request): Request;
186
+ /**
187
+ * Run the ISR caching ladder for a request.
188
+ *
189
+ * Returns a cached/rendered `Response` when ISR applies, or `null` when the
190
+ * caller should render live itself (request not ISR-eligible, or a read/hit
191
+ * cache op failed open).
192
+ *
193
+ * Ladder:
194
+ * - Bail (`null`) if not GET, a Range request, `shouldBypassIsr`, or the path
195
+ * has no ISR TTL. Range requests bail BEFORE any cache read/write so they
196
+ * render live (correct 206) and never touch the shared full-response entry.
197
+ * - JSON (X-VoidPages) path: edge match → hit serves `clientRevalidateResponse`;
198
+ * miss renders live JSON and returns it — the JSON path never writes the edge
199
+ * cache. The JSON edge variant is warmed only as a side-effect of the HTML
200
+ * render path (cold colo re-renders JSON).
201
+ * - HTML path: L1 edge match → L2 KV. Hit serves `isrCacheResponse`
202
+ * (stale hits also queue a background revalidation). Miss renders live and,
203
+ * when cacheable, writes KV + JSON edge + HTML edge in the background.
204
+ */
205
+ declare function handleIsr(request: Request, env: IsrCacheEnv, ctx: ExecutionContext, options: HandleIsrOptions, render: RenderFn): Promise<Response | null>;
206
+ //#endregion
207
+ export { HandleIsrOptions, IsrCacheEntry, IsrCacheEnv, IsrCacheMetadata, RenderFn, canonicalIsrPathname, clientRevalidateResponse, cloneWithoutVoidPages, deleteIsrCache, denormalizeIsrPath, extractPageDataFromHtml, handleIsr, hostRegistryKey, htmlEdgeKey, isEventStreamResponse, isStale, isrCacheResponse, isrKey, jsonEdgeKey, listIsrHosts, matchLiteralPattern, normalizeIsrPath, normalizePageDataForJsonCache, packIsrValue, populateJsonEdgeCache, readIsrCache, resolvePathTtl, resolveRevalidateTtl, serializeHeaders, shouldBypassIsr, unpackIsrValue, writeIsrCache };