@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.
- package/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +171 -14
- package/dist/index.d.ts +171 -14
- package/dist/index.mjs +24 -22
- package/dist/packem_shared/{CONTAINERS_FILENAME-0K-pjNb8.mjs → CONTAINERS_FILENAME-DlP6YM_Q.mjs} +1 -1
- package/dist/packem_shared/{CodegenDiagnosticError-54jWDxA9.mjs → CodegenDiagnosticError-DeblMkzO.mjs} +3 -2
- package/dist/packem_shared/{emitApi-hRVC-kE7.mjs → GENERATED_HEADER-r1OZrKAx.mjs} +451 -43
- package/dist/packem_shared/{buildOpenRpcDocument-BZGY1-jT.mjs → OPENRPC_VERSION-BYRhDy-7.mjs} +1 -1
- package/dist/packem_shared/QUEUES_FILENAME-B5_eWCRe.mjs +119 -0
- package/dist/packem_shared/{createCodegenProject-DGJm0_Pk.mjs → SCHEMA_SNAPSHOT_FILENAME-CgJUPhI3.mjs} +102 -43
- package/dist/packem_shared/{buildSchemaSnapshot-DzLDbWk3.mjs → SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs} +12 -0
- package/dist/packem_shared/{discoverWorkflows-DRDQdhfq.mjs → WORKFLOWS_FILENAME-D62dcBGg.mjs} +1 -1
- package/dist/packem_shared/{buildOpenApiDocument-yHVN66Xd.mjs → buildOpenApiDocument-C8NKrvpw.mjs} +1 -1
- package/dist/packem_shared/{discoverAuthApiCalls-C35R6z0T.mjs → discoverAuthApiCalls-CoirYbg6.mjs} +1 -1
- package/dist/packem_shared/{discoverCrons-BL6iGuJ3.mjs → discoverCrons-Cev7RRAf.mjs} +20 -17
- package/dist/packem_shared/{discoverFunctions-DEgAcRuD.mjs → discoverFunctions-BWMczzBx.mjs} +7 -2
- package/dist/packem_shared/{discoverHttpRoutes-C978pBiG.mjs → discoverHttpRoutes-CfP6cMzt.mjs} +2 -2
- package/dist/packem_shared/{discoverInserts-CRQdXvHO.mjs → discoverInserts-C7zxXkUf.mjs} +24 -2
- package/dist/packem_shared/{discoverMaskProcedures-B64zA740.mjs → discoverMaskProcedures-Cm81kwrb.mjs} +1 -1
- package/dist/packem_shared/{discoverMigrations-Doj_-BAA.mjs → discoverMigrations-Bi5nJ0mJ.mjs} +10 -4
- package/dist/packem_shared/{discoverNondeterministicCalls-4KiPQxQU.mjs → discoverNondeterministicCalls-C4M8AXmQ.mjs} +1 -1
- package/dist/packem_shared/{discoverQueries-BkIi0dBD.mjs → discoverQueries-B0wGT-xe.mjs} +1 -1
- package/dist/packem_shared/discoverR2sqlCalls-BVNMd428.mjs +80 -0
- package/dist/packem_shared/{discoverRlsMetadata-DpRB1HMe.mjs → discoverRlsMetadata-BS9GOGC5.mjs} +1 -1
- package/dist/packem_shared/{discoverSchema-BBulgGbH.mjs → discoverSchema-BnWHHJ4T.mjs} +296 -16
- package/dist/packem_shared/{discoverStorageRulesMetadata-DAqJUxUv.mjs → discoverStorageRulesMetadata-Da8BKXcI.mjs} +1 -1
- package/dist/packem_shared/{emitApp-CzZ6GbrD.mjs → emitApp-B1DgLrM8.mjs} +28 -4
- package/dist/packem_shared/{lintSchema-DicbOHvH.mjs → formatAdvisories-8NIv1k0I.mjs} +2 -1
- package/dist/packem_shared/{parse-validator-tuQtHrsr.mjs → parse-validator-Cabb60UV.mjs} +2 -1
- 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
|
|
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
|
|
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)
|
|
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
|
|
1656
|
-
|
|
1657
|
-
|
|
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
|
|
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 };
|