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

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-C8tAvxqP.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-DvDChMhC.mjs} +1 -1
  8. package/dist/packem_shared/{SCHEMA_SNAPSHOT_FILENAME-G5dkcxPR.mjs → SCHEMA_SNAPSHOT_FILENAME--TKFIHmr.mjs} +118 -23
  9. package/dist/packem_shared/{buildOpenApiDocument-B7mEdzA2.mjs → buildOpenApiDocument-DCQSLuFc.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-DIbr7U31.mjs} +103 -18
  25. package/dist/packem_shared/{emitApp-CzzrjVqH.mjs → emitApp-DbMM8iHe.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,26 @@ 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
+ // Threads the request's `ctx.log` / `ctx.metrics` into the facade so a send
1836
+ // emits the `notify.send` / `notify.skipped` observability signals. Because
1837
+ // `log`/`metrics` are built LATER in the context builder, this fragment is
1838
+ // injected after them (see `notifyBuild` below), NOT inside `everyContextBuild`.
1839
+ build: `
1840
+ const { notify, push } = createNotify(notifyConfig, env, { log, metrics });
1841
+ `,
1842
+ configField: "",
1843
+ contextField: `
1844
+ notify,
1845
+ push,`,
1846
+ importLines: [`import { createNotify } from "@lunora/notify";`, `import notifyConfig from "../notify.js";`],
1847
+ stub: ""
1848
+ };
1849
+ };
1784
1850
  const emitEnvFragments = (env) => {
1785
1851
  if (!env) {
1786
1852
  return EMPTY_HELPER_FRAGMENTS;
@@ -2525,6 +2591,7 @@ const emitShard = ({
2525
2591
  hasHyperdrive = false,
2526
2592
  hasImages = false,
2527
2593
  hasKv = false,
2594
+ hasNotify = false,
2528
2595
  hasPayments = false,
2529
2596
  hasPipelines = false,
2530
2597
  hasR2sql = false,
@@ -2548,6 +2615,7 @@ const emitShard = ({
2548
2615
  const kvFragments = emitKvFragments(hasKv);
2549
2616
  const flagsFragments = emitFlagsFragments(hasFlags, base.flags);
2550
2617
  const flagsOverrides = emitFlagsOverrides(flagKeys, hasFlags, base.flags);
2618
+ const notifyFragments = emitNotifyFragments(hasNotify);
2551
2619
  const envFragments = emitEnvFragments(env);
2552
2620
  const analyticsFragments = emitAnalyticsFragments(hasAnalytics);
2553
2621
  const imagesFragments = emitImagesFragments(hasImages);
@@ -2613,6 +2681,7 @@ const emitShard = ({
2613
2681
  const tableIndexes = buildTableIndexes(schema);
2614
2682
  const tableColumns = buildTableColumns(schema);
2615
2683
  const storageColumns = buildStorageColumns(schema);
2684
+ const ttlSweeps = buildTtlSweeps(schema);
2616
2685
  const doTypeImports = buildDoTypeImports(hasVectors, workflows.length > 0, queues.length > 0, hasFlags);
2617
2686
  if (hasShapes) {
2618
2687
  doTypeImports.push("WhereInput");
@@ -2705,6 +2774,7 @@ const LUNORA_RLS_READ_REGISTRY = buildRlsReadRegistry(Object.values(LUNORA_FUNCT
2705
2774
  ...accessFragments.importLines,
2706
2775
  ...kvFragments.importLines,
2707
2776
  ...flagsFragments.importLines,
2777
+ ...notifyFragments.importLines,
2708
2778
  ...envFragments.importLines,
2709
2779
  ...analyticsFragments.importLines,
2710
2780
  ...imagesFragments.importLines,
@@ -2987,10 +3057,8 @@ const sourcePollAtCache = new WeakMap<object, Map<string, number>>();
2987
3057
  return nextDueAt;
2988
3058
  }
2989
3059
  ` : "";
2990
- const sourceConstructorOverride = hasSourcedTables ? `
2991
- public constructor(state: ShardDOState, env: unknown) {
2992
- super(state, env);
2993
-
3060
+ const hasTtlTables = ttlSweeps.length > 0;
3061
+ const sourceBootstrap = hasSourcedTables ? `
2994
3062
  const autoSourced = Object.values((schema as unknown as SchemaLike).tables).some((definition) => {
2995
3063
  const source = (definition as { externalSource?: ExternalSourceLike }).externalSource;
2996
3064
 
@@ -3000,7 +3068,16 @@ const sourcePollAtCache = new WeakMap<object, Map<string, number>>();
3000
3068
  if (autoSourced) {
3001
3069
  void this.scheduleSourcePoll();
3002
3070
  }
3003
- }
3071
+ ` : "";
3072
+ const ttlBootstrap = hasTtlTables ? `
3073
+ if (LUNORA_TTL_SWEEPS.length > 0) {
3074
+ void this.scheduleTtlSweep();
3075
+ }
3076
+ ` : "";
3077
+ const sourceConstructorOverride = hasSourcedTables || hasTtlTables ? `
3078
+ public constructor(state: ShardDOState, env: unknown) {
3079
+ super(state, env);
3080
+ ${sourceBootstrap}${ttlBootstrap} }
3004
3081
  ` : "";
3005
3082
  const facadeBlock = hasTables ? `
3006
3083
  const facade = db as unknown as Record<string, ReturnType<typeof bindTableFacade>>;
@@ -3018,8 +3095,9 @@ ${schema.tables.map(
3018
3095
  const secrets = createSecrets(env);
3019
3096
  `;
3020
3097
  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}
3098
+ const everyContextField = `${accessFragments.contextField}${kvFragments.contextField}${flagsFragments.contextField}${notifyFragments.contextField}${analyticsFragments.contextField}${envFragments.contextField}
3022
3099
  secrets,`;
3100
+ const notifyBuild = notifyFragments.build;
3023
3101
  const actionOnlyHasAny = hasImages || hasHyperdrive || hasBrowser || hasR2sql || hasPipelines || hasX402;
3024
3102
  const actionOnlyBlock = actionOnlyHasAny ? `
3025
3103
  // ActionCtx-only helpers (external, non-deterministic I/O): constructed
@@ -3048,8 +3126,8 @@ interface FunctionReference {
3048
3126
  /** Foreign-key columns per table (\`v.id("target")\` fields) for the data browser. */
3049
3127
  const LUNORA_TABLE_REFS: Record<string, Record<string, string>> = ${JSON.stringify(tableReferences, void 0, 4)};
3050
3128
 
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)};
3129
+ /** Declared indexes per table (secondary, search, geo, rank, vector) for the schema viewer. */
3130
+ 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
3131
 
3054
3132
  /** Columns per table (typed, with PK/FK markers) for the studio's schema diagram, served via \`__lunora_admin__:describeTable\`. */
3055
3133
  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 +3135,9 @@ const LUNORA_TABLE_COLUMNS: Record<string, Array<{ isStorage?: boolean; name: st
3057
3135
  /** Storage-key columns per table (\`v.storage(...)\` fields) for the file browser's records↔files join. */
3058
3136
  const LUNORA_STORAGE_COLUMNS: Record<string, string[]> = ${JSON.stringify(storageColumns, void 0, 4)};
3059
3137
 
3138
+ /** Declarative TTL policies (\`.ttl(field, { after? })\`) the DO alarm sweep auto-expires rows for. */
3139
+ const LUNORA_TTL_SWEEPS: Array<{ after?: number; field: string; softDeleteField?: string; table: string }> = ${JSON.stringify(ttlSweeps, void 0, 4)};
3140
+
3060
3141
  /** Static schema advisories (computed by @lunora/advisor at codegen time) served via \`__lunora_admin__:getAdvisories\`. */
3061
3142
  const LUNORA_ADVISORIES: AdvisoryFinding[] = ${JSON.stringify(advisoryData, void 0, 4)};
3062
3143
 
@@ -3238,10 +3319,14 @@ ${customMutatorOverride}${shapeResolveOverride}${globalShapeReaderOverride}${ext
3238
3319
  return LUNORA_TABLE_REFS[table];
3239
3320
  }
3240
3321
 
3241
- protected override tableIndexes(table: string): Array<{ fields: string[]; name: string; type: "index" | "rank" | "search" | "vector"; unique?: boolean }> {
3322
+ protected override tableIndexes(table: string): Array<{ fields: string[]; name: string; type: "geo" | "index" | "rank" | "search" | "vector"; unique?: boolean }> {
3242
3323
  return LUNORA_TABLE_INDEXES[table] ?? [];
3243
3324
  }
3244
3325
 
3326
+ protected override ttlSweeps(): ReadonlyArray<{ after?: number; field: string; softDeleteField?: string; table: string }> {
3327
+ return LUNORA_TTL_SWEEPS;
3328
+ }
3329
+
3245
3330
  protected override tableColumns(table: string): Array<{ isStorage?: boolean; name: string; optional: boolean; pk?: boolean; ref?: string; type: string }> {
3246
3331
  return LUNORA_TABLE_COLUMNS[table] ?? [];
3247
3332
  }
@@ -3510,7 +3595,7 @@ ${facadeBlock}${paymentsBuild}
3510
3595
  // writing mutation's trace from the shared per-request field.
3511
3596
  const trace = this.makeTracer(logFunctionPath, observability, options.identity ? undefined : this.getCurrentTrace());
3512
3597
  const metrics = this.makeMetrics(logFunctionPath, observability);
3513
-
3598
+ ${notifyBuild}
3514
3599
  // \`ctx.now\`: the wall-clock instant (epoch ms) this function began,
3515
3600
  // captured ONCE so the whole handler body sees a single stable value.
3516
3601
  // Query/mutation handlers must be deterministic (they may be re-run on
@@ -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-DIbr7U31.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.53",
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.15",
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": {