@lunora/codegen 1.0.0-alpha.51 → 1.0.0-alpha.52

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 (28) hide show
  1. package/dist/index.d.mts +66 -4
  2. package/dist/index.d.ts +66 -4
  3. package/dist/index.mjs +22 -21
  4. package/dist/packem_shared/{FLAGS_FILENAME-Dg4mKUuz.mjs → FLAGS_FILENAME-D5igzqwR.mjs} +1 -1
  5. package/dist/packem_shared/{GENERATED_HEADER-C5paWNYO.mjs → GENERATED_HEADER-D5mkGdTx.mjs} +1 -1
  6. package/dist/packem_shared/NOTIFY_FILENAME-CK2IrsrP.mjs +152 -0
  7. package/dist/packem_shared/{OPENRPC_VERSION-7A2weq2u.mjs → OPENRPC_VERSION-Dnru3-Tg.mjs} +1 -1
  8. package/dist/packem_shared/{SCHEMA_SNAPSHOT_FILENAME-G5dkcxPR.mjs → SCHEMA_SNAPSHOT_FILENAME-7_LhHIAy.mjs} +118 -23
  9. package/dist/packem_shared/{buildOpenApiDocument-B7mEdzA2.mjs → buildOpenApiDocument-BH50tL6w.mjs} +80 -1
  10. package/dist/packem_shared/{discoverAuthApiCalls-Dx3K42rk.mjs → discoverAuthApiCalls-Dhwgf4J4.mjs} +1 -1
  11. package/dist/packem_shared/{discoverCrons-DvqkEWdx.mjs → discoverCrons-UbQLLmMS.mjs} +1 -1
  12. package/dist/packem_shared/{discoverFunctions-BJ-qR7Rg.mjs → discoverFunctions-COG_Xkyz.mjs} +27 -1
  13. package/dist/packem_shared/{discoverHttpRoutes-daCzuTe8.mjs → discoverHttpRoutes-CVXWZkzo.mjs} +2 -2
  14. package/dist/packem_shared/{discoverInserts-DI4q5NxE.mjs → discoverInserts-DdWnXmLS.mjs} +1 -1
  15. package/dist/packem_shared/{discoverMaskProcedures-BcTOEKNU.mjs → discoverMaskProcedures-BG58_fw3.mjs} +1 -1
  16. package/dist/packem_shared/{discoverMigrations-VNUFvCwr.mjs → discoverMigrations-Bw1U01zr.mjs} +1 -1
  17. package/dist/packem_shared/{discoverNondeterministicCalls-S0N2xLCq.mjs → discoverNondeterministicCalls-BbXEFZUT.mjs} +1 -1
  18. package/dist/packem_shared/{discoverQueries-CJnnnLpd.mjs → discoverQueries-CDmxu2ET.mjs} +1 -1
  19. package/dist/packem_shared/{discoverR2sqlCalls-pnpicWfz.mjs → discoverR2sqlCalls-Bn7QVv7J.mjs} +1 -1
  20. package/dist/packem_shared/{discoverRlsMetadata-DppniPUH.mjs → discoverRlsMetadata-D9uN35no.mjs} +1 -1
  21. package/dist/packem_shared/{discoverSandboxUsage-BUM2r90k.mjs → discoverSandboxUsage-B5jDU6Bt.mjs} +1 -1
  22. package/dist/packem_shared/{discoverSchema-BaSAvooG.mjs → discoverSchema-BJp5_T4o.mjs} +32 -1
  23. package/dist/packem_shared/{discoverStorageRulesMetadata-CnHl2rXD.mjs → discoverStorageRulesMetadata-BqMhuEjo.mjs} +1 -1
  24. package/dist/packem_shared/{emit-ClTbCLy4.mjs → emit-B4LUyyg7.mjs} +98 -18
  25. package/dist/packem_shared/{emitApp-CzzrjVqH.mjs → emitApp-BJ6weBMT.mjs} +11 -2
  26. package/dist/packem_shared/{formatAdvisories-BmF7Mplc.mjs → formatAdvisories-Cans5TWS.mjs} +17 -1
  27. package/dist/packem_shared/{parse-validator-BSJo1HGP.mjs → parse-validator-BDGV8iue.mjs} +1 -1
  28. package/package.json +7 -7
@@ -388,6 +388,8 @@ const SCALAR_TYPE_BY_KIND = {
388
388
  // not statically recoverable at codegen time — emit `unknown` so the
389
389
  // generated api types still compile without depending on the library.
390
390
  from: "unknown",
391
+ // A geographic point stored as a `{ lat, lng }` JSON object.
392
+ geoPoint: "{ lat: number; lng: number }",
391
393
  null: "null",
392
394
  number: "number",
393
395
  // The key of a stored R2 object — a string; the distinction is semantic only.
@@ -512,6 +514,10 @@ ${fields}
512
514
  const names = table.rankIndexes.map((index) => JSON.stringify(index.name)).join(" | ");
513
515
  return ` ${table.name}: ${names || "never"};`;
514
516
  }).join("\n");
517
+ const geoIndexNamesByTable = schema.tables.map((table) => {
518
+ const names = table.shardMode === "global" ? "" : (table.geoIndexes ?? []).map((index) => JSON.stringify(index.name)).join(" | ");
519
+ return ` ${table.name}: ${names || "never"};`;
520
+ }).join("\n");
515
521
  const vectorIndexNames = schema.vectorIndexes.map((index) => JSON.stringify(index.name)).join(" | ") || "never";
516
522
  const insertInterfaces = schema.tables.map((table) => renderInsertInterface(table)).join("\n\n");
517
523
  const insertMap = schema.tables.map((table) => ` ${table.name}: Insert_${table.name};`).join("\n");
@@ -592,6 +598,13 @@ ${rankIndexNamesByTable}
592
598
 
593
599
  export type RankIndexName<T extends keyof DataModel> = RankIndexNamesByTable[T];
594
600
 
601
+ /** Per-table geo-index name union. \`never\` for tables without a geoIndex. */
602
+ export interface GeoIndexNamesByTable {
603
+ ${geoIndexNamesByTable}
604
+ }
605
+
606
+ export type GeoIndexName<T extends keyof DataModel> = GeoIndexNamesByTable[T];
607
+
595
608
  /** Union of declared vector index names. \`never\` when none are declared. */
596
609
  export type VectorIndexName = ${vectorIndexNames};
597
610
 
@@ -631,16 +644,16 @@ export type WithArg<T extends keyof DataModel> = WithArgOf<DataModel, Relations,
631
644
  export type LoadWith<T extends keyof DataModel, W> = LoadWithOf<DataModel, Relations, T, W>;
632
645
 
633
646
  /** Read-only typed table accessor exposed on \`QueryCtx.db.<table>\`. */
634
- export type TableReaderFacade<T extends keyof DataModel> = TableReaderFacadeOf<DataModel, Relations, RankIndexNamesByTable, SearchIndexNamesByTable, T>;
647
+ export type TableReaderFacade<T extends keyof DataModel> = TableReaderFacadeOf<DataModel, Relations, RankIndexNamesByTable, SearchIndexNamesByTable, T, GeoIndexNamesByTable>;
635
648
 
636
649
  /** Read-write typed table accessor exposed on \`MutationCtx.db.<table>\` / \`ActionCtx.db.<table>\`. */
637
- export type TableWriterFacade<T extends keyof DataModel> = TableWriterFacadeOf<DataModel, InsertModel, Relations, RankIndexNamesByTable, SearchIndexNamesByTable, T>;
650
+ export type TableWriterFacade<T extends keyof DataModel> = TableWriterFacadeOf<DataModel, InsertModel, Relations, RankIndexNamesByTable, SearchIndexNamesByTable, T, GeoIndexNamesByTable>;
638
651
 
639
652
  /** Per-table read facade — \`ctx.db.<table>\` on a \`QueryCtx\`. */
640
- export type DatabaseReaderFacade = DatabaseReaderFacadeOf<DataModel, Relations, RankIndexNamesByTable, SearchIndexNamesByTable>;
653
+ export type DatabaseReaderFacade = DatabaseReaderFacadeOf<DataModel, Relations, RankIndexNamesByTable, SearchIndexNamesByTable, GeoIndexNamesByTable>;
641
654
 
642
655
  /** Per-table read-write facade — \`ctx.db.<table>\` on a \`MutationCtx\` / \`ActionCtx\`. */
643
- export type DatabaseWriterFacade = DatabaseWriterFacadeOf<DataModel, InsertModel, Relations, RankIndexNamesByTable, SearchIndexNamesByTable>;
656
+ export type DatabaseWriterFacade = DatabaseWriterFacadeOf<DataModel, InsertModel, Relations, RankIndexNamesByTable, SearchIndexNamesByTable, GeoIndexNamesByTable>;
644
657
 
645
658
  /** Insert builder returned by \`ctx.orm.insert(table)\`. */
646
659
  export interface OrmInsertBuilder<T extends keyof DataModel> {
@@ -1131,6 +1144,7 @@ const emitServer = ({
1131
1144
  hasHyperdrive = false,
1132
1145
  hasImages = false,
1133
1146
  hasKv = false,
1147
+ hasNotify = false,
1134
1148
  hasPayments = false,
1135
1149
  hasPipelines = false,
1136
1150
  hasR2sql = false,
@@ -1219,6 +1233,11 @@ export type Env = CloudflareBindings;`;
1219
1233
  const flagsContextField = hasFlags ? `
1220
1234
  /** Feature-flag evaluation (OpenFeature). Reads are memoized per request; evaluations never throw — a provider error resolves to the supplied default. */
1221
1235
  readonly flags: import("${base.flags}").LunoraFlags;` : "";
1236
+ const notifyContextField = hasNotify ? `
1237
+ /** Multi-channel notifications (@lunora/notify): send / chat / inApp / webhook plus the push device sub-facade. Sends are external I/O — confine them to action handlers. */
1238
+ readonly notify: import("@lunora/notify").LunoraNotify;
1239
+ /** Device push sub-facade — the same object as ctx.notify.push (register / send / broadcast). Sends belong in action handlers. */
1240
+ readonly push: import("@lunora/notify").LunoraPush;` : "";
1222
1241
  const hasWorkflows = workflows.length > 0;
1223
1242
  const workflowsTypeImport = hasWorkflows ? `import type { WorkflowHandle } from "@lunora/workflow";
1224
1243
  import type * as lunoraWorkflowDefinitions from "../workflows.js";
@@ -1349,19 +1368,19 @@ type TypedTableGet = <T extends TableName>(id: IdOfTable<T>) => Promise<Doc<T> |
1349
1368
  export interface QueryCtx extends Omit<QueryCtxBase, "db" | "storage"${authOmit}${envOmit}> {
1350
1369
  readonly db: Omit<DatabaseReader, "query" | "get"> & DatabaseReaderFacade & { query: TypedTableQuery; get: TypedTableGet };
1351
1370
  readonly orm: OrmReader;
1352
- readonly storage: ReadOnlyStorage<StorageBucketName>;${accessContextField}${kvContextField}${flagsContextField}${analyticsContextField}${envContextField}${authContextField}
1371
+ readonly storage: ReadOnlyStorage<StorageBucketName>;${accessContextField}${kvContextField}${flagsContextField}${notifyContextField}${analyticsContextField}${envContextField}${authContextField}
1353
1372
  }
1354
1373
 
1355
1374
  export interface MutationCtx extends Omit<MutationCtxBase, "db" | "storage"${workflowsOmit}${authOmit}${envOmit}> {
1356
1375
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
1357
1376
  readonly orm: OrmWriter;
1358
- readonly storage: ReadOnlyStorage<StorageBucketName>;${accessContextField}${kvContextField}${flagsContextField}${analyticsContextField}${envContextField}${workflowsContextField}${queuesContextField}${agentsContextField}${authContextField}
1377
+ readonly storage: ReadOnlyStorage<StorageBucketName>;${accessContextField}${kvContextField}${flagsContextField}${notifyContextField}${analyticsContextField}${envContextField}${workflowsContextField}${queuesContextField}${agentsContextField}${authContextField}
1359
1378
  }
1360
1379
 
1361
1380
  export interface ActionCtx extends Omit<ActionCtxBase, "db" | "storage"${workflowsOmit}${authOmit}${envOmit}> {
1362
1381
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
1363
1382
  readonly orm: OrmWriter;
1364
- readonly storage: StorageBase<StorageBucketName>;${accessContextField}${aiActionField}${paymentsActionField}${x402ActionField}${containersActionField}${kvContextField}${flagsContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${r2sqlActionField}${envContextField}${workflowsContextField}${queuesContextField}${agentsContextField}${authContextField}
1383
+ readonly storage: StorageBase<StorageBucketName>;${accessContextField}${aiActionField}${paymentsActionField}${x402ActionField}${containersActionField}${kvContextField}${flagsContextField}${notifyContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${r2sqlActionField}${envContextField}${workflowsContextField}${queuesContextField}${agentsContextField}${authContextField}
1365
1384
  }
1366
1385
 
1367
1386
  /**
@@ -1625,6 +1644,9 @@ const buildTableIndexes = (schema) => {
1625
1644
  ...table.searchIndexes.map((index) => {
1626
1645
  return { fields: [index.field, ...index.filterFields ?? []], name: index.name, type: "search" };
1627
1646
  }),
1647
+ ...(table.geoIndexes ?? []).map((index) => {
1648
+ return { fields: [index.field], name: index.name, type: "geo" };
1649
+ }),
1628
1650
  ...table.rankIndexes.map((index) => {
1629
1651
  return { fields: index.sortBy.map((key) => key.field), name: index.name, type: "rank" };
1630
1652
  }),
@@ -1638,6 +1660,21 @@ const buildTableIndexes = (schema) => {
1638
1660
  }
1639
1661
  return byTable;
1640
1662
  };
1663
+ const buildTtlSweeps = (schema) => {
1664
+ const sweeps = [];
1665
+ for (const table of schema.tables) {
1666
+ if (!table.ttl || table.shardMode === "global") {
1667
+ continue;
1668
+ }
1669
+ sweeps.push({
1670
+ ...table.ttl.after === void 0 ? {} : { after: table.ttl.after },
1671
+ field: table.ttl.field,
1672
+ ...table.softDelete ? { softDeleteField: table.softDelete.field } : {},
1673
+ table: table.name
1674
+ });
1675
+ }
1676
+ return sweeps;
1677
+ };
1641
1678
  const buildTableColumns = (schema) => {
1642
1679
  const byTable = {};
1643
1680
  for (const table of schema.tables) {
@@ -1690,7 +1727,16 @@ const emitAiFragments = (hasAi) => {
1690
1727
  // a handler is never locked to Workers AI. Falls back to `aiStub`.
1691
1728
  build: `
1692
1729
  const aiBinding = config.ai?.(env) ?? (env as Record<string, unknown>).AI;
1693
- const ai: LunoraAi = aiBinding ? createAi({ binding: aiBinding as AiBindingLike }) : aiStub;
1730
+ // Correlate AI-Gateway-routed calls with the Lunora trace: thread the
1731
+ // function path + trace id into createAi, which folds them into the
1732
+ // gateway's native \`metadata\` only when a gateway is configured (absent
1733
+ // otherwise). Mirror the tracer's anchor guard — a deferred subscription
1734
+ // re-run must not borrow a concurrent dispatch's trace, so read
1735
+ // \`getCurrentTrace()\` only on the synchronous (non-threaded-identity) path.
1736
+ const aiTrace = options.identity ? undefined : this.getCurrentTrace();
1737
+ const ai: LunoraAi = aiBinding
1738
+ ? createAi({ binding: aiBinding as AiBindingLike, env: env as Record<string, unknown>, metadata: { functionPath: options.functionPath, traceId: aiTrace?.traceId } })
1739
+ : aiStub;
1694
1740
  `,
1695
1741
  // Optional override for the Workers AI binding. When omitted, ctx.ai is
1696
1742
  // built from `env.AI` (the conventional binding the config layer
@@ -1781,6 +1827,22 @@ const emitFlagsFragments = (hasFlags, flagsSpecifier) => {
1781
1827
  stub: ""
1782
1828
  };
1783
1829
  };
1830
+ const emitNotifyFragments = (hasNotify) => {
1831
+ if (!hasNotify) {
1832
+ return EMPTY_HELPER_FRAGMENTS;
1833
+ }
1834
+ return {
1835
+ build: `
1836
+ const { notify, push } = createNotify(notifyConfig, env);
1837
+ `,
1838
+ configField: "",
1839
+ contextField: `
1840
+ notify,
1841
+ push,`,
1842
+ importLines: [`import { createNotify } from "@lunora/notify";`, `import notifyConfig from "../notify.js";`],
1843
+ stub: ""
1844
+ };
1845
+ };
1784
1846
  const emitEnvFragments = (env) => {
1785
1847
  if (!env) {
1786
1848
  return EMPTY_HELPER_FRAGMENTS;
@@ -2525,6 +2587,7 @@ const emitShard = ({
2525
2587
  hasHyperdrive = false,
2526
2588
  hasImages = false,
2527
2589
  hasKv = false,
2590
+ hasNotify = false,
2528
2591
  hasPayments = false,
2529
2592
  hasPipelines = false,
2530
2593
  hasR2sql = false,
@@ -2548,6 +2611,7 @@ const emitShard = ({
2548
2611
  const kvFragments = emitKvFragments(hasKv);
2549
2612
  const flagsFragments = emitFlagsFragments(hasFlags, base.flags);
2550
2613
  const flagsOverrides = emitFlagsOverrides(flagKeys, hasFlags, base.flags);
2614
+ const notifyFragments = emitNotifyFragments(hasNotify);
2551
2615
  const envFragments = emitEnvFragments(env);
2552
2616
  const analyticsFragments = emitAnalyticsFragments(hasAnalytics);
2553
2617
  const imagesFragments = emitImagesFragments(hasImages);
@@ -2613,6 +2677,7 @@ const emitShard = ({
2613
2677
  const tableIndexes = buildTableIndexes(schema);
2614
2678
  const tableColumns = buildTableColumns(schema);
2615
2679
  const storageColumns = buildStorageColumns(schema);
2680
+ const ttlSweeps = buildTtlSweeps(schema);
2616
2681
  const doTypeImports = buildDoTypeImports(hasVectors, workflows.length > 0, queues.length > 0, hasFlags);
2617
2682
  if (hasShapes) {
2618
2683
  doTypeImports.push("WhereInput");
@@ -2705,6 +2770,7 @@ const LUNORA_RLS_READ_REGISTRY = buildRlsReadRegistry(Object.values(LUNORA_FUNCT
2705
2770
  ...accessFragments.importLines,
2706
2771
  ...kvFragments.importLines,
2707
2772
  ...flagsFragments.importLines,
2773
+ ...notifyFragments.importLines,
2708
2774
  ...envFragments.importLines,
2709
2775
  ...analyticsFragments.importLines,
2710
2776
  ...imagesFragments.importLines,
@@ -2987,10 +3053,8 @@ const sourcePollAtCache = new WeakMap<object, Map<string, number>>();
2987
3053
  return nextDueAt;
2988
3054
  }
2989
3055
  ` : "";
2990
- const sourceConstructorOverride = hasSourcedTables ? `
2991
- public constructor(state: ShardDOState, env: unknown) {
2992
- super(state, env);
2993
-
3056
+ const hasTtlTables = ttlSweeps.length > 0;
3057
+ const sourceBootstrap = hasSourcedTables ? `
2994
3058
  const autoSourced = Object.values((schema as unknown as SchemaLike).tables).some((definition) => {
2995
3059
  const source = (definition as { externalSource?: ExternalSourceLike }).externalSource;
2996
3060
 
@@ -3000,7 +3064,16 @@ const sourcePollAtCache = new WeakMap<object, Map<string, number>>();
3000
3064
  if (autoSourced) {
3001
3065
  void this.scheduleSourcePoll();
3002
3066
  }
3003
- }
3067
+ ` : "";
3068
+ const ttlBootstrap = hasTtlTables ? `
3069
+ if (LUNORA_TTL_SWEEPS.length > 0) {
3070
+ void this.scheduleTtlSweep();
3071
+ }
3072
+ ` : "";
3073
+ const sourceConstructorOverride = hasSourcedTables || hasTtlTables ? `
3074
+ public constructor(state: ShardDOState, env: unknown) {
3075
+ super(state, env);
3076
+ ${sourceBootstrap}${ttlBootstrap} }
3004
3077
  ` : "";
3005
3078
  const facadeBlock = hasTables ? `
3006
3079
  const facade = db as unknown as Record<string, ReturnType<typeof bindTableFacade>>;
@@ -3017,8 +3090,8 @@ ${schema.tables.map(
3017
3090
  const secretsBuild = `
3018
3091
  const secrets = createSecrets(env);
3019
3092
  `;
3020
- const everyContextBuild = `${accessFragments.build}${kvFragments.build}${flagsFragments.build}${analyticsFragments.build}${envFragments.build}${secretsBuild}`;
3021
- const everyContextField = `${accessFragments.contextField}${kvFragments.contextField}${flagsFragments.contextField}${analyticsFragments.contextField}${envFragments.contextField}
3093
+ const everyContextBuild = `${accessFragments.build}${kvFragments.build}${flagsFragments.build}${notifyFragments.build}${analyticsFragments.build}${envFragments.build}${secretsBuild}`;
3094
+ const everyContextField = `${accessFragments.contextField}${kvFragments.contextField}${flagsFragments.contextField}${notifyFragments.contextField}${analyticsFragments.contextField}${envFragments.contextField}
3022
3095
  secrets,`;
3023
3096
  const actionOnlyHasAny = hasImages || hasHyperdrive || hasBrowser || hasR2sql || hasPipelines || hasX402;
3024
3097
  const actionOnlyBlock = actionOnlyHasAny ? `
@@ -3048,8 +3121,8 @@ interface FunctionReference {
3048
3121
  /** Foreign-key columns per table (\`v.id("target")\` fields) for the data browser. */
3049
3122
  const LUNORA_TABLE_REFS: Record<string, Record<string, string>> = ${JSON.stringify(tableReferences, void 0, 4)};
3050
3123
 
3051
- /** Declared indexes per table (secondary, search, rank, vector) for the schema viewer. */
3052
- const LUNORA_TABLE_INDEXES: Record<string, Array<{ fields: string[]; name: string; type: "index" | "rank" | "search" | "vector"; unique?: boolean }>> = ${JSON.stringify(tableIndexes, void 0, 4)};
3124
+ /** Declared indexes per table (secondary, search, geo, rank, vector) for the schema viewer. */
3125
+ const LUNORA_TABLE_INDEXES: Record<string, Array<{ fields: string[]; name: string; type: "geo" | "index" | "rank" | "search" | "vector"; unique?: boolean }>> = ${JSON.stringify(tableIndexes, void 0, 4)};
3053
3126
 
3054
3127
  /** Columns per table (typed, with PK/FK markers) for the studio's schema diagram, served via \`__lunora_admin__:describeTable\`. */
3055
3128
  const LUNORA_TABLE_COLUMNS: Record<string, Array<{ isStorage?: boolean; name: string; optional: boolean; pk?: boolean; ref?: string; type: string }>> = ${JSON.stringify(tableColumns, void 0, 4)};
@@ -3057,6 +3130,9 @@ const LUNORA_TABLE_COLUMNS: Record<string, Array<{ isStorage?: boolean; name: st
3057
3130
  /** Storage-key columns per table (\`v.storage(...)\` fields) for the file browser's records↔files join. */
3058
3131
  const LUNORA_STORAGE_COLUMNS: Record<string, string[]> = ${JSON.stringify(storageColumns, void 0, 4)};
3059
3132
 
3133
+ /** Declarative TTL policies (\`.ttl(field, { after? })\`) the DO alarm sweep auto-expires rows for. */
3134
+ const LUNORA_TTL_SWEEPS: Array<{ after?: number; field: string; softDeleteField?: string; table: string }> = ${JSON.stringify(ttlSweeps, void 0, 4)};
3135
+
3060
3136
  /** Static schema advisories (computed by @lunora/advisor at codegen time) served via \`__lunora_admin__:getAdvisories\`. */
3061
3137
  const LUNORA_ADVISORIES: AdvisoryFinding[] = ${JSON.stringify(advisoryData, void 0, 4)};
3062
3138
 
@@ -3238,10 +3314,14 @@ ${customMutatorOverride}${shapeResolveOverride}${globalShapeReaderOverride}${ext
3238
3314
  return LUNORA_TABLE_REFS[table];
3239
3315
  }
3240
3316
 
3241
- protected override tableIndexes(table: string): Array<{ fields: string[]; name: string; type: "index" | "rank" | "search" | "vector"; unique?: boolean }> {
3317
+ protected override tableIndexes(table: string): Array<{ fields: string[]; name: string; type: "geo" | "index" | "rank" | "search" | "vector"; unique?: boolean }> {
3242
3318
  return LUNORA_TABLE_INDEXES[table] ?? [];
3243
3319
  }
3244
3320
 
3321
+ protected override ttlSweeps(): ReadonlyArray<{ after?: number; field: string; softDeleteField?: string; table: string }> {
3322
+ return LUNORA_TTL_SWEEPS;
3323
+ }
3324
+
3245
3325
  protected override tableColumns(table: string): Array<{ isStorage?: boolean; name: string; optional: boolean; pk?: boolean; ref?: string; type: string }> {
3246
3326
  return LUNORA_TABLE_COLUMNS[table] ?? [];
3247
3327
  }
@@ -1,4 +1,4 @@
1
- import { A as APP_METHOD_CAPABILITIES, G as GENERATED_HEADER } from './emit-ClTbCLy4.mjs';
1
+ import { A as APP_METHOD_CAPABILITIES, G as GENERATED_HEADER } from './emit-B4LUyyg7.mjs';
2
2
 
3
3
  const hasFlagKey = (key) => `has${key.charAt(0).toUpperCase()}${key.slice(1)}`;
4
4
  const LONG_TAIL = APP_METHOD_CAPABILITIES.map(
@@ -11,6 +11,7 @@ const buildAccessImports = (hasAccess, hasAuth) => hasAccess ? [
11
11
  `import { createAccessResolver${hasAuth ? ", composeResolvers" : ""} } from "@lunora/cloudflare-access";`
12
12
  ] : [];
13
13
  const buildKvImports = (hasKv) => hasKv ? [`import { createKvIntrospectorFromEnv } from "@lunora/bindings/kv";`] : [];
14
+ const buildNotifyImports = (hasNotify) => hasNotify ? [`import notifyConfig from "../notify.js";`] : [];
14
15
  const hasEmailAgents = (options) => (options.emailAgents?.length ?? 0) > 0;
15
16
  const buildInboundImports = (options) => hasEmailAgents(options) ? [`import { dispatchAgentEmail } from "@lunora/agent/inbound";`] : [];
16
17
  const buildAgentDefinitionsImport = (options) => hasEmailAgents(options) ? [`import * as lunoraAgentDefinitions from "../agents.js";`] : [];
@@ -47,7 +48,7 @@ const buildImportLines = (options) => {
47
48
  return [
48
49
  ...hasAuth ? [
49
50
  `import type { LunoraAuth, LunoraAuthOptions } from "@lunora/auth";`,
50
- `import { createAuth, createAuthAdmin, ensureMigrated, handleAuthRequest, lunoraD1Adapter } from "@lunora/auth";`
51
+ `import { createAuth, createAuthAdmin, createAuthAuditReader, d1Executor, ensureMigrated, handleAuthRequest, lunoraD1Adapter } from "@lunora/auth";`
51
52
  ] : [],
52
53
  ...buildAccessImports(hasAccess, hasAuth),
53
54
  ...hasGlobal ? [
@@ -70,6 +71,7 @@ const buildImportLines = (options) => {
70
71
  ...buildIdentityImports(options.identity),
71
72
  ...buildAgentDefinitionsImport(options),
72
73
  ...hasGlobal || hasHyperdriveGlobal ? [`import schema from "../schema.js";`] : [],
74
+ ...buildNotifyImports(options.hasNotify),
73
75
  `import { LUNORA_CRONS } from "./crons.js";`,
74
76
  `import { LUNORA_FUNCTIONS } from "./functions.js";`,
75
77
  ...hasQueue ? [
@@ -352,6 +354,12 @@ const buildWorkerOptionLines = (options) => [
352
354
  // with no manual `createKvIntrospector` call. A deployment with no KV binding
353
355
  // yields an empty namespace list rather than crashing.
354
356
  ...options.hasKv ? [` options.kvIntrospector = createKvIntrospectorFromEnv(env);`] : [],
357
+ // The studio's Notifications page reads the app's registered `@lunora/notify`
358
+ // device subscriptions through the SAME store the handlers register into. The
359
+ // store is built from `env` via `lunora/notify.ts`'s `defineNotify({ store })`;
360
+ // when no `store` is configured (the in-memory default), the gated
361
+ // `__lunora_admin__:listPushSubscriptions` RPC returns an empty device list.
362
+ ...options.hasNotify ? [` options.notifySubscriptionStore = notifyConfig.store ? notifyConfig.store(env) : undefined;`] : [],
355
363
  // The studio's Logs → Archive feed is wired zero-config: when the operator sets
356
364
  // `LUNORA_LOG_ARCHIVE_TABLE` (the R2 Data Catalog table `pipelineLogSink` writes
357
365
  // to), the durable archive becomes readable; unset ⇒ `undefined` ⇒ the feed
@@ -378,6 +386,7 @@ const buildWorkerOptionLines = (options) => [
378
386
  const authInstance = getAuth();
379
387
 
380
388
  options.authAdmin = authInstance ? createAuthAdmin(authInstance) : undefined;
389
+ options.authAuditReader = createAuthAuditReader(d1Executor(this.authDeclaration.d1(env) as never));
381
390
  }`
382
391
  ] : [],
383
392
  // Cloudflare Access — runs AFTER the auth block so it can compose ahead of
@@ -12,8 +12,18 @@ const flattenIndexes = (table) => [
12
12
  }),
13
13
  ...table.vectorIndexes.filter((index) => index.field !== void 0).map((index) => {
14
14
  return { fields: [index.field], kind: "vector", name: index.name };
15
+ }),
16
+ ...(table.geoIndexes ?? []).map((index) => {
17
+ return { fields: [index.field], kind: "geo", name: index.name };
15
18
  })
16
19
  ];
20
+ const columnKindsOf = (table) => {
21
+ const kinds = {};
22
+ for (const [fieldName, validator] of Object.entries(table.shape)) {
23
+ kinds[fieldName] = validator.kind === "optional" ? validator.inner?.kind ?? "optional" : validator.kind;
24
+ }
25
+ return kinds;
26
+ };
17
27
  const toAdvisorSchema = (schema) => {
18
28
  return {
19
29
  rlsMode: schema.rlsMode,
@@ -27,6 +37,7 @@ const toAdvisorSchema = (schema) => {
27
37
  mode: table.externalSource.mode,
28
38
  unanalyzable: table.externalSource.unanalyzable
29
39
  } : void 0,
40
+ columnKinds: columnKindsOf(table),
30
41
  fields: Object.keys(table.shape),
31
42
  indexes: flattenIndexes(table),
32
43
  isPublic: table.isPublic ?? false,
@@ -42,7 +53,8 @@ const toAdvisorSchema = (schema) => {
42
53
  };
43
54
  }),
44
55
  shardKind: typeof table.shardMode === "string" ? table.shardMode : "shardBy",
45
- softDelete: table.softDelete
56
+ softDelete: table.softDelete,
57
+ ttl: table.ttl
46
58
  };
47
59
  })
48
60
  };
@@ -64,8 +76,10 @@ const lintSchema = (options) => runAdvisor(
64
76
  containerKeyAccesses: options.containerKeyAccesses,
65
77
  containerOverrides: options.containerOverrides,
66
78
  containers: options.containers,
79
+ exportSinks: options.exportSinks,
67
80
  failOpenGuards: options.failOpenGuards,
68
81
  flagSecurityDefaults: options.flagSecurityDefaults,
82
+ geoIndexUsages: options.geoIndexUsages,
69
83
  httpActionGuards: options.httpActionGuards,
70
84
  httpHeaderWrites: options.httpHeaderWrites,
71
85
  identityClaimReads: options.identityClaimReads,
@@ -78,6 +92,8 @@ const lintSchema = (options) => runAdvisor(
78
92
  mutatorWrites: options.mutatorWrites,
79
93
  nondeterministicCalls: options.nondeterministicCalls,
80
94
  normalizeIdAuthorizations: options.normalizeIdAuthorizations,
95
+ notifyCalls: options.notifyCalls,
96
+ notifyConfig: options.notifyConfig,
81
97
  ownerFieldWrites: options.ownerFieldWrites,
82
98
  paymentWebhooks: options.paymentWebhooks,
83
99
  privilegedDispatches: options.privilegedDispatches,
@@ -30,7 +30,7 @@ const applyColumnModifier = (base, modifier) => {
30
30
  }
31
31
  return { ...base, column };
32
32
  };
33
- const SCALAR_KINDS = /* @__PURE__ */ new Set(["any", "bigint", "boolean", "bytes", "date", "null", "number", "string", "timestamp"]);
33
+ const SCALAR_KINDS = /* @__PURE__ */ new Set(["any", "bigint", "boolean", "bytes", "date", "geoPoint", "null", "number", "string", "timestamp"]);
34
34
  const TRANSPARENT_MODIFIERS = /* @__PURE__ */ new Set(["check", "meta"]);
35
35
  const parseValidator = (expression) => {
36
36
  if (Node.isCallExpression(expression)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/codegen",
3
- "version": "1.0.0-alpha.51",
3
+ "version": "1.0.0-alpha.52",
4
4
  "description": "Code generator for Lunora: emits _generated/{api,server,dataModel}.ts from your schema",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,14 +46,14 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/advisor": "1.0.0-alpha.32",
50
- "@lunora/agent": "1.0.0-alpha.8",
51
- "@lunora/container": "1.0.0-alpha.13",
52
- "@lunora/errors": "1.0.0-alpha.6",
49
+ "@lunora/advisor": "1.0.0-alpha.33",
50
+ "@lunora/agent": "1.0.0-alpha.9",
51
+ "@lunora/container": "1.0.0-alpha.14",
52
+ "@lunora/errors": "1.0.0-alpha.7",
53
53
  "@lunora/queue": "1.0.0-alpha.9",
54
54
  "@lunora/scheduler": "1.0.0-alpha.11",
55
- "@lunora/values": "1.0.0-alpha.9",
56
- "@lunora/workflow": "1.0.0-alpha.11",
55
+ "@lunora/values": "1.0.0-alpha.10",
56
+ "@lunora/workflow": "1.0.0-alpha.12",
57
57
  "ts-morph": "^28.0.0"
58
58
  },
59
59
  "engines": {