@lunora/codegen 1.0.0-alpha.1 → 1.0.0-alpha.11

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 (30) hide show
  1. package/__assets__/package-og.svg +1 -1
  2. package/dist/index.d.mts +171 -14
  3. package/dist/index.d.ts +171 -14
  4. package/dist/index.mjs +24 -22
  5. package/dist/packem_shared/{CONTAINERS_FILENAME-0K-pjNb8.mjs → CONTAINERS_FILENAME-DlP6YM_Q.mjs} +1 -1
  6. package/dist/packem_shared/{CodegenDiagnosticError-54jWDxA9.mjs → CodegenDiagnosticError-DeblMkzO.mjs} +3 -2
  7. package/dist/packem_shared/{emitApi-hRVC-kE7.mjs → GENERATED_HEADER-r1OZrKAx.mjs} +451 -43
  8. package/dist/packem_shared/{buildOpenRpcDocument-BZGY1-jT.mjs → OPENRPC_VERSION-BYRhDy-7.mjs} +1 -1
  9. package/dist/packem_shared/QUEUES_FILENAME-B5_eWCRe.mjs +119 -0
  10. package/dist/packem_shared/{createCodegenProject-DGJm0_Pk.mjs → SCHEMA_SNAPSHOT_FILENAME-CgJUPhI3.mjs} +102 -43
  11. package/dist/packem_shared/{buildSchemaSnapshot-DzLDbWk3.mjs → SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs} +12 -0
  12. package/dist/packem_shared/{discoverWorkflows-DRDQdhfq.mjs → WORKFLOWS_FILENAME-D62dcBGg.mjs} +1 -1
  13. package/dist/packem_shared/{buildOpenApiDocument-yHVN66Xd.mjs → buildOpenApiDocument-C8NKrvpw.mjs} +1 -1
  14. package/dist/packem_shared/{discoverAuthApiCalls-C35R6z0T.mjs → discoverAuthApiCalls-CoirYbg6.mjs} +1 -1
  15. package/dist/packem_shared/{discoverCrons-BL6iGuJ3.mjs → discoverCrons-Cev7RRAf.mjs} +20 -17
  16. package/dist/packem_shared/{discoverFunctions-DEgAcRuD.mjs → discoverFunctions-BWMczzBx.mjs} +7 -2
  17. package/dist/packem_shared/{discoverHttpRoutes-C978pBiG.mjs → discoverHttpRoutes-CfP6cMzt.mjs} +2 -2
  18. package/dist/packem_shared/{discoverInserts-CRQdXvHO.mjs → discoverInserts-C7zxXkUf.mjs} +24 -2
  19. package/dist/packem_shared/{discoverMaskProcedures-B64zA740.mjs → discoverMaskProcedures-Cm81kwrb.mjs} +1 -1
  20. package/dist/packem_shared/{discoverMigrations-Doj_-BAA.mjs → discoverMigrations-Bi5nJ0mJ.mjs} +10 -4
  21. package/dist/packem_shared/{discoverNondeterministicCalls-4KiPQxQU.mjs → discoverNondeterministicCalls-C4M8AXmQ.mjs} +1 -1
  22. package/dist/packem_shared/{discoverQueries-BkIi0dBD.mjs → discoverQueries-B0wGT-xe.mjs} +1 -1
  23. package/dist/packem_shared/discoverR2sqlCalls-BVNMd428.mjs +80 -0
  24. package/dist/packem_shared/{discoverRlsMetadata-DpRB1HMe.mjs → discoverRlsMetadata-BS9GOGC5.mjs} +1 -1
  25. package/dist/packem_shared/{discoverSchema-BBulgGbH.mjs → discoverSchema-BnWHHJ4T.mjs} +296 -16
  26. package/dist/packem_shared/{discoverStorageRulesMetadata-DAqJUxUv.mjs → discoverStorageRulesMetadata-Da8BKXcI.mjs} +1 -1
  27. package/dist/packem_shared/{emitApp-CzZ6GbrD.mjs → emitApp-B1DgLrM8.mjs} +28 -4
  28. package/dist/packem_shared/{lintSchema-DicbOHvH.mjs → formatAdvisories-8NIv1k0I.mjs} +2 -1
  29. package/dist/packem_shared/{parse-validator-tuQtHrsr.mjs → parse-validator-Cabb60UV.mjs} +2 -1
  30. package/package.json +8 -7
@@ -1,5 +1,174 @@
1
1
  import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
2
2
 
3
+ const LITERAL_VALUE_RE = /^(?:"[^"\\]*"|'[^'\\]*'|-?\d+(?:\.\d+)?|true|false|null)$/u;
4
+
5
+ const PASS_THROUGH_KINDS = /* @__PURE__ */ new Set(["any", "bigint", "boolean", "date", "id", "null", "number", "storage", "string", "timestamp"]);
6
+ const hasColumnModifier = (node) => node.column !== void 0;
7
+ const emitScalarGuard = (kind, inExpr) => {
8
+ switch (kind) {
9
+ case "bigint": {
10
+ return `if (typeof ${inExpr} !== "bigint") return DEFER;
11
+ `;
12
+ }
13
+ case "boolean": {
14
+ return `if (typeof ${inExpr} !== "boolean") return DEFER;
15
+ `;
16
+ }
17
+ case "date":
18
+ case "number":
19
+ case "timestamp": {
20
+ return `if (typeof ${inExpr} !== "number" || !Number.isFinite(${inExpr})) return DEFER;
21
+ `;
22
+ }
23
+ case "null": {
24
+ return `if (${inExpr} !== null) return DEFER;
25
+ `;
26
+ }
27
+ // string / id / storage all parse as a bare string at runtime.
28
+ default: {
29
+ return `if (typeof ${inExpr} !== "string") return DEFER;
30
+ `;
31
+ }
32
+ }
33
+ };
34
+ const compileLiteral = (node, inExpr) => {
35
+ const literal = node.literalValue?.trim();
36
+ if (literal === void 0 || !LITERAL_VALUE_RE.test(literal)) {
37
+ return void 0;
38
+ }
39
+ return { out: inExpr, pre: `if (${inExpr} !== ${literal}) return DEFER;
40
+ ` };
41
+ };
42
+ const compileNode = (node, inExpr, context) => {
43
+ if (hasColumnModifier(node)) {
44
+ return void 0;
45
+ }
46
+ if (node.hasRefinement || node.sourceText !== void 0) {
47
+ return void 0;
48
+ }
49
+ if (PASS_THROUGH_KINDS.has(node.kind)) {
50
+ if (node.kind === "any") {
51
+ return { out: inExpr, pre: "" };
52
+ }
53
+ return { out: inExpr, pre: emitScalarGuard(node.kind, inExpr) };
54
+ }
55
+ switch (node.kind) {
56
+ case "array": {
57
+ return compileArray(node, inExpr, context);
58
+ }
59
+ case "literal": {
60
+ return compileLiteral(node, inExpr);
61
+ }
62
+ case "object": {
63
+ return compileObject(node, inExpr, context);
64
+ }
65
+ default: {
66
+ return void 0;
67
+ }
68
+ }
69
+ };
70
+ const compileArray = (node, inExpr, context) => {
71
+ const { inner } = node;
72
+ if (!inner) {
73
+ return void 0;
74
+ }
75
+ const id = context.next();
76
+ const array = `__arr${String(id)}`;
77
+ const index = `__i${String(id)}`;
78
+ const element = `__e${String(id)}`;
79
+ const innerEmit = compileNode(inner, element, context);
80
+ if (!innerEmit) {
81
+ return void 0;
82
+ }
83
+ const pre = `if (!Array.isArray(${inExpr})) return DEFER;
84
+ const ${array} = new Array(${inExpr}.length);
85
+ for (let ${index} = 0; ${index} < ${inExpr}.length; ${index}++) {
86
+ const ${element} = ${inExpr}[${index}];
87
+ ${innerEmit.pre}${array}[${index}] = ${innerEmit.out};
88
+ }
89
+ `;
90
+ return { out: array, pre };
91
+ };
92
+ const compileField = (key, node, access, context) => {
93
+ const keyLiteral = JSON.stringify(key);
94
+ if (node.kind === "optional") {
95
+ if (node.hasRefinement || node.sourceText !== void 0 || hasColumnModifier(node)) {
96
+ return void 0;
97
+ }
98
+ const { inner } = node;
99
+ if (!inner) {
100
+ return void 0;
101
+ }
102
+ const innerEmit = compileNode(inner, access, context);
103
+ if (!innerEmit) {
104
+ return void 0;
105
+ }
106
+ const id = context.next();
107
+ const has = `__has${String(id)}`;
108
+ const value = `__val${String(id)}`;
109
+ const pre = `let ${has} = false;
110
+ let ${value};
111
+ if (${access} !== undefined) {
112
+ ${innerEmit.pre}${value} = ${innerEmit.out};
113
+ ${has} = true;
114
+ }
115
+ `;
116
+ return { entry: `...(${has} ? { ${keyLiteral}: ${value} } : {})`, pre };
117
+ }
118
+ const emit = compileNode(node, access, context);
119
+ if (!emit) {
120
+ return void 0;
121
+ }
122
+ return { entry: `${keyLiteral}: ${emit.out}`, pre: emit.pre };
123
+ };
124
+ const compileFields = (shape, accessFor, context) => {
125
+ let pre = "";
126
+ const entries = [];
127
+ for (const key of Object.keys(shape)) {
128
+ const node = shape[key];
129
+ if (!node) {
130
+ return void 0;
131
+ }
132
+ const field = compileField(key, node, accessFor(key), context);
133
+ if (!field) {
134
+ return void 0;
135
+ }
136
+ pre += field.pre;
137
+ entries.push(field.entry);
138
+ }
139
+ return { entries: entries.join(", "), pre };
140
+ };
141
+ const compileObject = (node, inExpr, context) => {
142
+ const shape = node.shape ?? {};
143
+ const fields = compileFields(shape, (key) => `${inExpr}[${JSON.stringify(key)}]`, context);
144
+ if (!fields) {
145
+ return void 0;
146
+ }
147
+ const id = context.next();
148
+ const object = `__obj${String(id)}`;
149
+ const pre = `if (typeof ${inExpr} !== "object" || ${inExpr} === null || Array.isArray(${inExpr})) return DEFER;
150
+ ${fields.pre}const ${object} = { ${fields.entries} };
151
+ `;
152
+ return { out: object, pre };
153
+ };
154
+ const compileArgsValidator = (args) => {
155
+ let counter = 0;
156
+ const context = {
157
+ next: () => {
158
+ counter += 1;
159
+ return counter;
160
+ }
161
+ };
162
+ const fields = compileFields(args, (key) => `source[${JSON.stringify(key)}]`, context);
163
+ if (!fields) {
164
+ return void 0;
165
+ }
166
+ return `(source) => {
167
+ if (typeof source !== "object" || source === null || Array.isArray(source)) return DEFER;
168
+ ${fields.pre}return { ${fields.entries} };
169
+ }`;
170
+ };
171
+
3
172
  const GENERATED_HEADER = "// GENERATED by @lunora/codegen — do not edit.\n// Run `lunora codegen` to regenerate.\n\n";
4
173
  const baseSpecifiers = (useUmbrella = false) => useUmbrella ? {
5
174
  client: "lunorash/client",
@@ -7,17 +176,18 @@ const baseSpecifiers = (useUmbrella = false) => useUmbrella ? {
7
176
  server: "lunorash/server",
8
177
  serverDataModel: "lunorash/server/data-model",
9
178
  serverDrizzle: "lunorash/server/drizzle",
10
- serverTypes: "lunorash/server/types"
179
+ serverTypes: "lunorash/server/types",
180
+ values: "lunorash/values"
11
181
  } : {
12
182
  client: "@lunora/client",
13
183
  do: "@lunora/do",
14
184
  server: "@lunora/server",
15
185
  serverDataModel: "@lunora/server/data-model",
16
186
  serverDrizzle: "@lunora/server/drizzle",
17
- serverTypes: "@lunora/server/types"
187
+ serverTypes: "@lunora/server/types",
188
+ values: "@lunora/values"
18
189
  };
19
190
  const IDENTIFIER_RE = /^[A-Za-z_$][\w$]*$/u;
20
- const LITERAL_VALUE_RE = /^(?:"[^"\\]*"|'[^'\\]*'|-?\d+(?:\.\d+)?|true|false|null)$/u;
21
191
  const IMPORT_PATH_RE = /^[\w./-]+$/u;
22
192
  const assertIdentifier = (value, context) => {
23
193
  if (!IDENTIFIER_RE.test(value)) {
@@ -469,6 +639,17 @@ const renderFunctionRegistry = (functions, migrations) => {
469
639
  const migrationEntries = migrations.map(
470
640
  (migration) => ` ${JSON.stringify(migration.id)}: ${aliasByPath.get(migration.filePath) ?? ""}.${migration.exportName} as unknown as RegisteredDataMigration,`
471
641
  ).join("\n");
642
+ const installLines = functions.map((definition) => {
643
+ if (definition.lifecycle || Object.keys(definition.args).length === 0) {
644
+ return void 0;
645
+ }
646
+ const compiled = compileArgsValidator(definition.args);
647
+ if (compiled === void 0) {
648
+ return void 0;
649
+ }
650
+ const alias = aliasByPath.get(definition.filePath) ?? "";
651
+ return `installCompiledValidatorMap(${alias}.${definition.exportName}.args, ${compiled});`;
652
+ }).filter((line) => line !== void 0).join("\n");
472
653
  return {
473
654
  dispatchBody: dispatchEntries.length > 0 ? `
474
655
  ${dispatchEntries}
@@ -476,6 +657,7 @@ ${dispatchEntries}
476
657
  importBlock: importLines.length > 0 ? `${importLines}
477
658
 
478
659
  ` : "",
660
+ installBlock: installLines,
479
661
  migrationBody: migrationEntries.length > 0 ? `
480
662
  ${migrationEntries}
481
663
  ` : ""
@@ -539,6 +721,8 @@ const emitServer = ({
539
721
  hasKv = false,
540
722
  hasPayments = false,
541
723
  hasPipelines = false,
724
+ hasR2sql = false,
725
+ queues = [],
542
726
  schema,
543
727
  storageRuleBuckets = [],
544
728
  useUmbrella = false,
@@ -572,6 +756,11 @@ const emitServer = ({
572
756
  assertIdentifier(workflow.bindingName, `workflow binding "${workflow.bindingName}"`);
573
757
  return ` /** Workflow binding for the \`${workflow.exportName}\` workflow. */
574
758
  readonly ${workflow.bindingName}?: unknown;`;
759
+ }),
760
+ ...queues.map((queue) => {
761
+ assertIdentifier(queue.bindingName, `queue binding "${queue.bindingName}"`);
762
+ return ` /** Queue producer binding for the \`${queue.exportName}\` queue. */
763
+ readonly ${queue.bindingName}?: unknown;`;
575
764
  })
576
765
  ].join("\n");
577
766
  const envBlock = `
@@ -594,7 +783,7 @@ ${envBindingFields}` : ""}
594
783
  /** Alias for {@link CloudflareBindings} — the typed shape of \`env\`. */
595
784
  export type Env = CloudflareBindings;`;
596
785
  const kvContextField = hasKv ? `
597
- readonly kv: import("@lunora/kv").Kv;` : "";
786
+ readonly kv: import("@lunora/bindings/kv").Kv;` : "";
598
787
  const hyperdriveActionField = hasHyperdrive ? `
599
788
  /**
600
789
  * 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.
@@ -605,13 +794,18 @@ export type Env = CloudflareBindings;`;
605
794
  readonly browser: import("@lunora/browser").Browser;` : "";
606
795
  const imagesActionField = hasImages ? `
607
796
  /** Cloudflare Images transforms (resize/format/optimize). Non-deterministic — available only in actions. */
608
- readonly images: import("@lunora/images").Images;` : "";
797
+ readonly images: import("@lunora/bindings/images").Images;` : "";
609
798
  const analyticsContextField = hasAnalytics ? `
610
799
  /** Analytics Engine telemetry sink. Fire-and-forget and sampled; do not read it back in-handler. */
611
- readonly analytics: import("@lunora/analytics").AnalyticsClient;` : "";
800
+ readonly analytics: import("@lunora/bindings/analytics").AnalyticsClient;` : "";
612
801
  const pipelinesActionField = hasPipelines ? `
613
802
  /** Pipelines ingestion sink (durable, R2-backed). Fire-and-forget and batched; do not read it back in-handler. */
614
- readonly pipelines: import("@lunora/pipelines").PipelineClient;` : "";
803
+ readonly pipelines: import("@lunora/bindings/pipelines").PipelineClient;` : "";
804
+ const r2sqlActionField = hasR2sql ? `
805
+ /**
806
+ * 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.
807
+ */
808
+ readonly r2sql: import("@lunora/bindings/r2sql").R2SqlClient;` : "";
615
809
  const hasWorkflows = workflows.length > 0;
616
810
  const workflowsTypeImport = hasWorkflows ? `import type { WorkflowHandle } from "@lunora/workflow";
617
811
  import type * as lunoraWorkflowDefinitions from "../workflows.js";
@@ -632,6 +826,21 @@ ${workflows.map((workflow) => ` get(name: ${JSON.stringify(workflow.exportNam
632
826
  const workflowsOmit = hasWorkflows ? ` | "workflows"` : "";
633
827
  const workflowsContextField = hasWorkflows ? `
634
828
  readonly workflows: LunoraWorkflows;` : "";
829
+ const hasQueues = queues.length > 0;
830
+ const queuesTypeImport = hasQueues ? `import type { QueueProducer } from "@lunora/queue";
831
+ import type * as lunoraQueueDefinitions from "../queues.js";
832
+ ` : "";
833
+ const queuesTypeBlock = hasQueues ? `
834
+
835
+ /** Message body type carried by a \`defineQueue\` definition (its phantom \`__lunoraBody\`). */
836
+ type QueueBodyOf<Definition> = Definition extends { __lunoraBody?: infer Body } ? (unknown extends Body ? unknown : NonNullable<Body>) : unknown;
837
+
838
+ /** This project's declared queues, addressable from \`ctx.queues\` by their \`lunora/queues.ts\` export name. */
839
+ export interface LunoraQueues {
840
+ ${queues.map((queue) => ` readonly ${queue.exportName}: QueueProducer<QueueBodyOf<typeof lunoraQueueDefinitions.${queue.exportName}>>;`).join("\n")}
841
+ }` : "";
842
+ const queuesContextField = hasQueues ? `
843
+ readonly queues: LunoraQueues;` : "";
635
844
  const server = `${GENERATED_HEADER}import { createPolicyDsl, initLunora, v as vBase } from "${base.server}";
636
845
  import type {
637
846
  ActionBuilder,
@@ -653,11 +862,11 @@ import type {
653
862
  } from "${base.server}";
654
863
 
655
864
  import type { DataModel, DatabaseReaderFacade, DatabaseWriterFacade, Doc, Id as IdOfTable, OrmReader, OrmWriter, Relations, TableName } from "./dataModel.js";
656
- ${aiTypeImport}${paymentsTypeImport}${containersTypeImport}${workflowsTypeImport}
865
+ ${aiTypeImport}${paymentsTypeImport}${containersTypeImport}${workflowsTypeImport}${queuesTypeImport}
657
866
  export type { DataModel, Doc, Id, TableName } from "./dataModel.js";
658
867
 
659
868
  /** Storage buckets this schema declares (\`v.storage("name")\`), narrowing \`ctx.storage.bucket(name)\`. */
660
- export type StorageBucketName = ${storageBucketUnion};${envBlock}${workflowsTypeBlock}
869
+ export type StorageBucketName = ${storageBucketUnion};${envBlock}${workflowsTypeBlock}${queuesTypeBlock}
661
870
 
662
871
  /**
663
872
  * Project-typed contexts. The base contexts from \`@lunora/server\` are
@@ -695,13 +904,13 @@ export interface QueryCtx extends Omit<QueryCtxBase, "db" | "storage"> {
695
904
  export interface MutationCtx extends Omit<MutationCtxBase, "db" | "storage"${workflowsOmit}> {
696
905
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
697
906
  readonly orm: OrmWriter;
698
- readonly storage: ReadOnlyStorage<StorageBucketName>;${kvContextField}${analyticsContextField}${workflowsContextField}
907
+ readonly storage: ReadOnlyStorage<StorageBucketName>;${kvContextField}${analyticsContextField}${workflowsContextField}${queuesContextField}
699
908
  }
700
909
 
701
910
  export interface ActionCtx extends Omit<ActionCtxBase, "db" | "storage"${workflowsOmit}> {
702
911
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
703
912
  readonly orm: OrmWriter;
704
- readonly storage: StorageBase<StorageBucketName>;${aiActionField}${paymentsActionField}${containersActionField}${kvContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${workflowsContextField}
913
+ readonly storage: StorageBase<StorageBucketName>;${aiActionField}${paymentsActionField}${containersActionField}${kvContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${r2sqlActionField}${workflowsContextField}${queuesContextField}
705
914
  }
706
915
 
707
916
  /**
@@ -771,10 +980,21 @@ const renderLifecycleManifest = (functions) => {
771
980
  }
772
981
  return manifest;
773
982
  };
774
- const emitFunctions = (functions, migrations = []) => {
983
+ const emitFunctions = (functions, migrations = [], useUmbrella = false) => {
775
984
  const hasFunctions = functions.length > 0;
776
- const { dispatchBody, importBlock, migrationBody } = renderFunctionRegistry(functions, migrations);
985
+ const base = baseSpecifiers(useUmbrella);
986
+ const { dispatchBody, importBlock, installBlock, migrationBody } = renderFunctionRegistry(functions, migrations);
777
987
  const lifecycleHooks = renderLifecycleManifest(functions);
988
+ const compiledArgsImport = installBlock.length > 0 ? `import { DEFER_VALIDATION as DEFER, installCompiledValidatorMap } from "${base.values}";
989
+ ` : "";
990
+ const compiledArgsInstall = installBlock.length > 0 ? `
991
+ /**
992
+ * AOT-compiled argument validators (Worker-safe, no \`eval\`). Each is installed
993
+ * onto its function's live \`.args\` object and consulted by the interpreted
994
+ * parser as a zero-allocation fast path; anything it can't model is deferred.
995
+ */
996
+ ${installBlock}
997
+ ` : "";
778
998
  const caller = renderCaller(functions);
779
999
  const callerTypes = caller.types ? `
780
1000
  ${caller.types}
@@ -789,7 +1009,7 @@ ${caller.implementation}
789
1009
  const callRegisteredHelper = hasFunctions ? `${CALL_REGISTERED_HELPER}
790
1010
 
791
1011
  ` : "";
792
- return `${GENERATED_HEADER}${importBlock}import type { ActionCtx, MutationCtx, QueryCtx } from "./server.js";
1012
+ return `${GENERATED_HEADER}${importBlock}${compiledArgsImport}import type { ActionCtx, MutationCtx, QueryCtx } from "./server.js";
793
1013
  ${dataModelImport}
794
1014
  /**
795
1015
  * Single registered function, narrowed to the shape \`handleRpc\` needs.
@@ -815,7 +1035,7 @@ export interface RegisteredLunoraFunction {
815
1035
  * emits (\`api[namespace][fn].__lunoraRef === "namespace:fn"\`).
816
1036
  */
817
1037
  export const LUNORA_FUNCTIONS: Record<string, RegisteredLunoraFunction> = {${dispatchBody}};
818
-
1038
+ ${compiledArgsInstall}
819
1039
  /**
820
1040
  * Connection-lifecycle manifest: the function paths the generated ShardDO
821
1041
  * dispatches when a client's WebSocket connects (\`connect\`) or disconnects
@@ -1036,7 +1256,7 @@ const emitKvFragments = (hasKv) => {
1036
1256
  kv?: (env: Record<string, unknown>) => KVNamespaceLike;`,
1037
1257
  contextField: `
1038
1258
  kv,`,
1039
- importLines: [`import type { Kv, KVNamespaceLike } from "@lunora/kv";`, `import { createKv } from "@lunora/kv";`],
1259
+ importLines: [`import type { Kv, KVNamespaceLike } from "@lunora/bindings/kv";`, `import { createKv } from "@lunora/bindings/kv";`],
1040
1260
  stub: `
1041
1261
  const kvStub: Kv = {
1042
1262
  delete: async () => {
@@ -1076,8 +1296,8 @@ const emitAnalyticsFragments = (hasAnalytics) => {
1076
1296
  contextField: `
1077
1297
  analytics,`,
1078
1298
  importLines: [
1079
- `import type { AnalyticsClient, AnalyticsEngineDatasetLike } from "@lunora/analytics";`,
1080
- `import { createAnalytics } from "@lunora/analytics";`
1299
+ `import type { AnalyticsClient, AnalyticsEngineDatasetLike } from "@lunora/bindings/analytics";`,
1300
+ `import { createAnalytics } from "@lunora/bindings/analytics";`
1081
1301
  ],
1082
1302
  stub: `
1083
1303
  const analyticsStub: AnalyticsClient = {
@@ -1106,7 +1326,7 @@ const emitImagesFragments = (hasImages) => {
1106
1326
  // ActionCtx-only: woven onto the action ctx object, never query/mutation.
1107
1327
  contextField: `
1108
1328
  images,`,
1109
- importLines: [`import type { Images, ImagesBindingLike } from "@lunora/images";`, `import { createImages } from "@lunora/images";`],
1329
+ importLines: [`import type { Images, ImagesBindingLike } from "@lunora/bindings/images";`, `import { createImages } from "@lunora/bindings/images";`],
1110
1330
  stub: `
1111
1331
  const imagesStub: Images = {
1112
1332
  info: async () => {
@@ -1183,17 +1403,96 @@ const browserStub: Browser = {
1183
1403
  `
1184
1404
  };
1185
1405
  };
1186
- const emitContainers = (containers) => {
1406
+ const emitR2sqlFragments = (hasR2sql) => {
1407
+ if (!hasR2sql) {
1408
+ return EMPTY_HELPER_FRAGMENTS;
1409
+ }
1410
+ const r2sqlMissing = `throw new Error("ctx.r2sql: no R2 SQL credentials found. Set \\\`R2_SQL_TOKEN\\\`, \\\`R2_SQL_ACCOUNT_ID\\\` (or \\\`CLOUDFLARE_ACCOUNT_ID\\\`), and \\\`R2_SQL_BUCKET\\\` in your env/.dev.vars, or pass an \\\`r2sql\\\` config thunk to createShardDO().");`;
1411
+ return {
1412
+ build: `
1413
+ const r2sqlEnv = env as Record<string, unknown>;
1414
+ const r2sqlAccountId = (r2sqlEnv.R2_SQL_ACCOUNT_ID ?? r2sqlEnv.CLOUDFLARE_ACCOUNT_ID) as string | undefined;
1415
+ const r2sqlToken = r2sqlEnv.R2_SQL_TOKEN as string | undefined;
1416
+ const r2sqlBucket = r2sqlEnv.R2_SQL_BUCKET as string | undefined;
1417
+ const r2sql: R2SqlClient = config.r2sql
1418
+ ? config.r2sql(env)
1419
+ : r2sqlAccountId && r2sqlToken && r2sqlBucket
1420
+ ? createR2Sql({ accountId: r2sqlAccountId, apiToken: r2sqlToken, bucket: r2sqlBucket })
1421
+ : r2sqlStub;
1422
+ `,
1423
+ configField: `
1424
+ r2sql?: (env: Record<string, unknown>) => R2SqlClient;`,
1425
+ // ActionCtx-only: attached via the \`ctx.r2sql = r2sql\` assignment in the
1426
+ // \`isAction\` block, never the every-ctx object literal.
1427
+ contextField: "",
1428
+ importLines: [`import type { R2SqlClient } from "@lunora/bindings/r2sql";`, `import { createR2Sql } from "@lunora/bindings/r2sql";`],
1429
+ // The stub is typed `R2SqlClient`, so TS flags a missing method at build
1430
+ // time — but it must stay structurally in sync with that interface
1431
+ // (`@lunora/bindings/r2sql` client.ts) when a method is added there.
1432
+ stub: `
1433
+ const r2sqlStub: R2SqlClient = {
1434
+ describe: async () => {
1435
+ ${r2sqlMissing}
1436
+ },
1437
+ explain: async () => {
1438
+ ${r2sqlMissing}
1439
+ },
1440
+ from: () => {
1441
+ ${r2sqlMissing}
1442
+ },
1443
+ query: async () => {
1444
+ ${r2sqlMissing}
1445
+ },
1446
+ showDatabases: async () => {
1447
+ ${r2sqlMissing}
1448
+ },
1449
+ showTables: async () => {
1450
+ ${r2sqlMissing}
1451
+ },
1452
+ };
1453
+ `
1454
+ };
1455
+ };
1456
+ const emitPipelinesFragments = (hasPipelines) => {
1457
+ if (!hasPipelines) {
1458
+ return EMPTY_HELPER_FRAGMENTS;
1459
+ }
1460
+ 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().");`;
1461
+ return {
1462
+ build: `
1463
+ const pipelinesBinding = config.pipelines?.(env) ?? (env as Record<string, unknown>).PIPELINES;
1464
+ const pipelines: PipelineClient = pipelinesBinding ? createPipelines({ binding: pipelinesBinding as PipelineBindingLike }) : pipelinesStub;
1465
+ `,
1466
+ configField: `
1467
+ pipelines?: (env: Record<string, unknown>) => PipelineBindingLike;`,
1468
+ // ActionCtx-only: attached via the \`ctx.pipelines = pipelines\` assignment
1469
+ // in the \`isAction\` block, never the every-ctx object literal.
1470
+ contextField: "",
1471
+ importLines: [
1472
+ `import type { PipelineBindingLike, PipelineClient } from "@lunora/bindings/pipelines";`,
1473
+ `import { createPipelines } from "@lunora/bindings/pipelines";`
1474
+ ],
1475
+ stub: `
1476
+ const pipelinesStub: PipelineClient = {
1477
+ send: async () => {
1478
+ ${pipelinesMissing}
1479
+ },
1480
+ };
1481
+ `
1482
+ };
1483
+ };
1484
+ const emitContainers = (containers, jurisdiction) => {
1187
1485
  if (containers.length === 0) {
1188
1486
  return "";
1189
1487
  }
1488
+ const jurisdictionArgument = jurisdiction ? `, ${JSON.stringify(jurisdiction)}` : "";
1190
1489
  const classes = containers.map((container) => {
1191
1490
  assertIdentifier(container.exportName, `container export "${container.exportName}"`);
1192
1491
  assertIdentifier(container.className, `container class "${container.className}"`);
1193
1492
  return `/** Container DO for the \`${container.exportName}\` definition (binding \`${container.bindingName}\`). */
1194
1493
  export class ${container.className} extends LunoraContainer {
1195
1494
  public constructor(ctx: ConstructorParameters<typeof LunoraContainer>[0], env: Record<string, unknown>) {
1196
- super(ctx, env, ${container.exportName}, "${container.exportName}");
1495
+ super(ctx, env, ${container.exportName}, "${container.exportName}"${jurisdictionArgument});
1197
1496
  }
1198
1497
  }
1199
1498
  `;
@@ -1212,7 +1511,7 @@ import { ${imports} } from "../containers.js";
1212
1511
 
1213
1512
  ${classes}`;
1214
1513
  };
1215
- const emitContainerFragments = (containers) => {
1514
+ const emitContainerFragments = (containers, jurisdiction) => {
1216
1515
  if (containers.length === 0) {
1217
1516
  return { build: "", contextField: "", importLines: [], specs: "" };
1218
1517
  }
@@ -1225,8 +1524,11 @@ const emitContainerFragments = (containers) => {
1225
1524
  return ` { binding: "${container.bindingName}", exportName: "${container.exportName}"${maxInstances} },`;
1226
1525
  }).join("\n");
1227
1526
  return {
1527
+ // Schema `.jurisdiction("…")` pins every container DO this shard reaches
1528
+ // to the data-residency region; the arg is omitted when undeclared so
1529
+ // existing generated output is unchanged.
1228
1530
  build: `
1229
- const containers = createContainerContext(env, LUNORA_CONTAINERS);
1531
+ const containers = createContainerContext(env, LUNORA_CONTAINERS${jurisdiction ? `, ${JSON.stringify(jurisdiction)}` : ""});
1230
1532
  `,
1231
1533
  contextField: `
1232
1534
  containers,`,
@@ -1279,6 +1581,32 @@ type WorkflowOutputOf<Definition> = Definition extends { __output?: infer Output
1279
1581
 
1280
1582
  ${classes}`;
1281
1583
  };
1584
+ const emitQueues = (queues) => {
1585
+ const pushQueues = queues.filter((queue) => queue.mode === "push");
1586
+ if (pushQueues.length === 0) {
1587
+ return "";
1588
+ }
1589
+ for (const queue of pushQueues) {
1590
+ assertIdentifier(queue.exportName, `queue export "${queue.exportName}"`);
1591
+ }
1592
+ const imports = pushQueues.map((queue) => queue.exportName).join(", ");
1593
+ const entries = pushQueues.map((queue) => ` ${JSON.stringify(queue.name)}: { definition: ${queue.exportName}, exportName: ${JSON.stringify(queue.exportName)} },`).join("\n");
1594
+ return `${GENERATED_HEADER}/**
1595
+ * Push-consumer registry for the queues declared in \`lunora/queues.ts\`. The
1596
+ * composed worker's \`queue(batch, env, ctx)\` entry routes each delivered batch
1597
+ * by \`batch.queue\` to the matching \`defineQueue\` handler. Wired automatically
1598
+ * by \`defineApp\` — you don't import this directly.
1599
+ */
1600
+ import type { QueueRegistry } from "@lunora/queue";
1601
+
1602
+ import { ${imports} } from "../queues.js";
1603
+
1604
+ /** Stable wrangler queue name → { definition, exportName } for batch routing. */
1605
+ export const LUNORA_QUEUE_REGISTRY: QueueRegistry = {
1606
+ ${entries}
1607
+ };
1608
+ `;
1609
+ };
1282
1610
  const emitWorkflowFragments = (workflows) => {
1283
1611
  if (workflows.length === 0) {
1284
1612
  return { build: "", contextField: "", importLines: [], specs: "" };
@@ -1304,6 +1632,31 @@ ${specEntries}
1304
1632
  `
1305
1633
  };
1306
1634
  };
1635
+ const emitQueueFragments = (queues) => {
1636
+ if (queues.length === 0) {
1637
+ return { build: "", contextField: "", importLines: [], specs: "" };
1638
+ }
1639
+ for (const queue of queues) {
1640
+ assertIdentifier(queue.exportName, `queue export "${queue.exportName}"`);
1641
+ assertIdentifier(queue.bindingName, `queue binding "${queue.bindingName}"`);
1642
+ }
1643
+ const specEntries = queues.map((queue) => ` { binding: "${queue.bindingName}", exportName: "${queue.exportName}", name: ${JSON.stringify(queue.name)} },`).join("\n");
1644
+ return {
1645
+ build: `
1646
+ const queues = createQueueContext(env, LUNORA_QUEUES);
1647
+ `,
1648
+ contextField: `
1649
+ queues,`,
1650
+ importLines: [`import type { QueueBindingSpec } from "@lunora/queue";`, `import { createQueueContext } from "@lunora/queue";`],
1651
+ // eslint-disable-next-line no-secrets/no-secrets -- the emitted readonly-array type annotation is dense generated TS, not a credential
1652
+ specs: `
1653
+ /** Wiring specs for \`ctx.queues\` (codegen-derived from \`lunora/queues.ts\`). */
1654
+ const LUNORA_QUEUES: ReadonlyArray<QueueBindingSpec> = [
1655
+ ${specEntries}
1656
+ ];
1657
+ `
1658
+ };
1659
+ };
1307
1660
  const emitWorkflowsMetadataFragments = (workflows) => {
1308
1661
  if (workflows.length === 0) {
1309
1662
  return { constant: "", override: "" };
@@ -1330,6 +1683,33 @@ const LUNORA_WORKFLOWS_INFO: WorkflowsResult = ${JSON.stringify(metadata, void 0
1330
1683
  `
1331
1684
  };
1332
1685
  };
1686
+ const emitQueuesMetadataFragments = (queues) => {
1687
+ if (queues.length === 0) {
1688
+ return { constant: "", override: "" };
1689
+ }
1690
+ const metadata = {
1691
+ queues: queues.map((queue) => {
1692
+ return {
1693
+ binding: queue.bindingName,
1694
+ ...queue.tuning.deadLetterQueue === void 0 ? {} : { deadLetterQueue: queue.tuning.deadLetterQueue },
1695
+ exportName: queue.exportName,
1696
+ mode: queue.mode,
1697
+ name: queue.name
1698
+ };
1699
+ })
1700
+ };
1701
+ return {
1702
+ constant: `
1703
+ /** Read-only declared-queue metadata (discovered from \`lunora/queues.ts\`) served via \`__lunora_admin__:listQueues\` for the studio's queues view. */
1704
+ const LUNORA_QUEUES_INFO: QueuesResult = ${JSON.stringify(metadata, void 0, 4)};
1705
+ `,
1706
+ override: `
1707
+ protected override queuesMetadata(): QueuesResult {
1708
+ return LUNORA_QUEUES_INFO;
1709
+ }
1710
+ `
1711
+ };
1712
+ };
1333
1713
  const emitPaymentFragments = (hasPayments) => {
1334
1714
  if (!hasPayments) {
1335
1715
  return { build: "", configField: "", contextField: "", imports: [], stub: "" };
@@ -1385,13 +1765,14 @@ const paymentStub: LunoraPayment = {
1385
1765
  `
1386
1766
  };
1387
1767
  };
1388
- const buildDoTypeImports = (hasVectors, hasWorkflows) => [
1768
+ const buildDoTypeImports = (hasVectors, hasWorkflows, hasQueues) => [
1389
1769
  "AdvisoryFinding",
1390
1770
  "DatabaseWriterLike",
1391
1771
  "DataMigrationLike",
1392
1772
  "LogSink",
1393
1773
  "MaskPoliciesResult",
1394
1774
  "MigrationRunResult",
1775
+ ...hasQueues ? ["QueuesResult"] : [],
1395
1776
  "RunShardApplyCdcArgs",
1396
1777
  "RunShardMigrationArgs",
1397
1778
  "RlsPoliciesResult",
@@ -1420,7 +1801,10 @@ const emitShard = ({
1420
1801
  hasImages = false,
1421
1802
  hasKv = false,
1422
1803
  hasPayments = false,
1804
+ hasPipelines = false,
1805
+ hasR2sql = false,
1423
1806
  maskMetadata,
1807
+ queues = [],
1424
1808
  rlsMetadata,
1425
1809
  schema,
1426
1810
  storageRules,
@@ -1435,12 +1819,15 @@ const emitShard = ({
1435
1819
  const imagesFragments = emitImagesFragments(hasImages);
1436
1820
  const hyperdriveFragments = emitHyperdriveFragments(hasHyperdrive);
1437
1821
  const browserFragments = emitBrowserFragments(hasBrowser);
1822
+ const r2sqlFragments = emitR2sqlFragments(hasR2sql);
1823
+ const pipelinesFragments = emitPipelinesFragments(hasPipelines);
1824
+ const { build: queuesBuild, contextField: queuesContextField, importLines: queueImportLines, specs: queueSpecs } = emitQueueFragments(queues);
1438
1825
  const {
1439
1826
  build: containersBuild,
1440
1827
  contextField: containersContextField,
1441
1828
  importLines: containerImportLines,
1442
1829
  specs: containerSpecs
1443
- } = emitContainerFragments(containers);
1830
+ } = emitContainerFragments(containers, schema.jurisdiction);
1444
1831
  const {
1445
1832
  build: workflowsBuild,
1446
1833
  contextField: workflowsContextField,
@@ -1461,12 +1848,14 @@ const emitShard = ({
1461
1848
  const studioFeaturesData = studioFeatures ?? {
1462
1849
  mail: false,
1463
1850
  payments: false,
1851
+ queues: false,
1464
1852
  scheduler: false,
1465
1853
  storage: false,
1466
1854
  vectors: false,
1467
1855
  workflows: false
1468
1856
  };
1469
1857
  const { constant: workflowsMetadataConst, override: workflowsMetadataOverride } = emitWorkflowsMetadataFragments(workflows);
1858
+ const { constant: queuesMetadataConst, override: queuesMetadataOverride } = emitQueuesMetadataFragments(queues);
1470
1859
  const hasVectors = schema.vectorIndexes.length > 0;
1471
1860
  const hasGlobalTables = schema.tables.some((table) => table.shardMode === "global");
1472
1861
  const hasHyperdriveGlobal = schema.tables.some((table) => table.shardMode === "global" && table.globalBackend === "hyperdrive");
@@ -1481,23 +1870,24 @@ const emitShard = ({
1481
1870
  const tableIndexes = buildTableIndexes(schema);
1482
1871
  const tableColumns = buildTableColumns(schema);
1483
1872
  const storageColumns = buildStorageColumns(schema);
1484
- const doTypeImports = buildDoTypeImports(hasVectors, workflows.length > 0);
1873
+ const doTypeImports = buildDoTypeImports(hasVectors, workflows.length > 0, queues.length > 0);
1485
1874
  const relationFanout = emitRelationFanout(hasGlobalTables);
1486
1875
  const importLines = [
1487
1876
  `import type { ${doTypeImports.join(", ")} } from "${base.do}";`,
1488
1877
  `import { applyCdcChanges, createShardCtxDb, runDataMigration, runShardMigrations, ${relationFanout.importFragment}ShardDO as ShardDOBase } from "${base.do}";`,
1489
- // `asBucketStorage` (the bucket-aware `ctx.storage` wrapper) lives in
1878
+ // `asBucketStorage` (the bucket-aware `ctx.storage` wrapper) and
1879
+ // `createSecrets` (the `ctx.secrets` core built-in) live in
1490
1880
  // `@lunora/server`, the single source — imported here rather than stamped
1491
1881
  // inline into every generated shard.
1492
- `import { asBucketStorage } from "${base.server}";`
1882
+ `import { asBucketStorage, createSecrets } from "${base.server}";`
1493
1883
  ];
1494
1884
  if (hasTables) {
1495
1885
  importLines.push(`import { bindOrm, bindTableFacade } from "${base.server}";`);
1496
1886
  }
1497
1887
  if (hasVectors) {
1498
1888
  importLines.push(
1499
- `import type { SchemaLike as VectorSchemaLike, VectorizeIndexLike, VectorSearchLike } from "@lunora/vectors";`,
1500
- `import { createContextVectors, createVectors, createVectorSyncHook } from "@lunora/vectors";`
1889
+ `import type { SchemaLike as VectorSchemaLike, VectorizeIndexLike, VectorSearchLike } from "@lunora/bindings/vectors";`,
1890
+ `import { createContextVectors, createVectors, createVectorSyncHook } from "@lunora/bindings/vectors";`
1501
1891
  );
1502
1892
  }
1503
1893
  if (hasAi) {
@@ -1509,8 +1899,11 @@ const emitShard = ({
1509
1899
  ...imagesFragments.importLines,
1510
1900
  ...hyperdriveFragments.importLines,
1511
1901
  ...browserFragments.importLines,
1902
+ ...r2sqlFragments.importLines,
1903
+ ...pipelinesFragments.importLines,
1512
1904
  ...containerImportLines,
1513
1905
  ...workflowImportLines,
1906
+ ...queueImportLines,
1514
1907
  ...paymentsImports,
1515
1908
  ``,
1516
1909
  `import schema from "../schema.js";`,
@@ -1652,17 +2045,23 @@ ${schema.tables.map(
1652
2045
  (table) => ` facade[${JSON.stringify(table.name)}] = bindTableFacade(db, ${JSON.stringify(table.name)});`
1653
2046
  ).join("\n")}
1654
2047
  ` : "";
1655
- const everyContextBuild = `${kvFragments.build}${analyticsFragments.build}`;
1656
- const everyContextField = `${kvFragments.contextField}${analyticsFragments.contextField}`;
1657
- const actionOnlyHasAny = hasImages || hasHyperdrive || hasBrowser;
2048
+ const secretsBuild = `
2049
+ const secrets = createSecrets(env);
2050
+ `;
2051
+ const everyContextBuild = `${kvFragments.build}${analyticsFragments.build}${secretsBuild}`;
2052
+ const everyContextField = `${kvFragments.contextField}${analyticsFragments.contextField}
2053
+ secrets,`;
2054
+ const actionOnlyHasAny = hasImages || hasHyperdrive || hasBrowser || hasR2sql || hasPipelines;
1658
2055
  const actionOnlyBlock = actionOnlyHasAny ? `
1659
2056
  // ActionCtx-only helpers (external, non-deterministic I/O): constructed
1660
2057
  // and attached only for an \`action\` so query/mutation ctx never carry them.
1661
2058
  if (isAction) {
1662
- ${imagesFragments.build}${hyperdriveFragments.build}${browserFragments.build}${[
2059
+ ${imagesFragments.build}${hyperdriveFragments.build}${browserFragments.build}${r2sqlFragments.build}${pipelinesFragments.build}${[
1663
2060
  ...hasImages ? [" ctx.images = images;"] : [],
1664
2061
  ...hasHyperdrive ? [" ctx.sql = sql;"] : [],
1665
- ...hasBrowser ? [" ctx.browser = browser;"] : []
2062
+ ...hasBrowser ? [" ctx.browser = browser;"] : [],
2063
+ ...hasR2sql ? [" ctx.r2sql = r2sql;"] : [],
2064
+ ...hasPipelines ? [" ctx.pipelines = pipelines;"] : []
1666
2065
  ].join("\n")}
1667
2066
  }
1668
2067
  ` : "";
@@ -1702,14 +2101,14 @@ const LUNORA_STORAGE_RULES: StorageRulesResult = ${JSON.stringify(storageRulesDa
1702
2101
 
1703
2102
  /** 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. */
1704
2103
  const LUNORA_STUDIO_FEATURES: StudioFeaturesResult = ${JSON.stringify(studioFeaturesData, void 0, 4)};
1705
- ${workflowsMetadataConst}${containerSpecs}${workflowSpecs}
2104
+ ${workflowsMetadataConst}${queuesMetadataConst}${containerSpecs}${workflowSpecs}${queueSpecs}
1706
2105
  export interface ShardDOConfig {
1707
2106
  /** Opt into change-data-capture: records a post-image to \`__cdc_log\` on every write (backs streaming export + replay-PITR). */
1708
2107
  cdc?: boolean;
1709
2108
  /** 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. */
1710
2109
  observability?: (env: Record<string, unknown>) => LogSink | undefined;
1711
2110
  scheduler?: (env: Record<string, unknown>) => unknown;
1712
- storage?: (env: Record<string, unknown>) => unknown;${vectorsConfigField}${aiConfigField}${kvFragments.configField}${analyticsFragments.configField}${imagesFragments.configField}${hyperdriveFragments.configField}${browserFragments.configField}${paymentsConfigField}${d1ConfigField}${hyperdriveGlobalConfigField}
2111
+ storage?: (env: Record<string, unknown>) => unknown;${vectorsConfigField}${aiConfigField}${kvFragments.configField}${analyticsFragments.configField}${imagesFragments.configField}${hyperdriveFragments.configField}${browserFragments.configField}${r2sqlFragments.configField}${pipelinesFragments.configField}${paymentsConfigField}${d1ConfigField}${hyperdriveGlobalConfigField}
1713
2112
  }
1714
2113
 
1715
2114
  const schedulerStub = {
@@ -1747,7 +2146,7 @@ const storageStub = {
1747
2146
  throw new Error("ctx.storage: no storage configured. Pass \`storage\` to createShardDO().");
1748
2147
  },
1749
2148
  };
1750
- ${globalDatabaseStub}${vectorsStub}${aiStub}${kvFragments.stub}${analyticsFragments.stub}${imagesFragments.stub}${hyperdriveFragments.stub}${browserFragments.stub}${paymentStub}${bindTableHelper}
2149
+ ${globalDatabaseStub}${vectorsStub}${aiStub}${kvFragments.stub}${analyticsFragments.stub}${imagesFragments.stub}${hyperdriveFragments.stub}${browserFragments.stub}${r2sqlFragments.stub}${pipelinesFragments.stub}${paymentStub}${bindTableHelper}
1751
2150
  // Bound in-process \`ctx.run*\` composition depth so a self- or cyclically-
1752
2151
  // referencing call fails loudly with a clear error instead of overflowing the
1753
2152
  // stack. Tracked across the awaited handler chain (one DO invocation is
@@ -1892,7 +2291,7 @@ ${relationFanout.override}
1892
2291
  protected override studioFeatures(): StudioFeaturesResult {
1893
2292
  return LUNORA_STUDIO_FEATURES;
1894
2293
  }
1895
- ${workflowsMetadataOverride}
2294
+ ${workflowsMetadataOverride}${queuesMetadataOverride}
1896
2295
  protected override advisories(): AdvisoryFinding[] {
1897
2296
  return LUNORA_ADVISORIES;
1898
2297
  }
@@ -2126,7 +2525,7 @@ ${workflowsMetadataOverride}
2126
2525
  // dispatch path) fall back to the per-request fields as before.
2127
2526
  const userId = options.identity ? options.identity.userId : this.getCurrentUserId();
2128
2527
  const identity = options.identity ? options.identity.identity : this.getCurrentIdentity();
2129
- ${vectorsBuild}${aiBuild}${everyContextBuild}${containersBuild}${workflowsBuild}
2528
+ ${vectorsBuild}${aiBuild}${everyContextBuild}${containersBuild}${workflowsBuild}${queuesBuild}
2130
2529
  const scheduler = (config.scheduler?.(env) ?? schedulerStub) as SchedulerLike;
2131
2530
  // Build the storage adapter once and share it between \`ctx.storage\`
2132
2531
  // and \`ctx.db.system._storage\` so both read the same R2 binding. The
@@ -2149,6 +2548,14 @@ ${facadeBlock}${paymentsBuild}
2149
2548
  warn: (...args: unknown[]) => { this.recordUserLog(logFunctionPath, "warn", args, observability); },
2150
2549
  };
2151
2550
 
2551
+ // \`ctx.now\`: the wall-clock instant (epoch ms) this function began,
2552
+ // captured ONCE so the whole handler body sees a single stable value.
2553
+ // Query/mutation handlers must be deterministic (they may be re-run on
2554
+ // OCC retry / subscription re-eval), so they must read time through
2555
+ // \`ctx.now\` instead of \`Date.now()\` — the \`nondeterministic_query_mutation\`
2556
+ // advisor flags the latter. Actions may still use ambient \`Date.now()\`.
2557
+ const now = Date.now();
2558
+
2152
2559
  const ctx: Record<string, unknown> = {
2153
2560
  auth: {
2154
2561
  getIdentity: async () => identity ?? null,
@@ -2157,9 +2564,10 @@ ${facadeBlock}${paymentsBuild}
2157
2564
  db,
2158
2565
  fetch: globalThis.fetch.bind(globalThis),
2159
2566
  ip: this.getCurrentIp(),
2160
- log,${ormContextField}
2567
+ log,
2568
+ now,${ormContextField}
2161
2569
  scheduler,
2162
- storage,${vectorsContextField}${aiContextField}${everyContextField}${paymentsContextField}${containersContextField}${workflowsContextField}
2570
+ storage,${vectorsContextField}${aiContextField}${everyContextField}${paymentsContextField}${containersContextField}${workflowsContextField}${queuesContextField}
2163
2571
  };
2164
2572
  ${isActionLine}${actionOnlyBlock}
2165
2573
  ctx.runAction = (reference: FunctionReference, fnArgs: Record<string, unknown>) => dispatchRun("action", reference.__lunoraRef, fnArgs, ctx);
@@ -2423,4 +2831,4 @@ const emitWranglerCronTriggers = (crons) => {
2423
2831
  return triggers;
2424
2832
  };
2425
2833
 
2426
- export { GENERATED_HEADER, buildStorageColumns, emitApi, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitSeed, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers };
2834
+ export { GENERATED_HEADER, buildStorageColumns, emitApi, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitQueues, emitSeed, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers };