@lunora/codegen 1.0.0-alpha.19 → 1.0.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -109,6 +109,36 @@ interface RelationIR {
109
109
  /** Target table name. */
110
110
  table: string;
111
111
  }
112
+ /**
113
+ * Statically-discovered `.source(...)` config (plan 077). Only the bits the
114
+ * advisor lints + DO wiring need are captured; `map`/`tenantBy` are functions and
115
+ * cannot be serialized, so their presence is recorded as `hasTenantBy` rather than
116
+ * the function itself.
117
+ */
118
+ interface ExternalSourceIR {
119
+ /** The wrangler Hyperdrive binding name. */
120
+ binding: string;
121
+ /** Whether a `columns` projection allow-list was given. */
122
+ columns?: ReadonlyArray<string>;
123
+ /** `true` when a `reconcileEveryMs` was given (the incremental-mode delete-visibility companion). */
124
+ hasReconcile?: boolean;
125
+ /** `true` when a `tenantBy` mapper was given — the tenant-isolation boundary the `external_source_unscoped` lint checks. */
126
+ hasTenantBy: boolean;
127
+ /** The `idColumn` literal, when given (defaults to `"id"` at runtime). */
128
+ idColumn?: string;
129
+ /** Delete-detection mode literal, when given (`"full-pull"` | `"incremental"`). */
130
+ mode?: string;
131
+ /** The membership query literal, when statically knowable. */
132
+ query?: string;
133
+ /**
134
+ * `true` when `.source(...)` was present but its argument was **not** a static
135
+ * object literal (e.g. `.source(buildConfig())`), so none of the fields above
136
+ * could be read. The source still exists — this flag lets `hasSourcedTables`
137
+ * (codegen) and the `external_source_*` lints treat it as a source that can't be
138
+ * verified, instead of mistaking it for no `.source()` at all.
139
+ */
140
+ unanalyzable?: boolean;
141
+ }
112
142
  interface TableIR {
113
143
  /**
114
144
  * `true` when the table chain carried `.externallyManaged()` — its rows are
@@ -118,6 +148,13 @@ interface TableIR {
118
148
  */
119
149
  externallyManaged?: boolean;
120
150
  /**
151
+ * Set when the chain carried `.source(...)` — the table is materialized from an
152
+ * external Hyperdrive-backed database by a system poll loop (plan 077). Carries
153
+ * the statically-knowable bits the advisor lints read; the functions (`map`,
154
+ * `tenantBy`) are not serialized, only their presence (`hasTenantBy`).
155
+ */
156
+ externalSource?: ExternalSourceIR;
157
+ /**
121
158
  * Storage backend for a `.global()` table: `"d1"` (default) or
122
159
  * `"hyperdrive"` (a Postgres/MySQL database via Cloudflare Hyperdrive). Only
123
160
  * meaningful when `shardMode === "global"`; absent for sharded/root tables.
package/dist/index.d.ts CHANGED
@@ -109,6 +109,36 @@ interface RelationIR {
109
109
  /** Target table name. */
110
110
  table: string;
111
111
  }
112
+ /**
113
+ * Statically-discovered `.source(...)` config (plan 077). Only the bits the
114
+ * advisor lints + DO wiring need are captured; `map`/`tenantBy` are functions and
115
+ * cannot be serialized, so their presence is recorded as `hasTenantBy` rather than
116
+ * the function itself.
117
+ */
118
+ interface ExternalSourceIR {
119
+ /** The wrangler Hyperdrive binding name. */
120
+ binding: string;
121
+ /** Whether a `columns` projection allow-list was given. */
122
+ columns?: ReadonlyArray<string>;
123
+ /** `true` when a `reconcileEveryMs` was given (the incremental-mode delete-visibility companion). */
124
+ hasReconcile?: boolean;
125
+ /** `true` when a `tenantBy` mapper was given — the tenant-isolation boundary the `external_source_unscoped` lint checks. */
126
+ hasTenantBy: boolean;
127
+ /** The `idColumn` literal, when given (defaults to `"id"` at runtime). */
128
+ idColumn?: string;
129
+ /** Delete-detection mode literal, when given (`"full-pull"` | `"incremental"`). */
130
+ mode?: string;
131
+ /** The membership query literal, when statically knowable. */
132
+ query?: string;
133
+ /**
134
+ * `true` when `.source(...)` was present but its argument was **not** a static
135
+ * object literal (e.g. `.source(buildConfig())`), so none of the fields above
136
+ * could be read. The source still exists — this flag lets `hasSourcedTables`
137
+ * (codegen) and the `external_source_*` lints treat it as a source that can't be
138
+ * verified, instead of mistaking it for no `.source()` at all.
139
+ */
140
+ unanalyzable?: boolean;
141
+ }
112
142
  interface TableIR {
113
143
  /**
114
144
  * `true` when the table chain carried `.externallyManaged()` — its rows are
@@ -118,6 +148,13 @@ interface TableIR {
118
148
  */
119
149
  externallyManaged?: boolean;
120
150
  /**
151
+ * Set when the chain carried `.source(...)` — the table is materialized from an
152
+ * external Hyperdrive-backed database by a system poll loop (plan 077). Carries
153
+ * the statically-knowable bits the advisor lints read; the functions (`map`,
154
+ * `tenantBy`) are not serialized, only their presence (`hasTenantBy`).
155
+ */
156
+ externalSource?: ExternalSourceIR;
157
+ /**
121
158
  * Storage backend for a `.global()` table: `"d1"` (default) or
122
159
  * `"hyperdrive"` (a Postgres/MySQL database via Cloudflare Hyperdrive). Only
123
160
  * meaningful when `shardMode === "global"`; absent for sharded/root tables.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { formatAdvisories, lintSchema } from './packem_shared/formatAdvisories-LM8-Ixjw.mjs';
1
+ export { formatAdvisories, lintSchema } from './packem_shared/formatAdvisories-DdjK7sgh.mjs';
2
2
  export { CodegenDiagnosticError, diagnosticAt } from './packem_shared/CodegenDiagnosticError-DeblMkzO.mjs';
3
3
  export { default as discoverAuthApiCalls } from './packem_shared/discoverAuthApiCalls-CoirYbg6.mjs';
4
4
  export { CONTAINERS_FILENAME, discoverContainers } from './packem_shared/CONTAINERS_FILENAME-DlP6YM_Q.mjs';
@@ -15,15 +15,15 @@ export { default as discoverQueries } from './packem_shared/discoverQueries-B0wG
15
15
  export { QUEUES_FILENAME, discoverQueues } from './packem_shared/QUEUES_FILENAME-B5_eWCRe.mjs';
16
16
  export { default as discoverR2sqlCalls } from './packem_shared/discoverR2sqlCalls-BVNMd428.mjs';
17
17
  export { discoverRlsMetadata, default as discoverRlsProcedures } from './packem_shared/discoverRlsMetadata-BS9GOGC5.mjs';
18
- export { default as discoverSchema } from './packem_shared/discoverSchema-BnWHHJ4T.mjs';
18
+ export { default as discoverSchema } from './packem_shared/discoverSchema-KYq55qu3.mjs';
19
19
  export { SHAPES_FILENAME, discoverShapes } from './packem_shared/SHAPES_FILENAME-ChV7MqgE.mjs';
20
20
  export { default as discoverStorageRulesMetadata } from './packem_shared/discoverStorageRulesMetadata-Da8BKXcI.mjs';
21
21
  export { WORKFLOWS_FILENAME, discoverWorkflows } from './packem_shared/WORKFLOWS_FILENAME-CCisG0Vy.mjs';
22
- export { GENERATED_HEADER, emitApi, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers } from './packem_shared/GENERATED_HEADER-D-AbhmRh.mjs';
23
- export { emitApp } from './packem_shared/emitApp-CV81L74c.mjs';
24
- export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-DoK7_x1g.mjs';
25
- export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-DDT4mIzD.mjs';
26
- export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-BZH-O4uh.mjs';
22
+ export { GENERATED_HEADER, emitApi, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers } from './packem_shared/GENERATED_HEADER-CI272wRm.mjs';
23
+ export { emitApp } from './packem_shared/emitApp-cOSypPuO.mjs';
24
+ export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-Dx5QftUn.mjs';
25
+ export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-t5pV2NIN.mjs';
26
+ export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-BMez-jgr.mjs';
27
27
  export { SCHEMA_SNAPSHOT_VERSION, SchemaSnapshotParseError, buildSchemaSnapshot, diffSchemaSnapshots, evaluateSchemaDrift, parseSchemaSnapshot, serializeSchemaSnapshot } from './packem_shared/SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs';
28
28
  export { schemaFromIr } from './packem_shared/schemaFromIr-DTYsLBaA.mjs';
29
29
  export { LUNORA_ERROR_CODES, validatorIrToJsonSchema } from './packem_shared/LUNORA_ERROR_CODES-CySpQPD3.mjs';
@@ -2049,6 +2049,7 @@ const emitShard = ({
2049
2049
  const hasGlobalTables = schema.tables.some((table) => table.shardMode === "global");
2050
2050
  const hasHyperdriveGlobal = schema.tables.some((table) => table.shardMode === "global" && table.globalBackend === "hyperdrive");
2051
2051
  const hasD1Global = schema.tables.some((table) => table.shardMode === "global" && table.globalBackend !== "hyperdrive");
2052
+ const hasSourcedTables = schema.tables.some((table) => table.externalSource !== void 0);
2052
2053
  if (hasD1Global && hasHyperdriveGlobal) {
2053
2054
  throw new Error(
2054
2055
  'lunora codegen: mixing `.global()` (D1) and `.global({ backend: "hyperdrive" })` tables in one app is not supported yet — use a single global backend.'
@@ -2125,7 +2126,8 @@ const LUNORA_RLS_READ_REGISTRY = buildRlsReadRegistry(Object.values(LUNORA_FUNCT
2125
2126
  const shapeGuardImport = hasShapes ? "assertShapeShardable, " : "";
2126
2127
  const importLines = [
2127
2128
  `import type { ${doTypeImports.join(", ")} } from "${base.do}";`,
2128
- `import { applyCdcChanges, ${shapeGuardImport}createShardCtxDb, runDataMigration, runShardMigrations, ${relationFanout.importFragment}ShardDO as ShardDOBase } from "${base.do}";`,
2129
+ `import { applyCdcChanges, ${shapeGuardImport}createShardCtxDb, ${hasSourcedTables ? "isSourceDue, pullExternalSourceTick, " : ""}runDataMigration, runShardMigrations, ${relationFanout.importFragment}ShardDO as ShardDOBase } from "${base.do}";`,
2130
+ ...hasSourcedTables ? [`import type { ExternalSourceLike, SourceClientLike } from "${base.do}";`] : [],
2129
2131
  // `asBucketStorage` (the bucket-aware `ctx.storage` wrapper) and
2130
2132
  // `createSecrets` (the `ctx.secrets` core built-in) live in
2131
2133
  // `@lunora/server`, the single source — imported here rather than stamped
@@ -2172,6 +2174,8 @@ const LUNORA_RLS_READ_REGISTRY = buildRlsReadRegistry(Object.values(LUNORA_FUNCT
2172
2174
  d1?: (env: Record<string, unknown>, request?: { identity?: Record<string, unknown>; userId?: string }) => DatabaseWriterLike | undefined;` : "";
2173
2175
  const hyperdriveGlobalConfigField = hasHyperdriveGlobal ? `
2174
2176
  hyperdriveGlobal?: (env: Record<string, unknown>, request?: { identity?: Record<string, unknown>; userId?: string }) => DatabaseWriterLike | undefined;` : "";
2177
+ const sourceClientConfigField = hasSourcedTables ? `
2178
+ sourceClient?: (env: Record<string, unknown>, binding: string) => { query: <Row = Record<string, unknown>>(text: string, params?: readonly unknown[]) => Promise<Row[]> } | undefined;` : "";
2175
2179
  const globalDatabaseStub = hasGlobalTables ? `
2176
2180
  const globalDbStub: DatabaseWriterLike = {
2177
2181
  aggregate: async () => {
@@ -2320,6 +2324,113 @@ const vectorsStub: VectorSearchLike = {
2320
2324
 
2321
2325
  return rows;
2322
2326
  }
2327
+ ` : "";
2328
+ const sourceClientCacheConst = hasSourcedTables ? `
2329
+ const sourceClientCache = new WeakMap<object, Map<string, SourceClientLike>>();
2330
+ const sourcePollAtCache = new WeakMap<object, Map<string, number>>();
2331
+ ` : "";
2332
+ const externalSourceOverride = hasSourcedTables ? `
2333
+ protected override async pollExternalSources(): Promise<number> {
2334
+ const env = (this.env ?? {}) as Record<string, unknown>;
2335
+ const sourced = Object.entries((schema as unknown as SchemaLike).tables)
2336
+ .map(([table, definition]) => [table, (definition as { externalSource?: ExternalSourceLike }).externalSource] as const)
2337
+ .filter((entry): entry is [string, ExternalSourceLike] => entry[1] !== undefined);
2338
+
2339
+ if (sourced.length === 0) {
2340
+ return 0;
2341
+ }
2342
+
2343
+ const shardKey = this.currentShardKey();
2344
+ const scheduler = (config.scheduler?.(env) ?? schedulerStub) as SchedulerLike;
2345
+ const writer = createShardCtxDb({
2346
+ broadcast: (delta) => {
2347
+ this.recordChangedTable(delta.table);
2348
+ },
2349
+ cdc: config.cdc ?? false,
2350
+ scheduler,
2351
+ schema: schema as unknown as SchemaLike,
2352
+ sql: this.sql as SqlExec,
2353
+ });
2354
+
2355
+ let clients = sourceClientCache.get(this);
2356
+
2357
+ if (clients === undefined) {
2358
+ clients = new Map();
2359
+ sourceClientCache.set(this, clients);
2360
+ }
2361
+
2362
+ let polledAt = sourcePollAtCache.get(this);
2363
+
2364
+ if (polledAt === undefined) {
2365
+ polledAt = new Map();
2366
+ sourcePollAtCache.set(this, polledAt);
2367
+ }
2368
+
2369
+ const now = Date.now();
2370
+ // \`active\` counts non-manual sources: while > 0 the alarm re-arms; a
2371
+ // manual-only schema returns 0 so the shared alarm goes idle.
2372
+ let active = 0;
2373
+
2374
+ for (const [table, source] of sourced) {
2375
+ if (source.refresh === "manual") {
2376
+ continue;
2377
+ }
2378
+
2379
+ active += 1;
2380
+
2381
+ if (!isSourceDue(source.refresh, polledAt.get(table), now)) {
2382
+ continue;
2383
+ }
2384
+
2385
+ try {
2386
+ let client = clients.get(source.binding);
2387
+
2388
+ if (client === undefined) {
2389
+ client = config.sourceClient?.(env, source.binding);
2390
+
2391
+ if (client !== undefined) {
2392
+ clients.set(source.binding, client);
2393
+ }
2394
+ }
2395
+
2396
+ if (client === undefined) {
2397
+ // No SqlClient resolved for this binding (host never wired
2398
+ // \`config.sourceClient\`, or wired it wrong). Surface it in the
2399
+ // Logs panel and stamp \`polledAt\` so a persistent misconfig backs
2400
+ // off to \`refresh.everyMs\` instead of retrying every alarm tick.
2401
+ this.recordExternalSourceError(table, new Error(\`external-source: no sourceClient resolved for binding "\${source.binding}"\`));
2402
+ polledAt.set(table, now);
2403
+ continue;
2404
+ }
2405
+
2406
+ // eslint-disable-next-line no-await-in-loop -- one sourced table at a time; slices are independent but small and sequential keeps the writer transaction simple
2407
+ await pullExternalSourceTick(this.sql as SqlExec, writer, client, table, source, shardKey);
2408
+ polledAt.set(table, now);
2409
+ } catch (error) {
2410
+ this.recordExternalSourceError(table, error);
2411
+ // Stamp on failure too, so a persistently failing source throttles
2412
+ // to \`refresh.everyMs\` rather than being hammered every tick.
2413
+ polledAt.set(table, now);
2414
+ }
2415
+ }
2416
+
2417
+ return active;
2418
+ }
2419
+ ` : "";
2420
+ const sourceConstructorOverride = hasSourcedTables ? `
2421
+ public constructor(state: ShardDOState, env: unknown) {
2422
+ super(state, env);
2423
+
2424
+ const autoSourced = Object.values((schema as unknown as SchemaLike).tables).some((definition) => {
2425
+ const source = (definition as { externalSource?: ExternalSourceLike }).externalSource;
2426
+
2427
+ return source !== undefined && source.refresh !== "manual";
2428
+ });
2429
+
2430
+ if (autoSourced) {
2431
+ void this.scheduleSourcePoll();
2432
+ }
2433
+ }
2323
2434
  ` : "";
2324
2435
  const facadeBlock = hasTables ? `
2325
2436
  const facade = db as unknown as Record<string, ReturnType<typeof bindTableFacade>>;
@@ -2396,7 +2507,7 @@ export interface ShardDOConfig {
2396
2507
  /** Optional telemetry sink. When supplied, each \`ctx.log.*\` call is forwarded to \`sink.onLog\`. Pass the SAME sink you give \`createWorker({ observability })\` (which drives \`onRpc\`) to route both RPC and log events. */
2397
2508
  observability?: (env: Record<string, unknown>) => LogSink | undefined;
2398
2509
  scheduler?: (env: Record<string, unknown>) => unknown;
2399
- storage?: (env: Record<string, unknown>) => unknown;${vectorsConfigField}${aiConfigField}${kvFragments.configField}${flagsFragments.configField}${analyticsFragments.configField}${imagesFragments.configField}${hyperdriveFragments.configField}${browserFragments.configField}${r2sqlFragments.configField}${pipelinesFragments.configField}${paymentsConfigField}${d1ConfigField}${hyperdriveGlobalConfigField}
2510
+ storage?: (env: Record<string, unknown>) => unknown;${vectorsConfigField}${aiConfigField}${kvFragments.configField}${flagsFragments.configField}${analyticsFragments.configField}${imagesFragments.configField}${hyperdriveFragments.configField}${browserFragments.configField}${r2sqlFragments.configField}${pipelinesFragments.configField}${paymentsConfigField}${d1ConfigField}${hyperdriveGlobalConfigField}${sourceClientConfigField}
2400
2511
  }
2401
2512
 
2402
2513
  const schedulerStub = {
@@ -2434,7 +2545,7 @@ const storageStub = {
2434
2545
  throw new Error("ctx.storage: no storage configured. Pass \`storage\` to createShardDO().");
2435
2546
  },
2436
2547
  };
2437
- ${globalDatabaseStub}${vectorsStub}${aiStub}${kvFragments.stub}${flagsFragments.stub}${analyticsFragments.stub}${imagesFragments.stub}${hyperdriveFragments.stub}${browserFragments.stub}${r2sqlFragments.stub}${pipelinesFragments.stub}${paymentStub}${bindTableHelper}
2548
+ ${globalDatabaseStub}${sourceClientCacheConst}${vectorsStub}${aiStub}${kvFragments.stub}${flagsFragments.stub}${analyticsFragments.stub}${imagesFragments.stub}${hyperdriveFragments.stub}${browserFragments.stub}${r2sqlFragments.stub}${pipelinesFragments.stub}${paymentStub}${bindTableHelper}
2438
2549
  // Bound in-process \`ctx.run*\` composition depth so a self- or cyclically-
2439
2550
  // referencing call fails loudly with a clear error instead of overflowing the
2440
2551
  // stack. Tracked across the awaited handler chain (one DO invocation is
@@ -2475,7 +2586,7 @@ const dispatchRun = async (expected: FunctionKind, functionPath: string, args: R
2475
2586
  * from the worker entry so wrangler binds it by name.
2476
2587
  */
2477
2588
  export const createShardDO = (config: ShardDOConfig = {}): new (state: ShardDOState, env: unknown) => ShardDOBase =>
2478
- class extends ShardDOBase {
2589
+ class extends ShardDOBase {${sourceConstructorOverride}
2479
2590
  private migrated = false;
2480
2591
 
2481
2592
  public override async handleRpc(functionPath: string, args: Record<string, unknown>): Promise<unknown> {
@@ -2555,7 +2666,7 @@ ${relationFanout.override}
2555
2666
  iterator: (signal) => (registered.handler as (context: unknown, args: Record<string, unknown>, signal: AbortSignal) => AsyncIterable<unknown>)(this.buildCtx({ functionPath }), args, signal),
2556
2667
  };
2557
2668
  }
2558
- ${customMutatorOverride}${shapeResolveOverride}${globalShapeReaderOverride}
2669
+ ${customMutatorOverride}${shapeResolveOverride}${globalShapeReaderOverride}${externalSourceOverride}
2559
2670
  protected override lifecycleHookPaths(event: "connect" | "disconnect"): readonly string[] {
2560
2671
  return LUNORA_LIFECYCLE_HOOKS[event];
2561
2672
  }
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-D-AbhmRh.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-CI272wRm.mjs';
2
2
  import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
3
3
  import { argsObjectSchema, LUNORA_ERROR_CODES } from './LUNORA_ERROR_CODES-CySpQPD3.mjs';
4
4
 
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
3
  import { performance } from 'node:perf_hooks';
4
4
  import { Node, SyntaxKind, Project } from 'ts-morph';
5
- import { lintSchema } from './formatAdvisories-LM8-Ixjw.mjs';
5
+ import { lintSchema } from './formatAdvisories-DdjK7sgh.mjs';
6
6
  import { listLunoraSourceFiles, lunoraRelativePath, classifyProcedureCall, discoverFunctions } from './discoverFunctions-BWMczzBx.mjs';
7
7
  import discoverAuthApiCalls from './discoverAuthApiCalls-CoirYbg6.mjs';
8
8
  import { discoverContainers } from './CONTAINERS_FILENAME-DlP6YM_Q.mjs';
@@ -18,15 +18,15 @@ import discoverQueries from './discoverQueries-B0wGT-xe.mjs';
18
18
  import { discoverQueues } from './QUEUES_FILENAME-B5_eWCRe.mjs';
19
19
  import discoverR2sqlCalls from './discoverR2sqlCalls-BVNMd428.mjs';
20
20
  import discoverRlsProcedures, { discoverRlsMetadata } from './discoverRlsMetadata-BS9GOGC5.mjs';
21
- import discoverSchema from './discoverSchema-BnWHHJ4T.mjs';
21
+ import discoverSchema from './discoverSchema-KYq55qu3.mjs';
22
22
  import { discoverShapes } from './SHAPES_FILENAME-ChV7MqgE.mjs';
23
23
  import discoverStorageRulesMetadata from './discoverStorageRulesMetadata-Da8BKXcI.mjs';
24
24
  import { e as enclosingExportName$1 } from './discover-ast-CT6BgBr4.mjs';
25
25
  import { discoverWorkflows } from './WORKFLOWS_FILENAME-CCisG0Vy.mjs';
26
- import { buildStorageColumns, emitDataModel, emitApi, emitServer, emitFunctions, emitShard, emitCollections, emitContainers, emitWorkflows, emitQueues, emitCrons, emitVectors, emitDrizzleSchema, emitSeed, emitWranglerCronTriggers } from './GENERATED_HEADER-D-AbhmRh.mjs';
27
- import { emitApp } from './emitApp-CV81L74c.mjs';
28
- import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-DoK7_x1g.mjs';
29
- import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-DDT4mIzD.mjs';
26
+ import { buildStorageColumns, emitDataModel, emitApi, emitServer, emitFunctions, emitShard, emitCollections, emitContainers, emitWorkflows, emitQueues, emitCrons, emitVectors, emitDrizzleSchema, emitSeed, emitWranglerCronTriggers } from './GENERATED_HEADER-CI272wRm.mjs';
27
+ import { emitApp } from './emitApp-cOSypPuO.mjs';
28
+ import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-Dx5QftUn.mjs';
29
+ import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-t5pV2NIN.mjs';
30
30
  import { buildSchemaSnapshot, serializeSchemaSnapshot } from './SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs';
31
31
 
32
32
  const HTTP_VERBS = /* @__PURE__ */ new Set(["delete", "get", "head", "options", "patch", "post", "put"]);
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-D-AbhmRh.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-CI272wRm.mjs';
2
2
  import { s as sanitizeNamespace } from './paths-BRd6JHuF.mjs';
3
3
  import { LUNORA_ERROR_CODES, objectSchema, argsObjectSchema, validatorIrToJsonSchema } from './LUNORA_ERROR_CODES-CySpQPD3.mjs';
4
4
 
@@ -397,6 +397,42 @@ const softDeleteFieldOf = (optionsArgument) => {
397
397
  }
398
398
  return "deletedAt";
399
399
  };
400
+ const stringPropertyOf = (object, property) => {
401
+ const node = object.getProperty(property);
402
+ if (node && Node.isPropertyAssignment(node)) {
403
+ const initializer = node.getInitializer();
404
+ if (initializer && Node.isStringLiteral(initializer)) {
405
+ return initializer.getLiteralText();
406
+ }
407
+ }
408
+ return void 0;
409
+ };
410
+ const stringArrayPropertyOf = (object, property) => {
411
+ const node = object.getProperty(property);
412
+ if (node && Node.isPropertyAssignment(node)) {
413
+ const initializer = node.getInitializer();
414
+ if (initializer && Node.isArrayLiteralExpression(initializer)) {
415
+ const items = initializer.getElements().filter((element) => Node.isStringLiteral(element)).map((element) => element.getLiteralText());
416
+ return items.length > 0 ? items : void 0;
417
+ }
418
+ }
419
+ return void 0;
420
+ };
421
+ const parseSourceCall = (args) => {
422
+ const first = args[0];
423
+ if (!first || !Node.isObjectLiteralExpression(first)) {
424
+ return { binding: "", hasTenantBy: false, unanalyzable: true };
425
+ }
426
+ return {
427
+ binding: stringPropertyOf(first, "binding") ?? "",
428
+ columns: stringArrayPropertyOf(first, "columns"),
429
+ hasReconcile: first.getProperty("reconcileEveryMs") !== void 0,
430
+ hasTenantBy: first.getProperty("tenantBy") !== void 0,
431
+ idColumn: stringPropertyOf(first, "idColumn"),
432
+ mode: stringPropertyOf(first, "mode"),
433
+ query: stringPropertyOf(first, "query")
434
+ };
435
+ };
400
436
  const applyTableMethod = (accumulator, method, args, name) => {
401
437
  switch (method) {
402
438
  case "externallyManaged": {
@@ -436,6 +472,11 @@ const applyTableMethod = (accumulator, method, args, name) => {
436
472
  accumulator.softDelete = { field: softDeleteFieldOf(args[0]) };
437
473
  break;
438
474
  }
475
+ case "source": {
476
+ accumulator.externalSource = parseSourceCall(args);
477
+ accumulator.externallyManaged = true;
478
+ break;
479
+ }
439
480
  case "vectorize": {
440
481
  const vectorIndex = parseVectorizeCall(args, name);
441
482
  if (vectorIndex) {
@@ -478,6 +519,7 @@ const parseTableBuilder = (expression, name) => {
478
519
  }
479
520
  return {
480
521
  externallyManaged: accumulator.externallyManaged,
522
+ externalSource: accumulator.externalSource,
481
523
  globalBackend: accumulator.shardMode === "global" ? accumulator.globalBackend ?? "d1" : void 0,
482
524
  indexes: accumulator.indexes,
483
525
  name,
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-D-AbhmRh.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-CI272wRm.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`)."],
@@ -19,6 +19,12 @@ const toAdvisorSchema = (schema) => {
19
19
  tables: schema.tables.map((table) => {
20
20
  return {
21
21
  externallyManaged: table.externallyManaged ?? false,
22
+ externalSource: table.externalSource ? {
23
+ hasReconcile: table.externalSource.hasReconcile ?? false,
24
+ hasTenantBy: table.externalSource.hasTenantBy,
25
+ mode: table.externalSource.mode,
26
+ unanalyzable: table.externalSource.unanalyzable
27
+ } : void 0,
22
28
  fields: Object.keys(table.shape),
23
29
  indexes: flattenIndexes(table),
24
30
  name: table.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/codegen",
3
- "version": "1.0.0-alpha.19",
3
+ "version": "1.0.0-alpha.20",
4
4
  "description": "Code generator for Lunora: emits _generated/{api,server,dataModel}.ts from your schema",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,7 +46,7 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/advisor": "1.0.0-alpha.9",
49
+ "@lunora/advisor": "1.0.0-alpha.10",
50
50
  "@lunora/container": "1.0.0-alpha.5",
51
51
  "@lunora/queue": "1.0.0-alpha.1",
52
52
  "@lunora/scheduler": "1.0.0-alpha.3",