@lunora/codegen 1.0.0-alpha.4 → 1.0.0-alpha.41

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 (39) hide show
  1. package/LICENSE.md +6 -0
  2. package/__assets__/package-og.svg +1 -1
  3. package/dist/index.d.mts +1175 -25
  4. package/dist/index.d.ts +1175 -25
  5. package/dist/index.mjs +30 -24
  6. package/dist/packem_shared/{CONTAINERS_FILENAME-0K-pjNb8.mjs → CONTAINERS_FILENAME-DjpXMqhp.mjs} +1 -1
  7. package/dist/packem_shared/{CodegenDiagnosticError-54jWDxA9.mjs → CodegenDiagnosticError-DyQ5FwkM.mjs} +7 -5
  8. package/dist/packem_shared/FLAGS_FILENAME-fEZtzWXi.mjs +139 -0
  9. package/dist/packem_shared/GENERATED_HEADER-cUyu5VOU.mjs +3 -0
  10. package/dist/packem_shared/{LUNORA_ERROR_CODES-CySpQPD3.mjs → LUNORA_ERROR_CODES-DvTLozCu.mjs} +8 -1
  11. package/dist/packem_shared/MUTATORS_FILENAME-BZOfUhlY.mjs +81 -0
  12. package/dist/packem_shared/{OPENRPC_VERSION-C7zKVeKi.mjs → OPENRPC_VERSION-DxbqafcJ.mjs} +2 -2
  13. package/dist/packem_shared/QUEUES_FILENAME-Xv6Vy-Ku.mjs +145 -0
  14. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-C1yHg38Z.mjs +3438 -0
  15. package/dist/packem_shared/{SCHEMA_SNAPSHOT_VERSION-DzLDbWk3.mjs → SCHEMA_SNAPSHOT_VERSION-D0ARY6rL.mjs} +18 -2
  16. package/dist/packem_shared/SHAPES_FILENAME-DOhPGi-6.mjs +94 -0
  17. package/dist/packem_shared/{WORKFLOWS_FILENAME-DRDQdhfq.mjs → WORKFLOWS_FILENAME-B2By8S4s.mjs} +70 -2
  18. package/dist/packem_shared/{buildOpenApiDocument-Cns1EkCQ.mjs → buildOpenApiDocument-_mwWOc2N.mjs} +2 -2
  19. package/dist/packem_shared/{discoverAuthApiCalls-C35R6z0T.mjs → discoverAuthApiCalls-WMx8L2FG.mjs} +1 -1
  20. package/dist/packem_shared/{discoverCrons-BL6iGuJ3.mjs → discoverCrons-D-jrpm97.mjs} +22 -20
  21. package/dist/packem_shared/{discoverFunctions-DEgAcRuD.mjs → discoverFunctions-CZ91aenA.mjs} +72 -14
  22. package/dist/packem_shared/{discoverHttpRoutes-C978pBiG.mjs → discoverHttpRoutes-DNZLDCmm.mjs} +2 -2
  23. package/dist/packem_shared/{discoverInserts-CRQdXvHO.mjs → discoverInserts-DFRJhmCv.mjs} +24 -2
  24. package/dist/packem_shared/{discoverMaskProcedures-B64zA740.mjs → discoverMaskProcedures-B5iFnZ-X.mjs} +58 -2
  25. package/dist/packem_shared/{discoverMigrations-Doj_-BAA.mjs → discoverMigrations-CdKtMlBc.mjs} +10 -4
  26. package/dist/packem_shared/{discoverNondeterministicCalls-4KiPQxQU.mjs → discoverNondeterministicCalls-C_PrQPkA.mjs} +1 -1
  27. package/dist/packem_shared/{discoverQueries-BkIi0dBD.mjs → discoverQueries-B8W9UPrt.mjs} +1 -1
  28. package/dist/packem_shared/{discoverR2sqlCalls-BpDqvcUn.mjs → discoverR2sqlCalls-Bm7Rfnf4.mjs} +1 -1
  29. package/dist/packem_shared/{discoverRlsMetadata-DpRB1HMe.mjs → discoverRlsMetadata-B4GL4ZKm.mjs} +1 -1
  30. package/dist/packem_shared/{discoverSchema-BBulgGbH.mjs → discoverSchema-DFWVgU5o.mjs} +349 -17
  31. package/dist/packem_shared/{discoverStorageRulesMetadata-DAqJUxUv.mjs → discoverStorageRulesMetadata-nosjYcvN.mjs} +1 -1
  32. package/dist/packem_shared/{GENERATED_HEADER-DF1hQcix.mjs → emit-CFz-9dcD.mjs} +1100 -122
  33. package/dist/packem_shared/{emitApp-Ci_hcJNO.mjs → emitApp-D8rVuM__.mjs} +99 -30
  34. package/dist/packem_shared/formatAdvisories-DRhEiJUz.mjs +114 -0
  35. package/dist/packem_shared/{parse-validator-tuQtHrsr.mjs → parse-validator-BSJo1HGP.mjs} +18 -6
  36. package/dist/packem_shared/redact-oTmsol5A.mjs +33 -0
  37. package/package.json +9 -7
  38. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-C5UlpWKF.mjs +0 -922
  39. package/dist/packem_shared/formatAdvisories-8NIv1k0I.mjs +0 -69
@@ -1,27 +1,378 @@
1
+ import { LunoraError } from '@lunora/errors';
1
2
  import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
2
3
 
4
+ const CAPABILITY_ROWS = [
5
+ // The middleware (`accessContext()` / `accessRoles()`) imports the `/context`
6
+ // and `/roles` subpaths, NOT the bare `@lunora/cloudflare-access` specifier —
7
+ // so the per-procedure middleware never trips the global `ctx.access` wiring.
8
+ // A handler reading `ctx.access` is the signal that wires it onto every ctx.
9
+ // `access` has a synchronous facade type, so its ctx field stays bespoke in
10
+ // `emit.ts` (no `serverCtxField` here).
11
+ { contextProperty: "access", key: "access", moduleSpecifier: "@lunora/cloudflare-access" },
12
+ {
13
+ appMethod: { configKey: "ai", doc: "Override the Workers AI binding backing `ctx.ai` (defaults to `env.AI`).", method: "ai" },
14
+ contextProperty: "ai",
15
+ key: "ai",
16
+ moduleSpecifier: "@lunora/ai"
17
+ },
18
+ {
19
+ appMethod: {
20
+ configKey: "analytics",
21
+ doc: "Override the Analytics Engine dataset backing `ctx.analytics` (defaults to `env.ANALYTICS`).",
22
+ method: "analytics"
23
+ },
24
+ contextProperty: "analytics",
25
+ key: "analytics",
26
+ moduleSpecifier: "@lunora/bindings/analytics",
27
+ // `ctx.analytics` — Analytics Engine write helper. EVERY ctx: a write-only,
28
+ // fire-and-forget side effect, not a determinism hazard for reads.
29
+ serverCtxField: {
30
+ field: `
31
+ /** Analytics Engine telemetry sink. Fire-and-forget and sampled; do not read it back in-handler. */
32
+ readonly analytics: import("@lunora/bindings/analytics").AnalyticsClient;`,
33
+ tier: "every"
34
+ }
35
+ },
36
+ {
37
+ appMethod: {
38
+ configKey: "browser",
39
+ doc: "Override the Browser Rendering binding backing `ctx.browser` (defaults to `env.BROWSER`).",
40
+ method: "browser"
41
+ },
42
+ contextProperty: "browser",
43
+ key: "browser",
44
+ moduleSpecifier: "@lunora/browser",
45
+ // `ctx.browser` — Browser Rendering. ActionCtx ONLY: non-deterministic network I/O.
46
+ serverCtxField: {
47
+ field: `
48
+ /** Browser Rendering (screenshots/PDF/scrape). Non-deterministic — available only in actions. */
49
+ readonly browser: import("@lunora/browser").Browser;`,
50
+ tier: "action"
51
+ }
52
+ },
53
+ // `lunora/containers.ts` imports `defineContainer` from `@lunora/container`,
54
+ // and handlers reach live instances via `ctx.containers` — either signals the
55
+ // app wires containers, so the studio should show the Containers page. The ctx
56
+ // field is a per-declaration emitter (kept bespoke), so no `serverCtxField`.
57
+ { contextProperty: "containers", key: "container", moduleSpecifier: "@lunora/container" },
58
+ // `ctx.flags` — OpenFeature. Umbrella-aware specifier + its own provider
59
+ // client, so both the ctx field and the shard fragment stay bespoke.
60
+ { contextProperty: "flags", key: "flags", moduleSpecifier: "@lunora/flags" },
61
+ {
62
+ appMethod: {
63
+ configKey: "sql",
64
+ doc: "Wire the Hyperdrive SQL client backing `ctx.sql` — build it with `createHyperdrive` + `fromPostgresJs`/`fromNodePg`/`fromMysql2`.",
65
+ method: "hyperdrive"
66
+ },
67
+ contextProperty: "sql",
68
+ key: "hyperdrive",
69
+ moduleSpecifier: "@lunora/hyperdrive",
70
+ // `ctx.sql` — Hyperdrive (external Postgres/MySQL). ActionCtx ONLY: external,
71
+ // non-deterministic I/O whose writes are invisible to Lunora live queries.
72
+ serverCtxField: {
73
+ field: `
74
+ /**
75
+ * External database access via Hyperdrive. Non-deterministic — available only in actions. Writes here are NOT tracked by Lunora live queries; subscriptions will not re-run on external DB changes.
76
+ */
77
+ readonly sql: import("@lunora/hyperdrive").SqlClient;`,
78
+ tier: "action"
79
+ }
80
+ },
81
+ {
82
+ appMethod: { configKey: "images", doc: "Override the Images binding backing `ctx.images` (defaults to `env.IMAGES`).", method: "images" },
83
+ contextProperty: "images",
84
+ key: "images",
85
+ moduleSpecifier: "@lunora/bindings/images",
86
+ // `ctx.images` — Cloudflare Images binding transforms. ActionCtx ONLY: non-deterministic compute/network I/O.
87
+ serverCtxField: {
88
+ field: `
89
+ /** Cloudflare Images transforms (resize/format/optimize). Non-deterministic — available only in actions. */
90
+ readonly images: import("@lunora/bindings/images").Images;`,
91
+ tier: "action"
92
+ }
93
+ },
94
+ {
95
+ appMethod: { configKey: "kv", doc: "Override the Workers KV binding backing `ctx.kv` (defaults to `env.KV`).", method: "kv" },
96
+ contextProperty: "kv",
97
+ key: "kv",
98
+ moduleSpecifier: "@lunora/bindings/kv",
99
+ // `ctx.kv` — Workers KV. Typed on EVERY ctx (a KV read is allowed in a
100
+ // deterministic read path the way `ctx.db` is; the binding is user-named).
101
+ serverCtxField: { field: `
102
+ readonly kv: import("@lunora/bindings/kv").Kv;`, tier: "every" }
103
+ },
104
+ // `mail` is import-only — no `ctx.mail` helper (mail is reached through its own
105
+ // client), so only a `@lunora/mail` import flips it.
106
+ { key: "mail", moduleSpecifier: "@lunora/mail" },
107
+ {
108
+ appMethod: { configKey: "payment", doc: "Wire the payment options backing `ctx.payments`.", method: "payment" },
109
+ contextProperty: "payments",
110
+ key: "payments",
111
+ moduleSpecifier: "@lunora/payment"
112
+ },
113
+ // `ctx.x402` — the x402 agent-wallet pay rail. ActionCtx ONLY: it signs and
114
+ // settles real USDC over the network per request. Like `payments`, its ctx
115
+ // field is bespoke (a lazily-built, per-run-metered rail), so no
116
+ // `serverCtxField` — `emit.ts` hand-wires it.
117
+ {
118
+ appMethod: {
119
+ configKey: "x402",
120
+ doc: "Wire the x402 agent-wallet pay rail backing `ctx.x402` — a payment-enabled `fetch` that answers `402` challenges under a mandatory spend policy (ActionCtx-only; spends real funds).",
121
+ method: "x402"
122
+ },
123
+ contextProperty: "x402",
124
+ key: "x402",
125
+ moduleSpecifier: "@lunora/x402/pay"
126
+ },
127
+ // Pipelines is its own `@lunora/bindings/pipelines` subpath (distinct from
128
+ // `/analytics`), so a real import is a clean signal that won't be flipped by a
129
+ // plain analytics import; `ctx.pipelines` reads flip it too.
130
+ {
131
+ contextProperty: "pipelines",
132
+ key: "pipelines",
133
+ moduleSpecifier: "@lunora/bindings/pipelines",
134
+ // `ctx.pipelines` — Pipelines (R2-backed) ingestion sink. ActionCtx ONLY
135
+ // (write-only fire-and-forget, but external I/O — kept off query/mutation).
136
+ serverCtxField: {
137
+ field: `
138
+ /** Pipelines ingestion sink (durable, R2-backed). Fire-and-forget and batched; do not read it back in-handler. */
139
+ readonly pipelines: import("@lunora/bindings/pipelines").PipelineClient;`,
140
+ tier: "action"
141
+ }
142
+ },
143
+ {
144
+ appMethod: {
145
+ configKey: "r2sql",
146
+ doc: "Wire the R2 SQL client backing `ctx.r2sql` — build it with `createR2Sql({ accountId, apiToken, bucket })` (defaults to env `R2_SQL_TOKEN` / `R2_SQL_ACCOUNT_ID` / `R2_SQL_BUCKET`).",
147
+ method: "r2sql"
148
+ },
149
+ contextProperty: "r2sql",
150
+ key: "r2sql",
151
+ moduleSpecifier: "@lunora/bindings/r2sql",
152
+ // `ctx.r2sql` — R2 SQL (serverless query engine over Apache Iceberg tables).
153
+ // ActionCtx ONLY: external REST I/O, non-deterministic, and non-reactive
154
+ // (reads are not tracked by Lunora live queries).
155
+ serverCtxField: {
156
+ field: `
157
+ /**
158
+ * R2 SQL over Apache Iceberg tables (window functions, DISTINCT, set operations). Non-deterministic — available only in actions. Reads here are NOT tracked by Lunora live queries.
159
+ */
160
+ readonly r2sql: import("@lunora/bindings/r2sql").R2SqlClient;`,
161
+ tier: "action"
162
+ }
163
+ },
164
+ { contextProperty: "scheduler", key: "scheduler", moduleSpecifier: "@lunora/scheduler" },
165
+ { contextProperty: "storage", key: "storage", moduleSpecifier: "@lunora/storage" },
166
+ {
167
+ appMethod: { configKey: "vectors", doc: "Wire the Vectorize index map backing `ctx.vectors`.", method: "vectors" },
168
+ contextProperty: "vectors",
169
+ key: "vectors",
170
+ moduleSpecifier: "@lunora/bindings/vectors"
171
+ },
172
+ { contextProperty: "workflows", key: "workflows", moduleSpecifier: "@lunora/workflow" }
173
+ ];
174
+ const CAPABILITIES = CAPABILITY_ROWS;
175
+ const SERVER_CTX_FIELDS = new Map(
176
+ CAPABILITIES.flatMap((capability) => capability.serverCtxField ? [[capability.key, capability.serverCtxField]] : [])
177
+ );
178
+ const APP_METHOD_CAPABILITIES = CAPABILITY_ROWS.flatMap(
179
+ (capability) => "appMethod" in capability ? [{ appMethod: capability.appMethod, key: capability.key }] : []
180
+ );
181
+
182
+ const LITERAL_VALUE_RE = /^(?:"(?:[^"\\]|\\(?:["\\/bfnrt]|u[0-9A-Fa-f]{4}))*"|'[^'\\]*'|-?\d+(?:\.\d+)?|true|false|null)$/u;
183
+
184
+ const PASS_THROUGH_KINDS = /* @__PURE__ */ new Set(["any", "bigint", "boolean", "date", "id", "null", "number", "storage", "string", "timestamp"]);
185
+ const hasColumnModifier = (node) => node.column !== void 0;
186
+ const emitScalarGuard = (kind, inExpr) => {
187
+ switch (kind) {
188
+ case "bigint": {
189
+ return `if (typeof ${inExpr} !== "bigint") return DEFER;
190
+ `;
191
+ }
192
+ case "boolean": {
193
+ return `if (typeof ${inExpr} !== "boolean") return DEFER;
194
+ `;
195
+ }
196
+ case "date":
197
+ case "number":
198
+ case "timestamp": {
199
+ return `if (typeof ${inExpr} !== "number" || !Number.isFinite(${inExpr})) return DEFER;
200
+ `;
201
+ }
202
+ case "null": {
203
+ return `if (${inExpr} !== null) return DEFER;
204
+ `;
205
+ }
206
+ // string / id / storage all parse as a bare string at runtime.
207
+ default: {
208
+ return `if (typeof ${inExpr} !== "string") return DEFER;
209
+ `;
210
+ }
211
+ }
212
+ };
213
+ const compileLiteral = (node, inExpr) => {
214
+ const literal = node.literalValue?.trim();
215
+ if (literal === void 0 || !LITERAL_VALUE_RE.test(literal)) {
216
+ return void 0;
217
+ }
218
+ return { out: inExpr, pre: `if (${inExpr} !== ${literal}) return DEFER;
219
+ ` };
220
+ };
221
+ const compileNode = (node, inExpr, context) => {
222
+ if (hasColumnModifier(node)) {
223
+ return void 0;
224
+ }
225
+ if (node.hasRefinement || node.sourceText !== void 0) {
226
+ return void 0;
227
+ }
228
+ if (PASS_THROUGH_KINDS.has(node.kind)) {
229
+ if (node.kind === "any") {
230
+ return { out: inExpr, pre: "" };
231
+ }
232
+ return { out: inExpr, pre: emitScalarGuard(node.kind, inExpr) };
233
+ }
234
+ switch (node.kind) {
235
+ case "array": {
236
+ return compileArray(node, inExpr, context);
237
+ }
238
+ case "literal": {
239
+ return compileLiteral(node, inExpr);
240
+ }
241
+ case "object": {
242
+ return compileObject(node, inExpr, context);
243
+ }
244
+ default: {
245
+ return void 0;
246
+ }
247
+ }
248
+ };
249
+ const compileArray = (node, inExpr, context) => {
250
+ const { inner } = node;
251
+ if (!inner) {
252
+ return void 0;
253
+ }
254
+ const id = context.next();
255
+ const array = `__arr${String(id)}`;
256
+ const index = `__i${String(id)}`;
257
+ const element = `__e${String(id)}`;
258
+ const innerEmit = compileNode(inner, element, context);
259
+ if (!innerEmit) {
260
+ return void 0;
261
+ }
262
+ const pre = `if (!Array.isArray(${inExpr})) return DEFER;
263
+ const ${array} = new Array(${inExpr}.length);
264
+ for (let ${index} = 0; ${index} < ${inExpr}.length; ${index}++) {
265
+ const ${element} = ${inExpr}[${index}];
266
+ ${innerEmit.pre}${array}[${index}] = ${innerEmit.out};
267
+ }
268
+ `;
269
+ return { out: array, pre };
270
+ };
271
+ const compileField = (key, node, access, context) => {
272
+ const keyLiteral = JSON.stringify(key);
273
+ if (node.kind === "optional") {
274
+ if (node.hasRefinement || node.sourceText !== void 0 || hasColumnModifier(node)) {
275
+ return void 0;
276
+ }
277
+ const { inner } = node;
278
+ if (!inner) {
279
+ return void 0;
280
+ }
281
+ const innerEmit = compileNode(inner, access, context);
282
+ if (!innerEmit) {
283
+ return void 0;
284
+ }
285
+ const id = context.next();
286
+ const has = `__has${String(id)}`;
287
+ const value = `__val${String(id)}`;
288
+ const pre = `let ${has} = false;
289
+ let ${value};
290
+ if (${access} !== undefined) {
291
+ ${innerEmit.pre}${value} = ${innerEmit.out};
292
+ ${has} = true;
293
+ }
294
+ `;
295
+ return { entry: `...(${has} ? { ${keyLiteral}: ${value} } : {})`, pre };
296
+ }
297
+ const emit = compileNode(node, access, context);
298
+ if (!emit) {
299
+ return void 0;
300
+ }
301
+ return { entry: `${keyLiteral}: ${emit.out}`, pre: emit.pre };
302
+ };
303
+ const compileFields = (shape, accessFor, context) => {
304
+ let pre = "";
305
+ const entries = [];
306
+ for (const key of Object.keys(shape)) {
307
+ const node = shape[key];
308
+ if (!node) {
309
+ return void 0;
310
+ }
311
+ const field = compileField(key, node, accessFor(key), context);
312
+ if (!field) {
313
+ return void 0;
314
+ }
315
+ pre += field.pre;
316
+ entries.push(field.entry);
317
+ }
318
+ return { entries: entries.join(", "), pre };
319
+ };
320
+ const compileObject = (node, inExpr, context) => {
321
+ const shape = node.shape ?? {};
322
+ const fields = compileFields(shape, (key) => `${inExpr}[${JSON.stringify(key)}]`, context);
323
+ if (!fields) {
324
+ return void 0;
325
+ }
326
+ const id = context.next();
327
+ const object = `__obj${String(id)}`;
328
+ const pre = `if (typeof ${inExpr} !== "object" || ${inExpr} === null || Array.isArray(${inExpr})) return DEFER;
329
+ ${fields.pre}const ${object} = { ${fields.entries} };
330
+ `;
331
+ return { out: object, pre };
332
+ };
333
+ const compileArgsValidator = (args) => {
334
+ let counter = 0;
335
+ const context = {
336
+ next: () => {
337
+ counter += 1;
338
+ return counter;
339
+ }
340
+ };
341
+ const fields = compileFields(args, (key) => `source[${JSON.stringify(key)}]`, context);
342
+ if (!fields) {
343
+ return void 0;
344
+ }
345
+ return `(source) => {
346
+ if (typeof source !== "object" || source === null || Array.isArray(source)) return DEFER;
347
+ ${fields.pre}return { ${fields.entries} };
348
+ }`;
349
+ };
350
+
3
351
  const GENERATED_HEADER = "// GENERATED by @lunora/codegen — do not edit.\n// Run `lunora codegen` to regenerate.\n\n";
4
352
  const baseSpecifiers = (useUmbrella = false) => useUmbrella ? {
5
353
  client: "lunorash/client",
6
354
  do: "lunorash/do",
355
+ flags: "lunorash/flags",
7
356
  server: "lunorash/server",
8
357
  serverDataModel: "lunorash/server/data-model",
9
358
  serverDrizzle: "lunorash/server/drizzle",
10
- serverTypes: "lunorash/server/types"
359
+ serverTypes: "lunorash/server/types",
360
+ values: "lunorash/values"
11
361
  } : {
12
362
  client: "@lunora/client",
13
363
  do: "@lunora/do",
364
+ flags: "@lunora/flags",
14
365
  server: "@lunora/server",
15
366
  serverDataModel: "@lunora/server/data-model",
16
367
  serverDrizzle: "@lunora/server/drizzle",
17
- serverTypes: "@lunora/server/types"
368
+ serverTypes: "@lunora/server/types",
369
+ values: "@lunora/values"
18
370
  };
19
371
  const IDENTIFIER_RE = /^[A-Za-z_$][\w$]*$/u;
20
- const LITERAL_VALUE_RE = /^(?:"[^"\\]*"|'[^'\\]*'|-?\d+(?:\.\d+)?|true|false|null)$/u;
21
372
  const IMPORT_PATH_RE = /^[\w./-]+$/u;
22
373
  const assertIdentifier = (value, context) => {
23
374
  if (!IDENTIFIER_RE.test(value)) {
24
- throw new Error(`@lunora/codegen: ${context} is not a valid JS identifier: ${JSON.stringify(value)}`);
375
+ throw new LunoraError("INTERNAL", `@lunora/codegen: ${context} is not a valid JS identifier: ${JSON.stringify(value)}`);
25
376
  }
26
377
  };
27
378
  const renderPropertyKey = (fieldName) => IDENTIFIER_RE.test(fieldName) ? fieldName : JSON.stringify(fieldName);
@@ -48,7 +399,10 @@ const literalToType = (value) => {
48
399
  return "unknown";
49
400
  }
50
401
  if (!LITERAL_VALUE_RE.test(value)) {
51
- throw new Error(`@lunora/codegen: v.literal() argument is not a parseable string/number/boolean/null literal: ${JSON.stringify(value)}`);
402
+ throw new LunoraError(
403
+ "INTERNAL",
404
+ `@lunora/codegen: v.literal() argument is not a parseable string/number/boolean/null literal: ${JSON.stringify(value)}`
405
+ );
52
406
  }
53
407
  return value;
54
408
  };
@@ -430,21 +784,37 @@ import type { InsertModel } from "./dataModel.js";
430
784
  export const createSeedClient = (options?: SeedClientOptions): SeedClient<InsertModel> => createSeedClientBase<InsertModel>(schema, options);
431
785
  `;
432
786
  };
787
+ const emitCollections = (shapes, hasDatabase, useUmbrella = false) => {
788
+ if (shapes.length === 0 || !hasDatabase) {
789
+ return "";
790
+ }
791
+ const base = baseSpecifiers(useUmbrella);
792
+ const factories = shapes.map((shape) => {
793
+ assertIdentifier(shape.exportName, "shape export name");
794
+ return `/** Live collection for the \`${shape.exportName}\` replication shape — pass the shape's validated \`args\` (its partition selector). */
795
+ export const ${shape.exportName}Collection = (client: LunoraClient, args?: Record<string, unknown>): Collection<Row, string> =>
796
+ createCollection(lunoraCollectionOptions({ client, shape: { args, name: "${shape.exportName}" } }).config);`;
797
+ }).join("\n\n");
798
+ return `${GENERATED_HEADER}import type { LunoraClient } from "${base.client}";
799
+ import { lunoraCollectionOptions } from "@lunora/db/collections";
800
+ import type { Row } from "@lunora/db";
801
+ import type { Collection } from "@tanstack/db";
802
+ import { createCollection } from "@tanstack/db";
803
+
804
+ ${factories}
805
+ `;
806
+ };
433
807
  const moduleAlias = (filePath, index) => `lunora_${sanitizeNamespace(filePath)}_${String(index)}`;
434
808
  const CALL_REGISTERED_HELPER = `const callRegistered = async <R>(context: CallerCtx, functionPath: string, args: Record<string, unknown> | undefined): Promise<R> => {
435
809
  const registered = LUNORA_FUNCTIONS[functionPath];
436
810
 
437
811
  if (!registered) {
438
- throw Object.assign(new Error(\`function not registered: \${functionPath}\`), {
439
- name: "LunoraError",
440
- code: "FUNCTION_NOT_FOUND",
441
- status: 404,
442
- });
812
+ throw new LunoraError("FUNCTION_NOT_FOUND", \`function not registered: \${functionPath}\`);
443
813
  }
444
814
 
445
815
  return (await registered.handler(context, args ?? {})) as R;
446
816
  };`;
447
- const renderFunctionRegistry = (functions, migrations) => {
817
+ const renderFunctionRegistry = (functions, migrations, mutators = [], shapes = []) => {
448
818
  const aliasByPath = /* @__PURE__ */ new Map();
449
819
  const registerPath = (filePath) => {
450
820
  if (!aliasByPath.has(filePath)) {
@@ -457,9 +827,15 @@ const renderFunctionRegistry = (functions, migrations) => {
457
827
  for (const migration of migrations) {
458
828
  registerPath(migration.filePath);
459
829
  }
830
+ for (const mutator of mutators) {
831
+ registerPath(mutator.filePath);
832
+ }
833
+ for (const shape of shapes) {
834
+ registerPath(shape.filePath);
835
+ }
460
836
  const importLines = [...aliasByPath.entries()].map(([filePath, alias]) => {
461
837
  if (!IMPORT_PATH_RE.test(filePath)) {
462
- throw new Error(`@lunora/codegen: refusing to emit import for unsafe file path: ${JSON.stringify(filePath)}`);
838
+ throw new LunoraError("INTERNAL", `@lunora/codegen: refusing to emit import for unsafe file path: ${JSON.stringify(filePath)}`);
463
839
  }
464
840
  return `import * as ${alias} from "../${filePath}.js";`;
465
841
  }).join("\n");
@@ -469,15 +845,37 @@ const renderFunctionRegistry = (functions, migrations) => {
469
845
  const migrationEntries = migrations.map(
470
846
  (migration) => ` ${JSON.stringify(migration.id)}: ${aliasByPath.get(migration.filePath) ?? ""}.${migration.exportName} as unknown as RegisteredDataMigration,`
471
847
  ).join("\n");
848
+ const mutatorEntries = mutators.map(
849
+ (mutator) => ` "${sanitizeNamespace(mutator.filePath)}:${mutator.exportName}": ${aliasByPath.get(mutator.filePath) ?? ""}.${mutator.exportName} as unknown as RegisteredLunoraFunction,`
850
+ ).join("\n");
851
+ const shapeEntries = shapes.map((shape) => ` "${shape.exportName}": ${aliasByPath.get(shape.filePath) ?? ""}.${shape.exportName} as unknown as RegisteredShape,`).join("\n");
852
+ const mutatorPaths = mutators.map((mutator) => `${sanitizeNamespace(mutator.filePath)}:${mutator.exportName}`);
853
+ const combinedDispatch = [dispatchEntries, mutatorEntries].filter((entries) => entries.length > 0).join("\n");
854
+ const installLines = functions.map((definition) => {
855
+ if (definition.lifecycle || Object.keys(definition.args).length === 0) {
856
+ return void 0;
857
+ }
858
+ const compiled = compileArgsValidator(definition.args);
859
+ if (compiled === void 0) {
860
+ return void 0;
861
+ }
862
+ const alias = aliasByPath.get(definition.filePath) ?? "";
863
+ return `installCompiledValidatorMap(${alias}.${definition.exportName}.args, ${compiled});`;
864
+ }).filter((line) => line !== void 0).join("\n");
472
865
  return {
473
- dispatchBody: dispatchEntries.length > 0 ? `
474
- ${dispatchEntries}
866
+ dispatchBody: combinedDispatch.length > 0 ? `
867
+ ${combinedDispatch}
475
868
  ` : "",
476
869
  importBlock: importLines.length > 0 ? `${importLines}
477
870
 
478
871
  ` : "",
872
+ installBlock: installLines,
479
873
  migrationBody: migrationEntries.length > 0 ? `
480
874
  ${migrationEntries}
875
+ ` : "",
876
+ mutatorPaths,
877
+ shapeBody: shapeEntries.length > 0 ? `
878
+ ${shapeEntries}
481
879
  ` : ""
482
880
  };
483
881
  };
@@ -531,15 +929,21 @@ const buildStorageBucketNames = (schema, ruleBuckets = []) => {
531
929
  };
532
930
  const emitServer = ({
533
931
  containers = [],
932
+ env,
933
+ hasAccessFacade = false,
534
934
  hasAi = false,
535
935
  hasAnalytics = false,
536
936
  hasBrowser = false,
937
+ hasFlags = false,
537
938
  hasHyperdrive = false,
538
939
  hasImages = false,
539
940
  hasKv = false,
540
941
  hasPayments = false,
541
942
  hasPipelines = false,
542
943
  hasR2sql = false,
944
+ hasX402 = false,
945
+ identity,
946
+ queues = [],
543
947
  schema,
544
948
  storageRuleBuckets = [],
545
949
  useUmbrella = false,
@@ -555,6 +959,10 @@ const emitServer = ({
555
959
  ` : "";
556
960
  const paymentsActionField = hasPayments ? `
557
961
  readonly payments: LunoraPayment;` : "";
962
+ const x402TypeImport = hasX402 ? `import type { X402Pay } from "@lunora/x402/pay";
963
+ ` : "";
964
+ const x402ActionField = hasX402 ? `
965
+ readonly x402: X402Pay;` : "";
558
966
  const containersTypeImport = containers.length > 0 ? `import type { ContainerAccessor } from "@lunora/container";
559
967
  ` : "";
560
968
  const containersActionField = containers.length > 0 ? `
@@ -573,6 +981,11 @@ const emitServer = ({
573
981
  assertIdentifier(workflow.bindingName, `workflow binding "${workflow.bindingName}"`);
574
982
  return ` /** Workflow binding for the \`${workflow.exportName}\` workflow. */
575
983
  readonly ${workflow.bindingName}?: unknown;`;
984
+ }),
985
+ ...queues.map((queue) => {
986
+ assertIdentifier(queue.bindingName, `queue binding "${queue.bindingName}"`);
987
+ return ` /** Queue producer binding for the \`${queue.exportName}\` queue. */
988
+ readonly ${queue.bindingName}?: unknown;`;
576
989
  })
577
990
  ].join("\n");
578
991
  const envBlock = `
@@ -594,30 +1007,20 @@ ${envBindingFields}` : ""}
594
1007
 
595
1008
  /** Alias for {@link CloudflareBindings} — the typed shape of \`env\`. */
596
1009
  export type Env = CloudflareBindings;`;
597
- const kvContextField = hasKv ? `
598
- readonly kv: import("@lunora/kv").Kv;` : "";
599
- const hyperdriveActionField = hasHyperdrive ? `
600
- /**
601
- * External database access via Hyperdrive. Non-deterministic — available only in actions. Writes here are NOT tracked by Lunora live queries; subscriptions will not re-run on external DB changes.
602
- */
603
- readonly sql: import("@lunora/hyperdrive").SqlClient;` : "";
604
- const browserActionField = hasBrowser ? `
605
- /** Browser Rendering (screenshots/PDF/scrape). Non-deterministic — available only in actions. */
606
- readonly browser: import("@lunora/browser").Browser;` : "";
607
- const imagesActionField = hasImages ? `
608
- /** Cloudflare Images transforms (resize/format/optimize). Non-deterministic — available only in actions. */
609
- readonly images: import("@lunora/images").Images;` : "";
610
- const analyticsContextField = hasAnalytics ? `
611
- /** Analytics Engine telemetry sink. Fire-and-forget and sampled; do not read it back in-handler. */
612
- readonly analytics: import("@lunora/analytics").AnalyticsClient;` : "";
613
- const pipelinesActionField = hasPipelines ? `
614
- /** Pipelines ingestion sink (durable, R2-backed). Fire-and-forget and batched; do not read it back in-handler. */
615
- readonly pipelines: import("@lunora/pipelines").PipelineClient;` : "";
616
- const r2sqlActionField = hasR2sql ? `
617
- /**
618
- * R2 SQL over Apache Iceberg tables (window functions, DISTINCT, set operations). Non-deterministic — available only in actions. Reads here are NOT tracked by Lunora live queries.
619
- */
620
- readonly r2sql: import("@lunora/r2sql").R2SqlClient;` : "";
1010
+ const serverCapabilityField = (key, enabled) => enabled ? SERVER_CTX_FIELDS.get(key)?.field ?? "" : "";
1011
+ const kvContextField = serverCapabilityField("kv", hasKv);
1012
+ const analyticsContextField = serverCapabilityField("analytics", hasAnalytics);
1013
+ const hyperdriveActionField = serverCapabilityField("hyperdrive", hasHyperdrive);
1014
+ const browserActionField = serverCapabilityField("browser", hasBrowser);
1015
+ const imagesActionField = serverCapabilityField("images", hasImages);
1016
+ const pipelinesActionField = serverCapabilityField("pipelines", hasPipelines);
1017
+ const r2sqlActionField = serverCapabilityField("r2sql", hasR2sql);
1018
+ const accessContextField = hasAccessFacade ? `
1019
+ /** Verified Cloudflare Access identity — a synchronous facade over the resolved claims (email / groups / hasGroup / claims). Anonymous when no Access token is present. */
1020
+ readonly access: import("@lunora/cloudflare-access/context").AccessFacade;` : "";
1021
+ const flagsContextField = hasFlags ? `
1022
+ /** Feature-flag evaluation (OpenFeature). Reads are memoized per request; evaluations never throw — a provider error resolves to the supplied default. */
1023
+ readonly flags: import("${base.flags}").LunoraFlags;` : "";
621
1024
  const hasWorkflows = workflows.length > 0;
622
1025
  const workflowsTypeImport = hasWorkflows ? `import type { WorkflowHandle } from "@lunora/workflow";
623
1026
  import type * as lunoraWorkflowDefinitions from "../workflows.js";
@@ -638,6 +1041,45 @@ ${workflows.map((workflow) => ` get(name: ${JSON.stringify(workflow.exportNam
638
1041
  const workflowsOmit = hasWorkflows ? ` | "workflows"` : "";
639
1042
  const workflowsContextField = hasWorkflows ? `
640
1043
  readonly workflows: LunoraWorkflows;` : "";
1044
+ const hasQueues = queues.length > 0;
1045
+ const queuesTypeImport = hasQueues ? `import type { QueueProducer } from "@lunora/queue";
1046
+ import type * as lunoraQueueDefinitions from "../queues.js";
1047
+ ` : "";
1048
+ const queuesTypeBlock = hasQueues ? `
1049
+
1050
+ /** Message body type carried by a \`defineQueue\` definition (its phantom \`__lunoraBody\`). */
1051
+ type QueueBodyOf<Definition> = Definition extends { __lunoraBody?: infer Body } ? (unknown extends Body ? unknown : NonNullable<Body>) : unknown;
1052
+
1053
+ /** This project's declared queues, addressable from \`ctx.queues\` by their \`lunora/queues.ts\` export name. */
1054
+ export interface LunoraQueues {
1055
+ ${queues.map((queue) => ` readonly ${queue.exportName}: QueueProducer<QueueBodyOf<typeof lunoraQueueDefinitions.${queue.exportName}>>;`).join("\n")}
1056
+ }` : "";
1057
+ const queuesContextField = hasQueues ? `
1058
+ readonly queues: LunoraQueues;` : "";
1059
+ const identityTypeImport = identity ? `import type { InferIdentity } from "${base.server}";
1060
+ import type * as lunoraIdentityContract from "../identity.js";
1061
+ ` : "";
1062
+ const identityTypeBlock = identity ? `
1063
+
1064
+ /** This app's declared identity claim contract (\`defineIdentity\` in \`lunora/identity.ts\`) — the typed shape of \`ctx.auth.getIdentity()\`, the RLS policy \`ctx.auth.identity\`, and the \`authorizeShard\`/\`authorizeFanOut\` identity argument. */
1065
+ export type Identity = InferIdentity<typeof lunoraIdentityContract.${identity.exportName}>;
1066
+
1067
+ /** \`ctx.auth\` narrowed so \`getIdentity()\` resolves the declared {@link Identity} contract instead of the untyped claim bag. */
1068
+ type NarrowedAuth = Omit<QueryCtxBase["auth"], "getIdentity"> & { getIdentity: () => Promise<Identity | null> };` : "";
1069
+ const authOmit = identity ? ` | "auth"` : "";
1070
+ const authContextField = identity ? `
1071
+ readonly auth: NarrowedAuth;` : "";
1072
+ const policyIdentityArgument = identity ? ", Identity" : "";
1073
+ const envTypeImport = env ? `import type * as lunoraEnvContract from "../env.js";
1074
+ ` : "";
1075
+ const envTypeBlock = env ? `
1076
+
1077
+ /** This app's declared env contract (\`defineEnv\` in \`lunora/env.ts\`) — the validated, coercion-aware shape of \`ctx.env\`. */
1078
+ export type LunoraEnv = ReturnType<typeof lunoraEnvContract.${env.exportName}>;` : "";
1079
+ const envOmit = env ? ` | "env"` : "";
1080
+ const envContextField = env ? `
1081
+ /** Validated, typed environment declared by \`defineEnv\` in \`lunora/env.ts\` — parsed & coercion-aware config values (\`ctx.env.STRIPE_KEY\`); a missing or invalid value throws at read time. */
1082
+ readonly env: LunoraEnv;` : "";
641
1083
  const server = `${GENERATED_HEADER}import { createPolicyDsl, initLunora, v as vBase } from "${base.server}";
642
1084
  import type {
643
1085
  ActionBuilder,
@@ -659,11 +1101,11 @@ import type {
659
1101
  } from "${base.server}";
660
1102
 
661
1103
  import type { DataModel, DatabaseReaderFacade, DatabaseWriterFacade, Doc, Id as IdOfTable, OrmReader, OrmWriter, Relations, TableName } from "./dataModel.js";
662
- ${aiTypeImport}${paymentsTypeImport}${containersTypeImport}${workflowsTypeImport}
1104
+ ${aiTypeImport}${paymentsTypeImport}${x402TypeImport}${containersTypeImport}${workflowsTypeImport}${queuesTypeImport}${identityTypeImport}${envTypeImport}
663
1105
  export type { DataModel, Doc, Id, TableName } from "./dataModel.js";
664
1106
 
665
1107
  /** Storage buckets this schema declares (\`v.storage("name")\`), narrowing \`ctx.storage.bucket(name)\`. */
666
- export type StorageBucketName = ${storageBucketUnion};${envBlock}${workflowsTypeBlock}
1108
+ export type StorageBucketName = ${storageBucketUnion};${envBlock}${workflowsTypeBlock}${queuesTypeBlock}${identityTypeBlock}${envTypeBlock}
667
1109
 
668
1110
  /**
669
1111
  * Project-typed contexts. The base contexts from \`@lunora/server\` are
@@ -692,22 +1134,22 @@ type TypedTableQuery = (<T extends TableName>(table: T) => TableReader<Doc<T>>)
692
1134
  */
693
1135
  type TypedTableGet = <T extends TableName>(id: IdOfTable<T>) => Promise<Doc<T> | null>;
694
1136
 
695
- export interface QueryCtx extends Omit<QueryCtxBase, "db" | "storage"> {
1137
+ export interface QueryCtx extends Omit<QueryCtxBase, "db" | "storage"${authOmit}${envOmit}> {
696
1138
  readonly db: Omit<DatabaseReader, "query" | "get"> & DatabaseReaderFacade & { query: TypedTableQuery; get: TypedTableGet };
697
1139
  readonly orm: OrmReader;
698
- readonly storage: ReadOnlyStorage<StorageBucketName>;${kvContextField}${analyticsContextField}
1140
+ readonly storage: ReadOnlyStorage<StorageBucketName>;${accessContextField}${kvContextField}${flagsContextField}${analyticsContextField}${envContextField}${authContextField}
699
1141
  }
700
1142
 
701
- export interface MutationCtx extends Omit<MutationCtxBase, "db" | "storage"${workflowsOmit}> {
1143
+ export interface MutationCtx extends Omit<MutationCtxBase, "db" | "storage"${workflowsOmit}${authOmit}${envOmit}> {
702
1144
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
703
1145
  readonly orm: OrmWriter;
704
- readonly storage: ReadOnlyStorage<StorageBucketName>;${kvContextField}${analyticsContextField}${workflowsContextField}
1146
+ readonly storage: ReadOnlyStorage<StorageBucketName>;${accessContextField}${kvContextField}${flagsContextField}${analyticsContextField}${envContextField}${workflowsContextField}${queuesContextField}${authContextField}
705
1147
  }
706
1148
 
707
- export interface ActionCtx extends Omit<ActionCtxBase, "db" | "storage"${workflowsOmit}> {
1149
+ export interface ActionCtx extends Omit<ActionCtxBase, "db" | "storage"${workflowsOmit}${authOmit}${envOmit}> {
708
1150
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
709
1151
  readonly orm: OrmWriter;
710
- readonly storage: StorageBase<StorageBucketName>;${aiActionField}${paymentsActionField}${containersActionField}${kvContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${r2sqlActionField}${workflowsContextField}
1152
+ readonly storage: StorageBase<StorageBucketName>;${accessContextField}${aiActionField}${paymentsActionField}${x402ActionField}${containersActionField}${kvContextField}${flagsContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${r2sqlActionField}${envContextField}${workflowsContextField}${queuesContextField}${authContextField}
711
1153
  }
712
1154
 
713
1155
  /**
@@ -747,7 +1189,7 @@ export const internalAction = lunoraBuilders.internalAction as unknown as Intern
747
1189
  * Runtime-identical to \`@lunora/server\`'s \`definePolicy\`; only the types narrow,
748
1190
  * so the \`rls()\` chain discovers a policy authored either way the same.
749
1191
  */
750
- export const definePolicy = createPolicyDsl<DataModel, Relations>();
1192
+ export const definePolicy = createPolicyDsl<DataModel, Relations${policyIdentityArgument}>();
751
1193
 
752
1194
  /**
753
1195
  * The validator builder \`v\`, with \`v.id(...)\` constrained to THIS schema's
@@ -777,10 +1219,40 @@ const renderLifecycleManifest = (functions) => {
777
1219
  }
778
1220
  return manifest;
779
1221
  };
780
- const emitFunctions = (functions, migrations = []) => {
1222
+ const emitFunctions = (functions, migrations = [], useUmbrella = false, mutators = [], shapes = []) => {
781
1223
  const hasFunctions = functions.length > 0;
782
- const { dispatchBody, importBlock, migrationBody } = renderFunctionRegistry(functions, migrations);
1224
+ const base = baseSpecifiers(useUmbrella);
1225
+ const { dispatchBody, importBlock, installBlock, migrationBody, mutatorPaths, shapeBody } = renderFunctionRegistry(functions, migrations, mutators, shapes);
783
1226
  const lifecycleHooks = renderLifecycleManifest(functions);
1227
+ const shapeTypeImport = shapes.length > 0 ? `import type { RegisteredShape } from "${base.server}";
1228
+ ` : "";
1229
+ const shapeRegistry = shapes.length > 0 ? `
1230
+ /**
1231
+ * Replication-shape registry — one entry per \`defineShape\` in \`lunora/shapes.ts\`.
1232
+ * The generated ShardDO's \`resolveShape\` override looks a shape up by name and
1233
+ * evaluates its trusted \`compileWhere(ctx, args)\` to authorize + scope a
1234
+ * \`shape_subscribe\` (reads-as-permissions).
1235
+ */
1236
+ export const LUNORA_SHAPES: Record<string, RegisteredShape> = {${shapeBody}};
1237
+ ` : "";
1238
+ const mutatorPathsRegistry = mutatorPaths.length > 0 ? `
1239
+ /**
1240
+ * Custom-mutator function paths — the \`LUNORA_FUNCTIONS\` keys the generated
1241
+ * ShardDO's \`isCustomMutator\` override routes through the client-watermark push
1242
+ * protocol (\`x-lunora-client-id\`/\`x-lunora-client-seq\` ordering).
1243
+ */
1244
+ export const LUNORA_MUTATOR_PATHS: ReadonlySet<string> = new Set([${mutatorPaths.map((path) => JSON.stringify(path)).join(", ")}]);
1245
+ ` : "";
1246
+ const compiledArgsImport = installBlock.length > 0 ? `import { DEFER_VALIDATION as DEFER, installCompiledValidatorMap } from "${base.values}";
1247
+ ` : "";
1248
+ const compiledArgsInstall = installBlock.length > 0 ? `
1249
+ /**
1250
+ * AOT-compiled argument validators (Worker-safe, no \`eval\`). Each is installed
1251
+ * onto its function's live \`.args\` object and consulted by the interpreted
1252
+ * parser as a zero-allocation fast path; anything it can't model is deferred.
1253
+ */
1254
+ ${installBlock}
1255
+ ` : "";
784
1256
  const caller = renderCaller(functions);
785
1257
  const callerTypes = caller.types ? `
786
1258
  ${caller.types}
@@ -795,7 +1267,8 @@ ${caller.implementation}
795
1267
  const callRegisteredHelper = hasFunctions ? `${CALL_REGISTERED_HELPER}
796
1268
 
797
1269
  ` : "";
798
- return `${GENERATED_HEADER}${importBlock}import type { ActionCtx, MutationCtx, QueryCtx } from "./server.js";
1270
+ return `${GENERATED_HEADER}${importBlock}${compiledArgsImport}${shapeTypeImport}import { LunoraError } from "${base.server}";
1271
+ import type { ActionCtx, MutationCtx, QueryCtx } from "./server.js";
799
1272
  ${dataModelImport}
800
1273
  /**
801
1274
  * Single registered function, narrowed to the shape \`handleRpc\` needs.
@@ -821,7 +1294,7 @@ export interface RegisteredLunoraFunction {
821
1294
  * emits (\`api[namespace][fn].__lunoraRef === "namespace:fn"\`).
822
1295
  */
823
1296
  export const LUNORA_FUNCTIONS: Record<string, RegisteredLunoraFunction> = {${dispatchBody}};
824
-
1297
+ ${compiledArgsInstall}${shapeRegistry}${mutatorPathsRegistry}
825
1298
  /**
826
1299
  * Connection-lifecycle manifest: the function paths the generated ShardDO
827
1300
  * dispatches when a client's WebSocket connects (\`connect\`) or disconnects
@@ -843,11 +1316,7 @@ export const dispatchLunoraFunction = async (functionPath: string, context: unkn
843
1316
  const registered = LUNORA_FUNCTIONS[functionPath];
844
1317
 
845
1318
  if (!registered || registered.visibility === "internal") {
846
- throw Object.assign(new Error(\`function not registered: \${functionPath}\`), {
847
- name: "LunoraError",
848
- code: "FUNCTION_NOT_FOUND",
849
- status: 404,
850
- });
1319
+ throw new LunoraError("FUNCTION_NOT_FOUND", \`function not registered: \${functionPath}\`);
851
1320
  }
852
1321
 
853
1322
  return registered.handler(context, args);
@@ -1042,7 +1511,7 @@ const emitKvFragments = (hasKv) => {
1042
1511
  kv?: (env: Record<string, unknown>) => KVNamespaceLike;`,
1043
1512
  contextField: `
1044
1513
  kv,`,
1045
- importLines: [`import type { Kv, KVNamespaceLike } from "@lunora/kv";`, `import { createKv } from "@lunora/kv";`],
1514
+ importLines: [`import type { Kv, KVNamespaceLike } from "@lunora/bindings/kv";`, `import { createKv } from "@lunora/bindings/kv";`],
1046
1515
  stub: `
1047
1516
  const kvStub: Kv = {
1048
1517
  delete: async () => {
@@ -1067,6 +1536,131 @@ const kvStub: Kv = {
1067
1536
  `
1068
1537
  };
1069
1538
  };
1539
+ const emitFlagsFragments = (hasFlags, flagsSpecifier) => {
1540
+ if (!hasFlags) {
1541
+ return EMPTY_HELPER_FRAGMENTS;
1542
+ }
1543
+ return {
1544
+ build: `
1545
+ const flags: import("${flagsSpecifier}").LunoraFlags = createFlags({
1546
+ hooks: flagsConfig.hooks,
1547
+ logger: flagsConfig.logger,
1548
+ provider: () => config.flags?.(env) ?? flagsConfig.provider(env),
1549
+ targetingKey: () => flagsConfig.identify?.({ identity: identity ?? null, userId: userId ?? null }),
1550
+ });
1551
+ `,
1552
+ configField: `
1553
+ flags?: (env: Record<string, unknown>) => import("${flagsSpecifier}").Provider;`,
1554
+ contextField: `
1555
+ flags,`,
1556
+ importLines: [`import { createFlags } from "${flagsSpecifier}";`, `import flagsConfig from "../flags.js";`],
1557
+ stub: ""
1558
+ };
1559
+ };
1560
+ const emitEnvFragments = (env) => {
1561
+ if (!env) {
1562
+ return EMPTY_HELPER_FRAGMENTS;
1563
+ }
1564
+ return {
1565
+ build: `
1566
+ const envConfig = lunoraEnvContract.${env.exportName}(env);
1567
+ `,
1568
+ configField: "",
1569
+ contextField: `
1570
+ env: envConfig,`,
1571
+ importLines: [`import * as lunoraEnvContract from "../env.js";`],
1572
+ stub: ""
1573
+ };
1574
+ };
1575
+ const emitAccessFragments = (hasAccessFacade) => {
1576
+ if (!hasAccessFacade) {
1577
+ return EMPTY_HELPER_FRAGMENTS;
1578
+ }
1579
+ return {
1580
+ build: `
1581
+ const access = accessFacade(identity, userId);
1582
+ `,
1583
+ configField: "",
1584
+ contextField: `
1585
+ access,`,
1586
+ importLines: [`import { accessFacade } from "@lunora/cloudflare-access/context";`],
1587
+ stub: ""
1588
+ };
1589
+ };
1590
+ const emitFlagsOverrides = (flagKeys, hasFlags, flagsSpecifier) => {
1591
+ if (!hasFlags) {
1592
+ return { constant: "", evaluateOverride: "", subscriptionOverride: "" };
1593
+ }
1594
+ const clientBuild = (targetingKey) => `
1595
+ const env = (this.env ?? {}) as Record<string, unknown>;
1596
+ const flags: import("${flagsSpecifier}").LunoraFlags = createFlags({
1597
+ hooks: flagsConfig.hooks,
1598
+ logger: flagsConfig.logger,
1599
+ provider: () => config.flags?.(env) ?? flagsConfig.provider(env),
1600
+ targetingKey: ${targetingKey},
1601
+ });`;
1602
+ const constant = `
1603
+ /** Statically-discovered feature flags (\`ctx.flags.<type>("key")\` reads) served via \`__lunora_admin__:listFlags\` + the reactive \`__lunora_flags__:\` channel. */
1604
+ const LUNORA_FLAG_KEYS: ReadonlyArray<{ key: string; type: "boolean" | "number" | "object" | "string" }> = ${JSON.stringify(flagKeys, void 0, 4)};
1605
+ `;
1606
+ const evaluateOverride = `
1607
+ protected override async evaluateFlags(context?: Record<string, unknown>): Promise<FlagsResult> {${clientBuild("undefined")}
1608
+ const evalContext = context as import("${flagsSpecifier}").EvaluationContext | undefined;
1609
+ const evaluations: FlagsResult["flags"] = [];
1610
+
1611
+ for (const entry of LUNORA_FLAG_KEYS) {
1612
+ // eslint-disable-next-line no-await-in-loop -- flags evaluate sequentially; each shares the single memoized provider client
1613
+ const details =
1614
+ entry.type === "boolean"
1615
+ ? await flags.details.boolean(entry.key, false, evalContext)
1616
+ : entry.type === "number"
1617
+ ? await flags.details.number(entry.key, 0, evalContext)
1618
+ : entry.type === "string"
1619
+ ? await flags.details.string(entry.key, "", evalContext)
1620
+ : await flags.details.object(entry.key, {}, evalContext);
1621
+
1622
+ evaluations.push({ errorCode: details.errorCode, key: entry.key, reason: details.reason, type: entry.type, value: details.value, variant: details.variant });
1623
+ }
1624
+
1625
+ return { configured: true, flags: evaluations };
1626
+ }
1627
+ `;
1628
+ const subscriptionOverride = `
1629
+ protected override runFlagSubscriptionRead(_functionPath: string, args: Record<string, unknown>, identity?: { identity?: Record<string, unknown>; userId?: string }): Promise<unknown> {${clientBuild("() => flagsConfig.identify?.({ identity: identity?.identity ?? null, userId: identity?.userId ?? null })")}
1630
+ const key = typeof args.key === "string" ? args.key : "";
1631
+
1632
+ // SECURITY: the reactive channel is public (any socket, no auth). Serve
1633
+ // ONLY statically-discovered flag keys — an arbitrary client-supplied key
1634
+ // would let a subscriber probe the value of internal/unreleased flags the
1635
+ // app never exposes. Unknown key ⇒ the "nothing to deliver" sentinel.
1636
+ // eslint-disable-next-line unicorn/no-null -- the base hook's "nothing to deliver" sentinel
1637
+ if (key.length === 0 || !LUNORA_FLAG_KEYS.some((entry) => entry.key === key)) {
1638
+ return Promise.resolve(null);
1639
+ }
1640
+
1641
+ // SECURITY: evaluate under the socket's server-verified identity ONLY
1642
+ // (the targetingKey resolved above). Client-supplied targeting context is
1643
+ // NOT honored on this public channel — otherwise a subscriber could spoof
1644
+ // targeting attributes (e.g. plan/role) to unlock a flag gated on them.
1645
+ const context = undefined;
1646
+
1647
+ if (args.type === "number") {
1648
+ return flags.number(key, typeof args.default === "number" ? args.default : 0, context);
1649
+ }
1650
+
1651
+ if (args.type === "string") {
1652
+ return flags.string(key, typeof args.default === "string" ? args.default : "", context);
1653
+ }
1654
+
1655
+ if (args.type === "object") {
1656
+ return flags.object(key, (args.default ?? {}) as import("${flagsSpecifier}").JsonValue, context);
1657
+ }
1658
+
1659
+ return flags.boolean(key, typeof args.default === "boolean" ? args.default : false, context);
1660
+ }
1661
+ `;
1662
+ return { constant, evaluateOverride, subscriptionOverride };
1663
+ };
1070
1664
  const emitAnalyticsFragments = (hasAnalytics) => {
1071
1665
  if (!hasAnalytics) {
1072
1666
  return EMPTY_HELPER_FRAGMENTS;
@@ -1082,8 +1676,8 @@ const emitAnalyticsFragments = (hasAnalytics) => {
1082
1676
  contextField: `
1083
1677
  analytics,`,
1084
1678
  importLines: [
1085
- `import type { AnalyticsClient, AnalyticsEngineDatasetLike } from "@lunora/analytics";`,
1086
- `import { createAnalytics } from "@lunora/analytics";`
1679
+ `import type { AnalyticsClient, AnalyticsEngineDatasetLike } from "@lunora/bindings/analytics";`,
1680
+ `import { createAnalytics } from "@lunora/bindings/analytics";`
1087
1681
  ],
1088
1682
  stub: `
1089
1683
  const analyticsStub: AnalyticsClient = {
@@ -1112,7 +1706,7 @@ const emitImagesFragments = (hasImages) => {
1112
1706
  // ActionCtx-only: woven onto the action ctx object, never query/mutation.
1113
1707
  contextField: `
1114
1708
  images,`,
1115
- importLines: [`import type { Images, ImagesBindingLike } from "@lunora/images";`, `import { createImages } from "@lunora/images";`],
1709
+ importLines: [`import type { Images, ImagesBindingLike } from "@lunora/bindings/images";`, `import { createImages } from "@lunora/bindings/images";`],
1116
1710
  stub: `
1117
1711
  const imagesStub: Images = {
1118
1712
  info: async () => {
@@ -1211,10 +1805,10 @@ const emitR2sqlFragments = (hasR2sql) => {
1211
1805
  // ActionCtx-only: attached via the \`ctx.r2sql = r2sql\` assignment in the
1212
1806
  // \`isAction\` block, never the every-ctx object literal.
1213
1807
  contextField: "",
1214
- importLines: [`import type { R2SqlClient } from "@lunora/r2sql";`, `import { createR2Sql } from "@lunora/r2sql";`],
1808
+ importLines: [`import type { R2SqlClient } from "@lunora/bindings/r2sql";`, `import { createR2Sql } from "@lunora/bindings/r2sql";`],
1215
1809
  // The stub is typed `R2SqlClient`, so TS flags a missing method at build
1216
1810
  // time — but it must stay structurally in sync with that interface
1217
- // (`@lunora/r2sql` client.ts) when a method is added there.
1811
+ // (`@lunora/bindings/r2sql` client.ts) when a method is added there.
1218
1812
  stub: `
1219
1813
  const r2sqlStub: R2SqlClient = {
1220
1814
  describe: async () => {
@@ -1239,17 +1833,46 @@ const r2sqlStub: R2SqlClient = {
1239
1833
  `
1240
1834
  };
1241
1835
  };
1242
- const emitContainers = (containers) => {
1836
+ const emitPipelinesFragments = (hasPipelines) => {
1837
+ if (!hasPipelines) {
1838
+ return EMPTY_HELPER_FRAGMENTS;
1839
+ }
1840
+ const pipelinesMissing = `throw new Error("ctx.pipelines: no Pipelines binding found. Add a \\\`pipelines\\\` binding (env.PIPELINES) to wrangler.jsonc, or pass \\\`pipelines\\\` to createShardDO().");`;
1841
+ return {
1842
+ build: `
1843
+ const pipelinesBinding = config.pipelines?.(env) ?? (env as Record<string, unknown>).PIPELINES;
1844
+ const pipelines: PipelineClient = pipelinesBinding ? createPipelines({ binding: pipelinesBinding as PipelineBindingLike }) : pipelinesStub;
1845
+ `,
1846
+ configField: `
1847
+ pipelines?: (env: Record<string, unknown>) => PipelineBindingLike;`,
1848
+ // ActionCtx-only: attached via the \`ctx.pipelines = pipelines\` assignment
1849
+ // in the \`isAction\` block, never the every-ctx object literal.
1850
+ contextField: "",
1851
+ importLines: [
1852
+ `import type { PipelineBindingLike, PipelineClient } from "@lunora/bindings/pipelines";`,
1853
+ `import { createPipelines } from "@lunora/bindings/pipelines";`
1854
+ ],
1855
+ stub: `
1856
+ const pipelinesStub: PipelineClient = {
1857
+ send: async () => {
1858
+ ${pipelinesMissing}
1859
+ },
1860
+ };
1861
+ `
1862
+ };
1863
+ };
1864
+ const emitContainers = (containers, jurisdiction) => {
1243
1865
  if (containers.length === 0) {
1244
1866
  return "";
1245
1867
  }
1868
+ const jurisdictionArgument = jurisdiction ? `, ${JSON.stringify(jurisdiction)}` : "";
1246
1869
  const classes = containers.map((container) => {
1247
1870
  assertIdentifier(container.exportName, `container export "${container.exportName}"`);
1248
1871
  assertIdentifier(container.className, `container class "${container.className}"`);
1249
1872
  return `/** Container DO for the \`${container.exportName}\` definition (binding \`${container.bindingName}\`). */
1250
1873
  export class ${container.className} extends LunoraContainer {
1251
1874
  public constructor(ctx: ConstructorParameters<typeof LunoraContainer>[0], env: Record<string, unknown>) {
1252
- super(ctx, env, ${container.exportName}, "${container.exportName}");
1875
+ super(ctx, env, ${container.exportName}, "${container.exportName}"${jurisdictionArgument});
1253
1876
  }
1254
1877
  }
1255
1878
  `;
@@ -1261,14 +1884,21 @@ export class ${container.className} extends LunoraContainer {
1261
1884
  * requires each \`containers[].class_name\` to be exported by the worker:
1262
1885
  *
1263
1886
  * \`export * from "./lunora/_generated/containers.js";\`
1887
+ *
1888
+ * \`ContainerProxy\` is re-exported alongside them: the egress-interception path
1889
+ * (\`allowedHosts\`/\`deniedHosts\`/\`interceptHttps\` and the runtime
1890
+ * \`handle.egress\` controls) routes container outbound traffic through this
1891
+ * WorkerEntrypoint, so it too must be exported by the deployed worker.
1264
1892
  */
1265
- import LunoraContainer from "@lunora/container/do";
1893
+ import { LunoraContainer } from "@lunora/container/do";
1266
1894
 
1267
1895
  import { ${imports} } from "../containers.js";
1268
1896
 
1897
+ export { ContainerProxy } from "@lunora/container/do";
1898
+
1269
1899
  ${classes}`;
1270
1900
  };
1271
- const emitContainerFragments = (containers) => {
1901
+ const emitContainerFragments = (containers, jurisdiction) => {
1272
1902
  if (containers.length === 0) {
1273
1903
  return { build: "", contextField: "", importLines: [], specs: "" };
1274
1904
  }
@@ -1281,8 +1911,11 @@ const emitContainerFragments = (containers) => {
1281
1911
  return ` { binding: "${container.bindingName}", exportName: "${container.exportName}"${maxInstances} },`;
1282
1912
  }).join("\n");
1283
1913
  return {
1914
+ // Schema `.jurisdiction("…")` pins every container DO this shard reaches
1915
+ // to the data-residency region; the arg is omitted when undeclared so
1916
+ // existing generated output is unchanged.
1284
1917
  build: `
1285
- const containers = createContainerContext(env, LUNORA_CONTAINERS);
1918
+ const containers = createContainerContext(env, LUNORA_CONTAINERS${jurisdiction ? `, ${JSON.stringify(jurisdiction)}` : ""});
1286
1919
  `,
1287
1920
  contextField: `
1288
1921
  containers,`,
@@ -1335,6 +1968,32 @@ type WorkflowOutputOf<Definition> = Definition extends { __output?: infer Output
1335
1968
 
1336
1969
  ${classes}`;
1337
1970
  };
1971
+ const emitQueues = (queues) => {
1972
+ const pushQueues = queues.filter((queue) => queue.mode === "push");
1973
+ if (pushQueues.length === 0) {
1974
+ return "";
1975
+ }
1976
+ for (const queue of pushQueues) {
1977
+ assertIdentifier(queue.exportName, `queue export "${queue.exportName}"`);
1978
+ }
1979
+ const imports = pushQueues.map((queue) => queue.exportName).join(", ");
1980
+ const entries = pushQueues.map((queue) => ` ${JSON.stringify(queue.name)}: { definition: ${queue.exportName}, exportName: ${JSON.stringify(queue.exportName)} },`).join("\n");
1981
+ return `${GENERATED_HEADER}/**
1982
+ * Push-consumer registry for the queues declared in \`lunora/queues.ts\`. The
1983
+ * composed worker's \`queue(batch, env, ctx)\` entry routes each delivered batch
1984
+ * by \`batch.queue\` to the matching \`defineQueue\` handler. Wired automatically
1985
+ * by \`defineApp\` — you don't import this directly.
1986
+ */
1987
+ import type { QueueRegistry } from "@lunora/queue";
1988
+
1989
+ import { ${imports} } from "../queues.js";
1990
+
1991
+ /** Stable wrangler queue name → { definition, exportName } for batch routing. */
1992
+ export const LUNORA_QUEUE_REGISTRY: QueueRegistry = {
1993
+ ${entries}
1994
+ };
1995
+ `;
1996
+ };
1338
1997
  const emitWorkflowFragments = (workflows) => {
1339
1998
  if (workflows.length === 0) {
1340
1999
  return { build: "", contextField: "", importLines: [], specs: "" };
@@ -1360,6 +2019,31 @@ ${specEntries}
1360
2019
  `
1361
2020
  };
1362
2021
  };
2022
+ const emitQueueFragments = (queues) => {
2023
+ if (queues.length === 0) {
2024
+ return { build: "", contextField: "", importLines: [], specs: "" };
2025
+ }
2026
+ for (const queue of queues) {
2027
+ assertIdentifier(queue.exportName, `queue export "${queue.exportName}"`);
2028
+ assertIdentifier(queue.bindingName, `queue binding "${queue.bindingName}"`);
2029
+ }
2030
+ const specEntries = queues.map((queue) => ` { binding: "${queue.bindingName}", exportName: "${queue.exportName}", name: ${JSON.stringify(queue.name)} },`).join("\n");
2031
+ return {
2032
+ build: `
2033
+ const queues = createQueueContext(env, LUNORA_QUEUES);
2034
+ `,
2035
+ contextField: `
2036
+ queues,`,
2037
+ importLines: [`import type { QueueBindingSpec } from "@lunora/queue";`, `import { createQueueContext } from "@lunora/queue";`],
2038
+ // eslint-disable-next-line no-secrets/no-secrets -- the emitted readonly-array type annotation is dense generated TS, not a credential
2039
+ specs: `
2040
+ /** Wiring specs for \`ctx.queues\` (codegen-derived from \`lunora/queues.ts\`). */
2041
+ const LUNORA_QUEUES: ReadonlyArray<QueueBindingSpec> = [
2042
+ ${specEntries}
2043
+ ];
2044
+ `
2045
+ };
2046
+ };
1363
2047
  const emitWorkflowsMetadataFragments = (workflows) => {
1364
2048
  if (workflows.length === 0) {
1365
2049
  return { constant: "", override: "" };
@@ -1386,6 +2070,33 @@ const LUNORA_WORKFLOWS_INFO: WorkflowsResult = ${JSON.stringify(metadata, void 0
1386
2070
  `
1387
2071
  };
1388
2072
  };
2073
+ const emitQueuesMetadataFragments = (queues) => {
2074
+ if (queues.length === 0) {
2075
+ return { constant: "", override: "" };
2076
+ }
2077
+ const metadata = {
2078
+ queues: queues.map((queue) => {
2079
+ return {
2080
+ binding: queue.bindingName,
2081
+ ...queue.tuning.deadLetterQueue === void 0 ? {} : { deadLetterQueue: queue.tuning.deadLetterQueue },
2082
+ exportName: queue.exportName,
2083
+ mode: queue.mode,
2084
+ name: queue.name
2085
+ };
2086
+ })
2087
+ };
2088
+ return {
2089
+ constant: `
2090
+ /** Read-only declared-queue metadata (discovered from \`lunora/queues.ts\`) served via \`__lunora_admin__:listQueues\` for the studio's queues view. */
2091
+ const LUNORA_QUEUES_INFO: QueuesResult = ${JSON.stringify(metadata, void 0, 4)};
2092
+ `,
2093
+ override: `
2094
+ protected override queuesMetadata(): QueuesResult {
2095
+ return LUNORA_QUEUES_INFO;
2096
+ }
2097
+ `
2098
+ };
2099
+ };
1389
2100
  const emitPaymentFragments = (hasPayments) => {
1390
2101
  if (!hasPayments) {
1391
2102
  return { build: "", configField: "", contextField: "", imports: [], stub: "" };
@@ -1441,13 +2152,41 @@ const paymentStub: LunoraPayment = {
1441
2152
  `
1442
2153
  };
1443
2154
  };
1444
- const buildDoTypeImports = (hasVectors, hasWorkflows) => [
2155
+ const emitX402Fragments = (hasX402) => {
2156
+ if (!hasX402) {
2157
+ return { build: "", configField: "", contextField: "", imports: [], stub: "" };
2158
+ }
2159
+ return {
2160
+ imports: [`import type { X402Pay, X402PayConfig } from "@lunora/x402/pay";`, `import { lazyX402Pay } from "@lunora/x402/pay";`],
2161
+ // Built lazily off `secrets` (the Secrets Store facade already in scope) and
2162
+ // the `config.x402` thunk over env; falls back to `x402Stub`.
2163
+ build: `
2164
+ const x402: X402Pay = config.x402
2165
+ ? lazyX402Pay(config.x402(env), { getSecret: (name: string) => secrets.get(name) })
2166
+ : x402Stub;
2167
+ `,
2168
+ configField: `
2169
+ x402?: (env: Record<string, unknown>) => X402PayConfig;`,
2170
+ contextField: `
2171
+ x402,`,
2172
+ stub: `
2173
+ const x402Stub: X402Pay = {
2174
+ fetch: () => {
2175
+ throw new Error("ctx.x402: no pay rail configured. Pass \\\`x402\\\` to createShardDO().");
2176
+ },
2177
+ } as unknown as X402Pay;
2178
+ `
2179
+ };
2180
+ };
2181
+ const buildDoTypeImports = (hasVectors, hasWorkflows, hasQueues, hasFlags) => [
1445
2182
  "AdvisoryFinding",
1446
2183
  "DatabaseWriterLike",
1447
2184
  "DataMigrationLike",
2185
+ ...hasFlags ? ["FlagsResult"] : [],
1448
2186
  "LogSink",
1449
2187
  "MaskPoliciesResult",
1450
2188
  "MigrationRunResult",
2189
+ ...hasQueues ? ["QueuesResult"] : [],
1451
2190
  "RunShardApplyCdcArgs",
1452
2191
  "RunShardMigrationArgs",
1453
2192
  "RlsPoliciesResult",
@@ -1469,36 +2208,53 @@ const buildDoTypeImports = (hasVectors, hasWorkflows) => [
1469
2208
  const emitShard = ({
1470
2209
  advisories = [],
1471
2210
  containers = [],
2211
+ env,
2212
+ flagKeys = [],
2213
+ hasAccessFacade = false,
1472
2214
  hasAi = false,
1473
2215
  hasAnalytics = false,
1474
2216
  hasBrowser = false,
2217
+ hasFlags = false,
1475
2218
  hasHyperdrive = false,
1476
2219
  hasImages = false,
1477
2220
  hasKv = false,
1478
2221
  hasPayments = false,
2222
+ hasPipelines = false,
1479
2223
  hasR2sql = false,
2224
+ hasX402 = false,
1480
2225
  maskMetadata,
2226
+ mutators = [],
2227
+ queues = [],
1481
2228
  rlsMetadata,
1482
2229
  schema,
2230
+ shapes = [],
1483
2231
  storageRules,
1484
2232
  studioFeatures,
1485
2233
  useUmbrella = false,
1486
2234
  workflows = []
1487
2235
  }) => {
1488
2236
  const base = baseSpecifiers(useUmbrella);
2237
+ const hasMutators = mutators.length > 0;
2238
+ const hasShapes = shapes.length > 0;
1489
2239
  const { build: aiBuild, configField: aiConfigField, contextField: aiContextField, stub: aiStub } = emitAiFragments(hasAi);
2240
+ const accessFragments = emitAccessFragments(hasAccessFacade);
1490
2241
  const kvFragments = emitKvFragments(hasKv);
2242
+ const flagsFragments = emitFlagsFragments(hasFlags, base.flags);
2243
+ const flagsOverrides = emitFlagsOverrides(flagKeys, hasFlags, base.flags);
2244
+ const envFragments = emitEnvFragments(env);
1491
2245
  const analyticsFragments = emitAnalyticsFragments(hasAnalytics);
1492
2246
  const imagesFragments = emitImagesFragments(hasImages);
1493
2247
  const hyperdriveFragments = emitHyperdriveFragments(hasHyperdrive);
1494
2248
  const browserFragments = emitBrowserFragments(hasBrowser);
1495
2249
  const r2sqlFragments = emitR2sqlFragments(hasR2sql);
2250
+ const pipelinesFragments = emitPipelinesFragments(hasPipelines);
2251
+ const { build: queuesBuild, contextField: queuesContextField, importLines: queueImportLines, specs: queueSpecs } = emitQueueFragments(queues);
1496
2252
  const {
1497
2253
  build: containersBuild,
1498
2254
  contextField: containersContextField,
1499
2255
  importLines: containerImportLines,
1500
2256
  specs: containerSpecs
1501
- } = emitContainerFragments(containers);
2257
+ } = emitContainerFragments(containers, schema.jurisdiction);
1502
2258
  const {
1503
2259
  build: workflowsBuild,
1504
2260
  contextField: workflowsContextField,
@@ -1512,25 +2268,35 @@ const emitShard = ({
1512
2268
  imports: paymentsImports,
1513
2269
  stub: paymentStub
1514
2270
  } = emitPaymentFragments(hasPayments);
2271
+ const { build: x402Build, configField: x402ConfigField, imports: x402Imports, stub: x402Stub } = emitX402Fragments(hasX402);
1515
2272
  const advisoryData = advisories;
1516
2273
  const rlsData = rlsMetadata ?? { policies: [], roles: [] };
1517
2274
  const maskData = maskMetadata ?? { columns: [] };
1518
2275
  const storageRulesData = storageRules ?? { rules: [] };
1519
2276
  const studioFeaturesData = studioFeatures ?? {
2277
+ analytics: false,
2278
+ auth: false,
2279
+ containers: false,
2280
+ flags: false,
2281
+ kv: false,
1520
2282
  mail: false,
1521
2283
  payments: false,
2284
+ queues: false,
1522
2285
  scheduler: false,
1523
2286
  storage: false,
1524
2287
  vectors: false,
1525
2288
  workflows: false
1526
2289
  };
1527
2290
  const { constant: workflowsMetadataConst, override: workflowsMetadataOverride } = emitWorkflowsMetadataFragments(workflows);
2291
+ const { constant: queuesMetadataConst, override: queuesMetadataOverride } = emitQueuesMetadataFragments(queues);
1528
2292
  const hasVectors = schema.vectorIndexes.length > 0;
1529
2293
  const hasGlobalTables = schema.tables.some((table) => table.shardMode === "global");
1530
2294
  const hasHyperdriveGlobal = schema.tables.some((table) => table.shardMode === "global" && table.globalBackend === "hyperdrive");
1531
2295
  const hasD1Global = schema.tables.some((table) => table.shardMode === "global" && table.globalBackend !== "hyperdrive");
2296
+ const hasSourcedTables = schema.tables.some((table) => table.externalSource !== void 0);
1532
2297
  if (hasD1Global && hasHyperdriveGlobal) {
1533
- throw new Error(
2298
+ throw new LunoraError(
2299
+ "INTERNAL",
1534
2300
  'lunora codegen: mixing `.global()` (D1) and `.global({ backend: "hyperdrive" })` tables in one app is not supported yet — use a single global backend.'
1535
2301
  );
1536
2302
  }
@@ -1539,41 +2305,115 @@ const emitShard = ({
1539
2305
  const tableIndexes = buildTableIndexes(schema);
1540
2306
  const tableColumns = buildTableColumns(schema);
1541
2307
  const storageColumns = buildStorageColumns(schema);
1542
- const doTypeImports = buildDoTypeImports(hasVectors, workflows.length > 0);
2308
+ const doTypeImports = buildDoTypeImports(hasVectors, workflows.length > 0, queues.length > 0, hasFlags);
2309
+ if (hasShapes) {
2310
+ doTypeImports.push("WhereInput");
2311
+ }
1543
2312
  const relationFanout = emitRelationFanout(hasGlobalTables);
2313
+ const shapeResolveOverride = hasShapes ? `
2314
+ protected override resolveShape(name: string, args: Record<string, unknown>, identity?: { identity?: Record<string, unknown>; userId?: string }): { columns?: readonly string[]; effectiveWhere?: WhereInput; global?: boolean; table: string } | undefined {
2315
+ const shape = LUNORA_SHAPES[name];
2316
+
2317
+ if (!shape) {
2318
+ return undefined;
2319
+ }
2320
+
2321
+ this.ensureMigrated();
2322
+
2323
+ // Trusted ctx from the socket's OWN verified identity — the client
2324
+ // supplies only the shape name + args; \`compileWhere\` validates the
2325
+ // args then runs the shape's \`where(ctx, args)\` under that identity,
2326
+ // so which rows replicate is a server decision (reads-as-permissions).
2327
+ const ctx = this.buildCtx({ functionPath: \`__shape__:\${name}\`, identity });
2328
+ const shapeWhere = shape.compileWhere(ctx, args) as unknown as WhereInput;
2329
+
2330
+ // AND-compose with the table's RLS read base-where. A shape runs no
2331
+ // procedure, so the \`.use(rls(...))\` middleware never fires; without
2332
+ // this merge its reads would bypass every read policy on the table
2333
+ // (rows the caller can't see would replicate). \`composeShapeReadWhere\`
2334
+ // evaluates the table's read policies under this same trusted ctx —
2335
+ // exactly the request-time path — and fails closed under a
2336
+ // \`.rls("required")\` schema for a non-\`.public()\`, policy-less table.
2337
+ const effectiveWhere = composeShapeReadWhere(LUNORA_RLS_READ_REGISTRY, {
2338
+ ctx,
2339
+ identity: identity?.identity ?? null,
2340
+ rlsRequired: (schema as unknown as { rlsMode?: string }).rlsMode === "required",
2341
+ roles: (ctx as { auth?: { roles?: readonly string[] } }).auth?.roles ?? [],
2342
+ shapeWhere,
2343
+ table: shape.table,
2344
+ tablePublic: (schema as unknown as { tables: Record<string, { isPublic?: boolean }> }).tables[shape.table]?.isPublic === true,
2345
+ userId: identity?.userId ?? null,
2346
+ });
2347
+
2348
+ // A live shape pokes only from its OWN shard's op-log, so a \`where()\`
2349
+ // that joins to a \`.shardBy()\` table (rows in another DO) is rejected
2350
+ // here — the first point the compiled predicate + shard modes are both
2351
+ // known. Remedy: denormalize, or move the joined table to \`.global()\`.
2352
+ assertShapeShardable(effectiveWhere, schema as unknown as SchemaLike, shape.table);
2353
+
2354
+ // A \`.global()\` table lives in D1 (no per-DO op-log): flag it so the
2355
+ // base serves it through the latency-tiered poll path (\`readGlobalShapeRows\`)
2356
+ // instead of the CDC poke path.
2357
+ const isGlobal = (schema as unknown as SchemaLike).tables[shape.table]?.shardMode?.kind === "global";
2358
+
2359
+ return { columns: shape.columns, effectiveWhere, global: isGlobal, table: shape.table };
2360
+ }
2361
+ ` : "";
2362
+ const customMutatorOverride = hasMutators ? `
2363
+ protected override isCustomMutator(functionPath: string): boolean {
2364
+ return LUNORA_MUTATOR_PATHS.has(functionPath);
2365
+ }
2366
+ ` : "";
2367
+ const shapeReadRegistryConst = hasShapes ? `
2368
+ /** Per-table RLS read policies (hoisted from \`.use(rls(...))\` chains) the shape resolver AND-merges into each \`defineShape\` predicate so partial replication honours read policies. */
2369
+ const LUNORA_RLS_READ_REGISTRY = buildRlsReadRegistry(Object.values(LUNORA_FUNCTIONS));
2370
+ ` : "";
2371
+ const shapeGuardImport = hasShapes ? "assertShapeShardable, " : "";
1544
2372
  const importLines = [
1545
2373
  `import type { ${doTypeImports.join(", ")} } from "${base.do}";`,
1546
- `import { applyCdcChanges, createShardCtxDb, runDataMigration, runShardMigrations, ${relationFanout.importFragment}ShardDO as ShardDOBase } from "${base.do}";`,
1547
- // `asBucketStorage` (the bucket-aware `ctx.storage` wrapper) lives in
2374
+ `import { applyCdcChanges, ${shapeGuardImport}createShardCtxDb, ${hasSourcedTables ? "isSourceDue, pullExternalSourceTick, " : ""}runDataMigration, runShardMigrations, ${relationFanout.importFragment}ShardDO as ShardDOBase } from "${base.do}";`,
2375
+ ...hasSourcedTables ? [`import type { ExternalSourceLike, SourceClientLike } from "${base.do}";`] : [],
2376
+ // `asBucketStorage` (the bucket-aware `ctx.storage` wrapper) and
2377
+ // `createSecrets` (the `ctx.secrets` core built-in) live in
1548
2378
  // `@lunora/server`, the single source — imported here rather than stamped
1549
- // inline into every generated shard.
1550
- `import { asBucketStorage } from "${base.server}";`
2379
+ // inline into every generated shard. With shapes, also pull the RLS
2380
+ // read-registry builder + `composeShapeReadWhere` so `resolveShape` can
2381
+ // AND-merge a shape's predicate with the table's read base-where.
2382
+ hasShapes ? `import { asBucketStorage, buildRlsReadRegistry, composeShapeReadWhere, createSecrets, LunoraError } from "${base.server}";` : `import { asBucketStorage, createSecrets, LunoraError } from "${base.server}";`
1551
2383
  ];
1552
2384
  if (hasTables) {
1553
2385
  importLines.push(`import { bindOrm, bindTableFacade } from "${base.server}";`);
1554
2386
  }
1555
2387
  if (hasVectors) {
1556
2388
  importLines.push(
1557
- `import type { SchemaLike as VectorSchemaLike, VectorizeIndexLike, VectorSearchLike } from "@lunora/vectors";`,
1558
- `import { createContextVectors, createVectors, createVectorSyncHook } from "@lunora/vectors";`
2389
+ `import type { SchemaLike as VectorSchemaLike, VectorizeIndexLike, VectorSearchLike } from "@lunora/bindings/vectors";`,
2390
+ `import { createContextVectors, createVectors, createVectorSyncHook } from "@lunora/bindings/vectors";`
1559
2391
  );
1560
2392
  }
1561
2393
  if (hasAi) {
1562
2394
  importLines.push(`import type { AiBindingLike, LunoraAi } from "@lunora/ai";`, `import { createAi } from "@lunora/ai";`);
1563
2395
  }
1564
2396
  importLines.push(
2397
+ ...accessFragments.importLines,
1565
2398
  ...kvFragments.importLines,
2399
+ ...flagsFragments.importLines,
2400
+ ...envFragments.importLines,
1566
2401
  ...analyticsFragments.importLines,
1567
2402
  ...imagesFragments.importLines,
1568
2403
  ...hyperdriveFragments.importLines,
1569
2404
  ...browserFragments.importLines,
1570
2405
  ...r2sqlFragments.importLines,
2406
+ ...pipelinesFragments.importLines,
1571
2407
  ...containerImportLines,
1572
2408
  ...workflowImportLines,
2409
+ ...queueImportLines,
1573
2410
  ...paymentsImports,
2411
+ ...x402Imports,
1574
2412
  ``,
1575
2413
  `import schema from "../schema.js";`,
1576
- `import { LUNORA_FUNCTIONS, LUNORA_LIFECYCLE_HOOKS, LUNORA_MIGRATIONS } from "./functions.js";`
2414
+ // Local-first sync registries are pulled in alongside the function table
2415
+ // only when the project declares them, so the import list stays minimal.
2416
+ `import { ${["LUNORA_FUNCTIONS", "LUNORA_LIFECYCLE_HOOKS", "LUNORA_MIGRATIONS", ...hasMutators ? ["LUNORA_MUTATOR_PATHS"] : [], ...hasShapes ? ["LUNORA_SHAPES"] : []].join(", ")} } from "./functions.js";`
1577
2417
  );
1578
2418
  const vectorsConfigField = hasVectors ? `
1579
2419
  vectors?: (env: Record<string, unknown>) => Record<string, VectorizeIndexLike>;` : "";
@@ -1581,6 +2421,8 @@ const emitShard = ({
1581
2421
  d1?: (env: Record<string, unknown>, request?: { identity?: Record<string, unknown>; userId?: string }) => DatabaseWriterLike | undefined;` : "";
1582
2422
  const hyperdriveGlobalConfigField = hasHyperdriveGlobal ? `
1583
2423
  hyperdriveGlobal?: (env: Record<string, unknown>, request?: { identity?: Record<string, unknown>; userId?: string }) => DatabaseWriterLike | undefined;` : "";
2424
+ const sourceClientConfigField = hasSourcedTables ? `
2425
+ sourceClient?: (env: Record<string, unknown>, binding: string) => { query: <Row = Record<string, unknown>>(text: string, params?: readonly unknown[]) => Promise<Row[]> } | undefined;` : "";
1584
2426
  const globalDatabaseStub = hasGlobalTables ? `
1585
2427
  const globalDbStub: DatabaseWriterLike = {
1586
2428
  aggregate: async () => {
@@ -1698,6 +2540,144 @@ const vectorsStub: VectorSearchLike = {
1698
2540
  const bindTableHelper = "";
1699
2541
  const globalDatabaseThunk = hasHyperdriveGlobal ? "config.hyperdriveGlobal" : "config.d1";
1700
2542
  const globalDatabaseLine = hasGlobalTables ? ` const globalDb: DatabaseWriterLike = ${globalDatabaseThunk}?.(env, { identity, userId }) ?? globalDbStub;
2543
+ ` : "";
2544
+ const globalShapeReaderOverride = hasShapes && hasGlobalTables ? `
2545
+ protected override async readGlobalShapeRows(resolved: { columns?: readonly string[]; effectiveWhere?: WhereInput; global?: boolean; table: string }, identity?: { identity?: Record<string, unknown>; userId?: string }): Promise<Array<{ doc: Record<string, unknown>; id: string }>> {
2546
+ const env = this.env as Record<string, unknown>;
2547
+ const globalDb: DatabaseWriterLike = ${globalDatabaseThunk}?.(env, identity) ?? globalDbStub;
2548
+ const rows: Array<{ doc: Record<string, unknown>; id: string }> = [];
2549
+
2550
+ let cursor: null | string = null;
2551
+
2552
+ // Drain every page of the global membership so the seed/diff sees the
2553
+ // full rowset (D1 \`findMany\` is paginated). Stop one row past the cap:
2554
+ // a broad \`.global()\` shape would otherwise materialize an unbounded
2555
+ // array before the caller's \`withinGlobalShapeBound\` check rejects it,
2556
+ // so bail early and let the caller fail it closed.
2557
+ do {
2558
+ // eslint-disable-next-line no-await-in-loop -- sequential page drain to assemble the full membership
2559
+ const page = await globalDb.findMany(resolved.table, { cursor, where: resolved.effectiveWhere });
2560
+
2561
+ for (const doc of page.page) {
2562
+ rows.push({ doc, id: String((doc as { _id?: unknown })._id) });
2563
+
2564
+ if (rows.length > ShardDOBase.GLOBAL_SHAPE_MAX_ROWS) {
2565
+ return rows;
2566
+ }
2567
+ }
2568
+
2569
+ cursor = page.isDone ? null : page.continueCursor;
2570
+ } while (cursor !== null);
2571
+
2572
+ return rows;
2573
+ }
2574
+ ` : "";
2575
+ const sourceClientCacheConst = hasSourcedTables ? `
2576
+ const sourceClientCache = new WeakMap<object, Map<string, SourceClientLike>>();
2577
+ const sourcePollAtCache = new WeakMap<object, Map<string, number>>();
2578
+ ` : "";
2579
+ const externalSourceOverride = hasSourcedTables ? `
2580
+ protected override async pollExternalSources(): Promise<number> {
2581
+ const env = (this.env ?? {}) as Record<string, unknown>;
2582
+ const sourced = Object.entries((schema as unknown as SchemaLike).tables)
2583
+ .map(([table, definition]) => [table, (definition as { externalSource?: ExternalSourceLike }).externalSource] as const)
2584
+ .filter((entry): entry is [string, ExternalSourceLike] => entry[1] !== undefined);
2585
+
2586
+ if (sourced.length === 0) {
2587
+ return 0;
2588
+ }
2589
+
2590
+ const shardKey = this.currentShardKey();
2591
+ const scheduler = (config.scheduler?.(env) ?? schedulerStub) as SchedulerLike;
2592
+ const writer = createShardCtxDb({
2593
+ broadcast: (delta) => {
2594
+ this.recordChangedTable(delta.table);
2595
+ },
2596
+ cdc: config.cdc ?? false,
2597
+ scheduler,
2598
+ schema: schema as unknown as SchemaLike,
2599
+ sql: this.sql as SqlExec,
2600
+ });
2601
+
2602
+ let clients = sourceClientCache.get(this);
2603
+
2604
+ if (clients === undefined) {
2605
+ clients = new Map();
2606
+ sourceClientCache.set(this, clients);
2607
+ }
2608
+
2609
+ let polledAt = sourcePollAtCache.get(this);
2610
+
2611
+ if (polledAt === undefined) {
2612
+ polledAt = new Map();
2613
+ sourcePollAtCache.set(this, polledAt);
2614
+ }
2615
+
2616
+ const now = Date.now();
2617
+ // \`active\` counts non-manual sources: while > 0 the alarm re-arms; a
2618
+ // manual-only schema returns 0 so the shared alarm goes idle.
2619
+ let active = 0;
2620
+
2621
+ for (const [table, source] of sourced) {
2622
+ if (source.refresh === "manual") {
2623
+ continue;
2624
+ }
2625
+
2626
+ active += 1;
2627
+
2628
+ if (!isSourceDue(source.refresh, polledAt.get(table), now)) {
2629
+ continue;
2630
+ }
2631
+
2632
+ try {
2633
+ let client = clients.get(source.binding);
2634
+
2635
+ if (client === undefined) {
2636
+ client = config.sourceClient?.(env, source.binding);
2637
+
2638
+ if (client !== undefined) {
2639
+ clients.set(source.binding, client);
2640
+ }
2641
+ }
2642
+
2643
+ if (client === undefined) {
2644
+ // No SqlClient resolved for this binding (host never wired
2645
+ // \`config.sourceClient\`, or wired it wrong). Surface it in the
2646
+ // Logs panel and stamp \`polledAt\` so a persistent misconfig backs
2647
+ // off to \`refresh.everyMs\` instead of retrying every alarm tick.
2648
+ this.recordExternalSourceError(table, new Error(\`external-source: no sourceClient resolved for binding "\${source.binding}"\`));
2649
+ polledAt.set(table, now);
2650
+ continue;
2651
+ }
2652
+
2653
+ // eslint-disable-next-line no-await-in-loop -- one sourced table at a time; slices are independent but small and sequential keeps the writer transaction simple
2654
+ await pullExternalSourceTick(this.sql as SqlExec, writer, client, table, source, shardKey);
2655
+ polledAt.set(table, now);
2656
+ } catch (error) {
2657
+ this.recordExternalSourceError(table, error);
2658
+ // Stamp on failure too, so a persistently failing source throttles
2659
+ // to \`refresh.everyMs\` rather than being hammered every tick.
2660
+ polledAt.set(table, now);
2661
+ }
2662
+ }
2663
+
2664
+ return active;
2665
+ }
2666
+ ` : "";
2667
+ const sourceConstructorOverride = hasSourcedTables ? `
2668
+ public constructor(state: ShardDOState, env: unknown) {
2669
+ super(state, env);
2670
+
2671
+ const autoSourced = Object.values((schema as unknown as SchemaLike).tables).some((definition) => {
2672
+ const source = (definition as { externalSource?: ExternalSourceLike }).externalSource;
2673
+
2674
+ return source !== undefined && source.refresh !== "manual";
2675
+ });
2676
+
2677
+ if (autoSourced) {
2678
+ void this.scheduleSourcePoll();
2679
+ }
2680
+ }
1701
2681
  ` : "";
1702
2682
  const facadeBlock = hasTables ? `
1703
2683
  const facade = db as unknown as Record<string, ReturnType<typeof bindTableFacade>>;
@@ -1711,18 +2691,24 @@ ${schema.tables.map(
1711
2691
  (table) => ` facade[${JSON.stringify(table.name)}] = bindTableFacade(db, ${JSON.stringify(table.name)});`
1712
2692
  ).join("\n")}
1713
2693
  ` : "";
1714
- const everyContextBuild = `${kvFragments.build}${analyticsFragments.build}`;
1715
- const everyContextField = `${kvFragments.contextField}${analyticsFragments.contextField}`;
1716
- const actionOnlyHasAny = hasImages || hasHyperdrive || hasBrowser || hasR2sql;
2694
+ const secretsBuild = `
2695
+ const secrets = createSecrets(env);
2696
+ `;
2697
+ const everyContextBuild = `${accessFragments.build}${kvFragments.build}${flagsFragments.build}${analyticsFragments.build}${envFragments.build}${secretsBuild}`;
2698
+ const everyContextField = `${accessFragments.contextField}${kvFragments.contextField}${flagsFragments.contextField}${analyticsFragments.contextField}${envFragments.contextField}
2699
+ secrets,`;
2700
+ const actionOnlyHasAny = hasImages || hasHyperdrive || hasBrowser || hasR2sql || hasPipelines || hasX402;
1717
2701
  const actionOnlyBlock = actionOnlyHasAny ? `
1718
2702
  // ActionCtx-only helpers (external, non-deterministic I/O): constructed
1719
2703
  // and attached only for an \`action\` so query/mutation ctx never carry them.
1720
2704
  if (isAction) {
1721
- ${imagesFragments.build}${hyperdriveFragments.build}${browserFragments.build}${r2sqlFragments.build}${[
2705
+ ${imagesFragments.build}${hyperdriveFragments.build}${browserFragments.build}${r2sqlFragments.build}${pipelinesFragments.build}${x402Build}${[
1722
2706
  ...hasImages ? [" ctx.images = images;"] : [],
1723
2707
  ...hasHyperdrive ? [" ctx.sql = sql;"] : [],
1724
2708
  ...hasBrowser ? [" ctx.browser = browser;"] : [],
1725
- ...hasR2sql ? [" ctx.r2sql = r2sql;"] : []
2709
+ ...hasR2sql ? [" ctx.r2sql = r2sql;"] : [],
2710
+ ...hasPipelines ? [" ctx.pipelines = pipelines;"] : [],
2711
+ ...hasX402 ? [" ctx.x402 = x402;"] : []
1726
2712
  ].join("\n")}
1727
2713
  }
1728
2714
  ` : "";
@@ -1762,14 +2748,14 @@ const LUNORA_STORAGE_RULES: StorageRulesResult = ${JSON.stringify(storageRulesDa
1762
2748
 
1763
2749
  /** Which optional package-backed features this app wires up (discovered from imports / \`ctx.*\` reads / schema signals) served via \`__lunora_admin__:studioFeatures\` so the studio hides nav pages whose package isn't enabled. */
1764
2750
  const LUNORA_STUDIO_FEATURES: StudioFeaturesResult = ${JSON.stringify(studioFeaturesData, void 0, 4)};
1765
- ${workflowsMetadataConst}${containerSpecs}${workflowSpecs}
2751
+ ${flagsOverrides.constant}${shapeReadRegistryConst}${workflowsMetadataConst}${queuesMetadataConst}${containerSpecs}${workflowSpecs}${queueSpecs}
1766
2752
  export interface ShardDOConfig {
1767
2753
  /** Opt into change-data-capture: records a post-image to \`__cdc_log\` on every write (backs streaming export + replay-PITR). */
1768
2754
  cdc?: boolean;
1769
2755
  /** Optional telemetry sink. When supplied, each \`ctx.log.*\` call is forwarded to \`sink.onLog\`. Pass the SAME sink you give \`createWorker({ observability })\` (which drives \`onRpc\`) to route both RPC and log events. */
1770
2756
  observability?: (env: Record<string, unknown>) => LogSink | undefined;
1771
2757
  scheduler?: (env: Record<string, unknown>) => unknown;
1772
- storage?: (env: Record<string, unknown>) => unknown;${vectorsConfigField}${aiConfigField}${kvFragments.configField}${analyticsFragments.configField}${imagesFragments.configField}${hyperdriveFragments.configField}${browserFragments.configField}${r2sqlFragments.configField}${paymentsConfigField}${d1ConfigField}${hyperdriveGlobalConfigField}
2758
+ storage?: (env: Record<string, unknown>) => unknown;${vectorsConfigField}${aiConfigField}${kvFragments.configField}${flagsFragments.configField}${analyticsFragments.configField}${imagesFragments.configField}${hyperdriveFragments.configField}${browserFragments.configField}${r2sqlFragments.configField}${pipelinesFragments.configField}${paymentsConfigField}${x402ConfigField}${d1ConfigField}${hyperdriveGlobalConfigField}${sourceClientConfigField}
1773
2759
  }
1774
2760
 
1775
2761
  const schedulerStub = {
@@ -1807,7 +2793,7 @@ const storageStub = {
1807
2793
  throw new Error("ctx.storage: no storage configured. Pass \`storage\` to createShardDO().");
1808
2794
  },
1809
2795
  };
1810
- ${globalDatabaseStub}${vectorsStub}${aiStub}${kvFragments.stub}${analyticsFragments.stub}${imagesFragments.stub}${hyperdriveFragments.stub}${browserFragments.stub}${r2sqlFragments.stub}${paymentStub}${bindTableHelper}
2796
+ ${globalDatabaseStub}${sourceClientCacheConst}${vectorsStub}${aiStub}${kvFragments.stub}${flagsFragments.stub}${analyticsFragments.stub}${imagesFragments.stub}${hyperdriveFragments.stub}${browserFragments.stub}${r2sqlFragments.stub}${pipelinesFragments.stub}${paymentStub}${x402Stub}${bindTableHelper}
1811
2797
  // Bound in-process \`ctx.run*\` composition depth so a self- or cyclically-
1812
2798
  // referencing call fails loudly with a clear error instead of overflowing the
1813
2799
  // stack. Tracked across the awaited handler chain (one DO invocation is
@@ -1827,11 +2813,7 @@ const dispatchRun = async (expected: FunctionKind, functionPath: string, args: R
1827
2813
  }
1828
2814
 
1829
2815
  if (runDepth >= MAX_RUN_DEPTH) {
1830
- throw Object.assign(new Error(\`ctx.run*: composition depth limit (\${MAX_RUN_DEPTH}) exceeded — likely a cyclic runQuery/runMutation\`), {
1831
- name: "LunoraError",
1832
- code: "RUN_DEPTH_EXCEEDED",
1833
- status: 500,
1834
- });
2816
+ throw new LunoraError("RUN_DEPTH_EXCEEDED", \`ctx.run*: composition depth limit (\${MAX_RUN_DEPTH}) exceeded — likely a cyclic runQuery/runMutation\`);
1835
2817
  }
1836
2818
 
1837
2819
  runDepth += 1;
@@ -1848,7 +2830,7 @@ const dispatchRun = async (expected: FunctionKind, functionPath: string, args: R
1848
2830
  * from the worker entry so wrangler binds it by name.
1849
2831
  */
1850
2832
  export const createShardDO = (config: ShardDOConfig = {}): new (state: ShardDOState, env: unknown) => ShardDOBase =>
1851
- class extends ShardDOBase {
2833
+ class extends ShardDOBase {${sourceConstructorOverride}
1852
2834
  private migrated = false;
1853
2835
 
1854
2836
  public override async handleRpc(functionPath: string, args: Record<string, unknown>): Promise<unknown> {
@@ -1859,11 +2841,7 @@ export const createShardDO = (config: ShardDOConfig = {}): new (state: ShardDOSt
1859
2841
  // \`isSystemDispatch()\`). A client RPC never carries that flag, so its
1860
2842
  // internals stay not-found and never leak across the external boundary.
1861
2843
  if (!registered || (registered.visibility === "internal" && !this.isSystemDispatch())) {
1862
- throw Object.assign(new Error(\`function not registered: \${functionPath}\`), {
1863
- name: "LunoraError",
1864
- code: "FUNCTION_NOT_FOUND",
1865
- status: 404,
1866
- });
2844
+ throw new LunoraError("FUNCTION_NOT_FOUND", \`function not registered: \${functionPath}\`);
1867
2845
  }
1868
2846
 
1869
2847
  this.ensureMigrated();
@@ -1877,8 +2855,20 @@ export const createShardDO = (config: ShardDOConfig = {}): new (state: ShardDOSt
1877
2855
  // do external I/O that can't be rolled back, so both dispatch directly.
1878
2856
  // \`ctx.run*\` composition runs inside this span (it never re-enters
1879
2857
  // handleRpc); runInTransaction's own guard rejects accidental nesting.
2858
+ //
2859
+ // The replay bookkeeping (idempotency dedup row + custom-mutator
2860
+ // watermark advance) commits INSIDE this span via
2861
+ // \`commitMutationBookkeeping\`, so the writes, the dedup row, and the
2862
+ // watermark are atomic — a crash can't leave the writes durable without
2863
+ // the replay guard.
1880
2864
  if (registered.kind === "mutation") {
1881
- return this.runInTransaction(() => registered.handler(ctx, args));
2865
+ return this.runInTransaction(async () => {
2866
+ const result = await registered.handler(ctx, args);
2867
+
2868
+ this.commitMutationBookkeeping(result);
2869
+
2870
+ return result;
2871
+ });
1882
2872
  }
1883
2873
 
1884
2874
  return registered.handler(ctx, args);
@@ -1916,7 +2906,7 @@ ${relationFanout.override}
1916
2906
  iterator: (signal) => (registered.handler as (context: unknown, args: Record<string, unknown>, signal: AbortSignal) => AsyncIterable<unknown>)(this.buildCtx({ functionPath }), args, signal),
1917
2907
  };
1918
2908
  }
1919
-
2909
+ ${customMutatorOverride}${shapeResolveOverride}${globalShapeReaderOverride}${externalSourceOverride}
1920
2910
  protected override lifecycleHookPaths(event: "connect" | "disconnect"): readonly string[] {
1921
2911
  return LUNORA_LIFECYCLE_HOOKS[event];
1922
2912
  }
@@ -1952,7 +2942,7 @@ ${relationFanout.override}
1952
2942
  protected override studioFeatures(): StudioFeaturesResult {
1953
2943
  return LUNORA_STUDIO_FEATURES;
1954
2944
  }
1955
- ${workflowsMetadataOverride}
2945
+ ${flagsOverrides.evaluateOverride}${flagsOverrides.subscriptionOverride}${workflowsMetadataOverride}${queuesMetadataOverride}
1956
2946
  protected override advisories(): AdvisoryFinding[] {
1957
2947
  return LUNORA_ADVISORIES;
1958
2948
  }
@@ -1961,11 +2951,7 @@ ${workflowsMetadataOverride}
1961
2951
  const migration = LUNORA_MIGRATIONS[args.id];
1962
2952
 
1963
2953
  if (!migration) {
1964
- throw Object.assign(new Error(\`data migration "\${args.id}" is not registered\`), {
1965
- name: "LunoraError",
1966
- code: "MIGRATION_NOT_FOUND",
1967
- status: 404,
1968
- });
2954
+ throw new LunoraError("MIGRATION_NOT_FOUND", \`data migration "\${args.id}" is not registered\`, { status: 404 });
1969
2955
  }
1970
2956
 
1971
2957
  this.ensureMigrated();
@@ -2001,18 +2987,14 @@ ${workflowsMetadataOverride}
2001
2987
  const definition = (schema as unknown as SchemaLike).tables[args.table];
2002
2988
 
2003
2989
  if (!definition) {
2004
- throw Object.assign(new Error(\`unknown table: \${args.table}\`), { name: "LunoraError", code: "UNKNOWN_TABLE", status: 404 });
2990
+ throw new LunoraError("UNKNOWN_TABLE", \`unknown table: \${args.table}\`, { status: 404 });
2005
2991
  }
2006
2992
 
2007
2993
  // \`.global()\` tables live in D1, not this DO's SQLite — editing them
2008
2994
  // here would corrupt nothing but would fail confusingly, so reject up
2009
2995
  // front with a clear code the studio can surface.
2010
2996
  if (definition.shardMode?.kind === "global") {
2011
- throw Object.assign(new Error(\`table "\${args.table}" is global; edit it through D1, not the shard\`), {
2012
- name: "LunoraError",
2013
- code: "GLOBAL_TABLE_NOT_EDITABLE",
2014
- status: 400,
2015
- });
2997
+ throw new LunoraError("GLOBAL_TABLE_NOT_EDITABLE", \`table "\${args.table}" is global; edit it through D1, not the shard\`, { status: 400 });
2016
2998
  }
2017
2999
 
2018
3000
  this.ensureMigrated();
@@ -2056,17 +3038,13 @@ ${workflowsMetadataOverride}
2056
3038
  const definition = (schema as unknown as SchemaLike).tables[table];
2057
3039
 
2058
3040
  if (!definition) {
2059
- throw Object.assign(new Error(\`unknown table: \${table}\`), { name: "LunoraError", code: "UNKNOWN_TABLE", status: 404 });
3041
+ throw new LunoraError("UNKNOWN_TABLE", \`unknown table: \${table}\`, { status: 404 });
2060
3042
  }
2061
3043
 
2062
3044
  // \`.global()\` tables live in D1, not this DO's SQLite — the same
2063
3045
  // guard \`runShardWrite\` applies to single-row edits.
2064
3046
  if (definition.shardMode?.kind === "global") {
2065
- throw Object.assign(new Error(\`table "\${table}" is global; edit it through D1, not the shard\`), {
2066
- name: "LunoraError",
2067
- code: "GLOBAL_TABLE_NOT_EDITABLE",
2068
- status: 400,
2069
- });
3047
+ throw new LunoraError("GLOBAL_TABLE_NOT_EDITABLE", \`table "\${table}" is global; edit it through D1, not the shard\`, { status: 400 });
2070
3048
  }
2071
3049
 
2072
3050
  this.ensureMigrated();
@@ -2107,7 +3085,7 @@ ${workflowsMetadataOverride}
2107
3085
  // \`rankBefore\` is optional on \`DatabaseWriterLike\` (the D1 twin omits it),
2108
3086
  // but the shard writer from \`createShardCtxDb\` always defines it.
2109
3087
  if (!writer.rankBefore) {
2110
- throw Object.assign(new Error("rankBefore is unavailable on the shard writer"), { name: "LunoraError", code: "NOT_IMPLEMENTED", status: 500 });
3088
+ throw new LunoraError("NOT_IMPLEMENTED", "rankBefore is unavailable on the shard writer", { status: 500 });
2111
3089
  }
2112
3090
 
2113
3091
  return writer.rankBefore(args.table, args.index, {
@@ -2137,7 +3115,7 @@ ${workflowsMetadataOverride}
2137
3115
  // directions live in the schema's rankIndex, so the shard reads them itself;
2138
3116
  // \`args.directions\` is only the coordinator's comparator hint and isn't forwarded.
2139
3117
  if (!writer.rankPageRows) {
2140
- throw Object.assign(new Error("rankPage is unavailable on the shard writer"), { name: "LunoraError", code: "NOT_IMPLEMENTED", status: 500 });
3118
+ throw new LunoraError("NOT_IMPLEMENTED", "rankPage is unavailable on the shard writer", { status: 500 });
2141
3119
  }
2142
3120
 
2143
3121
  return writer.rankPageRows(args.table, args.index, {
@@ -2186,7 +3164,7 @@ ${workflowsMetadataOverride}
2186
3164
  // dispatch path) fall back to the per-request fields as before.
2187
3165
  const userId = options.identity ? options.identity.userId : this.getCurrentUserId();
2188
3166
  const identity = options.identity ? options.identity.identity : this.getCurrentIdentity();
2189
- ${vectorsBuild}${aiBuild}${everyContextBuild}${containersBuild}${workflowsBuild}
3167
+ ${vectorsBuild}${aiBuild}${everyContextBuild}${containersBuild}${workflowsBuild}${queuesBuild}
2190
3168
  const scheduler = (config.scheduler?.(env) ?? schedulerStub) as SchedulerLike;
2191
3169
  // Build the storage adapter once and share it between \`ctx.storage\`
2192
3170
  // and \`ctx.db.system._storage\` so both read the same R2 binding. The
@@ -2228,7 +3206,7 @@ ${facadeBlock}${paymentsBuild}
2228
3206
  log,
2229
3207
  now,${ormContextField}
2230
3208
  scheduler,
2231
- storage,${vectorsContextField}${aiContextField}${everyContextField}${paymentsContextField}${containersContextField}${workflowsContextField}
3209
+ storage,${vectorsContextField}${aiContextField}${everyContextField}${paymentsContextField}${containersContextField}${workflowsContextField}${queuesContextField}
2232
3210
  };
2233
3211
  ${isActionLine}${actionOnlyBlock}
2234
3212
  ctx.runAction = (reference: FunctionReference, fnArgs: Record<string, unknown>) => dispatchRun("action", reference.__lunoraRef, fnArgs, ctx);
@@ -2492,4 +3470,4 @@ const emitWranglerCronTriggers = (crons) => {
2492
3470
  return triggers;
2493
3471
  };
2494
3472
 
2495
- export { GENERATED_HEADER, buildStorageColumns, emitApi, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitSeed, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers };
3473
+ export { APP_METHOD_CAPABILITIES as A, CAPABILITIES as C, GENERATED_HEADER as G, emitCollections as a, emitContainers as b, emitCrons as c, emitDataModel as d, emitApi as e, emitDrizzleSchema as f, emitFunctions as g, emitServer as h, emitShard as i, emitVectors as j, emitWorkflows as k, emitWranglerCronTriggers as l, buildStorageColumns as m, emitQueues as n, emitSeed as o };