@lunora/codegen 1.0.0-alpha.3 → 1.0.0-alpha.30

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 (35) hide show
  1. package/__assets__/package-og.svg +1 -1
  2. package/dist/index.d.mts +1084 -25
  3. package/dist/index.d.ts +1084 -25
  4. package/dist/index.mjs +29 -23
  5. package/dist/packem_shared/{CONTAINERS_FILENAME-0K-pjNb8.mjs → CONTAINERS_FILENAME-DjpXMqhp.mjs} +1 -1
  6. package/dist/packem_shared/{CodegenDiagnosticError-54jWDxA9.mjs → CodegenDiagnosticError-DyQ5FwkM.mjs} +7 -5
  7. package/dist/packem_shared/FLAGS_FILENAME-BjpS5w08.mjs +139 -0
  8. package/dist/packem_shared/{GENERATED_HEADER-CuLyj0WQ.mjs → GENERATED_HEADER-f6nMvllu.mjs} +853 -65
  9. package/dist/packem_shared/MUTATORS_FILENAME-BZOfUhlY.mjs +81 -0
  10. package/dist/packem_shared/{OPENRPC_VERSION-BGUrsrt_.mjs → OPENRPC_VERSION-u5SqDDGk.mjs} +1 -1
  11. package/dist/packem_shared/QUEUES_FILENAME-BF0iUmx7.mjs +119 -0
  12. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-Cpfn80SH.mjs +3481 -0
  13. package/dist/packem_shared/{SCHEMA_SNAPSHOT_VERSION-DzLDbWk3.mjs → SCHEMA_SNAPSHOT_VERSION-D0ARY6rL.mjs} +18 -2
  14. package/dist/packem_shared/SHAPES_FILENAME-DOhPGi-6.mjs +94 -0
  15. package/dist/packem_shared/{WORKFLOWS_FILENAME-DRDQdhfq.mjs → WORKFLOWS_FILENAME-CCC_42o3.mjs} +42 -2
  16. package/dist/packem_shared/{buildOpenApiDocument-CXwnbp4b.mjs → buildOpenApiDocument-AgKWmlry.mjs} +1 -1
  17. package/dist/packem_shared/{discoverAuthApiCalls-C35R6z0T.mjs → discoverAuthApiCalls-URDoTADN.mjs} +1 -1
  18. package/dist/packem_shared/{discoverCrons-BL6iGuJ3.mjs → discoverCrons-DdZOeroL.mjs} +20 -17
  19. package/dist/packem_shared/{discoverFunctions-DEgAcRuD.mjs → discoverFunctions-BJdF7lRs.mjs} +63 -9
  20. package/dist/packem_shared/{discoverHttpRoutes-C978pBiG.mjs → discoverHttpRoutes-CcwP-Rkr.mjs} +2 -2
  21. package/dist/packem_shared/{discoverInserts-CRQdXvHO.mjs → discoverInserts-BpFhM32L.mjs} +24 -2
  22. package/dist/packem_shared/{discoverMaskProcedures-B64zA740.mjs → discoverMaskProcedures-DT-v8wvj.mjs} +58 -2
  23. package/dist/packem_shared/{discoverMigrations-Doj_-BAA.mjs → discoverMigrations-cG1idBVM.mjs} +10 -4
  24. package/dist/packem_shared/{discoverNondeterministicCalls-4KiPQxQU.mjs → discoverNondeterministicCalls-CEOjmfk4.mjs} +1 -1
  25. package/dist/packem_shared/{discoverQueries-BkIi0dBD.mjs → discoverQueries-DeVBQVAG.mjs} +1 -1
  26. package/dist/packem_shared/{discoverR2sqlCalls-BpDqvcUn.mjs → discoverR2sqlCalls-CWCAU-xJ.mjs} +1 -1
  27. package/dist/packem_shared/{discoverRlsMetadata-DpRB1HMe.mjs → discoverRlsMetadata-BL0oukaK.mjs} +1 -1
  28. package/dist/packem_shared/{discoverSchema-BBulgGbH.mjs → discoverSchema-CeXJWVKV.mjs} +349 -17
  29. package/dist/packem_shared/{discoverStorageRulesMetadata-DAqJUxUv.mjs → discoverStorageRulesMetadata-CFHByjIl.mjs} +1 -1
  30. package/dist/packem_shared/{emitApp-CzSxVDaG.mjs → emitApp-DKt3rPtr.mjs} +84 -8
  31. package/dist/packem_shared/{formatAdvisories-8NIv1k0I.mjs → formatAdvisories-C9wNBNvL.mjs} +47 -3
  32. package/dist/packem_shared/{parse-validator-tuQtHrsr.mjs → parse-validator-D6zI2i85.mjs} +5 -3
  33. package/dist/packem_shared/redact-oTmsol5A.mjs +33 -0
  34. package/package.json +9 -7
  35. package/dist/packem_shared/SCHEMA_SNAPSHOT_FILENAME-BQ_kCZ81.mjs +0 -903
@@ -1,6 +1,213 @@
1
- import { SyntaxKind, Node } from 'ts-morph';
2
- import { diagnosticAt } from './CodegenDiagnosticError-54jWDxA9.mjs';
3
- import { a as parseObjectShape } from './parse-validator-tuQtHrsr.mjs';
1
+ import { LunoraError } from '@lunora/errors';
2
+ import { Node, SyntaxKind } from 'ts-morph';
3
+ import { diagnosticAt } from './CodegenDiagnosticError-DyQ5FwkM.mjs';
4
+ import { a as parseObjectShape } from './parse-validator-D6zI2i85.mjs';
5
+ import { createRequire } from 'node:module';
6
+ import { join } from 'node:path';
7
+
8
+ const renderLiteralValue = (value) => {
9
+ switch (typeof value) {
10
+ case "bigint": {
11
+ return value.toString();
12
+ }
13
+ case "boolean":
14
+ case "number": {
15
+ return String(value);
16
+ }
17
+ case "string": {
18
+ return JSON.stringify(value);
19
+ }
20
+ default: {
21
+ return value === null ? "null" : void 0;
22
+ }
23
+ }
24
+ };
25
+ const columnMetaToIR = (column) => {
26
+ const hasDefault = column?.["defaultValue"] !== void 0 || column?.["defaultFn"] !== void 0 || column?.["serverDefault"] !== void 0;
27
+ return {
28
+ ...hasDefault ? { hasDefault: true } : {},
29
+ ...column?.["onUpdateFn"] === void 0 ? {} : { hasOnUpdate: true },
30
+ notNull: column?.["notNull"] ?? true,
31
+ ...column?.["unique"] === true ? { unique: true } : {}
32
+ };
33
+ };
34
+ const runtimeValidatorToIR = (validator) => {
35
+ const meta = validator?._meta ?? {};
36
+ const kind = validator?.kind ?? "unknown";
37
+ const ir = { column: columnMetaToIR(meta["column"]), kind };
38
+ switch (kind) {
39
+ case "array":
40
+ case "optional": {
41
+ ir.inner = runtimeValidatorToIR(meta["inner"]);
42
+ break;
43
+ }
44
+ case "id": {
45
+ ir.tableName = meta["tableName"];
46
+ break;
47
+ }
48
+ case "literal": {
49
+ ir.literalValue = renderLiteralValue(meta["value"]);
50
+ break;
51
+ }
52
+ case "object": {
53
+ const shape = meta["shape"] ?? {};
54
+ ir.shape = Object.fromEntries(Object.entries(shape).map(([name, child]) => [name, runtimeValidatorToIR(child)]));
55
+ break;
56
+ }
57
+ case "record": {
58
+ ir.valueType = runtimeValidatorToIR(meta["valueValidator"]);
59
+ if (meta["keyValidator"] !== void 0) {
60
+ ir.keyType = runtimeValidatorToIR(meta["keyValidator"]);
61
+ }
62
+ break;
63
+ }
64
+ case "storage": {
65
+ if (typeof meta["bucket"] === "string") {
66
+ ir.bucket = meta["bucket"];
67
+ }
68
+ break;
69
+ }
70
+ case "union": {
71
+ const members = meta["members"] ?? [];
72
+ ir.members = members.map((member) => runtimeValidatorToIR(member));
73
+ break;
74
+ }
75
+ }
76
+ return ir;
77
+ };
78
+ const shardModeToIR = (shardMode) => {
79
+ if (shardMode?.kind === "global") {
80
+ return "global";
81
+ }
82
+ if (shardMode?.kind === "shardBy" && typeof shardMode.field === "string") {
83
+ return { field: shardMode.field, kind: "shardBy" };
84
+ }
85
+ return "root";
86
+ };
87
+ const warnDroppedTableFeatures = (builder, bareName) => {
88
+ const dropped = [];
89
+ if (builder.relationMap && Object.keys(builder.relationMap).length > 0) {
90
+ dropped.push("relations");
91
+ }
92
+ if (builder.rankIndexes && builder.rankIndexes.length > 0) {
93
+ dropped.push("rank indexes");
94
+ }
95
+ if (builder.vectorIndexes && builder.vectorIndexes.length > 0) {
96
+ dropped.push("vector indexes");
97
+ }
98
+ if (dropped.length > 0) {
99
+ console.warn(
100
+ `@lunora/codegen: package schema-extension table "${bareName}" declares ${dropped.join(" + ")}, which are not yet introspected from a node_modules extension — they will be absent from the generated types.`
101
+ );
102
+ }
103
+ };
104
+ const runtimeTableToIR = (builder, bareName) => {
105
+ const shape = builder.shape ?? {};
106
+ const indexes = (builder.indexes ?? []).map((index) => {
107
+ return { fields: index.fields, name: index.name, ...index.unique ? { unique: true } : {} };
108
+ });
109
+ warnDroppedTableFeatures(builder, bareName);
110
+ return {
111
+ ...builder.isExternallyManaged ? { externallyManaged: true } : {},
112
+ indexes,
113
+ name: bareName,
114
+ rankIndexes: [],
115
+ relations: [],
116
+ searchIndexes: (builder.searchIndexes ?? []).map((index) => {
117
+ return {
118
+ field: index.field,
119
+ ...index.filterFields ? { filterFields: index.filterFields } : {},
120
+ name: index.name
121
+ };
122
+ }),
123
+ shape: Object.fromEntries(Object.entries(shape).map(([name, validator]) => [name, runtimeValidatorToIR(validator)])),
124
+ shardMode: shardModeToIR(builder.shardMode),
125
+ // Inline vector/rank indexes and relations are not yet introspected from a
126
+ // package extension; their presence is surfaced via `warnDroppedTableFeatures`.
127
+ vectorIndexes: []
128
+ };
129
+ };
130
+ const resolvedExtensionToIR = (value) => {
131
+ const { key, tables } = value;
132
+ if (typeof key !== "string" || typeof tables !== "object" || tables === null) {
133
+ return void 0;
134
+ }
135
+ const bareTables = Object.entries(tables).map(([bareName, builder]) => runtimeTableToIR(builder, bareName));
136
+ const vectorIndexes = value["vectorIndexes"] ?? {};
137
+ const bareVectorIndexes = Object.entries(vectorIndexes ?? {}).map(([name, index]) => {
138
+ return {
139
+ ...index.dimensions === void 0 ? {} : { dimensions: index.dimensions },
140
+ ...index.field === void 0 ? {} : { field: index.field },
141
+ ...index.metadata === void 0 ? {} : { metadata: index.metadata },
142
+ ...index.metric === void 0 ? {} : { metric: index.metric },
143
+ name,
144
+ table: index.table ?? ""
145
+ };
146
+ });
147
+ return { bareTables, bareVectorIndexes, key };
148
+ };
149
+ const receiverIdentifierText = (argument) => {
150
+ if (Node.isIdentifier(argument)) {
151
+ return argument.getText();
152
+ }
153
+ if (Node.isPropertyAccessExpression(argument)) {
154
+ const receiver = argument.getExpression();
155
+ return Node.isIdentifier(receiver) ? receiver.getText() : void 0;
156
+ }
157
+ return void 0;
158
+ };
159
+ const accessPathOf = (argument) => {
160
+ const receiverName = receiverIdentifierText(argument);
161
+ if (receiverName === void 0) {
162
+ return void 0;
163
+ }
164
+ const property = Node.isPropertyAccessExpression(argument) ? argument.getName() : void 0;
165
+ const propertyPath = property === void 0 ? [] : [property];
166
+ for (const declaration of argument.getSourceFile().getImportDeclarations()) {
167
+ const moduleSpecifier = declaration.getModuleSpecifierValue();
168
+ for (const named of declaration.getNamedImports()) {
169
+ const localName = named.getAliasNode()?.getText() ?? named.getName();
170
+ if (localName === receiverName) {
171
+ return { importedName: named.getName(), moduleSpecifier, propertyPath };
172
+ }
173
+ }
174
+ if (declaration.getDefaultImport()?.getText() === receiverName) {
175
+ return { importedName: "default", moduleSpecifier, propertyPath };
176
+ }
177
+ if (declaration.getNamespaceImport()?.getText() === receiverName && property !== void 0) {
178
+ return { importedName: property, moduleSpecifier, propertyPath: [] };
179
+ }
180
+ }
181
+ return void 0;
182
+ };
183
+ const readExtensionValue = (module_, access) => {
184
+ let current = module_[access.importedName];
185
+ for (const segment of access.propertyPath) {
186
+ if (typeof current !== "object" || current === null) {
187
+ return void 0;
188
+ }
189
+ current = current[segment];
190
+ }
191
+ return current;
192
+ };
193
+ const resolvePackageExtension = (argument, projectRoot) => {
194
+ const access = accessPathOf(argument);
195
+ if (!access) {
196
+ return void 0;
197
+ }
198
+ try {
199
+ const projectRequire = createRequire(join(projectRoot, "noop.cjs"));
200
+ const resolved = projectRequire.resolve(access.moduleSpecifier);
201
+ const loadedModule = projectRequire(resolved);
202
+ const value = readExtensionValue(loadedModule, access);
203
+ if (typeof value !== "object" || value === null) {
204
+ return void 0;
205
+ }
206
+ return resolvedExtensionToIR(value);
207
+ } catch {
208
+ return void 0;
209
+ }
210
+ };
4
211
 
5
212
  const VECTOR_METRICS = /* @__PURE__ */ new Set(["cosine", "dot-product", "euclidean"]);
6
213
  const ON_DELETE_ACTIONS = /* @__PURE__ */ new Set(["cascade", "restrict", "set null"]);
@@ -179,6 +386,54 @@ const parseGlobalBackend = (args) => {
179
386
  }
180
387
  return "d1";
181
388
  };
389
+ const softDeleteFieldOf = (optionsArgument) => {
390
+ if (optionsArgument && Node.isObjectLiteralExpression(optionsArgument)) {
391
+ const fieldProperty = optionsArgument.getProperty("field");
392
+ if (fieldProperty && Node.isPropertyAssignment(fieldProperty)) {
393
+ const initializer = fieldProperty.getInitializer();
394
+ if (initializer && Node.isStringLiteral(initializer)) {
395
+ return initializer.getLiteralText();
396
+ }
397
+ }
398
+ }
399
+ return "deletedAt";
400
+ };
401
+ const stringPropertyOf = (object, property) => {
402
+ const node = object.getProperty(property);
403
+ if (node && Node.isPropertyAssignment(node)) {
404
+ const initializer = node.getInitializer();
405
+ if (initializer && Node.isStringLiteral(initializer)) {
406
+ return initializer.getLiteralText();
407
+ }
408
+ }
409
+ return void 0;
410
+ };
411
+ const stringArrayPropertyOf = (object, property) => {
412
+ const node = object.getProperty(property);
413
+ if (node && Node.isPropertyAssignment(node)) {
414
+ const initializer = node.getInitializer();
415
+ if (initializer && Node.isArrayLiteralExpression(initializer)) {
416
+ const items = initializer.getElements().filter((element) => Node.isStringLiteral(element)).map((element) => element.getLiteralText());
417
+ return items.length > 0 ? items : void 0;
418
+ }
419
+ }
420
+ return void 0;
421
+ };
422
+ const parseSourceCall = (args) => {
423
+ const first = args[0];
424
+ if (!first || !Node.isObjectLiteralExpression(first)) {
425
+ return { binding: "", hasTenantBy: false, unanalyzable: true };
426
+ }
427
+ return {
428
+ binding: stringPropertyOf(first, "binding") ?? "",
429
+ columns: stringArrayPropertyOf(first, "columns"),
430
+ hasReconcile: first.getProperty("reconcileEveryMs") !== void 0,
431
+ hasTenantBy: first.getProperty("tenantBy") !== void 0,
432
+ idColumn: stringPropertyOf(first, "idColumn"),
433
+ mode: stringPropertyOf(first, "mode"),
434
+ query: stringPropertyOf(first, "query")
435
+ };
436
+ };
182
437
  const applyTableMethod = (accumulator, method, args, name) => {
183
438
  switch (method) {
184
439
  case "externallyManaged": {
@@ -194,6 +449,10 @@ const applyTableMethod = (accumulator, method, args, name) => {
194
449
  accumulator.indexes.push(parseIndexCall(args));
195
450
  break;
196
451
  }
452
+ case "public": {
453
+ accumulator.isPublic = true;
454
+ break;
455
+ }
197
456
  case "rankIndex": {
198
457
  accumulator.rankIndexes.push(parseRankIndexCall(args));
199
458
  break;
@@ -214,6 +473,15 @@ const applyTableMethod = (accumulator, method, args, name) => {
214
473
  accumulator.shardMode = { field: field && Node.isStringLiteral(field) ? field.getLiteralText() : "_unknown_", kind: "shardBy" };
215
474
  break;
216
475
  }
476
+ case "softDelete": {
477
+ accumulator.softDelete = { field: softDeleteFieldOf(args[0]) };
478
+ break;
479
+ }
480
+ case "source": {
481
+ accumulator.externalSource = parseSourceCall(args);
482
+ accumulator.externallyManaged = true;
483
+ break;
484
+ }
217
485
  case "vectorize": {
218
486
  const vectorIndex = parseVectorizeCall(args, name);
219
487
  if (vectorIndex) {
@@ -227,6 +495,7 @@ const parseTableBuilder = (expression, name) => {
227
495
  const accumulator = {
228
496
  externallyManaged: false,
229
497
  indexes: [],
498
+ isPublic: false,
230
499
  rankIndexes: [],
231
500
  relations: [],
232
501
  searchIndexes: [],
@@ -251,16 +520,22 @@ const parseTableBuilder = (expression, name) => {
251
520
  break;
252
521
  }
253
522
  }
523
+ if (accumulator.softDelete && !(accumulator.softDelete.field in shape)) {
524
+ shape = { ...shape, [accumulator.softDelete.field]: { inner: { kind: "number" }, kind: "optional" } };
525
+ }
254
526
  return {
255
527
  externallyManaged: accumulator.externallyManaged,
528
+ externalSource: accumulator.externalSource,
256
529
  globalBackend: accumulator.shardMode === "global" ? accumulator.globalBackend ?? "d1" : void 0,
257
530
  indexes: accumulator.indexes,
531
+ isPublic: accumulator.isPublic,
258
532
  name,
259
533
  rankIndexes: accumulator.rankIndexes,
260
534
  relations: accumulator.relations,
261
535
  searchIndexes: accumulator.searchIndexes,
262
536
  shape,
263
537
  shardMode: accumulator.shardMode,
538
+ softDelete: accumulator.softDelete,
264
539
  vectorIndexes: accumulator.vectorIndexes
265
540
  };
266
541
  };
@@ -354,7 +629,27 @@ const extensionTargetIdentifier = (argument) => {
354
629
  return argument;
355
630
  }
356
631
  if (Node.isPropertyAccessExpression(argument)) {
357
- return argument.getNameNode();
632
+ const receiver = argument.getExpression();
633
+ return Node.isIdentifier(receiver) ? receiver : void 0;
634
+ }
635
+ return void 0;
636
+ };
637
+ const pluginConfigObject = (expression) => {
638
+ if (Node.isObjectLiteralExpression(expression)) {
639
+ return expression;
640
+ }
641
+ if (Node.isCallExpression(expression)) {
642
+ const callee = expression.getExpression();
643
+ let name;
644
+ if (Node.isIdentifier(callee)) {
645
+ name = callee.getText();
646
+ } else if (Node.isPropertyAccessExpression(callee)) {
647
+ name = callee.getName();
648
+ }
649
+ if (name === "definePlugin") {
650
+ const configArgument = expression.getArguments()[1];
651
+ return configArgument && Node.isObjectLiteralExpression(configArgument) ? configArgument : void 0;
652
+ }
358
653
  }
359
654
  return void 0;
360
655
  };
@@ -363,8 +658,9 @@ const nextExpressionFromDeclaration = (declaration, argument) => {
363
658
  if (!initializer) {
364
659
  return void 0;
365
660
  }
366
- if (Node.isPropertyAccessExpression(argument) && Node.isObjectLiteralExpression(initializer)) {
367
- return objectPropertyInitializer(initializer, argument.getName());
661
+ if (Node.isPropertyAccessExpression(argument)) {
662
+ const configObject = pluginConfigObject(initializer);
663
+ return configObject ? objectPropertyInitializer(configObject, argument.getName()) : void 0;
368
664
  }
369
665
  return initializer;
370
666
  };
@@ -434,24 +730,30 @@ const parseExtensionVectorIndexes = (options) => {
434
730
  }
435
731
  return parseStandaloneVectorIndexes(object);
436
732
  };
437
- const mergeExtension = (key, options) => {
438
- const bareTables = parseExtensionTables(options);
733
+ const namespaceExtension = (key, bareTables, bareVectorIndexes) => {
439
734
  const bareNames = new Set(bareTables.map((table) => table.name));
440
735
  const tables = bareTables.map((table) => namespaceExtensionTable(table, key, bareNames));
441
- const vectorIndexes = parseExtensionVectorIndexes(options).map((index) => {
736
+ const vectorIndexes = bareVectorIndexes.map((index) => {
442
737
  return { ...index, name: prefixTableName(key, index.name), table: rewriteReference(index.table, key, bareNames) };
443
738
  });
444
739
  return { tables, vectorIndexes };
445
740
  };
446
- const mergeExtendCall = (extendCall) => {
741
+ const mergeExtension = (key, options) => namespaceExtension(key, parseExtensionTables(options), parseExtensionVectorIndexes(options));
742
+ const mergeExtendCall = (extendCall, projectRoot) => {
447
743
  const extendArgument = extendCall.getArguments()[0];
448
744
  if (!extendArgument) {
449
745
  return void 0;
450
746
  }
451
747
  const resolved = resolveSchemaExtensionCall(extendArgument);
452
748
  if (!resolved) {
749
+ if (projectRoot !== void 0) {
750
+ const fromPackage = resolvePackageExtension(extendArgument, projectRoot);
751
+ if (fromPackage) {
752
+ return namespaceExtension(fromPackage.key, fromPackage.bareTables, fromPackage.bareVectorIndexes);
753
+ }
754
+ }
453
755
  console.warn(
454
- `@lunora/codegen: skipping \`.extend(${extendArgument.getText()})\` — its \`defineSchemaExtension(...)\` definition could not be resolved from local sources (cross-package node_modules/.d.ts resolution is a deferred phase). Extension tables will be absent from the generated types.`
756
+ `@lunora/codegen: skipping \`.extend(${extendArgument.getText()})\` — its \`defineSchemaExtension(...)\` could not be resolved from local sources, and ${projectRoot === void 0 ? "no project root was available to resolve the package" : "the package could not be imported/introspected"}. Extension tables will be absent from the generated types.`
455
757
  );
456
758
  return void 0;
457
759
  }
@@ -481,6 +783,36 @@ const extendCallsOf = (defineSchemaCall) => {
481
783
  }
482
784
  return calls;
483
785
  };
786
+ const chainedStringLiteralArgument = (defineSchemaCall, methodName, noun, allowed, expected) => {
787
+ let current = defineSchemaCall;
788
+ for (; ; ) {
789
+ const parent = current.getParent();
790
+ if (!parent || !Node.isPropertyAccessExpression(parent)) {
791
+ break;
792
+ }
793
+ const callParent = parent.getParent();
794
+ if (!callParent || !Node.isCallExpression(callParent)) {
795
+ break;
796
+ }
797
+ if (parent.getName() === methodName) {
798
+ const argument = callParent.getArguments()[0];
799
+ if (!argument || !Node.isStringLiteral(argument)) {
800
+ throw diagnosticAt(callParent, `\`.${methodName}(...)\` expects a string literal (${expected})`);
801
+ }
802
+ const value = argument.getLiteralText();
803
+ if (!allowed.has(value)) {
804
+ throw diagnosticAt(argument, `unknown ${noun} ${JSON.stringify(value)} — expected ${expected}`);
805
+ }
806
+ return value;
807
+ }
808
+ current = callParent;
809
+ }
810
+ return void 0;
811
+ };
812
+ const JURISDICTIONS = /* @__PURE__ */ new Set(["eu", "fedramp", "us"]);
813
+ const jurisdictionOf = (defineSchemaCall) => chainedStringLiteralArgument(defineSchemaCall, "jurisdiction", "jurisdiction", JURISDICTIONS, '"eu", "us", or "fedramp"');
814
+ const RLS_MODES = /* @__PURE__ */ new Set(["required"]);
815
+ const rlsModeOf = (defineSchemaCall) => chainedStringLiteralArgument(defineSchemaCall, "rls", "rls mode", RLS_MODES, '"required"');
484
816
  const parseBaseTables = (object) => {
485
817
  const tables = [];
486
818
  for (const property of object.getProperties()) {
@@ -496,11 +828,11 @@ const parseBaseTables = (object) => {
496
828
  }
497
829
  return tables;
498
830
  };
499
- const applyExtensions = (defineSchemaCall, tables) => {
831
+ const applyExtensions = (defineSchemaCall, tables, projectRoot) => {
500
832
  const existingTableNames = new Set(tables.map((table) => table.name));
501
833
  const vectorIndexes = [];
502
834
  for (const extendCall of extendCallsOf(defineSchemaCall)) {
503
- const merged = mergeExtendCall(extendCall);
835
+ const merged = mergeExtendCall(extendCall, projectRoot);
504
836
  if (!merged) {
505
837
  continue;
506
838
  }
@@ -518,14 +850,14 @@ const applyExtensions = (defineSchemaCall, tables) => {
518
850
  }
519
851
  return vectorIndexes;
520
852
  };
521
- const discoverSchema = (project, schemaPath) => {
853
+ const discoverSchema = (project, schemaPath, projectRoot) => {
522
854
  const file = project.addSourceFileAtPath(schemaPath);
523
855
  const defineSchemaCall = file.getDescendantsOfKind(SyntaxKind.CallExpression).find((call) => {
524
856
  const callee = call.getExpression();
525
857
  return Node.isIdentifier(callee) && callee.getText() === "defineSchema";
526
858
  });
527
859
  if (!defineSchemaCall) {
528
- throw new Error(`defineSchema() not found in ${schemaPath}`);
860
+ throw new LunoraError("INTERNAL", `defineSchema() not found in ${schemaPath}`);
529
861
  }
530
862
  const argument = defineSchemaCall.getArguments()[0];
531
863
  if (!argument || !Node.isObjectLiteralExpression(argument)) {
@@ -534,9 +866,9 @@ const discoverSchema = (project, schemaPath) => {
534
866
  const tables = parseBaseTables(argument);
535
867
  const standaloneArgument = defineSchemaCall.getArguments()[1];
536
868
  const standaloneVectorIndexes = standaloneArgument && Node.isObjectLiteralExpression(standaloneArgument) ? parseStandaloneVectorIndexes(standaloneArgument) : [];
537
- const extensionStandaloneVectorIndexes = applyExtensions(defineSchemaCall, tables);
869
+ const extensionStandaloneVectorIndexes = applyExtensions(defineSchemaCall, tables, projectRoot);
538
870
  const vectorIndexes = [...tables.flatMap((table) => table.vectorIndexes), ...standaloneVectorIndexes, ...extensionStandaloneVectorIndexes];
539
- return { tables, vectorIndexes };
871
+ return { jurisdiction: jurisdictionOf(defineSchemaCall), rlsMode: rlsModeOf(defineSchemaCall), tables, vectorIndexes };
540
872
  };
541
873
 
542
874
  export { discoverSchema as default };
@@ -1,5 +1,5 @@
1
1
  import { Node } from 'ts-morph';
2
- import { listLunoraSourceFiles, lunoraRelativePath, classifyProcedureCall } from './discoverFunctions-DEgAcRuD.mjs';
2
+ import { listLunoraSourceFiles, lunoraRelativePath, classifyProcedureCall } from './discoverFunctions-BJdF7lRs.mjs';
3
3
 
4
4
  const STORAGE_OPERATIONS = /* @__PURE__ */ new Set(["delete", "list", "read", "write"]);
5
5
  const isStorageRulesCall = (node) => {
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-CuLyj0WQ.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-f6nMvllu.mjs';
2
2
 
3
3
  const LONG_TAIL = [
4
4
  ["hasAi", "ai", "ai", "Override the Workers AI binding backing `ctx.ai` (defaults to `env.AI`)."],
@@ -22,8 +22,28 @@ const LONG_TAIL = [
22
22
  ["hasVectors", "vectors", "vectors", "Wire the Vectorize index map backing `ctx.vectors`."]
23
23
  ];
24
24
  const hasAnyLongTail = (options) => LONG_TAIL.some(([flag]) => options[flag]);
25
+ const buildIdentityImports = (identity) => identity ? [`import * as lunoraIdentityContract from "../identity.js";`] : [];
26
+ const buildAccessImports = (hasAccess, hasAuth) => hasAccess ? [
27
+ `import type { CreateAccessResolverOptions } from "@lunora/cloudflare-access";`,
28
+ `import { createAccessResolver${hasAuth ? ", composeResolvers" : ""} } from "@lunora/cloudflare-access";`
29
+ ] : [];
30
+ const buildKvImports = (hasKv) => hasKv ? [`import { createKvIntrospectorFromEnv } from "@lunora/bindings/kv";`] : [];
25
31
  const buildImportLines = (options) => {
26
- const { hasAuth, hasFramework, hasGlobal, hasHyperdriveGlobal, hasScheduler, hasStorage, hasWorkflow, useUmbrella, wantsOpenApi, wantsOpenRpc } = options;
32
+ const {
33
+ hasAccess,
34
+ hasAuth,
35
+ hasFramework,
36
+ hasGlobal,
37
+ hasHyperdriveGlobal,
38
+ hasKv,
39
+ hasQueue,
40
+ hasScheduler,
41
+ hasStorage,
42
+ hasWorkflow,
43
+ useUmbrella,
44
+ wantsOpenApi,
45
+ wantsOpenRpc
46
+ } = options;
27
47
  const runtimeModule = useUmbrella ? "lunorash/runtime" : "@lunora/runtime";
28
48
  const runtimeTypeImports = ["ExecutionContextLike", "LunoraWorker", "Route", "ScheduledControllerLike", "ShardNamespaceLike", "WorkerOptions"];
29
49
  if (hasGlobal) {
@@ -42,6 +62,7 @@ const buildImportLines = (options) => {
42
62
  `import type { LunoraAuth, LunoraAuthOptions } from "@lunora/auth";`,
43
63
  `import { createAuth, createAuthAdmin, ensureMigrated, handleAuthRequest, lunoraD1Adapter } from "@lunora/auth";`
44
64
  ] : [],
65
+ ...buildAccessImports(hasAccess, hasAuth),
45
66
  ...hasGlobal ? [
46
67
  `import type { D1CtxDbOptions, D1DatabaseLike, D1Exec } from "@lunora/d1";`,
47
68
  `import { createD1CtxDb, facetGlobalColumn, listGlobalTables, readGlobalTablePage } from "@lunora/d1";`
@@ -51,15 +72,18 @@ const buildImportLines = (options) => {
51
72
  `import { createHyperdriveGlobalCtxDb } from "@lunora/hyperdrive/global";`,
52
73
  `import type { SqlCtxDbOptions, SqlExec } from "@lunora/sql-store";`
53
74
  ] : [],
75
+ ...buildKvImports(hasKv),
54
76
  ...hasScheduler ? [`import type { DurableObjectNamespaceLike } from "@lunora/scheduler";`, `import { createScheduler } from "@lunora/scheduler";`] : [],
55
77
  ...hasStorage ? [`import type { R2BucketLike, Storage } from "@lunora/storage";`, `import { createBucketStorage, createStorage } from "@lunora/storage";`] : [],
56
78
  ...hasWorkflow ? [`import { createWorkflowsRestClient } from "@lunora/workflow";`] : [],
57
79
  `import type { ${[...runtimeTypeImports].toSorted((a, b) => a.localeCompare(b)).join(", ")} } from "${runtimeModule}";`,
58
80
  `import { ${runtimeValueImports} } from "${runtimeModule}";`,
59
81
  ``,
82
+ ...buildIdentityImports(options.identity),
60
83
  ...hasGlobal || hasHyperdriveGlobal ? [`import schema from "../schema.js";`] : [],
61
84
  `import { LUNORA_CRONS } from "./crons.js";`,
62
85
  `import { LUNORA_FUNCTIONS } from "./functions.js";`,
86
+ ...hasQueue ? [`import { dispatchQueueBatch } from "@lunora/queue";`, `import { LUNORA_QUEUE_REGISTRY } from "./queues.js";`] : [],
63
87
  ...wantsOpenApi ? [`import { openApiSpec } from "./openapi.js";`] : [],
64
88
  ...wantsOpenRpc ? [`import { openRpcSpec } from "./openrpc.js";`] : [],
65
89
  `import { createShardDO } from "./shard.js";`
@@ -119,9 +143,10 @@ interface AuthDeclaration<Env> {
119
143
  ] : []
120
144
  ];
121
145
  const buildFieldLines = (options) => [
146
+ ...options.hasAccess ? [` private accessSelector?: Selector<Env, CreateAccessResolverOptions>;`] : [],
122
147
  ` private adminToken?: Selector<Env, string>;`,
123
148
  ...options.hasAuth ? [` private authDeclaration?: AuthDeclaration<Env>;`] : [],
124
- ` private readonly extendFns: ((env: Env) => Partial<WorkerOptions>)[] = [];`,
149
+ ` private readonly extendFns: ((env: Env, derived: Readonly<WorkerOptions>) => Partial<WorkerOptions>)[] = [];`,
125
150
  ...options.hasGlobal ? [` private globalDeclaration?: GlobalDeclaration<Env>;`] : [],
126
151
  ...options.hasHyperdriveGlobal ? [` private hyperdriveGlobalDeclaration?: HyperdriveGlobalDeclaration<Env>;`] : [],
127
152
  ` private readonly routeMap: Record<string, Route> = {};`,
@@ -139,6 +164,14 @@ const buildLongTailMethods = (options) => LONG_TAIL.filter(([flag]) => options[f
139
164
  }`
140
165
  );
141
166
  const buildMethodBlocks = (options) => [
167
+ ...options.hasAccess ? [
168
+ ` /** Wire Cloudflare Access (Zero Trust) — verifies the \`Cf-Access-Jwt-Assertion\` JWT and feeds the identity into \`ctx.auth\` / RLS via \`resolveIdentity\`. When \`.auth(...)\` is also configured, Access is composed ahead of it (Access wins when its JWT is present; everyone else falls through to the app session). */
169
+ public access(selector: Selector<Env, CreateAccessResolverOptions>): this {
170
+ this.accessSelector = selector;
171
+
172
+ return this;
173
+ }`
174
+ ] : [],
142
175
  ` /** Bearer token gating the \`/_lunora/admin/*\` endpoints the studio calls. */
143
176
  public admin(selector: Selector<Env, string>): this {
144
177
  this.adminToken = selector;
@@ -153,8 +186,8 @@ const buildMethodBlocks = (options) => [
153
186
  return this;
154
187
  }`
155
188
  ] : [],
156
- ` /** Escape hatch — merge raw \`WorkerOptions\` (anything not yet sugared) over the derived options at build time. */
157
- public extend(fn: (env: Env) => Partial<WorkerOptions>): this {
189
+ ` /** Escape hatch — merge raw \`WorkerOptions\` (anything not yet sugared) over the derived options at build time. The second \`derived\` argument is a snapshot of the options assembled so far (after \`.auth(...)\` etc.), so you can compose rather than clobber — e.g. wrap \`derived.resolveIdentity\` instead of replacing it. */
190
+ public extend(fn: (env: Env, derived: Readonly<WorkerOptions>) => Partial<WorkerOptions>): this {
158
191
  this.extendFns.push(fn);
159
192
 
160
193
  return this;
@@ -274,7 +307,7 @@ const buildShardFactoryBody = (options) => {
274
307
  const namespace = this.schedulerDeclaration?.namespace(env);
275
308
  const origin = this.schedulerDeclaration?.origin?.(env);
276
309
 
277
- return namespace && origin ? createScheduler({ namespace, originUrl: origin }) : undefined;
310
+ return namespace && origin ? createScheduler({${options.jurisdiction ? ` jurisdiction: ${JSON.stringify(options.jurisdiction)},` : ""} namespace, originUrl: origin }) : undefined;
278
311
  },
279
312
  }
280
313
  : {}),`
@@ -321,6 +354,12 @@ const buildWorkerOptionLines = (options) => [
321
354
  Object.assign(options, this.buildStorageAdmin(env));
322
355
  }`
323
356
  ] : [],
357
+ // The studio's KV browser is wired zero-config: `createKvIntrospectorFromEnv`
358
+ // scans `env` for every bound Workers KV namespace, so each `kv_namespaces`
359
+ // entry in wrangler.jsonc appears under its binding name (any name, any count)
360
+ // with no manual `createKvIntrospector` call. A deployment with no KV binding
361
+ // yields an empty namespace list rather than crashing.
362
+ ...options.hasKv ? [` options.kvIntrospector = createKvIntrospectorFromEnv(env);`] : [],
324
363
  ...options.hasAuth ? [
325
364
  ` if (this.authDeclaration) {
326
365
  options.authHandler = (request) => {
@@ -343,13 +382,45 @@ const buildWorkerOptionLines = (options) => [
343
382
 
344
383
  options.authAdmin = authInstance ? createAuthAdmin(authInstance) : undefined;
345
384
  }`
385
+ ] : [],
386
+ // Cloudflare Access — runs AFTER the auth block so it can compose ahead of
387
+ // the better-auth resolver rather than clobber it. With `.auth()` present,
388
+ // a request carrying a verified Access JWT is authenticated by Access and
389
+ // everyone else falls through to the app session; without it, Access is the
390
+ // sole resolver.
391
+ ...options.hasAccess ? [
392
+ options.hasAuth ? ` if (this.accessSelector) {
393
+ const accessResolver = createAccessResolver(this.accessSelector(env));
394
+ const fallback = options.resolveIdentity;
395
+
396
+ options.resolveIdentity = fallback ? composeResolvers(accessResolver, fallback) : accessResolver;
397
+ }` : ` if (this.accessSelector) {
398
+ options.resolveIdentity = createAccessResolver(this.accessSelector(env));
399
+ }`
346
400
  ] : []
347
401
  ];
348
402
  const buildBaseWorkerOptions = (options) => [
349
403
  ` cronJobs: LUNORA_CRONS,`,
350
404
  ` functions: LUNORA_FUNCTIONS,`,
405
+ // The declared `defineIdentity(...)` contract — wires the runtime trust
406
+ // boundary so `wrapResolverWithContract` validates every resolved identity
407
+ // against it before it becomes `ctx.auth`. Emitted only when the app declares
408
+ // a contract, so apps without one keep unchanged output.
409
+ ...options.identity ? [` identity: lunoraIdentityContract.${options.identity.exportName},`] : [],
410
+ // Schema `.jurisdiction("…")` pins every DO the worker reaches to the
411
+ // Cloudflare data-residency region. Emitted only when declared, so apps
412
+ // without it keep the un-pinned global namespace (and unchanged output).
413
+ ...options.jurisdiction ? [` jurisdiction: ${JSON.stringify(options.jurisdiction)},`] : [],
351
414
  ...options.wantsOpenApi ? [` openApiSpec,`] : [],
352
415
  ...options.wantsOpenRpc ? [` openRpcSpec,`] : [],
416
+ // The push-consumer handler backing the worker's `queue(batch, …)` entry:
417
+ // routes each delivered batch to its `defineQueue` handler. Built from
418
+ // `@lunora/queue` here (keeping the runtime decoupled) and wired only when the
419
+ // app declares push queues in `lunora/queues.ts`.
420
+ ...options.hasQueue ? [
421
+ ` queue: (batch: unknown, queueEnv: unknown, _context: ExecutionContextLike): Promise<void> =>`,
422
+ ` dispatchQueueBatch(batch as Parameters<typeof dispatchQueueBatch>[0], LUNORA_QUEUE_REGISTRY, { env: queueEnv as Record<string, unknown> }),`
423
+ ] : [],
353
424
  ` routes: this.routeMap,`,
354
425
  ` shardDO: this.shardSelector?.(env) ?? (undefined as unknown as ShardNamespaceLike),`
355
426
  ];
@@ -558,7 +629,12 @@ ${buildWorkerLine}
558
629
  worker ??= buildWorker(rawEnv as Env);
559
630
 
560
631
  return worker.serverQuery(request, rawEnv, reference, args, options);
561
- },
632
+ },${options.hasQueue ? `
633
+ queue: async (batch: unknown, rawEnv: unknown, context: ExecutionContextLike): Promise<void> => {
634
+ worker ??= buildWorker(rawEnv as Env);
635
+
636
+ return worker.queue?.(batch, rawEnv, context);
637
+ },` : ""}
562
638
  };
563
639
 
564
640
  if (this.emailHandler) {
@@ -581,7 +657,7 @@ ${buildBaseWorkerOptions(options).join("\n")}
581
657
  }
582
658
 
583
659
  ${workerOptionLines.join("\n\n")}${workerOptionLines.length > 0 ? "\n\n" : ""} for (const fn of this.extendFns) {
584
- Object.assign(options, fn(env));
660
+ Object.assign(options, fn(env, { ...options }));
585
661
  }
586
662
 
587
663
  return options;