void 1.2.1 → 1.3.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.
Files changed (166) hide show
  1. package/dist/{account-cmd-CdUll_35.mjs → account-cmd-B4i71m7k.mjs} +3 -3
  2. package/dist/{application-analysis-fEqoSeMp.mjs → application-analysis-DYdVoMAY.mjs} +8 -6
  3. package/dist/{application-routing-DAhNP4u3.mjs → application-routing-C8EqRDfv.mjs} +3 -1
  4. package/dist/{auth-link-C87vGXM-.mjs → auth-link-BKwHPbdv.mjs} +3 -3
  5. package/dist/{auth-router-6I81r-ix.mjs → auth-router-DqLx8E0F.mjs} +2 -2
  6. package/dist/{better-auth-shared-D-6z0q0f.mjs → better-auth-shared-7FM3Uk2N.mjs} +48 -2
  7. package/dist/{build-cmd-ex1g-j_e.mjs → build-cmd-Dy4psnt1.mjs} +1 -1
  8. package/dist/{cache-oHzrnY3z.mjs → cache-DV37S6dl.mjs} +1 -1
  9. package/dist/{cancel-deploy-viAMD0Q_.mjs → cancel-deploy-DMPyu1Vt.mjs} +1 -1
  10. package/dist/{cf-build-output-BhVxldCw.mjs → cf-build-output-Bapzc_8Z.mjs} +102 -1
  11. package/dist/cf-build-output-DlBkWdNU.mjs +1 -0
  12. package/dist/cli/cli.mjs +41 -32
  13. package/dist/cli/cloudflare-operation-process.mjs +27 -6
  14. package/dist/cli/env-schema-probe.mjs +2 -2
  15. package/dist/{client-BEkdj1kx.mjs → client-BEMtySaV.mjs} +8 -1
  16. package/dist/client-DyxPQnF_.mjs +1 -0
  17. package/dist/{cloudflare-cmd-DMzCv12s.mjs → cloudflare-cmd-BaqIigwI.mjs} +1 -1
  18. package/dist/{cloudflare-connect-CCRTVg-I.mjs → cloudflare-connect-pNLQmr_e.mjs} +1 -1
  19. package/dist/{cloudflare-operations-DsSaw2BT.mjs → cloudflare-operations-PCM4wucv.mjs} +1 -1
  20. package/dist/{cloudflare-operations-Dh9dEHJj.mjs → cloudflare-operations-vP3b5CQf.mjs} +3 -3
  21. package/dist/config-BTu1o3Dc.mjs +49 -0
  22. package/dist/{config-DQfHOKh9.d.mts → config-Du88-7Y8.d.mts} +2 -1
  23. package/dist/config-entry.d.mts +1 -1
  24. package/dist/{connect-k7R2rPVA.mjs → connect-DwVsBvVh.mjs} +4 -4
  25. package/dist/{create-project-DTuy-l0z.mjs → create-project-CgrnvzyA.mjs} +1 -1
  26. package/dist/{create-project-DV71ol5d.mjs → create-project-CjzbBlTv.mjs} +1 -1
  27. package/dist/{db-DVOkLH5X.mjs → db-rvo64snS.mjs} +13 -13
  28. package/dist/{delete-BbP5eopu.mjs → delete-DWX6dP4M.mjs} +1 -1
  29. package/dist/deploy-CtHE2A3R.mjs +2 -0
  30. package/dist/{deploy-7H32EnUs.mjs → deploy-hRPvw8ci.mjs} +229 -69
  31. package/dist/{deploy-managed-static-DlvcvnQT.mjs → deploy-managed-static-CDYA2p3L.mjs} +30 -14
  32. package/dist/{deploy-transport-BzGEj0Ge.mjs → deploy-transport-JCxw7QDi.mjs} +3 -3
  33. package/dist/deploy.mjs +4 -3
  34. package/dist/{dispatch-DoUnxTNo.mjs → dispatch-OVLeWW8K.mjs} +71 -2
  35. package/dist/{domain-CA2wBgra.mjs → domain-CH5iBD9Y.mjs} +2 -2
  36. package/dist/{email-DXpCc77w.mjs → email-CbTO1l78.mjs} +58 -9
  37. package/dist/{email-cloudflare-D03g2TkS.mjs → email-cloudflare-B8-pq_-W.mjs} +106 -5
  38. package/dist/{env-DgOQ9_8S.mjs → env-BBdw236G.mjs} +3 -3
  39. package/dist/{env-BJjULvL0.mjs → env-BaMz0j7H.mjs} +6 -1
  40. package/dist/{env-validation-BmsdCZEk.mjs → env-validation-BfBVvBNe.mjs} +1 -1
  41. package/dist/{env-validation-Dwq3PJzK.mjs → env-validation-XcukUlFM.mjs} +1 -1
  42. package/dist/{gen-BfmcIi2g.mjs → gen-CCx3t2Ju.mjs} +1 -1
  43. package/dist/gen-TgXy93Rr.mjs +1 -0
  44. package/dist/{github-cmd-CY9LUqLh.mjs → github-cmd-BzkRLkbg.mjs} +1 -1
  45. package/dist/{handler-BbkSPzGF.d.mts → handler-CQv5YDS0.d.mts} +1 -1
  46. package/dist/{help-CxSlPpzJ.mjs → help-CKbrhZha.mjs} +16 -1
  47. package/dist/help-qOZr3wng.mjs +1 -0
  48. package/dist/index.d.mts +2 -4
  49. package/dist/index.mjs +317 -122
  50. package/dist/{info-7VDlhtyy.mjs → info-oBm8yK8J.mjs} +2 -2
  51. package/dist/{init-DxYyOs4-.mjs → init-CBp_uMve.mjs} +217 -197
  52. package/dist/{link-Ddmod1dZ.mjs → link-Dt_QXmM4.mjs} +2 -2
  53. package/dist/{list-MogaugiO.mjs → list-DF6asTdt.mjs} +2 -2
  54. package/dist/{live-Bt8Y_0bg.d.mts → live-CXJ9yC98.d.mts} +1 -1
  55. package/dist/{local-d1-BHBnKWRb.mjs → local-d1-CUhH3qtj.mjs} +1 -1
  56. package/dist/{login-Cttnagmm.mjs → login-BjSyhXlT.mjs} +2 -2
  57. package/dist/login-Cdo8Aco1.mjs +1 -0
  58. package/dist/{logs-CAszUWZI.mjs → logs-DmvBl6xA.mjs} +2 -2
  59. package/dist/{migrate-DyT2jJ8y.mjs → migrate-BXRHpYwR.mjs} +1 -1
  60. package/dist/migrate-COPncCGx.mjs +1 -0
  61. package/dist/nitro.mjs +2 -2
  62. package/dist/{node-WB7c2N4L.mjs → node-DtD7ZiPf.mjs} +4 -4
  63. package/dist/{operator-args-UnE8Y2eK.mjs → operator-args-BsJIYXaT.mjs} +8 -1
  64. package/dist/operator-cmd-DDiX7Y3f.mjs +1 -0
  65. package/dist/{operator-cmd-skshjMGl.mjs → operator-cmd-DjZKGGgN.mjs} +4 -3
  66. package/dist/pages/client.d.mts +17 -4
  67. package/dist/pages/client.mjs +161 -29
  68. package/dist/pages/index.d.mts +18 -2
  69. package/dist/pages/index.mjs +2 -52
  70. package/dist/pages/islands-plugin.d.mts +1 -1
  71. package/dist/pages/islands-plugin.mjs +9 -3
  72. package/dist/pages/protocol.d.mts +2 -2
  73. package/dist/platform-args-BC8_hsDj.mjs +1 -0
  74. package/dist/{platform-args-DVbVClZl.mjs → platform-args-BMD2gjc_.mjs} +1 -1
  75. package/dist/{platform-auth-config-C-pm5Qzx.mjs → platform-auth-config-BgnuMbml.mjs} +3 -3
  76. package/dist/{platform-auth-protection-SHhvim1L.mjs → platform-auth-protection-NjRejttU.mjs} +2 -2
  77. package/dist/{platform-auth-recovery-DXVWs-wA.mjs → platform-auth-recovery-Ug12d9W5.mjs} +2 -2
  78. package/dist/{platform-cmd-PiNRkopU.mjs → platform-cmd-BYKLMPCn.mjs} +1 -1
  79. package/dist/{platform-cmd-D5I1beUX.mjs → platform-cmd-HByuZAO7.mjs} +4 -4
  80. package/dist/{platform-domain-jpmBfZeb.mjs → platform-domain-DLSQ8GF8.mjs} +2 -2
  81. package/dist/{platform-lifecycle-DyKmuNY6.mjs → platform-lifecycle-3dwFJmnq.mjs} +102 -40
  82. package/dist/{platform-lifecycle-DH3Yr1qQ.mjs → platform-lifecycle-BRqcxerG.mjs} +1 -1
  83. package/dist/{platform-management-7m-PLaPJ.mjs → platform-management-ZK6XcbCo.mjs} +1 -1
  84. package/dist/{platform-management--vDwSn35.mjs → platform-management-ZZ6bQM5j.mjs} +1 -1
  85. package/dist/{platform-recovery-BiNpZZab.mjs → platform-recovery-8vNE5dNN.mjs} +2 -2
  86. package/dist/{plugin-inference-DUjCd55H.mjs → plugin-inference-CeiB6BG5.mjs} +53 -18
  87. package/dist/{prepare-BcAE5Sdj.mjs → prepare-5BR7Splm.mjs} +1 -1
  88. package/dist/prepare-B3VrSF1Z.mjs +1 -0
  89. package/dist/{prepare-DA-17DhO.mjs → prepare-Dseww74N.mjs} +4 -4
  90. package/dist/{prerender-render-Cijv7UFo.mjs → prerender-render-CaShbHJ3.mjs} +13 -1
  91. package/dist/prerender-render.d.mts +1 -0
  92. package/dist/prerender-render.mjs +1 -1
  93. package/dist/{project-cmd-DA4K5faU.mjs → project-cmd-CjLNNxwe.mjs} +15 -15
  94. package/dist/{project-team-CQBNveX3.mjs → project-team-DE5drjcl.mjs} +1 -1
  95. package/dist/{project-token-CCalFyQf.mjs → project-token-XEYEXfiJ.mjs} +1 -1
  96. package/dist/{project-zero-trust-DPnkiEuT.mjs → project-zero-trust-6VPU8zmV.mjs} +1 -1
  97. package/dist/{protocol-BBDpJz3G.d.mts → protocol-BekmFxCI.d.mts} +2 -2
  98. package/dist/{provision-DtROpNNI.mjs → provision-B7UFrBH7.mjs} +1 -1
  99. package/dist/{provision-BFLCrnUp.mjs → provision-Chq0NRAV.mjs} +1 -1
  100. package/dist/{requests-BpH0kImL.mjs → requests-CG-Pj8ze.mjs} +1 -1
  101. package/dist/{rollback-CXS1tAa-.mjs → rollback-CIrm2txZ.mjs} +2 -2
  102. package/dist/{route-types-BZi26DVS.mjs → route-types--PAOzHlH.mjs} +49 -28
  103. package/dist/routes-stub.d.mts +22 -13
  104. package/dist/runtime/better-auth-mysql.mjs +1 -1
  105. package/dist/runtime/better-auth-pg.mjs +1 -1
  106. package/dist/runtime/better-auth.mjs +1 -1
  107. package/dist/runtime/handler.d.mts +1 -1
  108. package/dist/runtime/live-client.d.mts +1 -1
  109. package/dist/runtime/live.d.mts +1 -1
  110. package/dist/runtime/log.mjs +19 -1
  111. package/dist/runtime/routing.d.mts +2 -2
  112. package/dist/runtime/routing.mjs +4 -3
  113. package/dist/runtime/validator.d.mts +1 -1
  114. package/dist/runtime/ws-server.d.mts +1 -1
  115. package/dist/runtime/ws.d.mts +1 -1
  116. package/dist/{scan-8IzYXiU_.mjs → scan-N9WzdcAl.mjs} +302 -23
  117. package/dist/{secret-BbXHfLAt.mjs → secret-BL6g7mF5.mjs} +3 -3
  118. package/dist/static-asset-marker-Btf-6oCD.mjs +32 -0
  119. package/dist/{types-7AIz0OZu.d.mts → types-w72dljaA.d.mts} +6 -1
  120. package/dist/{version-metadata-tS39iW09.mjs → version-metadata-DmaZNTYW.mjs} +1 -1
  121. package/dist/worker-conditions-AWFRcFxR.mjs +11 -0
  122. package/dist/{wrangler-F96NnLyb.mjs → wrangler-CDiEBCdS.mjs} +2 -1
  123. package/dist/wrangler-DOPr4jyI.mjs +1 -0
  124. package/dist/{wrapper-D3sZyh2C.mjs → wrapper-CEiDeCqD.mjs} +6 -10
  125. package/dist/{ws-BH-im_tX.d.mts → ws-D1zWiB_P.d.mts} +1 -1
  126. package/docs/guide/auth.md +3 -1
  127. package/docs/guide/edge/static-assets.md +10 -0
  128. package/docs/guide/email/domains.md +1 -1
  129. package/docs/guide/email/receiving.md +2 -0
  130. package/docs/guide/email/sending.md +1 -1
  131. package/docs/guide/pages-routing/actions-and-forms.md +19 -13
  132. package/docs/guide/pages-routing/markdown.md +2 -0
  133. package/docs/guide/pages-routing/overview.md +33 -2
  134. package/docs/guide/platform/administration/email.md +18 -0
  135. package/docs/guide/platform/installation/prerequisites.md +9 -4
  136. package/docs/guide/platform/installation/uninstall.md +2 -0
  137. package/docs/guide/queues.md +13 -1
  138. package/docs/guide/server-routing.md +38 -2
  139. package/docs/guide/type-safety.md +7 -7
  140. package/docs/guide/websockets.md +4 -2
  141. package/docs/reference/api/pages.md +4 -4
  142. package/docs/reference/cli/email.md +13 -0
  143. package/docs/reference/cli/platform-email.md +2 -2
  144. package/docs/reference/cli.md +1 -0
  145. package/docs/reference/structure.md +3 -1
  146. package/package.json +8 -7
  147. package/templates/assets/icons.svg +49 -0
  148. package/templates/assets/react.svg +1 -0
  149. package/templates/assets/solid.svg +1 -0
  150. package/templates/assets/stack.svg +35 -0
  151. package/templates/assets/svelte.svg +1 -0
  152. package/templates/assets/void.svg +5 -0
  153. package/templates/assets/vue.svg +1 -0
  154. package/dist/cf-build-output-BJHuSsVN.mjs +0 -1
  155. package/dist/client-BiRDZTSd.mjs +0 -1
  156. package/dist/config-CyUJzi3q.mjs +0 -32
  157. package/dist/deploy-CE0Ev0Ob.mjs +0 -2
  158. package/dist/gen-NrOlWZaG.mjs +0 -1
  159. package/dist/help-BeAFFE5C.mjs +0 -1
  160. package/dist/login-DgDwlKc_.mjs +0 -1
  161. package/dist/migrate-n49ZeS1X.mjs +0 -1
  162. package/dist/operator-cmd-d2MvJOrl.mjs +0 -1
  163. package/dist/parse-filename-DioPHiR9.mjs +0 -77
  164. package/dist/platform-args-kQrgxScY.mjs +0 -1
  165. package/dist/prepare-C4I0f--h.mjs +0 -1
  166. package/dist/wrangler-BLb3_luP.mjs +0 -1
@@ -1,13 +1,136 @@
1
- import { a as join, n as dirname, r as extname, t as basename } from "./dist-ipQi0Q8y.mjs";
1
+ import { a as join, l as toSlash, n as dirname, r as extname, t as basename } from "./dist-ipQi0Q8y.mjs";
2
2
  import { n as readProjectPaths } from "./project-paths-DGu_QZ_Q.mjs";
3
3
  import { t as glob } from "./dist-D15hFhP0.mjs";
4
4
  import { n as isIdentifier, o as moduleExportName, r as isLiteral } from "./ast-CsZ_q_gc.mjs";
5
5
  import { a as staticLiteral, i as staticExportInitializers, t as bindingNames } from "./ast-bindings-C3yaPs1y.mjs";
6
- import { n as parseRouteFilename } from "./parse-filename-DioPHiR9.mjs";
7
6
  import { i as voidWarn } from "./log-BMwmN5Ae.mjs";
8
7
  import { t as MagicString } from "./dist-Cegxq8gs.mjs";
9
- import { existsSync, readFileSync } from "node:fs";
8
+ import { existsSync, readFileSync, statSync } from "node:fs";
10
9
  import { parseSync } from "vite";
10
+ //#region src/shared/route-extensions.ts
11
+ const ROUTE_EXTENSIONS = [
12
+ "ts",
13
+ "tsx",
14
+ "js",
15
+ "jsx",
16
+ "mjs",
17
+ "mts"
18
+ ];
19
+ const ROUTE_EXTENSIONS_SET = new Set(ROUTE_EXTENSIONS.map((ext) => `.${ext}`));
20
+ //#endregion
21
+ //#region src/router/parse-filename.ts
22
+ const ENV_SUFFIXES = ["dev", "prod"];
23
+ function parsePath(relativePath, isWebSocketRoute) {
24
+ const filePath = relativePath;
25
+ let path = relativePath.replace(/\\/g, "/");
26
+ for (const ext of ROUTE_EXTENSIONS) if (path.endsWith(`.${ext}`)) {
27
+ path = path.slice(0, -(ext.length + 1));
28
+ break;
29
+ }
30
+ if (isWebSocketRoute && path.endsWith(".ws")) path = path.slice(0, -3);
31
+ const segments = path.split("/");
32
+ let filename = segments.pop();
33
+ let env = null;
34
+ for (const suffix of ENV_SUFFIXES) if (filename.endsWith(`.${suffix}`)) {
35
+ env = suffix;
36
+ filename = filename.slice(0, -(suffix.length + 1));
37
+ break;
38
+ }
39
+ segments.push(filename);
40
+ if (segments[segments.length - 1] === "index") segments.pop();
41
+ const filtered = segments.filter((s) => !s.startsWith("(") || !s.endsWith(")"));
42
+ const params = [];
43
+ let catchAll = false;
44
+ return {
45
+ pattern: "/" + filtered.map((segment) => {
46
+ if (segment.startsWith("[...") && segment.endsWith("]")) {
47
+ const paramName = segment.slice(4, -1);
48
+ catchAll = true;
49
+ if (paramName) {
50
+ params.push(paramName);
51
+ return `:${paramName}{.+}`;
52
+ }
53
+ return "*";
54
+ }
55
+ if (segment.startsWith("[") && segment.endsWith("]")) {
56
+ const paramName = segment.slice(1, -1);
57
+ params.push(paramName);
58
+ return `:${paramName}`;
59
+ }
60
+ return segment;
61
+ }).join("/"),
62
+ methods: [],
63
+ params,
64
+ catchAll,
65
+ env,
66
+ filePath
67
+ };
68
+ }
69
+ /**
70
+ * Parse a route filename (relative to routes/) into a RouteDefinition.
71
+ *
72
+ * Rules (applied in order):
73
+ * 1. Strip file extension (.ts, .tsx, .js, .jsx, .mts, .mjs)
74
+ * 2. Check for env suffix (.dev, .prod) → strip and record
75
+ * 3. Strip trailing "index" segment
76
+ * 4. Remove route group directories: (groupname)
77
+ * 5. Convert [param] → :param, [...param] → :param{.+}, [...] → *
78
+ * 6. Prefix with /
79
+ */
80
+ function parseRouteFilename(relativePath) {
81
+ return parsePath(relativePath, false);
82
+ }
83
+ /** Parse a `.ws.*` route filename into a regular route definition. */
84
+ function parseWebSocketFilename(relativePath) {
85
+ return parsePath(relativePath, true);
86
+ }
87
+ function isRouteEnabledForEnvironment(route, environment) {
88
+ return environment === void 0 || route.env === null || route.env === environment;
89
+ }
90
+ //#endregion
91
+ //#region src/shared/source-analysis-cache.ts
92
+ /**
93
+ * How long after a file changes its stat is trusted to identify its contents.
94
+ * Some file systems keep coarse timestamps (FAT: 2 s, HFS+: 1 s, ext4: one
95
+ * kernel tick) and Linux reuses inodes, so a second write inside that window
96
+ * can leave the signature unchanged. Like Git's racily clean index entries,
97
+ * values derived from recently changed files are recomputed on the next read.
98
+ */
99
+ const RACY_WINDOW_MS = 3e3;
100
+ const entries = /* @__PURE__ */ new Map();
101
+ /**
102
+ * Derive a value from a source file, reusing it while the file is unchanged.
103
+ * A dev server rescans every source file on each file event, and several
104
+ * plugins scan the same pages, but only edited files need to be read and
105
+ * parsed again. `kind` separates analyses of the same file. `derive` must be a
106
+ * pure function of the source, and callers must not mutate its result.
107
+ */
108
+ function deriveFromSource(path, kind, derive) {
109
+ const key = toSlash(path);
110
+ const stat = statSync(key, {
111
+ bigint: true,
112
+ throwIfNoEntry: false
113
+ });
114
+ if (!stat) return derive(readFileSync(key, "utf-8"));
115
+ const signature = `${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
116
+ const changedAt = Number((stat.mtimeNs > stat.ctimeNs ? stat.mtimeNs : stat.ctimeNs) / 1000000n);
117
+ const trusted = Date.now() - changedAt > RACY_WINDOW_MS;
118
+ const entry = entries.get(key);
119
+ if (trusted && entry?.signature === signature && entry.derived.has(kind)) return entry.derived.get(kind);
120
+ const value = derive(readFileSync(key, "utf-8"));
121
+ if (!trusted) entries.delete(key);
122
+ else if (entry?.signature === signature) entry.derived.set(kind, value);
123
+ else entries.set(key, {
124
+ signature,
125
+ derived: /* @__PURE__ */ new Map([[kind, value]])
126
+ });
127
+ return value;
128
+ }
129
+ /** Forget values derived from a file. Dev servers call this for watcher events. */
130
+ function invalidateSourceAnalysis(path) {
131
+ entries.delete(toSlash(path));
132
+ }
133
+ //#endregion
11
134
  //#region src/pages/island-imports.ts
12
135
  /** The whole JS/TS module, or each `<script>` block of a Vue/Svelte SFC with its offset in the file. */
13
136
  function scriptBlocks(code, filename) {
@@ -150,6 +273,99 @@ function resolveLayoutIds(page, layouts, namedLayouts) {
150
273
  return resolveEffectiveLayoutEntries(page, layouts, namedLayouts).map((layout) => layout.id);
151
274
  }
152
275
  //#endregion
276
+ //#region src/pages/manifest.ts
277
+ /**
278
+ * Generates the component import map and layout tree as code strings.
279
+ * Used by both SSR renderer and client entry virtual modules.
280
+ *
281
+ * Client entries load this eagerly, so it grows with every page. Each page id
282
+ * is written once, identical layout chains are shared, and static routes are
283
+ * matched by exact lookup instead of a RegExp per page. The emitted bindings
284
+ * (`components`, `layoutTree`, `routeMeta`, `matchRoute`) keep their shape;
285
+ * `matchRoute` takes a decoded pathname (see `decodePagePath`).
286
+ */
287
+ function generateComponentManifest(scan, pagesDir) {
288
+ const load = (filePath) => `() => import(${JSON.stringify(`${pagesDir}/${filePath}`)})`;
289
+ const pageIds = new Set(scan.pages.map((page) => page.componentId));
290
+ const layoutKey = (id) => {
291
+ if (!pageIds.has(id)) return id;
292
+ const slash = id.lastIndexOf("/");
293
+ return id.slice(0, slash + 1) + "_" + id.slice(slash + 1);
294
+ };
295
+ const chains = [];
296
+ const chainIndexes = /* @__PURE__ */ new Map();
297
+ const staticRows = [];
298
+ const dynamicRows = [];
299
+ let inStaticPrefix = true;
300
+ for (const page of scan.pages) {
301
+ const chain = JSON.stringify(resolveLayoutIds(page, scan.layouts, scan.namedLayouts).map(layoutKey));
302
+ let chainIndex = chain === "[]" ? -1 : chainIndexes.get(chain);
303
+ if (chainIndex === void 0) {
304
+ chainIndex = chains.push(chain) - 1;
305
+ chainIndexes.set(chain, chainIndex);
306
+ }
307
+ const id = JSON.stringify(page.componentId);
308
+ inStaticPrefix &&= isStaticPattern(page.pattern);
309
+ if (inStaticPrefix) {
310
+ const pattern = page.pattern === `/${page.componentId}` ? "" : `, ${JSON.stringify(page.pattern)}`;
311
+ staticRows.push(` [${id}, ${load(page.componentPath)}, ${chainIndex}${pattern}],`);
312
+ } else dynamicRows.push(` [${patternToRegex(page.pattern)}, ${id}, ${load(page.componentPath)}, ${chainIndex}],`);
313
+ }
314
+ const lines = [];
315
+ lines.push("const components = {};");
316
+ lines.push(`const __voidLayoutChains = [${chains.join(", ")}];`);
317
+ lines.push("const __voidStaticRoutes = [", ...staticRows, "];");
318
+ lines.push("const __voidDynamicRoutes = [", ...dynamicRows, "];");
319
+ lines.push(`const layoutTree = {};
320
+ const __voidStaticRouteIds = new Map();
321
+ for (const [id, load, chain, pattern = "/" + id] of __voidStaticRoutes) {
322
+ components[id] = load;
323
+ if (chain >= 0) layoutTree[id] = __voidLayoutChains[chain];
324
+ if (!__voidStaticRouteIds.has(pattern)) __voidStaticRouteIds.set(pattern, id);
325
+ }
326
+ const routes = [];
327
+ for (const [pattern, id, load, chain] of __voidDynamicRoutes) {
328
+ components[id] = load;
329
+ if (chain >= 0) layoutTree[id] = __voidLayoutChains[chain];
330
+ routes.push({ pattern, id });
331
+ }`);
332
+ for (const layout of [...scan.layouts, ...scan.namedLayouts]) lines.push(`components[${JSON.stringify(layoutKey(toLayoutId(layout)))}] = ${load(layout.filePath)};`);
333
+ lines.push("const routeMeta = {");
334
+ for (const page of scan.pages) if (page.island) lines.push(` ${JSON.stringify(page.componentId)}: { island: true },`);
335
+ lines.push("};");
336
+ lines.push(`function matchRoute(path) {
337
+ const id = __voidStaticRouteIds.get(path);
338
+ if (id !== undefined) return id;
339
+ for (const r of routes) {
340
+ if (r.pattern.test(path)) return r.id;
341
+ }
342
+ return null;
343
+ }`);
344
+ return lines.join("\n");
345
+ }
346
+ const REGEX_SYNTAX = ".*+?^${}()|[]\\";
347
+ /** The capture group for `*`, `:name{.+}` and `:name`; other segments match as written. */
348
+ function captureGroup(segment) {
349
+ if (segment === "*") return "(.*)";
350
+ if (segment.length < 2 || segment[0] !== ":") return;
351
+ const name = segment.endsWith("{.+}") ? segment.slice(1, -4) : "";
352
+ return name && !name.includes("{") && !name.includes("}") ? "(.+)" : "([^\\/]+)";
353
+ }
354
+ /** Whether `patternToRegex` matches only the pattern's own text. */
355
+ function isStaticPattern(pattern) {
356
+ return pattern.split("/").every((segment) => captureGroup(segment) === void 0);
357
+ }
358
+ /** The RegExp literal a page pattern matches with, the same on the client and the server. */
359
+ function patternToRegex(pattern) {
360
+ return `/^${pattern.split("/").map((segment) => {
361
+ const group = captureGroup(segment);
362
+ if (group) return group;
363
+ let literal = "";
364
+ for (const char of segment) literal += REGEX_SYNTAX.includes(char) ? "\\" + char : char;
365
+ return literal;
366
+ }).join("\\/")}$/`;
367
+ }
368
+ //#endregion
153
369
  //#region src/pages/scan.ts
154
370
  const COMPONENT_EXTENSIONS = [
155
371
  "vue",
@@ -158,13 +374,6 @@ const COMPONENT_EXTENSIONS = [
158
374
  "svelte",
159
375
  "md"
160
376
  ];
161
- const SERVER_EXTENSIONS = [
162
- "ts",
163
- "tsx",
164
- "js",
165
- "mjs",
166
- "mts"
167
- ];
168
377
  const PAGE_METHODS = /* @__PURE__ */ new Set([
169
378
  "loader",
170
379
  "action",
@@ -173,6 +382,8 @@ const PAGE_METHODS = /* @__PURE__ */ new Set([
173
382
  const MAX_REVALIDATE = 31536e3;
174
383
  /** Layout paths already reported; a dev server rescans pages on every file change. */
175
384
  const warnedLayoutPaths = /* @__PURE__ */ new Set();
385
+ /** Unreachable pages already reported, for the same reason. */
386
+ const warnedShadowedPages = /* @__PURE__ */ new Set();
176
387
  function componentLayout(source, filename) {
177
388
  if (!source.includes("layout")) return;
178
389
  for (const script of scriptBlocks(source, filename)) {
@@ -255,6 +466,23 @@ function resolvePageRendering(page, options) {
255
466
  shouldPrerender: prerenderMode === "force" || prerenderMode === "auto" && inferAutoPrerender(page, options)
256
467
  };
257
468
  }
469
+ /**
470
+ * The environment a file under pages/ is limited to, read from its name the way
471
+ * scanPages does: `home.dev.tsx`, `home.dev.island.vue` and `home.dev.server.ts`
472
+ * give 'dev'. Files the scanner skips (`_` segments, other extensions) give null.
473
+ */
474
+ function pageFileEnvironment(relativePath) {
475
+ const segments = toSlash(relativePath).split("/");
476
+ if (segments.some((segment) => segment.startsWith("_"))) return null;
477
+ const file = segments[segments.length - 1];
478
+ const ext = extname(file);
479
+ let base = basename(file, ext);
480
+ if (ROUTE_EXTENSIONS_SET.has(ext) && base.endsWith(".server")) base = base.slice(0, -7);
481
+ else if (COMPONENT_EXTENSIONS.includes(ext.slice(1))) {
482
+ if (base.endsWith(".island")) base = base.slice(0, -7);
483
+ } else return null;
484
+ return base.endsWith(".dev") ? "dev" : base.endsWith(".prod") ? "prod" : null;
485
+ }
258
486
  async function scanPages(root, options) {
259
487
  const pagesDir = options?.pagesDir ?? options?.paths?.pagesDir ?? readProjectPaths(root).pagesDir;
260
488
  if (!existsSync(pagesDir)) return {
@@ -270,11 +498,12 @@ async function scanPages(root, options) {
270
498
  const pages = [];
271
499
  const layouts = [];
272
500
  const namedLayouts = [];
273
- for (const file of files) {
501
+ for (const file of files.sort()) {
274
502
  const segments = file.split("/");
275
503
  if (segments.some((s) => s.startsWith("_") && s !== "_layouts")) continue;
276
504
  const ext = extname(file);
277
505
  let base = basename(file, ext);
506
+ if (base.endsWith(".server") && ROUTE_EXTENSIONS_SET.has(ext)) continue;
278
507
  let island;
279
508
  let hasIslandSuffix = false;
280
509
  if (base.endsWith(".island")) {
@@ -302,8 +531,8 @@ async function scanPages(root, options) {
302
531
  });
303
532
  continue;
304
533
  }
305
- const parseName = hasIslandSuffix ? file.replace(`.island${ext}`, ".ts") : file.replace(ext, ".ts");
306
- const route = parseRouteFilename(parseName);
534
+ const route = parseRouteFilename(hasIslandSuffix ? file.replace(`.island${ext}`, ".ts") : file.replace(ext, ".ts"));
535
+ if (!isRouteEnabledForEnvironment(route, options?.environment)) continue;
307
536
  const componentId = file.replace(ext, "").replace(/\.island$/, "").replace(/\/index$/, "").replace(/^index$/, "") || "index";
308
537
  let serverPath = null;
309
538
  const methods = [];
@@ -312,11 +541,11 @@ async function scanPages(root, options) {
312
541
  let ssr;
313
542
  let hasGetPrerenderPaths;
314
543
  const serverBase = hasIslandSuffix ? file.replace(`.island${ext}`, "") : file.replace(ext, "");
315
- for (const serverExt of SERVER_EXTENSIONS) {
544
+ for (const serverExt of ROUTE_EXTENSIONS) {
316
545
  const candidate = `${serverBase}.server.${serverExt}`;
317
546
  if (existsSync(join(pagesDir, candidate))) {
318
547
  serverPath = candidate;
319
- const metadata = detectPageServerMetadata(readFileSync(join(pagesDir, candidate), "utf-8"), candidate);
548
+ const metadata = deriveFromSource(join(pagesDir, candidate), "page-server", (source) => detectPageServerMetadata(source, candidate));
320
549
  methods.push(...metadata.methods);
321
550
  revalidate = metadata.revalidate;
322
551
  prerender = metadata.prerender;
@@ -338,11 +567,11 @@ async function scanPages(root, options) {
338
567
  if (rendering.shouldPrerender && revalidate === void 0) revalidate = MAX_REVALIDATE;
339
568
  let metadata;
340
569
  let layout;
341
- if (ext === ".md") {
342
- const mdContent = readFileSync(join(pagesDir, file), "utf-8");
343
- metadata = extractFrontmatterMeta(mdContent);
344
- layout = extractFrontmatterLayout(mdContent);
345
- } else layout = componentLayout(readFileSync(join(pagesDir, file), "utf-8"), file);
570
+ if (ext === ".md") ({metadata, layout} = deriveFromSource(join(pagesDir, file), "page-markdown", (source) => ({
571
+ metadata: extractFrontmatterMeta(source),
572
+ layout: extractFrontmatterLayout(source)
573
+ })));
574
+ else layout = deriveFromSource(join(pagesDir, file), "page-layout", (source) => componentLayout(source, file));
346
575
  pages.push({
347
576
  pattern: route.pattern,
348
577
  params: route.params,
@@ -357,8 +586,9 @@ async function scanPages(root, options) {
357
586
  ...rendering,
358
587
  hasGetPrerenderPaths,
359
588
  island,
360
- metadata,
361
- layout
589
+ metadata: metadata ? { ...metadata } : void 0,
590
+ layout,
591
+ ...route.env ? { env: route.env } : {}
362
592
  });
363
593
  }
364
594
  pages.sort((a, b) => {
@@ -367,6 +597,14 @@ async function scanPages(root, options) {
367
597
  if (aDynamic !== b.pattern.includes(":")) return aDynamic ? 1 : -1;
368
598
  return a.pattern.localeCompare(b.pattern);
369
599
  });
600
+ if (options?.checkRoutes !== false) {
601
+ const environments = options?.environment ? [options.environment] : ["dev", "prod"];
602
+ for (const environment of environments) {
603
+ const live = pages.filter((page) => isRouteEnabledForEnvironment({ env: page.env ?? null }, environment));
604
+ assertOnePagePerRoute(live);
605
+ warnShadowedPages(pagesDir, live);
606
+ }
607
+ }
370
608
  for (const page of pages) {
371
609
  if (page.island) continue;
372
610
  const dir = dirname(page.componentPath);
@@ -393,6 +631,47 @@ async function scanPages(root, options) {
393
631
  namedLayouts
394
632
  };
395
633
  }
634
+ function compareText(left, right) {
635
+ return left < right ? -1 : left > right ? 1 : 0;
636
+ }
637
+ /** Group sorted pages by `key`, keeping only groups with more than one page. */
638
+ function pagesSharing(pages, key) {
639
+ const groups = /* @__PURE__ */ new Map();
640
+ for (const page of pages) {
641
+ const group = groups.get(key(page));
642
+ if (group) group.push(page);
643
+ else groups.set(key(page), [page]);
644
+ }
645
+ return [...groups.values()].filter((group) => group.length > 1);
646
+ }
647
+ /**
648
+ * Two files for one path have no stable winner: the server and the client can
649
+ * each pick a different one, and the order can change between runs.
650
+ */
651
+ function assertOnePagePerRoute(pages) {
652
+ assertNoneShare(pages, (page) => page.pattern, "pages: Multiple page files resolve to the same route:", "Keep one page file per route and rename or remove the others.");
653
+ assertNoneShare(pages, (page) => page.componentId, "pages: Multiple page files resolve to the same page id:", "Rename or remove one of them.");
654
+ }
655
+ function assertNoneShare(pages, key, title, hint) {
656
+ const clashes = pagesSharing(pages, key).map((group) => ({
657
+ key: key(group[0]),
658
+ files: group.map((page) => page.componentPath).sort(compareText)
659
+ })).sort((left, right) => compareText(left.key, right.key)).map((clash) => ` ${JSON.stringify(clash.key)}: ${clash.files.map((file) => JSON.stringify(file)).join(", ")}`);
660
+ if (clashes.length > 0) throw new Error([
661
+ title,
662
+ ...clashes,
663
+ hint
664
+ ].join("\n"));
665
+ }
666
+ /** Dynamic routes that match the same URLs, such as [id] and [slug]: only the first is ever used. */
667
+ function warnShadowedPages(pagesDir, pages) {
668
+ for (const [used, ...shadowed] of pagesSharing(pages, (page) => patternToRegex(page.pattern))) for (const page of shadowed) {
669
+ const key = `${pagesDir}\0${used.componentPath}\0${page.componentPath}`;
670
+ if (warnedShadowedPages.has(key)) continue;
671
+ warnedShadowedPages.add(key);
672
+ voidWarn(`pages: Page '${page.componentPath}' (${page.pattern}) is never used: '${used.componentPath}' (${used.pattern}) matches the same URLs first. Remove one of them.`);
673
+ }
674
+ }
396
675
  const LEADING_SCRIPT_RE = /^<script\b[^>]*>[\s\S]*?<\/script>\s*\n?/;
397
676
  /** Return the raw YAML frontmatter of markdown source, after any leading <script> block. */
398
677
  function markdownFrontmatter(source) {
@@ -424,4 +703,4 @@ function extractFrontmatterLayout(source) {
424
703
  return value;
425
704
  }
426
705
  //#endregion
427
- export { resolveNamedLayout as a, findIslandImports as c, resolveLayoutIds as i, findStaticCssImports as l, resolveEffectiveLayoutChain as n, toLayoutId as o, resolveLayoutChain as r, editIslandSource as s, scanPages as t };
706
+ export { resolveLayoutChain as a, editIslandSource as c, deriveFromSource as d, invalidateSourceAnalysis as f, ROUTE_EXTENSIONS as g, parseWebSocketFilename as h, resolveEffectiveLayoutChain as i, findIslandImports as l, parseRouteFilename as m, scanPages as n, resolveLayoutIds as o, isRouteEnabledForEnvironment as p, generateComponentManifest as r, resolveNamedLayout as s, pageFileEnvironment as t, findStaticCssImports as u };
@@ -1,10 +1,10 @@
1
1
  import { n as readDeploymentPlatform, r as readProjectConfig } from "./project-rqDnVsJN.mjs";
2
2
  import { c as getToken } from "./auth-B6iOCjKI.mjs";
3
3
  import { A as confirm, H as outro, L as log, O as cancel, U as password, tt as isCancel } from "./dist-Cor9XGoz.mjs";
4
- import { r as PlatformClient } from "./client-BEkdj1kx.mjs";
4
+ import { r as PlatformClient } from "./client-BEMtySaV.mjs";
5
5
  import { m as printSuccess, o as formatCount, p as note } from "./output-BpgYODYx.mjs";
6
6
  import { a as resolveProjectBySlug, t as getRequestedProjectSlug } from "./resolve-project-Xoi6eTWY.mjs";
7
- import { _ as validateSecretValues } from "./env-validation-Dwq3PJzK.mjs";
7
+ import { _ as validateSecretValues } from "./env-validation-XcukUlFM.mjs";
8
8
  import { readFileSync } from "node:fs";
9
9
  //#region src/cli/secret.ts
10
10
  async function runSecretCommand(root, args) {
@@ -93,7 +93,7 @@ async function resolveSecretBackend(root, args) {
93
93
  }
94
94
  async function resolveCloudflareSecretBackend(root, args) {
95
95
  if (args.projectName) throw new Error("secret: --project selects a Void platform project and is not used for direct Cloudflare apps.");
96
- const { deleteCloudflareSecret, listCloudflareSecrets, putCloudflareSecret, resolveCloudflareOperationContext, syncCloudflareSecrets } = await import("./cloudflare-operations-DsSaw2BT.mjs");
96
+ const { deleteCloudflareSecret, listCloudflareSecrets, putCloudflareSecret, resolveCloudflareOperationContext, syncCloudflareSecrets } = await import("./cloudflare-operations-PCM4wucv.mjs");
97
97
  const context = resolveCloudflareOperationContext(root);
98
98
  return {
99
99
  target: "Cloudflare Worker",
@@ -0,0 +1,32 @@
1
+ //#region src/runtime/static-asset-marker.ts
2
+ const STATIC_ASSET_KEY = "__voidStaticAsset";
3
+ /**
4
+ * Records that the fallback answered with a file from the ASSETS binding: the
5
+ * Cache-Control it carried and its body stream. The value lives on the
6
+ * context, not on the response object: `c.header()` after the handler rebuilds
7
+ * `c.res`, so a response identity check would miss the file. The rebuild keeps
8
+ * the body stream, while a middleware that swaps the body does not.
9
+ */
10
+ function markStaticAsset(c, response) {
11
+ const mark = {
12
+ policy: response.headers.get("cache-control") ?? "",
13
+ body: response.body
14
+ };
15
+ c.set(STATIC_ASSET_KEY, mark);
16
+ return response;
17
+ }
18
+ /**
19
+ * True when the final response is still the marked file: a success status, the
20
+ * same body and Cache-Control the fallback served, and no cookie added by
21
+ * later middleware.
22
+ */
23
+ function isUnchangedStaticAsset(c) {
24
+ const mark = c.get(STATIC_ASSET_KEY);
25
+ return mark !== void 0 && [
26
+ 200,
27
+ 206,
28
+ 304
29
+ ].includes(c.res.status) && c.res.body === mark.body && (c.res.headers.get("cache-control") ?? "") === mark.policy && !c.res.headers.has("set-cookie");
30
+ }
31
+ //#endregion
32
+ export { markStaticAsset as n, isUnchangedStaticAsset as t };
@@ -1,3 +1,6 @@
1
+ //#region src/router/parse-filename.d.ts
2
+ type RouteEnvironment = "dev" | "prod";
3
+ //#endregion
1
4
  //#region src/pages/types.d.ts
2
5
  type ExplicitBoolean = true | false | undefined;
3
6
  type SsrMode = "auto" | "server" | "client";
@@ -43,6 +46,8 @@ type PageDefinition = {
43
46
  };
44
47
  /** Named layout selection: "name", "!name", or false */
45
48
  layout?: string | false;
49
+ /** Environment from a `.dev` / `.prod` file suffix. Unset means both. */
50
+ env?: RouteEnvironment;
46
51
  };
47
52
  type LayoutDefinition = {
48
53
  /** Directory path relative to pages/, e.g. "" (root) or "users" */
@@ -76,4 +81,4 @@ type DevCssAsset = {
76
81
  css: string;
77
82
  };
78
83
  //#endregion
79
- export { PageRenderMode as a, PageDefinition as i, LayoutDefinition as n, PageScanResult as o, NamedLayoutDefinition as r, DevCssAsset as t };
84
+ export { PageRenderMode as a, PageDefinition as i, LayoutDefinition as n, PageScanResult as o, NamedLayoutDefinition as r, RouteEnvironment as s, DevCssAsset as t };
@@ -1,5 +1,5 @@
1
1
  import { a as join } from "./dist-ipQi0Q8y.mjs";
2
- import { l as bindingNamesForField, p as findBindingNameOwner } from "./wrangler-F96NnLyb.mjs";
2
+ import { l as bindingNamesForField, p as findBindingNameOwner } from "./wrangler-CDiEBCdS.mjs";
3
3
  //#region src/shared/untrusted-env.ts
4
4
  /** Authentication inputs that Wrangler may consume from the environment. */
5
5
  const WRANGLER_AUTH_CREDENTIAL_ENV_VARS = [
@@ -0,0 +1,11 @@
1
+ //#region src/cli/worker-conditions.ts
2
+ const WORKER_CONDITIONS = [
3
+ "workerd",
4
+ "worker",
5
+ "browser",
6
+ "module",
7
+ "import",
8
+ "default"
9
+ ];
10
+ //#endregion
11
+ export { WORKER_CONDITIONS as t };
@@ -666,9 +666,10 @@ function writeProvisionedWranglerConfig(root, patch, options) {
666
666
  const matches = [];
667
667
  for (let i = 0; i < currentList.length; i++) if (currentList[i].binding === entry.binding) matches.push(i);
668
668
  if (matches.length > 1) throw new Error(`deploy: cannot update "${configPath}" — queue producer binding ${JSON.stringify(entry.binding)} is declared ${matches.length} times across producers. A producer binding must map to exactly one queue, so wrangler rejects this config. Remove the duplicate producers and retry.`);
669
+ const voidOwned = reconcileManagedQueues && managedProducerBindings.has(String(entry.binding));
669
670
  if (matches.length === 1) {
670
671
  const existing = currentList[matches[0]];
671
- if (existing.queue !== entry.queue) throw new Error(`deploy: cannot update "${configPath}" — queue producer binding ${JSON.stringify(entry.binding)} is already bound to queue ${JSON.stringify(existing.queue)}, but queue ${JSON.stringify(entry.queue)} also requests it. A producer binding must map to exactly one queue. Rename or remove the conflicting producer and retry.`);
672
+ if (existing.queue !== entry.queue && !voidOwned) throw new Error(`deploy: cannot update "${configPath}" — queue producer binding ${JSON.stringify(entry.binding)} is already bound to queue ${JSON.stringify(existing.queue)}, but queue ${JSON.stringify(entry.queue)} also requests it. A producer binding must map to exactly one queue. Rename or remove the conflicting producer and retry.`);
672
673
  }
673
674
  index = matches.length === 1 ? matches[0] : -1;
674
675
  workerBindingOwners ??= collectWorkerBindingNames(parseJsonc(text));
@@ -0,0 +1 @@
1
+ export { b as readRootWranglerConfig } from "./wrangler-CDiEBCdS.mjs";
@@ -3,7 +3,8 @@ import { n as readProjectPaths } from "./project-paths-DGu_QZ_Q.mjs";
3
3
  import { l as getDatabaseDialect, p as readConfig } from "./config-DHgSEWT7.mjs";
4
4
  import { n as isIdentifier, r as isLiteral } from "./ast-CsZ_q_gc.mjs";
5
5
  import { t as MagicString } from "./dist-Cegxq8gs.mjs";
6
- import { r as voidDbBundlerPlugin, t as generateQueueResolverSource } from "./dispatch-DoUnxTNo.mjs";
6
+ import { c as voidDbBundlerPlugin, r as cloudflareQueueName, t as generateQueueResolverSource } from "./dispatch-OVLeWW8K.mjs";
7
+ import { t as WORKER_CONDITIONS } from "./worker-conditions-AWFRcFxR.mjs";
7
8
  import { createRequire } from "node:module";
8
9
  import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
9
10
  import { fileURLToPath } from "node:url";
@@ -237,14 +238,6 @@ async function bundleMigrationHandler(root, workerDir, handlerPath, dialect) {
237
238
  if (isPostgresql || isMysql) patchWorkerRequireBundle(outDir, isPostgresql ? "PostgreSQL" : "MySQL", "migration bundle");
238
239
  for (const file of readdirSync(outDir)) copyFileSync(join(outDir, file), join(workerDir, file));
239
240
  }
240
- const WORKER_CONDITIONS = [
241
- "workerd",
242
- "worker",
243
- "browser",
244
- "module",
245
- "import",
246
- "default"
247
- ];
248
241
  const SQL_WORKER_EXTERNAL = [
249
242
  /^cloudflare:/,
250
243
  /^node:/,
@@ -414,6 +407,7 @@ async function bundleEntry(root, entry) {
414
407
  await build({
415
408
  root: tmpDir,
416
409
  logLevel: "silent",
410
+ resolve: { conditions: [...WORKER_CONDITIONS, "production"] },
417
411
  build: {
418
412
  outDir,
419
413
  emptyOutDir: true,
@@ -608,7 +602,9 @@ function generateHandlersSource(root, features, dialect) {
608
602
  lines.push(`import queueHandler${i} from "${absPath}";`);
609
603
  }
610
604
  lines.push("");
611
- lines.push(generateQueueResolverSource(features.queues.map((queue) => queue.name)));
605
+ const queueNames = features.queues.map((queue) => queue.name);
606
+ const cloudflareNames = features.cloudflareQueueNames;
607
+ lines.push(generateQueueResolverSource(queueNames, cloudflareNames && queueNames.map((name) => cloudflareQueueName(name, cloudflareNames))));
612
608
  lines.push("");
613
609
  lines.push(scoped ? "async function __runQueue(batch, env, ctx) {" : "export async function handleQueue(batch, env, ctx) {");
614
610
  lines.push(" const queueSuffix = batch.queue;");
@@ -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-BbkSPzGF.mjs";
3
+ import { a as CloudEnv } from "./handler-CQv5YDS0.mjs";
4
4
  import { WebSocketRouteMap } from "void/routes";
5
5
  //#region src/runtime/ws.d.ts
6
6
  type MaybePromise<T> = T | Promise<T>;
@@ -129,6 +129,8 @@ Local development needs no secret setup. To preview a production build locally,
129
129
 
130
130
  Responses to signed-in requests get `Cache-Control: no-store`, so neither the browser cache nor a shared cache keeps a user's data. A route keeps its own policy when it sets `no-store`, or `private` together with `no-cache`. To answer repeat requests with `304`, use `private, no-cache` with Hono's [`etag()`](https://hono.dev/docs/middleware/builtin/etag) middleware. If you set the `ETag` yourself, make it differ per user, so the `304` doesn't reuse another account's page. The browser cache keeps a copy of a `private, no-cache` page, so the Back button can show it again after the user signs out. Keep the default `no-store` for pages that must not stay in the browser cache.
131
131
 
132
+ Static files from your build and `public/` keep their own cache policy for signed-in requests, so the browser reuses cached JavaScript and CSS. When your app handles the request, the policy is marked `private`, and a request that refreshes the session cookie gets `no-store`.
133
+
132
134
  `POST`, `PUT`, `PATCH` and `DELETE` requests and WebSocket connections from another origin don't use the signed-in session unless that origin is in `trustedOrigins`. If a separate frontend calls your app, add its origin there (see [Customization](#customization)). If your routes use their own cookies, check the request `Origin` yourself, for example with Hono's [`csrf()`](https://hono.dev/docs/middleware/builtin/csrf) middleware.
133
135
 
134
136
  Deployed apps on Cloudflare limit auth requests per client IP with Better Auth's defaults, for example 3 sign-in attempts per 10 seconds. Each Worker instance keeps its own count. Change or turn off the limits with `rateLimit` in `auth.ts`. To share one count across instances, set `rateLimit: { storage: 'database' }`, then run `void db generate` for a direct Cloudflare deploy.
@@ -184,7 +186,7 @@ For the full set of available options, see the official [Better Auth options ref
184
186
 
185
187
  ## Database and Migrations
186
188
 
187
- Auth tables live alongside your app's tables. During local development, Void creates them automatically.
189
+ Auth tables live alongside your app's tables. During local development, Void creates them automatically. While the dev server runs, saving `auth.ts`, or a local file it imports, updates the auth schema Void generates for your app. New auth tables and columns are created in the local database the next time the dev server starts.
188
190
 
189
191
  Void platforms create auth tables automatically. For a direct Cloudflare deploy, include them in your checked-in migrations:
190
192
 
@@ -37,6 +37,16 @@ With `void deploy --platform cloudflare`, generated JavaScript bundles with cont
37
37
 
38
38
  Use [Custom Headers](./headers) to choose a cache policy for other files. Apply immutable caching only to URLs that change whenever their content changes.
39
39
 
40
+ Cloudflare serves these fingerprinted JavaScript bundles without starting your Worker, so they do not count as Worker requests, when your app:
41
+
42
+ - has no global middleware
43
+ - has no header, redirect, rewrite or fallback rules, and no `routing.notFound` setting
44
+ - uses the default `base` of `/`
45
+ - has no route or page that matches paths in the assets directory (`assets/` by default)
46
+ - builds at most 99 fingerprinted bundles
47
+
48
+ Otherwise these bundles go through your Worker and keep the same caching.
49
+
40
50
  ## Non-hashed assets
41
51
 
42
52
  Other static files, such as `index.html` and `favicon.ico`, are cached until the next deploy. You do not need to purge them manually.
@@ -16,7 +16,7 @@ void email domain add acme.com
16
16
 
17
17
  `add` opens a Cloudflare API-token template. Restrict the token to the selected account and mail zone before creating it; Workers Scripts permission applies across that account. The CLI accepts a masked paste or a newly copied token and asks you to confirm those restrictions. The platform encrypts the credential for the zone connection, so projects sharing that zone do not need separate ingress Workers.
18
18
 
19
- Platform-managed custom inbound email requires the apex of a Cloudflare zone. Cloudflare’s catch-all only covers that apex, so a subdomain of the selected zone cannot receive arbitrary addresses. `add` refuses to register a new apex that already has MX records, or whose catch-all already routes elsewhere, so it never takes over mail hosted with another provider such as Microsoft 365 or Google Workspace. Use an apex that has no mail yet, or the platform’s shared mail address. Running `add` again for a registered apex, for example to renew its token, keeps working. Each custom apex belongs to one project; a project can register more than one apex.
19
+ Platform-managed custom inbound email requires the apex of a Cloudflare zone. Cloudflare’s catch-all only covers that apex, so a subdomain of the selected zone cannot receive arbitrary addresses. `add` refuses to register a new apex that already has MX records, or whose catch-all already routes elsewhere, so it never takes over mail hosted with another provider such as Microsoft 365 or Google Workspace. Use an apex that has no mail yet, or the platform’s shared mail address. To only send from an apex that keeps its current mail provider, ask your platform administrator to register it as [outbound-only](../platform/administration/email.md#sending-from-a-domain-that-already-receives-mail). Running `add` again for a registered apex, for example to renew its token, keeps working. Each custom apex belongs to one project; a project can register more than one apex.
20
20
 
21
21
  If setup is interrupted, use `void email domain status <domain>` to inspect the saved operation before retrying.
22
22
 
@@ -94,6 +94,8 @@ deployed `email/` handlers without per-project DNS setup once the platform has v
94
94
  handlers once its inbound readiness is `ready`. Use `void email domain status`
95
95
  to check current provider routing and any remaining setup steps.
96
96
 
97
+ A project with an [email alias](../../reference/cli/email.md#void-email-alias) also receives at `<alias>+anything@<mail domain>`. Routing ignores which prefix was used: with the slug `acme-store` and the alias `acme`, both `acme-store+support@…` and `acme+support@…` reach `email/support.ts`.
98
+
97
99
  For direct deployments, follow [Your own Cloudflare account](./domains.md#your-own-cloudflare-account) to configure inbound addresses.
98
100
 
99
101
  The local dev server provides the outbound email inbox but has no inbound email trigger. Test inbound handlers with `createInboundTestHarness` below.