void 0.7.13 → 0.8.1

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 (133) hide show
  1. package/dist/{agents-DqkFfc2c.mjs → agents-Dkgafwfr.mjs} +1 -1
  2. package/dist/{auth-cmd-bS88H4Lb.mjs → auth-cmd-B1-7r6ng.mjs} +3 -3
  3. package/dist/{better-auth-shared-D2JaoCs4.mjs → better-auth-shared-CbAF6sOM.mjs} +1 -1
  4. package/dist/{better-auth-shared-HXlHRw02.d.mts → better-auth-shared-Fzyk5yS1.d.mts} +2 -7
  5. package/dist/{cache-DUDCNty6.mjs → cache-CGYC-2LW.mjs} +5 -5
  6. package/dist/{cancel-deploy-DOuSbPwX.mjs → cancel-deploy-B4j31z7d.mjs} +4 -4
  7. package/dist/canonical-json-CEyQaVDa.mjs +13 -0
  8. package/dist/cli/cli.mjs +44 -31
  9. package/dist/{client-C_DYiXeu.mjs → client-C-nH6yz3.mjs} +5 -5
  10. package/dist/{collect-HqAuilH6.mjs → collect-9rO3JFNM.mjs} +1 -1
  11. package/dist/{config-DyUSwbeX.mjs → config--wXmVOoe.mjs} +10 -8
  12. package/dist/{config-CeRQg3Lz.mjs → config-BVEC0lti.mjs} +11 -2
  13. package/dist/{config-CvHtTM0q.mjs → config-hhMYPVRT.mjs} +3 -12
  14. package/dist/{create-project-hvFe6rbI.mjs → create-project-BGnQMy5N.mjs} +44 -18
  15. package/dist/{db-YEYSTfGw.mjs → db-CT5Xg6ds.mjs} +42 -37
  16. package/dist/{delete-XyM_VD9o.mjs → delete-eMGTHHB9.mjs} +5 -5
  17. package/dist/{deploy-CPAEgVQm.mjs → deploy-DbmbcKID.mjs} +209 -127
  18. package/dist/{discover-CKGQpis8.mjs → discover-C8N1I4tK.mjs} +9 -6
  19. package/dist/{domain-Dvj7u8tr.mjs → domain-UfLrp2vc.mjs} +64 -22
  20. package/dist/{drizzle-Cx7cVwrE.mjs → drizzle-DmaANP4-.mjs} +1 -1
  21. package/dist/{env-Bjl0rzuf.mjs → env-Cvdbbm_N.mjs} +7 -7
  22. package/dist/{env-helpers-Dw95CU8e.d.mts → env-helpers-Bt9yYg5I.d.mts} +1 -1
  23. package/dist/{env-types-BSQghXbx.mjs → env-types-D6qI1ThV.mjs} +8 -19
  24. package/dist/{env-validation-Buy5pe3u.mjs → env-validation-DBJsxZLz.mjs} +4 -4
  25. package/dist/{gen-DU8NIdrH.mjs → gen-Cn2hZ8RR.mjs} +37 -31
  26. package/dist/{handler-DJTuxB10.d.mts → handler-BPhL8AmU.d.mts} +3 -3
  27. package/dist/{headers-CuVHn6Kl.mjs → headers-wAPJigtE.mjs} +3 -3
  28. package/dist/index.d.mts +3 -3
  29. package/dist/index.mjs +273 -104
  30. package/dist/{init-Br5DTaG7.mjs → init-CbNCFFWx.mjs} +105 -35
  31. package/dist/{link-DdXqlUiJ.mjs → link-C5x1E-XZ.mjs} +5 -5
  32. package/dist/{list-9Bxa5Odn.mjs → list-ezL7hqDV.mjs} +5 -5
  33. package/dist/{login-C_MfDjhu.mjs → login-CkLr-AaI.mjs} +3 -3
  34. package/dist/{logs-DjqCpEER.mjs → logs-MnGILmLz.mjs} +4 -4
  35. package/dist/{mcp-vA0880_K.mjs → mcp-B84MmVAW.mjs} +3 -3
  36. package/dist/{node-gxRQXIRT.mjs → node-fpywdcbN.mjs} +6 -6
  37. package/dist/{output-D3g5otMC.mjs → output-DeiS4oEX.mjs} +1 -1
  38. package/dist/pages/client.d.mts +1 -1
  39. package/dist/pages/head-client.d.mts +1 -1
  40. package/dist/pages/head.d.mts +1 -1
  41. package/dist/pages/index.d.mts +28 -3
  42. package/dist/pages/index.mjs +6 -5
  43. package/dist/pages/islands-plugin.d.mts +1 -1
  44. package/dist/pages/islands-plugin.mjs +7 -5
  45. package/dist/pages/protocol.d.mts +2 -2
  46. package/dist/pages/protocol.mjs +1 -1
  47. package/dist/{pathe.M-eThtNZ-D-kmWkCS.mjs → pathe.M-eThtNZ-BrPhGF_K.mjs} +1 -1
  48. package/dist/plan-aD3wRDqF.mjs +271 -0
  49. package/dist/{plugin-inference-BexV6LL3.mjs → plugin-inference-Ba1uGdIy.mjs} +26 -20
  50. package/dist/{prepare-BEkH89oX.mjs → prepare-BrAISonG.mjs} +29 -19
  51. package/dist/{preset-CYQh93UF.mjs → preset-BRsCi8ZB.mjs} +10 -8
  52. package/dist/{project-DhB4A8_g.mjs → project-CH9pdo16.mjs} +1 -1
  53. package/dist/{project-cmd-CeXO06hG.mjs → project-cmd-JQUzv3qF.mjs} +10 -10
  54. package/dist/project-paths-CCMrHYQm.mjs +100 -0
  55. package/dist/{project-slug-KRvHQEQI.mjs → project-slug-De4UjtD9.mjs} +0 -1
  56. package/dist/{project-tsconfig-Bfy55qvs.mjs → project-tsconfig-HAGjfY6w.mjs} +10 -5
  57. package/dist/{protocol-D7r_deVZ.d.mts → protocol-mcSsdMij.d.mts} +3 -3
  58. package/dist/providers--_pvxpNN.d.mts +7 -0
  59. package/dist/{resolve-project-DdjLQ2tB.mjs → resolve-project-CNpUbUHd.mjs} +1 -1
  60. package/dist/{rollback-C8OSMxmq.mjs → rollback-CoAAvC18.mjs} +4 -4
  61. package/dist/{route-types-qVL-v-CB.mjs → route-types-jxRfWuCb.mjs} +1 -1
  62. package/dist/{runner-C0d3lJo1.mjs → runner-BkGBfr4e.mjs} +4 -2
  63. package/dist/{runner-pg-Bd_172vF.mjs → runner-pg-BXP0JpHW.mjs} +1 -1
  64. package/dist/runtime/ai.mjs +2 -2
  65. package/dist/runtime/auth.d.mts +1 -1
  66. package/dist/runtime/better-auth-pg.d.mts +1 -1
  67. package/dist/runtime/better-auth-pg.mjs +2 -2
  68. package/dist/runtime/better-auth.d.mts +1 -1
  69. package/dist/runtime/better-auth.mjs +2 -2
  70. package/dist/runtime/client.d.mts +2 -2
  71. package/dist/runtime/client.mjs +1 -1
  72. package/dist/runtime/env-helpers.d.mts +1 -1
  73. package/dist/runtime/env-public-client.d.mts +1 -1
  74. package/dist/runtime/env-public.d.mts +2 -2
  75. package/dist/runtime/env-public.mjs +1 -1
  76. package/dist/runtime/env.mjs +1 -1
  77. package/dist/runtime/fetch-stream.d.mts +1 -1
  78. package/dist/runtime/fetch-stream.mjs +1 -1
  79. package/dist/runtime/fetch.d.mts +1 -1
  80. package/dist/runtime/fetch.mjs +1 -1
  81. package/dist/runtime/handler.d.mts +1 -1
  82. package/dist/runtime/handler.mjs +1 -1
  83. package/dist/runtime/isr.mjs +1 -1
  84. package/dist/runtime/live.d.mts +2 -2
  85. package/dist/runtime/live.mjs +1 -1
  86. package/dist/runtime/migration-handler.d.mts +33 -1
  87. package/dist/runtime/migration-handler.mjs +332 -24
  88. package/dist/runtime/remote/index.mjs +1 -1
  89. package/dist/runtime/sandbox.d.mts +73 -3
  90. package/dist/runtime/sandbox.mjs +239 -3
  91. package/dist/runtime/validator.d.mts +1 -1
  92. package/dist/runtime/ws-server.d.mts +2 -2
  93. package/dist/runtime/ws.d.mts +3 -3
  94. package/dist/{scan-CKpylFj9.mjs → scan-2YmJkYAf.mjs} +4 -3
  95. package/dist/{scan-CkK1Hzpa.mjs → scan-DzzHqtiT.mjs} +82 -43
  96. package/dist/{secret-NDvlqAN2.mjs → secret-mz8t_CML.mjs} +5 -5
  97. package/dist/{skills-SAG0WyVn.mjs → skills-B_ynF4lA.mjs} +3 -3
  98. package/dist/{subcommand-prompt-q9T06dpg.mjs → subcommand-prompt-CPuV7tPW.mjs} +2 -2
  99. package/dist/sveltekit.mjs +1 -1
  100. package/dist/{validate-DW3ViKTA.mjs → validate-CNWm-PsL.mjs} +49 -9
  101. package/dist/{yarn-pnp-6LD6_3Ej.mjs → yarn-pnp-QgEYVC3V.mjs} +1 -1
  102. package/package.json +3 -2
  103. package/schema.json +5 -0
  104. package/skills/void/docs/guide/ai.md +1 -1
  105. package/skills/void/docs/guide/app-types.md +1 -1
  106. package/skills/void/docs/guide/database/postgresql.md +4 -3
  107. package/skills/void/docs/guide/edge/headers.md +1 -1
  108. package/skills/void/docs/guide/edge/static-assets.md +34 -0
  109. package/skills/void/docs/guide/sandboxes.md +3 -2
  110. package/skills/void/docs/guide/ssr.md +25 -2
  111. package/skills/void/docs/integrations/cloudflare.md +5 -3
  112. package/skills/void/docs/integrations/frameworks/overview.md +1 -1
  113. package/skills/void/docs/integrations/nodejs-bun-deno.md +10 -9
  114. package/skills/void/docs/reference/api.md +204 -13
  115. package/skills/void/docs/reference/cli.md +53 -7
  116. package/skills/void/docs/reference/config.md +27 -4
  117. package/skills/void/docs/reference/resource-inference.md +1 -1
  118. /package/dist/{auth-DmuALf16.d.mts → auth-SfRBhXan.d.mts} +0 -0
  119. /package/dist/{auth-migrations-BwLPwRgH.mjs → auth-migrations-BqJoGqGQ.mjs} +0 -0
  120. /package/dist/{cf-access-BW8K93Fm.mjs → cf-access-DKDsgwOU.mjs} +0 -0
  121. /package/dist/{defer-2ARBu8Et.mjs → defer-DkoEwda-.mjs} +0 -0
  122. /package/dist/{dist-q8b2Mjgb.mjs → dist-DR9sIMbM.mjs} +0 -0
  123. /package/dist/{dist-D2L3_KTK.mjs → dist-abzUneor.mjs} +0 -0
  124. /package/dist/{dotenv-HQNhRalS.mjs → dotenv-Bkoqyq9r.mjs} +0 -0
  125. /package/dist/{env-raw-DtfQ9E31.mjs → env-raw-zk5JQKZe.mjs} +0 -0
  126. /package/dist/{fetch-error-DZ868Xq4.d.mts → fetch-error-BbixZ9vW.d.mts} +0 -0
  127. /package/dist/{fetch-error-CEr0ACTl.mjs → fetch-error-rXermVU3.mjs} +0 -0
  128. /package/dist/{head-C7QW7UY1.d.mts → head-CZs8dMxG.d.mts} +0 -0
  129. /package/dist/{log-7ChR5Fbc.mjs → log-CWWZV4V1.mjs} +0 -0
  130. /package/dist/{magic-string.es-DS_lmuBe.mjs → magic-string.es-C1Fb0uxq.mjs} +0 -0
  131. /package/dist/{providers-BlFNPYen.mjs → providers-uC0PJg1c.mjs} +0 -0
  132. /package/dist/{standard-schema-D2qvCEYV.d.mts → standard-schema-WhHcCaqJ.d.mts} +0 -0
  133. /package/dist/{types-DG_Ynnyd.d.mts → types-C8wsi1hv.d.mts} +0 -0
@@ -1,19 +1,255 @@
1
+ import { i as getRawRuntimeEnv } from "../env-raw-zk5JQKZe.mjs";
1
2
  import { requireRuntimeBinding } from "./env.mjs";
2
3
  import { Sandbox, getSandbox as getSandbox$1 } from "@cloudflare/sandbox";
3
4
  //#region src/runtime/sandbox.ts
4
5
  const DEFAULT_SANDBOX_ID = "default";
5
6
  const DEFAULT_SANDBOX_BINDING = "SANDBOX";
7
+ const SANDBOX_USAGE_QUEUE_BINDING = "USAGE_EVENTS_QUEUE";
8
+ const SANDBOX_GATE_BINDING = "__VOID_PROXY";
9
+ const HEARTBEAT_MS = 5 * 6e4;
10
+ /**
11
+ * Thrown by `getSandbox()` when the platform gate denies a new acquire because
12
+ * the user has hit a concurrency or runtime budget cap. Surface this to the
13
+ * caller so they can render a friendly error; do not retry transparently.
14
+ */
15
+ var SandboxLimitError = class extends Error {
16
+ name = "SandboxLimitError";
17
+ reason;
18
+ constructor(reason, message) {
19
+ super(message ?? `Sandbox limit reached: ${reason}`);
20
+ this.reason = reason;
21
+ }
22
+ };
6
23
  let sandboxBindingName = DEFAULT_SANDBOX_BINDING;
7
24
  function __setSandboxBindingName(bindingName) {
8
25
  sandboxBindingName = bindingName;
9
26
  }
10
- function getSandbox(id = DEFAULT_SANDBOX_ID, options) {
27
+ const STORAGE_KEY_START_TS = "__voidStartTs";
28
+ const STORAGE_KEY_LAST_FLUSH_TS = "__voidLastFlushTs";
29
+ function getUsageContext() {
30
+ const runtimeEnv = getRawRuntimeEnv();
31
+ return {
32
+ userId: runtimeEnv.__VOID_USER_ID,
33
+ projectId: runtimeEnv.__PROJECT_ID,
34
+ slug: runtimeEnv.__VOID_PROJECT_SLUG
35
+ };
36
+ }
37
+ function getSandboxInstanceId(ctx) {
38
+ return ctx.id.name ?? ctx.id.toString();
39
+ }
40
+ /**
41
+ * Flush the accumulated runtime delta to the usage queue. No-op when the
42
+ * usage-queue binding is not bound (e.g. local dev without the platform
43
+ * proxy). Errors are swallowed and logged — usage emission must never crash
44
+ * the sandbox lifecycle.
45
+ */
46
+ async function emitRuntimeSeconds(ctx, sandboxRuntimeSeconds) {
47
+ if (sandboxRuntimeSeconds <= 0) return;
48
+ const queue = getRawRuntimeEnv()[SANDBOX_USAGE_QUEUE_BINDING];
49
+ if (!queue) return;
50
+ const event = {
51
+ requests: 0,
52
+ responseBytes: 0,
53
+ sandboxRuntimeSeconds,
54
+ sandboxInstanceId: getSandboxInstanceId(ctx),
55
+ ...getUsageContext()
56
+ };
57
+ try {
58
+ await queue.send(event);
59
+ } catch (error) {
60
+ console.error("[void/sandbox] failed to emit usage event:", error);
61
+ }
62
+ }
63
+ /**
64
+ * Resolve the `{ gate, projectId, aiToken }` triple needed to call a gate
65
+ * route. Returns `null` when any piece is missing — local dev (no proxy
66
+ * binding) and partial deploys (binding present but identity vars absent)
67
+ * both fall through to the no-op path so a missing platform setup never
68
+ * blocks sandbox acquisition.
69
+ *
70
+ * Wire field is named `aiToken` because the shared proxy auth middleware
71
+ * (packages/proxy/src/index.ts) verifies HMAC against `body.aiToken` for
72
+ * every route. Naming it `proxyToken` here would diverge from the existing
73
+ * AI/ISR convention without changing the on-the-wire contract.
74
+ */
75
+ function resolveGateIdentity() {
76
+ const runtimeEnv = getRawRuntimeEnv();
77
+ const gate = runtimeEnv[SANDBOX_GATE_BINDING];
78
+ if (!gate) return null;
79
+ const projectId = runtimeEnv.__PROJECT_ID;
80
+ const aiToken = runtimeEnv.__VOID_PROXY_TOKEN;
81
+ if (!projectId || !aiToken) return null;
82
+ return {
83
+ gate,
84
+ projectId,
85
+ aiToken
86
+ };
87
+ }
88
+ /**
89
+ * Release notification to the gate so it can drop the active lease counter.
90
+ * Returns the in-flight fetch as a Promise (errors swallowed) so callers can
91
+ * route it through `ctx.waitUntil()` and keep it alive past lifecycle
92
+ * teardown. Failure is silent — the gate maintains its own TTL fallback.
93
+ */
94
+ function notifyGateRelease(ctx) {
95
+ const identity = resolveGateIdentity();
96
+ if (!identity) return Promise.resolve();
97
+ const sandboxId = getSandboxInstanceId(ctx);
98
+ return identity.gate.fetch("https://gate/sandbox/release", {
99
+ method: "POST",
100
+ headers: { "content-type": "application/json" },
101
+ body: JSON.stringify({
102
+ projectId: identity.projectId,
103
+ aiToken: identity.aiToken,
104
+ sandboxId
105
+ })
106
+ }).then(() => void 0).catch(() => {});
107
+ }
108
+ /**
109
+ * Heartbeat-driven keepalive ping for the gate's concurrency slot. The DO's
110
+ * `STALE_MS` (1h) sweep would otherwise reap slots for long-running sandbox
111
+ * sessions whose `acquiredAt` ages past the threshold between acquire and
112
+ * release, letting new acquires exceed `sandboxMaxConcurrentInstances`. The
113
+ * 5 min heartbeat sits well inside the sweep window, so each successful
114
+ * refresh resets the stale clock. Returns the in-flight fetch as a Promise
115
+ * (errors swallowed) so callers can route it through `ctx.waitUntil()` and
116
+ * keep the alarm path itself off the critical path — a failed refresh just
117
+ * means the slot ages out naturally, which matches the pre-existing failure
118
+ * mode if the gate goes down entirely.
119
+ */
120
+ function notifyGateRefresh(ctx) {
121
+ const identity = resolveGateIdentity();
122
+ if (!identity) return Promise.resolve();
123
+ const sandboxId = getSandboxInstanceId(ctx);
124
+ return identity.gate.fetch("https://gate/sandbox/refresh", {
125
+ method: "POST",
126
+ headers: { "content-type": "application/json" },
127
+ body: JSON.stringify({
128
+ projectId: identity.projectId,
129
+ aiToken: identity.aiToken,
130
+ sandboxId
131
+ })
132
+ }).then(() => void 0).catch(() => {});
133
+ }
134
+ /**
135
+ * Sandbox variant that participates in Void platform metering: it records
136
+ * container runtime seconds into DO storage, emits periodic heartbeats to
137
+ * the usage queue, and notifies the gate when the container stops.
138
+ *
139
+ * Generated code from `void inference` wires user `Sandbox` subclasses to
140
+ * extend this class on platform deploys so per-tenant accounting works
141
+ * without user opt-in. On local dev (no usage-queue / gate bindings), all
142
+ * overrides degrade to no-ops and the sandbox behaves like the upstream
143
+ * `Sandbox` class.
144
+ */
145
+ var VoidPlatformSandbox = class extends Sandbox {
146
+ async onStart() {
147
+ const now = Date.now();
148
+ const stored = await this.ctx.storage.get([STORAGE_KEY_START_TS, STORAGE_KEY_LAST_FLUSH_TS]);
149
+ const orphanedAnchor = stored.get(STORAGE_KEY_LAST_FLUSH_TS) ?? stored.get(STORAGE_KEY_START_TS);
150
+ if (orphanedAnchor !== void 0 && orphanedAnchor < now) {
151
+ const sandboxRuntimeSeconds = (now - orphanedAnchor) / 1e3;
152
+ await emitRuntimeSeconds(this.ctx, sandboxRuntimeSeconds);
153
+ await this.ctx.storage.delete([STORAGE_KEY_START_TS, STORAGE_KEY_LAST_FLUSH_TS]);
154
+ }
155
+ try {
156
+ await super.onStart();
157
+ } catch (err) {
158
+ this.ctx.waitUntil(notifyGateRelease(this.ctx));
159
+ throw err;
160
+ }
161
+ const startedAt = Date.now();
162
+ await this.ctx.storage.put({
163
+ [STORAGE_KEY_START_TS]: startedAt,
164
+ [STORAGE_KEY_LAST_FLUSH_TS]: startedAt
165
+ });
166
+ await this.ctx.storage.setAlarm(startedAt + HEARTBEAT_MS);
167
+ }
168
+ async alarm(alarmProps) {
169
+ await super.alarm(alarmProps);
170
+ const lastFlush = await this.ctx.storage.get(STORAGE_KEY_LAST_FLUSH_TS);
171
+ if (typeof lastFlush !== "number") return;
172
+ const now = Date.now();
173
+ const sandboxRuntimeSeconds = Math.max(0, (now - lastFlush) / 1e3);
174
+ await emitRuntimeSeconds(this.ctx, sandboxRuntimeSeconds);
175
+ this.ctx.waitUntil(notifyGateRefresh(this.ctx));
176
+ await Promise.all([this.ctx.storage.put(STORAGE_KEY_LAST_FLUSH_TS, now), this.ctx.storage.setAlarm(now + HEARTBEAT_MS)]);
177
+ }
178
+ async onStop() {
179
+ await this.flushFinal();
180
+ this.ctx.waitUntil(notifyGateRelease(this.ctx));
181
+ await super.onStop();
182
+ }
183
+ async onActivityExpired() {
184
+ await this.flushFinal();
185
+ this.ctx.waitUntil(notifyGateRelease(this.ctx));
186
+ await super.onActivityExpired();
187
+ }
188
+ async flushFinal() {
189
+ const lastFlush = await this.ctx.storage.get(STORAGE_KEY_LAST_FLUSH_TS);
190
+ if (typeof lastFlush === "number") {
191
+ const sandboxRuntimeSeconds = Math.max(0, (Date.now() - lastFlush) / 1e3);
192
+ await emitRuntimeSeconds(this.ctx, sandboxRuntimeSeconds);
193
+ }
194
+ await this.ctx.storage.delete(STORAGE_KEY_START_TS);
195
+ await this.ctx.storage.delete(STORAGE_KEY_LAST_FLUSH_TS);
196
+ }
197
+ };
198
+ /**
199
+ * Resolve the platform-provided sandbox stub for the given id, optionally
200
+ * coordinating with the platform gate to enforce per-user concurrency and
201
+ * runtime budgets.
202
+ *
203
+ * When the gate binding (`__VOID_PROXY` by default) is present AND the
204
+ * platform's `__PROJECT_ID` + `__VOID_PROXY_TOKEN` identity vars are
205
+ * available, we POST to `https://gate/sandbox/acquire` with the HMAC-signed
206
+ * body the shared proxy auth middleware expects. A `409` response means the
207
+ * user has exceeded a budget — we throw `SandboxLimitError` so the caller
208
+ * can render a clear message. Any other non-OK status (or a thrown transport
209
+ * error from the service binding) is treated as "gate unavailable" and we
210
+ * fall through to returning the stub so a broken gate doesn't break sandbox
211
+ * access entirely.
212
+ *
213
+ * When the gate binding is absent (local dev) or the identity vars are
214
+ * missing (partial deploy), the gate check is skipped entirely and we
215
+ * return the stub directly.
216
+ *
217
+ * The stub is constructed BEFORE calling the gate so we can derive the
218
+ * normalized DO name (`stub.id.name`) and pass it to the gate as the
219
+ * `sandboxId`. This keeps acquire and release using the same key — without
220
+ * it, mixed-case caller ids (e.g. `MyProject-123`) would acquire a lease
221
+ * under the raw id but release it under the normalized lowercase form that
222
+ * `ctx.id.name` reports inside the DO, leaking the lease until TTL.
223
+ * Constructing the stub does NOT start a container — that only happens on
224
+ * the first method call against the stub.
225
+ */
226
+ async function getSandbox(id = DEFAULT_SANDBOX_ID, options) {
11
227
  const { binding = sandboxBindingName, ...sandboxOptions } = options ?? {};
12
- return getSandbox$1(requireRuntimeBinding(binding), id, {
228
+ const stub = getSandbox$1(requireRuntimeBinding(binding), id, {
13
229
  normalizeId: true,
14
230
  ...sandboxOptions
15
231
  });
232
+ const sandboxId = stub.id.name ?? id;
233
+ const identity = resolveGateIdentity();
234
+ if (identity) try {
235
+ const res = await identity.gate.fetch("https://gate/sandbox/acquire", {
236
+ method: "POST",
237
+ headers: { "content-type": "application/json" },
238
+ body: JSON.stringify({
239
+ projectId: identity.projectId,
240
+ aiToken: identity.aiToken,
241
+ sandboxId
242
+ })
243
+ });
244
+ if (res.status === 409) {
245
+ const body = await res.json().catch(() => ({}));
246
+ throw new SandboxLimitError(body.reason === "concurrency" || body.reason === "runtime_budget" ? body.reason : "concurrency");
247
+ }
248
+ } catch (error) {
249
+ if (error instanceof SandboxLimitError) throw error;
250
+ }
251
+ return stub;
16
252
  }
17
253
  const sandbox = { get: getSandbox };
18
254
  //#endregion
19
- export { DEFAULT_SANDBOX_BINDING, DEFAULT_SANDBOX_ID, Sandbox, __setSandboxBindingName, getSandbox, sandbox };
255
+ export { DEFAULT_SANDBOX_BINDING, DEFAULT_SANDBOX_ID, HEARTBEAT_MS, SANDBOX_GATE_BINDING, SANDBOX_USAGE_QUEUE_BINDING, Sandbox, SandboxLimitError, VoidPlatformSandbox, __setSandboxBindingName, getSandbox, sandbox };
@@ -1,2 +1,2 @@
1
- import { C as HandlerInput, T as runValidation, w as ValidatorSlots } from "../handler-DJTuxB10.mjs";
1
+ import { C as HandlerInput, T as runValidation, w as ValidatorSlots } from "../handler-BPhL8AmU.mjs";
2
2
  export { HandlerInput, ValidatorSlots, runValidation };
@@ -1,5 +1,5 @@
1
- import { n as AuthState } from "../auth-DmuALf16.mjs";
2
- import { i as VoidAuthRuntimeOptions, n as VoidAuthEnv } from "../better-auth-shared-HXlHRw02.mjs";
1
+ import { n as AuthState } from "../auth-SfRBhXan.mjs";
2
+ import { i as VoidAuthRuntimeOptions, n as VoidAuthEnv } from "../better-auth-shared-Fzyk5yS1.mjs";
3
3
  import { AnyWebSocketDefinition } from "./ws.mjs";
4
4
 
5
5
  //#region src/runtime/ws-server.d.ts
@@ -1,6 +1,6 @@
1
- import { r as AuthUser } from "../auth-DmuALf16.mjs";
2
- import { t as StandardSchemaV1 } from "../standard-schema-D2qvCEYV.mjs";
3
- import { i as CloudEnv } from "../handler-DJTuxB10.mjs";
1
+ import { r as AuthUser } from "../auth-SfRBhXan.mjs";
2
+ import { t as StandardSchemaV1 } from "../standard-schema-WhHcCaqJ.mjs";
3
+ import { i as CloudEnv } from "../handler-BPhL8AmU.mjs";
4
4
  import { WebSocketRouteMap } from "void/routes";
5
5
 
6
6
  //#region src/runtime/ws.d.ts
@@ -1,5 +1,6 @@
1
- import { a as join, n as dirname, r as extname, t as basename } from "./pathe.M-eThtNZ-D-kmWkCS.mjs";
2
- import { t as glob } from "./dist-q8b2Mjgb.mjs";
1
+ import { a as join, n as dirname, r as extname, t as basename } from "./pathe.M-eThtNZ-BrPhGF_K.mjs";
2
+ import { n as readProjectPaths } from "./project-paths-CCMrHYQm.mjs";
3
+ import { t as glob } from "./dist-DR9sIMbM.mjs";
3
4
  import { existsSync, readFileSync } from "node:fs";
4
5
  //#region src/router/parse-filename.ts
5
6
  const EXTENSIONS = [
@@ -167,7 +168,7 @@ const LAYOUT_STRING_RE = /export\s+const\s+layout\s*=\s*["']([^"']+)["']\s*;?/;
167
168
  const LAYOUT_FALSE_RE = /export\s+const\s+layout\s*=\s*false\s*;?/;
168
169
  const MAX_REVALIDATE = 31536e3;
169
170
  async function scanPages(root, options) {
170
- const pagesDir = join(root, "pages");
171
+ const pagesDir = options?.pagesDir ?? options?.paths?.pagesDir ?? readProjectPaths(root).pagesDir;
171
172
  if (!existsSync(pagesDir)) return {
172
173
  pages: [],
173
174
  layouts: [],
@@ -1,7 +1,8 @@
1
- import { a as join, t as basename } from "./pathe.M-eThtNZ-D-kmWkCS.mjs";
2
- import { n as globSync, t as glob } from "./dist-q8b2Mjgb.mjs";
3
- import { d as isCallExpression, f as isIdentifier, h as isObjectExpression, p as isLiteral } from "./plugin-inference-BexV6LL3.mjs";
4
- import { o as parseRouteFilename, s as parseWebSocketFilename, t as scanPages } from "./scan-CKpylFj9.mjs";
1
+ import { a as join, i as isAbsolute, t as basename } from "./pathe.M-eThtNZ-BrPhGF_K.mjs";
2
+ import { n as readProjectPaths } from "./project-paths-CCMrHYQm.mjs";
3
+ import { n as globSync, t as glob } from "./dist-DR9sIMbM.mjs";
4
+ import { d as isCallExpression, f as isIdentifier, h as isObjectExpression, p as isLiteral } from "./plugin-inference-Ba1uGdIy.mjs";
5
+ import { o as parseRouteFilename, s as parseWebSocketFilename, t as scanPages } from "./scan-2YmJkYAf.mjs";
5
6
  import { readFileSync } from "node:fs";
6
7
  import { parseSync } from "vite";
7
8
  //#region src/crons/scan.ts
@@ -31,9 +32,13 @@ function extractCronExpressions(source, filePath) {
31
32
  }
32
33
  throw new Error(`cron: Invalid job file '${filePath}'. Add \`export const cron = "<schedule>"\`.`);
33
34
  }
35
+ function resolveDir(root, dir) {
36
+ return isAbsolute(dir) ? dir : join(root, dir);
37
+ }
34
38
  function parseJobFiles(root, jobsDir, files) {
39
+ const jobsDirPath = resolveDir(root, jobsDir);
35
40
  return files.filter((filePath) => !filePath.split("/").some((segment) => segment.startsWith("_"))).sort((leftFilePath, rightFilePath) => leftFilePath.localeCompare(rightFilePath)).map((filePath) => {
36
- const source = readFileSync(join(root, jobsDir, filePath), "utf-8");
41
+ const source = readFileSync(join(jobsDirPath, filePath), "utf-8");
37
42
  return {
38
43
  filePath,
39
44
  name: extractJobName(filePath),
@@ -43,7 +48,7 @@ function parseJobFiles(root, jobsDir, files) {
43
48
  }
44
49
  async function scanJobs(root, jobsDir = "crons") {
45
50
  return parseJobFiles(root, jobsDir, await glob(`**/*.{${JOB_EXTENSIONS.join(",")}}`, {
46
- cwd: `${root}/${jobsDir}`,
51
+ cwd: resolveDir(root, jobsDir),
47
52
  absolute: false
48
53
  }).catch(() => []));
49
54
  }
@@ -52,7 +57,7 @@ function scanJobsSync(root, jobsDir = "crons") {
52
57
  let files;
53
58
  try {
54
59
  files = globSync(pattern, {
55
- cwd: `${root}/${jobsDir}`,
60
+ cwd: resolveDir(root, jobsDir),
56
61
  absolute: false
57
62
  });
58
63
  } catch {
@@ -80,8 +85,9 @@ function extractConsumerOption(source, name) {
80
85
  return match ? Number(match[1]) : void 0;
81
86
  }
82
87
  function parseQueueFiles(root, queuesDir, files) {
88
+ const queuesDirPath = isAbsolute(queuesDir) ? queuesDir : join(root, queuesDir);
83
89
  return files.filter((filePath) => !filePath.split("/").some((segment) => segment.startsWith("_"))).sort((leftFilePath, rightFilePath) => leftFilePath.localeCompare(rightFilePath)).map((filePath) => {
84
- const source = readFileSync(join(root, queuesDir, filePath), "utf-8");
90
+ const source = readFileSync(join(queuesDirPath, filePath), "utf-8");
85
91
  const name = extractName(filePath);
86
92
  return {
87
93
  filePath,
@@ -96,7 +102,7 @@ function parseQueueFiles(root, queuesDir, files) {
96
102
  }
97
103
  async function scanQueues(root, queuesDir = "queues") {
98
104
  return parseQueueFiles(root, queuesDir, await glob(`**/*.{${QUEUE_EXTENSIONS.join(",")}}`, {
99
- cwd: `${root}/${queuesDir}`,
105
+ cwd: isAbsolute(queuesDir) ? queuesDir : join(root, queuesDir),
100
106
  absolute: false
101
107
  }).catch(() => []));
102
108
  }
@@ -105,7 +111,7 @@ function scanQueuesSync(root, queuesDir = "queues") {
105
111
  let files;
106
112
  try {
107
113
  files = globSync(pattern, {
108
- cwd: `${root}/${queuesDir}`,
114
+ cwd: isAbsolute(queuesDir) ? queuesDir : join(root, queuesDir),
109
115
  absolute: false
110
116
  });
111
117
  } catch {
@@ -159,7 +165,7 @@ function detectHttpExports(absPath) {
159
165
  }
160
166
  //#endregion
161
167
  //#region src/router/websocket.ts
162
- const HANDLER_EXTENSIONS$1 = [
168
+ const HANDLER_EXTENSIONS = [
163
169
  "ts",
164
170
  "mts",
165
171
  "js",
@@ -223,9 +229,9 @@ function detectWebSocketDefinition(absPath) {
223
229
  }
224
230
  return null;
225
231
  }
226
- function buildWebSocketRoute(root, filePath) {
232
+ function buildWebSocketRoute(root, filePath, paths = readProjectPaths(root)) {
227
233
  const parsed = parseWebSocketFilename(filePath);
228
- const info = detectWebSocketDefinition(join(root, "routes", filePath));
234
+ const info = detectWebSocketDefinition(join(paths.routesDir, filePath));
229
235
  const className = deriveWebSocketClassName(filePath);
230
236
  return {
231
237
  ...parsed,
@@ -235,63 +241,75 @@ function buildWebSocketRoute(root, filePath) {
235
241
  hasAuthGate: info?.hasAuthGate ?? false
236
242
  };
237
243
  }
238
- function parseWebSocketFiles(root, files) {
239
- return files.filter((f) => isWebSocketRouteFile(f)).filter((f) => !f.split("/").some((segment) => segment.startsWith("_"))).map((filePath) => buildWebSocketRoute(root, filePath)).sort((a, b) => {
244
+ function parseWebSocketFiles(root, files, paths = readProjectPaths(root)) {
245
+ return files.filter((f) => isWebSocketRouteFile(f)).filter((f) => !f.split("/").some((segment) => segment.startsWith("_"))).map((filePath) => buildWebSocketRoute(root, filePath, paths)).sort((a, b) => {
240
246
  if (a.catchAll !== b.catchAll) return a.catchAll ? 1 : -1;
241
247
  return a.pattern.localeCompare(b.pattern);
242
248
  });
243
249
  }
244
- async function scanWebSocketRoutes(root) {
245
- return parseWebSocketFiles(root, await glob(`**/*.{${HANDLER_EXTENSIONS$1.join(",")}}`, {
246
- cwd: `${root}/routes`,
250
+ async function scanWebSocketRoutes(root, paths = readProjectPaths(root)) {
251
+ return parseWebSocketFiles(root, await glob(`**/*.{${HANDLER_EXTENSIONS.join(",")}}`, {
252
+ cwd: paths.routesDir,
247
253
  absolute: false
248
- }).catch(() => []));
254
+ }).catch(() => []), paths);
249
255
  }
250
- function scanWebSocketRoutesSync(root) {
251
- const pattern = `**/*.{${HANDLER_EXTENSIONS$1.join(",")}}`;
256
+ function scanWebSocketRoutesSync(root, paths = readProjectPaths(root)) {
257
+ const pattern = `**/*.{${HANDLER_EXTENSIONS.join(",")}}`;
252
258
  let files;
253
259
  try {
254
260
  files = globSync(pattern, {
255
- cwd: `${root}/routes`,
261
+ cwd: paths.routesDir,
256
262
  absolute: false
257
263
  });
258
264
  } catch {
259
265
  files = [];
260
266
  }
261
- return parseWebSocketFiles(root, files);
267
+ return parseWebSocketFiles(root, files, paths);
262
268
  }
263
269
  function isWebSocketPatternFile(filePath) {
264
270
  return isWebSocketRouteFile(filePath);
265
271
  }
266
272
  //#endregion
267
273
  //#region src/router/scan.ts
268
- const HANDLER_EXTENSIONS = [
274
+ const ROUTE_PATTERN = `**/*.{${[
269
275
  "ts",
270
276
  "mts",
271
277
  "js",
272
278
  "mjs"
273
- ];
279
+ ].join(",")}}`;
280
+ function parseMiddlewareDefinitions(files) {
281
+ return files.filter((f) => !f.startsWith("_")).map((f) => {
282
+ const match = f.split("/").pop().match(/^(\d+)\./);
283
+ return {
284
+ order: match ? parseInt(match[1], 10) : Infinity,
285
+ filePath: f
286
+ };
287
+ }).sort((a, b) => a.order - b.order);
288
+ }
274
289
  /**
275
290
  * Scan routes/ and middleware/ directories and return a structured manifest.
276
291
  * Files starting with _ are ignored.
277
292
  */
278
293
  async function scanRoutes(root, options) {
279
- const pattern = `**/*.{${HANDLER_EXTENSIONS.join(",")}}`;
294
+ const paths = options?.paths ?? readProjectPaths(root);
280
295
  const [routeFiles, websockets, middlewareFiles, jobs, queues, pages] = await Promise.all([
281
- glob(pattern, {
282
- cwd: `${root}/routes`,
296
+ glob(ROUTE_PATTERN, {
297
+ cwd: paths.routesDir,
283
298
  absolute: false
284
299
  }),
285
- scanWebSocketRoutes(root),
286
- glob(pattern, {
287
- cwd: `${root}/middleware`,
300
+ scanWebSocketRoutes(root, paths),
301
+ glob(ROUTE_PATTERN, {
302
+ cwd: paths.middlewareDir,
288
303
  absolute: false
289
304
  }).catch(() => []),
290
- scanJobs(root),
291
- scanQueues(root),
292
- scanPages(root, { output: options?.output })
305
+ scanJobs(root, paths.cronsDir),
306
+ scanQueues(root, paths.queuesDir),
307
+ scanPages(root, {
308
+ output: options?.output,
309
+ paths
310
+ })
293
311
  ]);
294
- const routesDir = `${root}/routes`;
312
+ const routesDir = paths.routesDir;
295
313
  return {
296
314
  routes: routeFiles.filter((f) => !isWebSocketPatternFile(f)).filter((f) => !f.split("/").some((s) => s.startsWith("_"))).map((f) => {
297
315
  const route = parseRouteFilename(f);
@@ -302,17 +320,38 @@ async function scanRoutes(root, options) {
302
320
  return a.pattern.localeCompare(b.pattern);
303
321
  }),
304
322
  websockets,
305
- middleware: middlewareFiles.filter((f) => !f.startsWith("_")).map((f) => {
306
- const match = f.split("/").pop().match(/^(\d+)\./);
307
- return {
308
- order: match ? parseInt(match[1], 10) : Infinity,
309
- filePath: f
310
- };
311
- }).sort((a, b) => a.order - b.order),
323
+ middleware: parseMiddlewareDefinitions(middlewareFiles),
312
324
  jobs,
313
325
  queues,
314
326
  pages
315
327
  };
316
328
  }
329
+ function scanMiddlewareSync(root, paths = readProjectPaths(root)) {
330
+ let middlewareFiles;
331
+ try {
332
+ middlewareFiles = globSync(ROUTE_PATTERN, {
333
+ cwd: paths.middlewareDir,
334
+ absolute: false
335
+ });
336
+ } catch {
337
+ middlewareFiles = [];
338
+ }
339
+ return parseMiddlewareDefinitions(middlewareFiles);
340
+ }
341
+ function scanRoutePatternsSync(root, paths = readProjectPaths(root)) {
342
+ let routeFiles;
343
+ try {
344
+ routeFiles = globSync(ROUTE_PATTERN, {
345
+ cwd: paths.routesDir,
346
+ absolute: false
347
+ });
348
+ } catch {
349
+ routeFiles = [];
350
+ }
351
+ return routeFiles.filter((f) => !isWebSocketPatternFile(f)).filter((f) => !f.split("/").some((s) => s.startsWith("_"))).map((f) => parseRouteFilename(f)).sort((a, b) => {
352
+ if (a.catchAll !== b.catchAll) return a.catchAll ? 1 : -1;
353
+ return a.pattern.localeCompare(b.pattern);
354
+ });
355
+ }
317
356
  //#endregion
318
- export { scanJobsSync as i, scanWebSocketRoutesSync as n, scanQueuesSync as r, scanRoutes as t };
357
+ export { scanQueuesSync as a, scanWebSocketRoutesSync as i, scanRoutePatternsSync as n, scanJobsSync as o, scanRoutes as r, scanMiddlewareSync as t };
@@ -1,8 +1,8 @@
1
- import { n as cliTitle } from "./output-D3g5otMC.mjs";
2
- import { c as getToken, n as PlatformClient } from "./client-C_DYiXeu.mjs";
3
- import { _ as me, c as R, g as ge, n as Ce, u as Se, v as ue, x as q, y as ye } from "./dist-D2L3_KTK.mjs";
4
- import { r as readProjectConfig } from "./project-DhB4A8_g.mjs";
5
- import { r as resolveProjectBySlug, t as getRequestedProjectSlug } from "./resolve-project-DdjLQ2tB.mjs";
1
+ import { n as cliTitle } from "./output-DeiS4oEX.mjs";
2
+ import { c as getToken, n as PlatformClient } from "./client-C-nH6yz3.mjs";
3
+ import { _ as me, c as R, g as ge, n as Ce, u as Se, v as ue, x as q, y as ye } from "./dist-abzUneor.mjs";
4
+ import { r as readProjectConfig } from "./project-CH9pdo16.mjs";
5
+ import { r as resolveProjectBySlug, t as getRequestedProjectSlug } from "./resolve-project-CNpUbUHd.mjs";
6
6
  import { readFileSync } from "node:fs";
7
7
  //#region src/cli/secret.ts
8
8
  async function runSecretCommand(root, args) {
@@ -1,6 +1,6 @@
1
- import { a as join, o as relative } from "./pathe.M-eThtNZ-D-kmWkCS.mjs";
2
- import { c as R } from "./dist-D2L3_KTK.mjs";
3
- import { r as getPackageDir } from "./agents-DqkFfc2c.mjs";
1
+ import { a as join, s as relative } from "./pathe.M-eThtNZ-BrPhGF_K.mjs";
2
+ import { c as R } from "./dist-abzUneor.mjs";
3
+ import { r as getPackageDir } from "./agents-Dkgafwfr.mjs";
4
4
  import { existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, symlinkSync } from "node:fs";
5
5
  //#region src/cli/skills.ts
6
6
  function parseSkills(skillsDir) {
@@ -1,5 +1,5 @@
1
- import { s as import_picocolors } from "./output-D3g5otMC.mjs";
2
- import { a as F, b as z, d as _t, m as et, o as H, r as E, s as J, t as $ } from "./dist-D2L3_KTK.mjs";
1
+ import { s as import_picocolors } from "./output-DeiS4oEX.mjs";
2
+ import { a as F, b as z, d as _t, m as et, o as H, r as E, s as J, t as $ } from "./dist-abzUneor.mjs";
3
3
  //#region src/cli/subcommand-prompt.ts
4
4
  function promptSubcommand(command, subcommands) {
5
5
  return new Promise((resolve) => {
@@ -1,4 +1,4 @@
1
- import { a as join, i as isAbsolute, o as relative, s as resolve } from "./pathe.M-eThtNZ-D-kmWkCS.mjs";
1
+ import { a as join, c as resolve, i as isAbsolute, s as relative } from "./pathe.M-eThtNZ-BrPhGF_K.mjs";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  //#region src/sveltekit.ts
4
4
  /**
@@ -1,6 +1,7 @@
1
1
  import { n as __exportAll } from "./chunk-DJd-R1mw.mjs";
2
- import { a as join } from "./pathe.M-eThtNZ-D-kmWkCS.mjs";
3
- import { i as stripSqlExt, r as readJournal } from "./collect-HqAuilH6.mjs";
2
+ import { a as join } from "./pathe.M-eThtNZ-BrPhGF_K.mjs";
3
+ import { i as stripSqlExt, r as readJournal } from "./collect-9rO3JFNM.mjs";
4
+ import { a as tokenizeSql, t as DESTRUCTIVE_PRAGMA } from "./plan-aD3wRDqF.mjs";
4
5
  import { existsSync, readdirSync } from "node:fs";
5
6
  //#region src/migrations/validate.ts
6
7
  var validate_exports = /* @__PURE__ */ __exportAll({
@@ -15,16 +16,55 @@ var validate_exports = /* @__PURE__ */ __exportAll({
15
16
  */
16
17
  const DESTRUCTIVE_PATTERNS = [
17
18
  /\bDROP\s+TABLE\b/i,
18
- /\bDROP\s+COLUMN\b/i,
19
- /\bALTER\s+TABLE\s+\S+\s+RENAME\b/i,
20
- /\bTRUNCATE\b/i
19
+ /\bDROP\s+VIEW\b/i,
20
+ /\bDROP\s+INDEX\b/i,
21
+ /\bDROP\s+TRIGGER\b/i,
22
+ /\bALTER\s+TABLE\b[\s\S]*\bDROP\s+COLUMN\b/i,
23
+ /\bALTER\s+TABLE\b[\s\S]*\bRENAME\s+TO\b/i,
24
+ /\bALTER\s+TABLE\b[\s\S]*\bRENAME\s+COLUMN\b/i,
25
+ /\bALTER\s+TABLE\b[\s\S]*\bRENAME\b[\s\S]*\bTO\b/i,
26
+ /\bTRUNCATE\b/i,
27
+ /\bINSERT\s+OR\s+REPLACE\b/i
21
28
  ];
22
- const PRAGMA_COMMENT = "-- void:allow-destructive";
29
+ function splitExecutableStatements(sql) {
30
+ return sql.split(";").map((statement) => statement.trim()).filter(Boolean);
31
+ }
32
+ function hasTopLevelKeyword(statement, keyword) {
33
+ let depth = 0;
34
+ for (let index = 0; index < statement.length; index++) {
35
+ const character = statement[index];
36
+ if (character === "(") {
37
+ depth++;
38
+ continue;
39
+ }
40
+ if (character === ")") {
41
+ depth = Math.max(0, depth - 1);
42
+ continue;
43
+ }
44
+ if (depth !== 0) continue;
45
+ if (statement.slice(index, index + keyword.length).toUpperCase() === keyword && !/[A-Z0-9_]/i.test(statement[index - 1] ?? "") && !/[A-Z0-9_]/i.test(statement[index + keyword.length] ?? "")) return true;
46
+ }
47
+ return false;
48
+ }
49
+ function hasUnboundedWrite(statement) {
50
+ if (/\bDELETE\s+FROM\b/i.test(statement)) return !hasTopLevelKeyword(statement, "WHERE");
51
+ if (/\bUPDATE\b[\s\S]*\bSET\b/i.test(statement)) return !hasTopLevelKeyword(statement, "WHERE");
52
+ return false;
53
+ }
23
54
  /**
24
55
  * Check if a SQL migration contains destructive operations.
25
56
  */
26
57
  function isDestructive(sql) {
27
- return DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(sql));
58
+ const normalized = sql.replace(/\r\n?/g, "\n");
59
+ let executable = "";
60
+ for (const segment of tokenizeSql(normalized)) if (segment.kind === "sql") executable += normalized.slice(segment.start, segment.end);
61
+ else executable += " ".repeat(segment.end - segment.start);
62
+ return splitExecutableStatements(executable).some((statement) => {
63
+ return DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(statement)) || hasUnboundedWrite(statement);
64
+ });
65
+ }
66
+ function hasDestructivePragma(sql) {
67
+ return sql.replace(/\r\n?/g, "\n").split("\n").some((line) => line.trim() === DESTRUCTIVE_PRAGMA);
28
68
  }
29
69
  /**
30
70
  * Validate migration files. Returns the validated list with destructive flags.
@@ -35,14 +75,14 @@ function validateMigrations(files) {
35
75
  const errors = [];
36
76
  for (const file of files) {
37
77
  const destructive = isDestructive(file.sql);
38
- if (destructive && !file.sql.includes(PRAGMA_COMMENT)) errors.push(file.name);
78
+ if (destructive && !hasDestructivePragma(file.sql)) errors.push(file.name);
39
79
  results.push({
40
80
  name: file.name,
41
81
  sql: file.sql,
42
82
  destructive
43
83
  });
44
84
  }
45
- if (errors.length > 0) throw new Error(`Destructive migrations detected without pragma:\n` + errors.map((f) => ` - ${f}`).join("\n") + `\n\nAdd "${PRAGMA_COMMENT}" to each file to allow the migration locally and on deploy.`);
85
+ if (errors.length > 0) throw new Error(`Destructive migrations detected without pragma:\n` + errors.map((f) => ` - ${f}`).join("\n") + `\n\nAdd "${DESTRUCTIVE_PRAGMA}" to each file to allow the migration locally and on deploy.`);
46
86
  return results;
47
87
  }
48
88
  /**