@lunora/codegen 1.0.0-alpha.15 → 1.0.0-alpha.17

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
@@ -201,6 +201,59 @@ interface MigrationIR {
201
201
  table: string;
202
202
  }
203
203
  /**
204
+ * A `defineShape({...})` declaration discovered in `lunora/shapes.ts`
205
+ * (local-first sync engine, Phase 7). The emitted `LUNORA_SHAPES` registry keys
206
+ * on {@link ShapeIR.exportName}; the generated DO's `resolveShape` override
207
+ * dispatches a `shape_subscribe` to the matching registered shape. Discovery is
208
+ * marker-driven (the `__lunoraShape` brand) — no field metadata is lifted here
209
+ * because the runtime object (`columns`/`compileWhere`) carries the authority.
210
+ */
211
+ interface ShapeIR {
212
+ /** Export binding name — the shape's registry key and import member. */
213
+ exportName: string;
214
+ /** Path relative to `<projectRoot>/lunora/` without extension — always `"shapes"`. */
215
+ filePath: string;
216
+ /**
217
+ * The `table` string literal from the `defineShape({ table })` call, lifted
218
+ * only for static advisor lints (the runtime object stays authoritative).
219
+ * `undefined` when `table` is not a plain string literal — lints skip those.
220
+ */
221
+ table?: string;
222
+ }
223
+ /**
224
+ * A `defineMutator({...})` declaration discovered in `lunora/mutators.ts`
225
+ * (local-first sync engine, Phase 7). The emitted registry registers the
226
+ * authoritative `server` impl into the DO's `LUNORA_FUNCTIONS` table (so
227
+ * `handleRpc` transaction-wraps it) and records its path in
228
+ * `LUNORA_MUTATOR_PATHS` so the DO's `isCustomMutator` override routes the
229
+ * client-watermark push protocol. The client `client` impl is split into the
230
+ * browser bundle separately — only the path crosses to the server side.
231
+ */
232
+ interface MutatorIR {
233
+ /** Export binding name — the mutator's registry key and import member. */
234
+ exportName: string;
235
+ /** Path relative to `<projectRoot>/lunora/` without extension — always `"mutators"`. */
236
+ filePath: string;
237
+ }
238
+ /**
239
+ * A whole-row `ctx.db.replace(id, document)` write discovered inside a custom
240
+ * mutator's inline `server` impl (`lunora/mutators.ts`) — the input the
241
+ * `mutator_full_row_replace` advisor lint consumes. A `replace` overwrites the
242
+ * entire row, so a concurrent edit to a different column on a synced table is
243
+ * clobbered; `ctx.db.patch(id, { field })` merges at the column level instead.
244
+ * Structurally identical to `AdvisorMutatorWrite` so it passes straight through
245
+ * to the advisor without conversion, exactly as `InsertWriteIR` does for
246
+ * `AdvisorInsertWrite`.
247
+ */
248
+ interface MutatorWriteIR {
249
+ /** The mutator export whose `server` impl performs the replace, e.g. `renameChannel`. */
250
+ exportName: string;
251
+ /** Openable source path the replace appears in — always `lunora/mutators.ts`. */
252
+ file: string;
253
+ /** 1-based line of the `replace(...)` call. */
254
+ line: number;
255
+ }
256
+ /**
204
257
  * A single cron job lifted from a `cronJobs()` builder in `lunora/crons.ts`.
205
258
  * Mirrors `@lunora/scheduler`'s `CronJob`: {@link CronJobIR.cron} is the compiled
206
259
  * standard cron expression, {@link CronJobIR.functionPath} is the target
@@ -783,7 +836,7 @@ interface ProjectIR {
783
836
  * pass straight through without conversion. Returns the findings; surfacing them
784
837
  * (console, error overlay, studio Advisors table) is the caller's choice.
785
838
  */
786
- declare const lintSchema: (schema: SchemaIR, queries?: ReadonlyArray<QueryReadIR>, inserts?: ReadonlyArray<InsertWriteIR>, authApiCalls?: ReadonlyArray<AuthApiCallIR>, rlsProcedures?: ReadonlyArray<RlsProcedureIR>, containers?: ReadonlyArray<ContainerIR>, workflows?: ReadonlyArray<WorkflowIR>, workflowCalls?: ReadonlyArray<WorkflowCallIR>, maskProcedures?: ReadonlyArray<MaskProcedureIR>, nondeterministicCalls?: ReadonlyArray<NondeterministicCallIR>, procedureProtections?: ReadonlyArray<ProcedureMiddlewareIR>, argumentValidators?: ReadonlyArray<ArgumentValidatorIR>, secretLiterals?: ReadonlyArray<SecretLiteralIR>, sqlInterpolations?: ReadonlyArray<SqlInterpolationIR>, adminRoutes?: ReadonlyArray<AdminRouteIR>, r2sqlCalls?: ReadonlyArray<R2sqlCallIR>) => Finding[];
839
+ declare const lintSchema: (schema: SchemaIR, queries?: ReadonlyArray<QueryReadIR>, inserts?: ReadonlyArray<InsertWriteIR>, authApiCalls?: ReadonlyArray<AuthApiCallIR>, rlsProcedures?: ReadonlyArray<RlsProcedureIR>, containers?: ReadonlyArray<ContainerIR>, workflows?: ReadonlyArray<WorkflowIR>, workflowCalls?: ReadonlyArray<WorkflowCallIR>, maskProcedures?: ReadonlyArray<MaskProcedureIR>, nondeterministicCalls?: ReadonlyArray<NondeterministicCallIR>, procedureProtections?: ReadonlyArray<ProcedureMiddlewareIR>, argumentValidators?: ReadonlyArray<ArgumentValidatorIR>, secretLiterals?: ReadonlyArray<SecretLiteralIR>, sqlInterpolations?: ReadonlyArray<SqlInterpolationIR>, adminRoutes?: ReadonlyArray<AdminRouteIR>, r2sqlCalls?: ReadonlyArray<R2sqlCallIR>, shapes?: ReadonlyArray<ShapeIR>, mutatorWrites?: ReadonlyArray<MutatorWriteIR>) => Finding[];
787
840
  /**
788
841
  * Render advisor findings as a single multi-line string for console surfacing:
789
842
  * a one-line summary header followed by one `[LEVEL] name: detail` line per
@@ -898,6 +951,23 @@ declare const discoverMaskProcedures: (project: Project, lunoraDirectory: string
898
951
  * key); `table` is best-effort and left `""` when not a literal.
899
952
  */
900
953
  declare const discoverMigrations: (project: Project, lunoraDirectory: string) => MigrationIR[];
954
+ /** The only file custom mutators may be declared in — mirrors `lunora/queues.ts`. */
955
+ declare const MUTATORS_FILENAME = "mutators.ts";
956
+ /**
957
+ * Decide whether a call's callee is `defineMutator` — either the bare imported
958
+ * identifier (`defineMutator(...)`) or a namespace member access
959
+ * (`server.defineMutator(...)`). Both are valid ES module syntax, so discovery
960
+ * must see mutators declared either way.
961
+ */
962
+
963
+ /**
964
+ * Discover every custom mutator the project declares: exported
965
+ * `defineMutator()` calls in `lunora/mutators.ts`. Returns `[]` when the file
966
+ * doesn't exist. Only the export binding is lifted — the runtime object carries
967
+ * the authoritative `server` impl + `handler`, so codegen never evaluates the
968
+ * body. The client `client` impl is split into the browser bundle separately.
969
+ */
970
+ declare const discoverMutators: (project: Project, lunoraDirectory: string) => MutatorIR[];
901
971
  /**
902
972
  * Discover non-deterministic API calls (`Date.now`, `new Date()`, `Date()`,
903
973
  * `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues` — including
@@ -963,6 +1033,16 @@ declare const discoverRlsMetadata: (project: Project, lunoraDirectory: string) =
963
1033
  * return a structural IR. Throws if the file or call cannot be found.
964
1034
  */
965
1035
  declare const discoverSchema: (project: Project, schemaPath: string, projectRoot?: string) => SchemaIR;
1036
+ /** The only file shapes may be declared in — mirrors `lunora/queues.ts`. */
1037
+ declare const SHAPES_FILENAME = "shapes.ts";
1038
+ /**
1039
+ * Discover every replication shape the project declares: exported
1040
+ * `defineShape()` calls in `lunora/shapes.ts`. Returns `[]` when the file
1041
+ * doesn't exist. Only the export binding is lifted — the runtime object carries
1042
+ * the authoritative `table`/`columns`/`compileWhere`, so codegen never
1043
+ * evaluates the predicate.
1044
+ */
1045
+ declare const discoverShapes: (project: Project, lunoraDirectory: string) => ShapeIR[];
966
1046
  /**
967
1047
  * Aggregate the schema-wide storage-rule metadata the studio's inspector reads:
968
1048
  * every statically-discovered `(bucket, on, prefix, procedure)` entry across all
@@ -1001,6 +1081,22 @@ declare const emitApi: (functions: ReadonlyArray<FunctionIR>, workflows?: Readon
1001
1081
  * Returns `""` when `@lunora/seed` is not a declared dependency, so projects
1002
1082
  * that don't use it keep a clean `_generated/` and never import the package.
1003
1083
  */
1084
+
1085
+ /**
1086
+ * Emit `_generated/collections.ts` — one TanStack DB collection factory per
1087
+ * `defineShape` in `lunora/shapes.ts` (the local-first partial-replication
1088
+ * surface). Each factory builds a live collection that syncs its shape's rowset
1089
+ * through the client's poke protocol (`subscribeShape`), so an app feeds the
1090
+ * result straight to `useLiveQuery`.
1091
+ *
1092
+ * Returns `""` (so `writeIfPresent` skips the file) unless the project both
1093
+ * declares shapes AND installs `@lunora/db` — the add-on that ships
1094
+ * `lunoraCollectionOptions`. `@lunora/db` stays a scoped install even under the
1095
+ * `lunorash` umbrella (an opt-in add-on, like `@lunora/auth`), so its import is
1096
+ * always `@lunora/db/collections`; only the in-umbrella `@lunora/client` import
1097
+ * is remapped to `lunorash/client`.
1098
+ */
1099
+ declare const emitCollections: (shapes: ReadonlyArray<ShapeIR>, hasDatabase: boolean, useUmbrella?: boolean) => string;
1004
1100
  interface EmitServerOptions {
1005
1101
  containers?: ReadonlyArray<ContainerIR>;
1006
1102
  hasAi?: boolean;
@@ -1047,7 +1143,7 @@ declare const emitServer: ({
1047
1143
  useUmbrella,
1048
1144
  workflows
1049
1145
  }?: EmitServerOptions) => string;
1050
- declare const emitFunctions: (functions: ReadonlyArray<FunctionIR>, migrations?: ReadonlyArray<MigrationIR>, useUmbrella?: boolean) => string;
1146
+ declare const emitFunctions: (functions: ReadonlyArray<FunctionIR>, migrations?: ReadonlyArray<MigrationIR>, useUmbrella?: boolean, mutators?: ReadonlyArray<MutatorIR>, shapes?: ReadonlyArray<ShapeIR>) => string;
1051
1147
  /**
1052
1148
  * Storage-column map per table for the file browser: `{ table: [field, …] }` for
1053
1149
  * every field declared `v.storage(...)` (unwrapping `v.optional(...)`). The
@@ -1110,10 +1206,14 @@ interface EmitShardOptions {
1110
1206
  /** A `lunora/` source reads `ctx.r2sql` (R2 SQL) — wires `ctx.r2sql` onto the ActionCtx only. */
1111
1207
  hasR2sql?: boolean;
1112
1208
  maskMetadata?: MaskMetadataIR;
1209
+ /** Custom mutators declared via `defineMutator` in `lunora/mutators.ts` — wires the `isCustomMutator` push-protocol override. */
1210
+ mutators?: ReadonlyArray<MutatorIR>;
1113
1211
  /** Queues declared via `defineQueue` exports in `lunora/queues.ts` — wires the typed `ctx.queues` producers. */
1114
1212
  queues?: ReadonlyArray<QueueIR>;
1115
1213
  rlsMetadata?: RlsMetadataIR;
1116
1214
  schema: SchemaIR;
1215
+ /** Replication shapes declared via `defineShape` in `lunora/shapes.ts` — wires the `resolveShape` subscription override. */
1216
+ shapes?: ReadonlyArray<ShapeIR>;
1117
1217
  storageRules?: StorageRulesMetadataIR;
1118
1218
  studioFeatures?: StudioFeaturesResult;
1119
1219
  /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
@@ -1135,9 +1235,11 @@ declare const emitShard: ({
1135
1235
  hasPipelines,
1136
1236
  hasR2sql,
1137
1237
  maskMetadata,
1238
+ mutators,
1138
1239
  queues,
1139
1240
  rlsMetadata,
1140
1241
  schema,
1242
+ shapes,
1141
1243
  storageRules,
1142
1244
  studioFeatures,
1143
1245
  useUmbrella,
@@ -1591,6 +1693,8 @@ interface CodegenResult {
1591
1693
  generated: {
1592
1694
  api: string; /** Fluent worker-composition builder (`_generated/app.ts`) — `defineApp()`. Always written. */
1593
1695
  app: string;
1696
+ /** Partial-replication collection factories (`_generated/collections.ts`); `""` (and not written) unless the project declares shapes and installs `@lunora/db`. */
1697
+ collections: string;
1594
1698
  /** Container DO classes (`_generated/containers.ts`); `""` (and not written) when no containers are declared. */
1595
1699
  containers: string;
1596
1700
  crons: string;
@@ -1725,4 +1829,4 @@ declare const LUNORA_SOLUTION_RULES: ReadonlyArray<LunoraSolutionRule>;
1725
1829
  */
1726
1830
  declare const findLunoraSolution: (message: string) => LunoraSolution | undefined;
1727
1831
  declare const VERSION = "0.0.0";
1728
- export { type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, LUNORA_SOLUTION_RULES, type LunoraSolution, type LunoraSolutionRule, type MaskProcedureIR, type MigrationIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverNondeterministicCalls, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSchema, discoverStorageRulesMetadata, discoverWorkflows, emitApi, emitApp, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, findLunoraSolution, formatAdvisories, lintSchema, parseSchemaSnapshot, refreshCodegenProject, runCodegen, schemaFromIr, serializeSchemaSnapshot, validatorIrToJsonSchema };
1832
+ export { type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, LUNORA_SOLUTION_RULES, type LunoraSolution, type LunoraSolutionRule, MUTATORS_FILENAME, type MaskProcedureIR, type MigrationIR, type MutatorIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, SHAPES_FILENAME, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type ShapeIR, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverMutators, discoverNondeterministicCalls, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSchema, discoverShapes, discoverStorageRulesMetadata, discoverWorkflows, emitApi, emitApp, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, findLunoraSolution, formatAdvisories, lintSchema, parseSchemaSnapshot, refreshCodegenProject, runCodegen, schemaFromIr, serializeSchemaSnapshot, validatorIrToJsonSchema };
package/dist/index.d.ts CHANGED
@@ -201,6 +201,59 @@ interface MigrationIR {
201
201
  table: string;
202
202
  }
203
203
  /**
204
+ * A `defineShape({...})` declaration discovered in `lunora/shapes.ts`
205
+ * (local-first sync engine, Phase 7). The emitted `LUNORA_SHAPES` registry keys
206
+ * on {@link ShapeIR.exportName}; the generated DO's `resolveShape` override
207
+ * dispatches a `shape_subscribe` to the matching registered shape. Discovery is
208
+ * marker-driven (the `__lunoraShape` brand) — no field metadata is lifted here
209
+ * because the runtime object (`columns`/`compileWhere`) carries the authority.
210
+ */
211
+ interface ShapeIR {
212
+ /** Export binding name — the shape's registry key and import member. */
213
+ exportName: string;
214
+ /** Path relative to `&lt;projectRoot>/lunora/` without extension — always `"shapes"`. */
215
+ filePath: string;
216
+ /**
217
+ * The `table` string literal from the `defineShape({ table })` call, lifted
218
+ * only for static advisor lints (the runtime object stays authoritative).
219
+ * `undefined` when `table` is not a plain string literal — lints skip those.
220
+ */
221
+ table?: string;
222
+ }
223
+ /**
224
+ * A `defineMutator({...})` declaration discovered in `lunora/mutators.ts`
225
+ * (local-first sync engine, Phase 7). The emitted registry registers the
226
+ * authoritative `server` impl into the DO's `LUNORA_FUNCTIONS` table (so
227
+ * `handleRpc` transaction-wraps it) and records its path in
228
+ * `LUNORA_MUTATOR_PATHS` so the DO's `isCustomMutator` override routes the
229
+ * client-watermark push protocol. The client `client` impl is split into the
230
+ * browser bundle separately — only the path crosses to the server side.
231
+ */
232
+ interface MutatorIR {
233
+ /** Export binding name — the mutator's registry key and import member. */
234
+ exportName: string;
235
+ /** Path relative to `&lt;projectRoot>/lunora/` without extension — always `"mutators"`. */
236
+ filePath: string;
237
+ }
238
+ /**
239
+ * A whole-row `ctx.db.replace(id, document)` write discovered inside a custom
240
+ * mutator's inline `server` impl (`lunora/mutators.ts`) — the input the
241
+ * `mutator_full_row_replace` advisor lint consumes. A `replace` overwrites the
242
+ * entire row, so a concurrent edit to a different column on a synced table is
243
+ * clobbered; `ctx.db.patch(id, { field })` merges at the column level instead.
244
+ * Structurally identical to `AdvisorMutatorWrite` so it passes straight through
245
+ * to the advisor without conversion, exactly as `InsertWriteIR` does for
246
+ * `AdvisorInsertWrite`.
247
+ */
248
+ interface MutatorWriteIR {
249
+ /** The mutator export whose `server` impl performs the replace, e.g. `renameChannel`. */
250
+ exportName: string;
251
+ /** Openable source path the replace appears in — always `lunora/mutators.ts`. */
252
+ file: string;
253
+ /** 1-based line of the `replace(...)` call. */
254
+ line: number;
255
+ }
256
+ /**
204
257
  * A single cron job lifted from a `cronJobs()` builder in `lunora/crons.ts`.
205
258
  * Mirrors `@lunora/scheduler`'s `CronJob`: {@link CronJobIR.cron} is the compiled
206
259
  * standard cron expression, {@link CronJobIR.functionPath} is the target
@@ -783,7 +836,7 @@ interface ProjectIR {
783
836
  * pass straight through without conversion. Returns the findings; surfacing them
784
837
  * (console, error overlay, studio Advisors table) is the caller's choice.
785
838
  */
786
- declare const lintSchema: (schema: SchemaIR, queries?: ReadonlyArray<QueryReadIR>, inserts?: ReadonlyArray<InsertWriteIR>, authApiCalls?: ReadonlyArray<AuthApiCallIR>, rlsProcedures?: ReadonlyArray<RlsProcedureIR>, containers?: ReadonlyArray<ContainerIR>, workflows?: ReadonlyArray<WorkflowIR>, workflowCalls?: ReadonlyArray<WorkflowCallIR>, maskProcedures?: ReadonlyArray<MaskProcedureIR>, nondeterministicCalls?: ReadonlyArray<NondeterministicCallIR>, procedureProtections?: ReadonlyArray<ProcedureMiddlewareIR>, argumentValidators?: ReadonlyArray<ArgumentValidatorIR>, secretLiterals?: ReadonlyArray<SecretLiteralIR>, sqlInterpolations?: ReadonlyArray<SqlInterpolationIR>, adminRoutes?: ReadonlyArray<AdminRouteIR>, r2sqlCalls?: ReadonlyArray<R2sqlCallIR>) => Finding[];
839
+ declare const lintSchema: (schema: SchemaIR, queries?: ReadonlyArray<QueryReadIR>, inserts?: ReadonlyArray<InsertWriteIR>, authApiCalls?: ReadonlyArray<AuthApiCallIR>, rlsProcedures?: ReadonlyArray<RlsProcedureIR>, containers?: ReadonlyArray<ContainerIR>, workflows?: ReadonlyArray<WorkflowIR>, workflowCalls?: ReadonlyArray<WorkflowCallIR>, maskProcedures?: ReadonlyArray<MaskProcedureIR>, nondeterministicCalls?: ReadonlyArray<NondeterministicCallIR>, procedureProtections?: ReadonlyArray<ProcedureMiddlewareIR>, argumentValidators?: ReadonlyArray<ArgumentValidatorIR>, secretLiterals?: ReadonlyArray<SecretLiteralIR>, sqlInterpolations?: ReadonlyArray<SqlInterpolationIR>, adminRoutes?: ReadonlyArray<AdminRouteIR>, r2sqlCalls?: ReadonlyArray<R2sqlCallIR>, shapes?: ReadonlyArray<ShapeIR>, mutatorWrites?: ReadonlyArray<MutatorWriteIR>) => Finding[];
787
840
  /**
788
841
  * Render advisor findings as a single multi-line string for console surfacing:
789
842
  * a one-line summary header followed by one `[LEVEL] name: detail` line per
@@ -898,6 +951,23 @@ declare const discoverMaskProcedures: (project: Project, lunoraDirectory: string
898
951
  * key); `table` is best-effort and left `""` when not a literal.
899
952
  */
900
953
  declare const discoverMigrations: (project: Project, lunoraDirectory: string) => MigrationIR[];
954
+ /** The only file custom mutators may be declared in — mirrors `lunora/queues.ts`. */
955
+ declare const MUTATORS_FILENAME = "mutators.ts";
956
+ /**
957
+ * Decide whether a call's callee is `defineMutator` — either the bare imported
958
+ * identifier (`defineMutator(...)`) or a namespace member access
959
+ * (`server.defineMutator(...)`). Both are valid ES module syntax, so discovery
960
+ * must see mutators declared either way.
961
+ */
962
+
963
+ /**
964
+ * Discover every custom mutator the project declares: exported
965
+ * `defineMutator()` calls in `lunora/mutators.ts`. Returns `[]` when the file
966
+ * doesn't exist. Only the export binding is lifted — the runtime object carries
967
+ * the authoritative `server` impl + `handler`, so codegen never evaluates the
968
+ * body. The client `client` impl is split into the browser bundle separately.
969
+ */
970
+ declare const discoverMutators: (project: Project, lunoraDirectory: string) => MutatorIR[];
901
971
  /**
902
972
  * Discover non-deterministic API calls (`Date.now`, `new Date()`, `Date()`,
903
973
  * `Math.random`, `crypto.randomUUID`, `crypto.getRandomValues` — including
@@ -963,6 +1033,16 @@ declare const discoverRlsMetadata: (project: Project, lunoraDirectory: string) =
963
1033
  * return a structural IR. Throws if the file or call cannot be found.
964
1034
  */
965
1035
  declare const discoverSchema: (project: Project, schemaPath: string, projectRoot?: string) => SchemaIR;
1036
+ /** The only file shapes may be declared in — mirrors `lunora/queues.ts`. */
1037
+ declare const SHAPES_FILENAME = "shapes.ts";
1038
+ /**
1039
+ * Discover every replication shape the project declares: exported
1040
+ * `defineShape()` calls in `lunora/shapes.ts`. Returns `[]` when the file
1041
+ * doesn't exist. Only the export binding is lifted — the runtime object carries
1042
+ * the authoritative `table`/`columns`/`compileWhere`, so codegen never
1043
+ * evaluates the predicate.
1044
+ */
1045
+ declare const discoverShapes: (project: Project, lunoraDirectory: string) => ShapeIR[];
966
1046
  /**
967
1047
  * Aggregate the schema-wide storage-rule metadata the studio's inspector reads:
968
1048
  * every statically-discovered `(bucket, on, prefix, procedure)` entry across all
@@ -1001,6 +1081,22 @@ declare const emitApi: (functions: ReadonlyArray<FunctionIR>, workflows?: Readon
1001
1081
  * Returns `""` when `@lunora/seed` is not a declared dependency, so projects
1002
1082
  * that don't use it keep a clean `_generated/` and never import the package.
1003
1083
  */
1084
+
1085
+ /**
1086
+ * Emit `_generated/collections.ts` — one TanStack DB collection factory per
1087
+ * `defineShape` in `lunora/shapes.ts` (the local-first partial-replication
1088
+ * surface). Each factory builds a live collection that syncs its shape's rowset
1089
+ * through the client's poke protocol (`subscribeShape`), so an app feeds the
1090
+ * result straight to `useLiveQuery`.
1091
+ *
1092
+ * Returns `""` (so `writeIfPresent` skips the file) unless the project both
1093
+ * declares shapes AND installs `@lunora/db` — the add-on that ships
1094
+ * `lunoraCollectionOptions`. `@lunora/db` stays a scoped install even under the
1095
+ * `lunorash` umbrella (an opt-in add-on, like `@lunora/auth`), so its import is
1096
+ * always `@lunora/db/collections`; only the in-umbrella `@lunora/client` import
1097
+ * is remapped to `lunorash/client`.
1098
+ */
1099
+ declare const emitCollections: (shapes: ReadonlyArray<ShapeIR>, hasDatabase: boolean, useUmbrella?: boolean) => string;
1004
1100
  interface EmitServerOptions {
1005
1101
  containers?: ReadonlyArray<ContainerIR>;
1006
1102
  hasAi?: boolean;
@@ -1047,7 +1143,7 @@ declare const emitServer: ({
1047
1143
  useUmbrella,
1048
1144
  workflows
1049
1145
  }?: EmitServerOptions) => string;
1050
- declare const emitFunctions: (functions: ReadonlyArray<FunctionIR>, migrations?: ReadonlyArray<MigrationIR>, useUmbrella?: boolean) => string;
1146
+ declare const emitFunctions: (functions: ReadonlyArray<FunctionIR>, migrations?: ReadonlyArray<MigrationIR>, useUmbrella?: boolean, mutators?: ReadonlyArray<MutatorIR>, shapes?: ReadonlyArray<ShapeIR>) => string;
1051
1147
  /**
1052
1148
  * Storage-column map per table for the file browser: `{ table: [field, …] }` for
1053
1149
  * every field declared `v.storage(...)` (unwrapping `v.optional(...)`). The
@@ -1110,10 +1206,14 @@ interface EmitShardOptions {
1110
1206
  /** A `lunora/` source reads `ctx.r2sql` (R2 SQL) — wires `ctx.r2sql` onto the ActionCtx only. */
1111
1207
  hasR2sql?: boolean;
1112
1208
  maskMetadata?: MaskMetadataIR;
1209
+ /** Custom mutators declared via `defineMutator` in `lunora/mutators.ts` — wires the `isCustomMutator` push-protocol override. */
1210
+ mutators?: ReadonlyArray<MutatorIR>;
1113
1211
  /** Queues declared via `defineQueue` exports in `lunora/queues.ts` — wires the typed `ctx.queues` producers. */
1114
1212
  queues?: ReadonlyArray<QueueIR>;
1115
1213
  rlsMetadata?: RlsMetadataIR;
1116
1214
  schema: SchemaIR;
1215
+ /** Replication shapes declared via `defineShape` in `lunora/shapes.ts` — wires the `resolveShape` subscription override. */
1216
+ shapes?: ReadonlyArray<ShapeIR>;
1117
1217
  storageRules?: StorageRulesMetadataIR;
1118
1218
  studioFeatures?: StudioFeaturesResult;
1119
1219
  /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
@@ -1135,9 +1235,11 @@ declare const emitShard: ({
1135
1235
  hasPipelines,
1136
1236
  hasR2sql,
1137
1237
  maskMetadata,
1238
+ mutators,
1138
1239
  queues,
1139
1240
  rlsMetadata,
1140
1241
  schema,
1242
+ shapes,
1141
1243
  storageRules,
1142
1244
  studioFeatures,
1143
1245
  useUmbrella,
@@ -1591,6 +1693,8 @@ interface CodegenResult {
1591
1693
  generated: {
1592
1694
  api: string; /** Fluent worker-composition builder (`_generated/app.ts`) — `defineApp()`. Always written. */
1593
1695
  app: string;
1696
+ /** Partial-replication collection factories (`_generated/collections.ts`); `""` (and not written) unless the project declares shapes and installs `@lunora/db`. */
1697
+ collections: string;
1594
1698
  /** Container DO classes (`_generated/containers.ts`); `""` (and not written) when no containers are declared. */
1595
1699
  containers: string;
1596
1700
  crons: string;
@@ -1725,4 +1829,4 @@ declare const LUNORA_SOLUTION_RULES: ReadonlyArray<LunoraSolutionRule>;
1725
1829
  */
1726
1830
  declare const findLunoraSolution: (message: string) => LunoraSolution | undefined;
1727
1831
  declare const VERSION = "0.0.0";
1728
- export { type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, LUNORA_SOLUTION_RULES, type LunoraSolution, type LunoraSolutionRule, type MaskProcedureIR, type MigrationIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverNondeterministicCalls, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSchema, discoverStorageRulesMetadata, discoverWorkflows, emitApi, emitApp, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, findLunoraSolution, formatAdvisories, lintSchema, parseSchemaSnapshot, refreshCodegenProject, runCodegen, schemaFromIr, serializeSchemaSnapshot, validatorIrToJsonSchema };
1832
+ export { type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, FLAGS_FILENAME, type FieldSnapshot, type FlagsIR, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, LUNORA_SOLUTION_RULES, type LunoraSolution, type LunoraSolutionRule, MUTATORS_FILENAME, type MaskProcedureIR, type MigrationIR, type MutatorIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, QUEUES_FILENAME, type QueryReadIR, type QueueIR, type R2sqlCallIR, type RelationSnapshot, type RlsMetadataIR, type RlsPolicyIR, type RlsProcedureIR, type RlsRoleIR, SCHEMA_SNAPSHOT_FILENAME, SCHEMA_SNAPSHOT_VERSION, SHAPES_FILENAME, type SchemaDrift, type SchemaDriftDecision, type SchemaIR, type SchemaSnapshot, SchemaSnapshotParseError, type ShapeIR, type StorageRuleIR, type StorageRulesMetadataIR, type TableIR, type TableSnapshot, VERSION, type ValidatorIR, type VectorIndexIR, WORKFLOWS_FILENAME, type WorkflowIR, buildOpenApiDocument, buildOpenRpcDocument, buildSchemaSnapshot, createCodegenProject, diagnosticAt, diffSchemaSnapshots, discoverAuthApiCalls, discoverContainers, discoverCrons, discoverFlags, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverMutators, discoverNondeterministicCalls, discoverQueries, discoverQueues, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSchema, discoverShapes, discoverStorageRulesMetadata, discoverWorkflows, emitApi, emitApp, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, findLunoraSolution, formatAdvisories, lintSchema, parseSchemaSnapshot, refreshCodegenProject, runCodegen, schemaFromIr, serializeSchemaSnapshot, validatorIrToJsonSchema };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { formatAdvisories, lintSchema } from './packem_shared/formatAdvisories-8NIv1k0I.mjs';
1
+ export { formatAdvisories, lintSchema } from './packem_shared/formatAdvisories-LM8-Ixjw.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';
@@ -9,19 +9,21 @@ export { default as discoverHttpRoutes } from './packem_shared/discoverHttpRoute
9
9
  export { default as discoverInserts } from './packem_shared/discoverInserts-C7zxXkUf.mjs';
10
10
  export { default as discoverMaskProcedures } from './packem_shared/discoverMaskProcedures-Cm81kwrb.mjs';
11
11
  export { default as discoverMigrations } from './packem_shared/discoverMigrations-Bi5nJ0mJ.mjs';
12
+ export { MUTATORS_FILENAME, discoverMutators } from './packem_shared/MUTATORS_FILENAME-BhqdPtKp.mjs';
12
13
  export { default as discoverNondeterministicCalls } from './packem_shared/discoverNondeterministicCalls-C4M8AXmQ.mjs';
13
14
  export { default as discoverQueries } from './packem_shared/discoverQueries-B0wGT-xe.mjs';
14
15
  export { QUEUES_FILENAME, discoverQueues } from './packem_shared/QUEUES_FILENAME-B5_eWCRe.mjs';
15
16
  export { default as discoverR2sqlCalls } from './packem_shared/discoverR2sqlCalls-BVNMd428.mjs';
16
17
  export { discoverRlsMetadata, default as discoverRlsProcedures } from './packem_shared/discoverRlsMetadata-BS9GOGC5.mjs';
17
18
  export { default as discoverSchema } from './packem_shared/discoverSchema-BnWHHJ4T.mjs';
19
+ export { SHAPES_FILENAME, discoverShapes } from './packem_shared/SHAPES_FILENAME-ChV7MqgE.mjs';
18
20
  export { default as discoverStorageRulesMetadata } from './packem_shared/discoverStorageRulesMetadata-Da8BKXcI.mjs';
19
21
  export { WORKFLOWS_FILENAME, discoverWorkflows } from './packem_shared/WORKFLOWS_FILENAME-D62dcBGg.mjs';
20
- export { GENERATED_HEADER, emitApi, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers } from './packem_shared/GENERATED_HEADER-CkZUgM4_.mjs';
21
- export { emitApp } from './packem_shared/emitApp-DGmlt5fq.mjs';
22
- export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-BSoDzIn8.mjs';
23
- export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-VINwUAxf.mjs';
24
- export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-DsBxQ5C_.mjs';
22
+ export { GENERATED_HEADER, emitApi, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers } from './packem_shared/GENERATED_HEADER-BoVKLcnE.mjs';
23
+ export { emitApp } from './packem_shared/emitApp-Bh0HsQVK.mjs';
24
+ export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-CB2brlbc.mjs';
25
+ export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-BMCOe3B6.mjs';
26
+ export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-BA9tjjOl.mjs';
25
27
  export { SCHEMA_SNAPSHOT_VERSION, SchemaSnapshotParseError, buildSchemaSnapshot, diffSchemaSnapshots, evaluateSchemaDrift, parseSchemaSnapshot, serializeSchemaSnapshot } from './packem_shared/SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs';
26
28
  export { schemaFromIr } from './packem_shared/schemaFromIr-DTYsLBaA.mjs';
27
29
  export { LUNORA_ERROR_CODES, validatorIrToJsonSchema } from './packem_shared/LUNORA_ERROR_CODES-CySpQPD3.mjs';
@@ -600,6 +600,26 @@ import type { InsertModel } from "./dataModel.js";
600
600
  export const createSeedClient = (options?: SeedClientOptions): SeedClient<InsertModel> => createSeedClientBase<InsertModel>(schema, options);
601
601
  `;
602
602
  };
603
+ const emitCollections = (shapes, hasDatabase, useUmbrella = false) => {
604
+ if (shapes.length === 0 || !hasDatabase) {
605
+ return "";
606
+ }
607
+ const base = baseSpecifiers(useUmbrella);
608
+ const factories = shapes.map((shape) => {
609
+ assertIdentifier(shape.exportName, "shape export name");
610
+ return `/** Live collection for the \`${shape.exportName}\` replication shape — pass the shape's validated \`args\` (its partition selector). */
611
+ export const ${shape.exportName}Collection = (client: LunoraClient, args?: Record<string, unknown>): Collection<Row, string> =>
612
+ createCollection(lunoraCollectionOptions({ client, shape: { args, name: "${shape.exportName}" } }).config);`;
613
+ }).join("\n\n");
614
+ return `${GENERATED_HEADER}import type { LunoraClient } from "${base.client}";
615
+ import { lunoraCollectionOptions } from "@lunora/db/collections";
616
+ import type { Row } from "@lunora/db";
617
+ import type { Collection } from "@tanstack/db";
618
+ import { createCollection } from "@tanstack/db";
619
+
620
+ ${factories}
621
+ `;
622
+ };
603
623
  const moduleAlias = (filePath, index) => `lunora_${sanitizeNamespace(filePath)}_${String(index)}`;
604
624
  const CALL_REGISTERED_HELPER = `const callRegistered = async <R>(context: CallerCtx, functionPath: string, args: Record<string, unknown> | undefined): Promise<R> => {
605
625
  const registered = LUNORA_FUNCTIONS[functionPath];
@@ -614,7 +634,7 @@ const CALL_REGISTERED_HELPER = `const callRegistered = async <R>(context: Caller
614
634
 
615
635
  return (await registered.handler(context, args ?? {})) as R;
616
636
  };`;
617
- const renderFunctionRegistry = (functions, migrations) => {
637
+ const renderFunctionRegistry = (functions, migrations, mutators = [], shapes = []) => {
618
638
  const aliasByPath = /* @__PURE__ */ new Map();
619
639
  const registerPath = (filePath) => {
620
640
  if (!aliasByPath.has(filePath)) {
@@ -627,6 +647,12 @@ const renderFunctionRegistry = (functions, migrations) => {
627
647
  for (const migration of migrations) {
628
648
  registerPath(migration.filePath);
629
649
  }
650
+ for (const mutator of mutators) {
651
+ registerPath(mutator.filePath);
652
+ }
653
+ for (const shape of shapes) {
654
+ registerPath(shape.filePath);
655
+ }
630
656
  const importLines = [...aliasByPath.entries()].map(([filePath, alias]) => {
631
657
  if (!IMPORT_PATH_RE.test(filePath)) {
632
658
  throw new Error(`@lunora/codegen: refusing to emit import for unsafe file path: ${JSON.stringify(filePath)}`);
@@ -639,6 +665,12 @@ const renderFunctionRegistry = (functions, migrations) => {
639
665
  const migrationEntries = migrations.map(
640
666
  (migration) => ` ${JSON.stringify(migration.id)}: ${aliasByPath.get(migration.filePath) ?? ""}.${migration.exportName} as unknown as RegisteredDataMigration,`
641
667
  ).join("\n");
668
+ const mutatorEntries = mutators.map(
669
+ (mutator) => ` "${sanitizeNamespace(mutator.filePath)}:${mutator.exportName}": ${aliasByPath.get(mutator.filePath) ?? ""}.${mutator.exportName} as unknown as RegisteredLunoraFunction,`
670
+ ).join("\n");
671
+ const shapeEntries = shapes.map((shape) => ` "${shape.exportName}": ${aliasByPath.get(shape.filePath) ?? ""}.${shape.exportName} as unknown as RegisteredShape,`).join("\n");
672
+ const mutatorPaths = mutators.map((mutator) => `${sanitizeNamespace(mutator.filePath)}:${mutator.exportName}`);
673
+ const combinedDispatch = [dispatchEntries, mutatorEntries].filter((entries) => entries.length > 0).join("\n");
642
674
  const installLines = functions.map((definition) => {
643
675
  if (definition.lifecycle || Object.keys(definition.args).length === 0) {
644
676
  return void 0;
@@ -651,8 +683,8 @@ const renderFunctionRegistry = (functions, migrations) => {
651
683
  return `installCompiledValidatorMap(${alias}.${definition.exportName}.args, ${compiled});`;
652
684
  }).filter((line) => line !== void 0).join("\n");
653
685
  return {
654
- dispatchBody: dispatchEntries.length > 0 ? `
655
- ${dispatchEntries}
686
+ dispatchBody: combinedDispatch.length > 0 ? `
687
+ ${combinedDispatch}
656
688
  ` : "",
657
689
  importBlock: importLines.length > 0 ? `${importLines}
658
690
 
@@ -660,6 +692,10 @@ ${dispatchEntries}
660
692
  installBlock: installLines,
661
693
  migrationBody: migrationEntries.length > 0 ? `
662
694
  ${migrationEntries}
695
+ ` : "",
696
+ mutatorPaths,
697
+ shapeBody: shapeEntries.length > 0 ? `
698
+ ${shapeEntries}
663
699
  ` : ""
664
700
  };
665
701
  };
@@ -984,11 +1020,30 @@ const renderLifecycleManifest = (functions) => {
984
1020
  }
985
1021
  return manifest;
986
1022
  };
987
- const emitFunctions = (functions, migrations = [], useUmbrella = false) => {
1023
+ const emitFunctions = (functions, migrations = [], useUmbrella = false, mutators = [], shapes = []) => {
988
1024
  const hasFunctions = functions.length > 0;
989
1025
  const base = baseSpecifiers(useUmbrella);
990
- const { dispatchBody, importBlock, installBlock, migrationBody } = renderFunctionRegistry(functions, migrations);
1026
+ const { dispatchBody, importBlock, installBlock, migrationBody, mutatorPaths, shapeBody } = renderFunctionRegistry(functions, migrations, mutators, shapes);
991
1027
  const lifecycleHooks = renderLifecycleManifest(functions);
1028
+ const shapeTypeImport = shapes.length > 0 ? `import type { RegisteredShape } from "${base.server}";
1029
+ ` : "";
1030
+ const shapeRegistry = shapes.length > 0 ? `
1031
+ /**
1032
+ * Replication-shape registry — one entry per \`defineShape\` in \`lunora/shapes.ts\`.
1033
+ * The generated ShardDO's \`resolveShape\` override looks a shape up by name and
1034
+ * evaluates its trusted \`compileWhere(ctx, args)\` to authorize + scope a
1035
+ * \`shape_subscribe\` (reads-as-permissions).
1036
+ */
1037
+ export const LUNORA_SHAPES: Record<string, RegisteredShape> = {${shapeBody}};
1038
+ ` : "";
1039
+ const mutatorPathsRegistry = mutatorPaths.length > 0 ? `
1040
+ /**
1041
+ * Custom-mutator function paths — the \`LUNORA_FUNCTIONS\` keys the generated
1042
+ * ShardDO's \`isCustomMutator\` override routes through the client-watermark push
1043
+ * protocol (\`x-lunora-client-id\`/\`x-lunora-client-seq\` ordering).
1044
+ */
1045
+ export const LUNORA_MUTATOR_PATHS: ReadonlySet<string> = new Set([${mutatorPaths.map((path) => JSON.stringify(path)).join(", ")}]);
1046
+ ` : "";
992
1047
  const compiledArgsImport = installBlock.length > 0 ? `import { DEFER_VALIDATION as DEFER, installCompiledValidatorMap } from "${base.values}";
993
1048
  ` : "";
994
1049
  const compiledArgsInstall = installBlock.length > 0 ? `
@@ -1013,7 +1068,7 @@ ${caller.implementation}
1013
1068
  const callRegisteredHelper = hasFunctions ? `${CALL_REGISTERED_HELPER}
1014
1069
 
1015
1070
  ` : "";
1016
- return `${GENERATED_HEADER}${importBlock}${compiledArgsImport}import type { ActionCtx, MutationCtx, QueryCtx } from "./server.js";
1071
+ return `${GENERATED_HEADER}${importBlock}${compiledArgsImport}${shapeTypeImport}import type { ActionCtx, MutationCtx, QueryCtx } from "./server.js";
1017
1072
  ${dataModelImport}
1018
1073
  /**
1019
1074
  * Single registered function, narrowed to the shape \`handleRpc\` needs.
@@ -1039,7 +1094,7 @@ export interface RegisteredLunoraFunction {
1039
1094
  * emits (\`api[namespace][fn].__lunoraRef === "namespace:fn"\`).
1040
1095
  */
1041
1096
  export const LUNORA_FUNCTIONS: Record<string, RegisteredLunoraFunction> = {${dispatchBody}};
1042
- ${compiledArgsInstall}
1097
+ ${compiledArgsInstall}${shapeRegistry}${mutatorPathsRegistry}
1043
1098
  /**
1044
1099
  * Connection-lifecycle manifest: the function paths the generated ShardDO
1045
1100
  * dispatches when a client's WebSocket connects (\`connect\`) or disconnects
@@ -1910,15 +1965,19 @@ const emitShard = ({
1910
1965
  hasPipelines = false,
1911
1966
  hasR2sql = false,
1912
1967
  maskMetadata,
1968
+ mutators = [],
1913
1969
  queues = [],
1914
1970
  rlsMetadata,
1915
1971
  schema,
1972
+ shapes = [],
1916
1973
  storageRules,
1917
1974
  studioFeatures,
1918
1975
  useUmbrella = false,
1919
1976
  workflows = []
1920
1977
  }) => {
1921
1978
  const base = baseSpecifiers(useUmbrella);
1979
+ const hasMutators = mutators.length > 0;
1980
+ const hasShapes = shapes.length > 0;
1922
1981
  const { build: aiBuild, configField: aiConfigField, contextField: aiContextField, stub: aiStub } = emitAiFragments(hasAi);
1923
1982
  const kvFragments = emitKvFragments(hasKv);
1924
1983
  const flagsFragments = emitFlagsFragments(hasFlags);
@@ -1980,15 +2039,79 @@ const emitShard = ({
1980
2039
  const tableColumns = buildTableColumns(schema);
1981
2040
  const storageColumns = buildStorageColumns(schema);
1982
2041
  const doTypeImports = buildDoTypeImports(hasVectors, workflows.length > 0, queues.length > 0, hasFlags);
2042
+ if (hasShapes) {
2043
+ doTypeImports.push("WhereInput");
2044
+ }
1983
2045
  const relationFanout = emitRelationFanout(hasGlobalTables);
2046
+ const shapeResolveOverride = hasShapes ? `
2047
+ protected override resolveShape(name: string, args: Record<string, unknown>, identity?: { identity?: Record<string, unknown>; userId?: string }): { columns?: readonly string[]; effectiveWhere?: WhereInput; global?: boolean; table: string } | undefined {
2048
+ const shape = LUNORA_SHAPES[name];
2049
+
2050
+ if (!shape) {
2051
+ return undefined;
2052
+ }
2053
+
2054
+ this.ensureMigrated();
2055
+
2056
+ // Trusted ctx from the socket's OWN verified identity — the client
2057
+ // supplies only the shape name + args; \`compileWhere\` validates the
2058
+ // args then runs the shape's \`where(ctx, args)\` under that identity,
2059
+ // so which rows replicate is a server decision (reads-as-permissions).
2060
+ const ctx = this.buildCtx({ functionPath: \`__shape__:\${name}\`, identity });
2061
+ const shapeWhere = shape.compileWhere(ctx, args) as unknown as WhereInput;
2062
+
2063
+ // AND-compose with the table's RLS read base-where. A shape runs no
2064
+ // procedure, so the \`.use(rls(...))\` middleware never fires; without
2065
+ // this merge its reads would bypass every read policy on the table
2066
+ // (rows the caller can't see would replicate). \`composeShapeReadWhere\`
2067
+ // evaluates the table's read policies under this same trusted ctx —
2068
+ // exactly the request-time path — and fails closed under a
2069
+ // \`.rls("required")\` schema for a non-\`.public()\`, policy-less table.
2070
+ const effectiveWhere = composeShapeReadWhere(LUNORA_RLS_READ_REGISTRY, {
2071
+ ctx,
2072
+ identity: identity?.identity ?? null,
2073
+ rlsRequired: (schema as unknown as { rlsMode?: string }).rlsMode === "required",
2074
+ roles: (ctx as { auth?: { roles?: readonly string[] } }).auth?.roles ?? [],
2075
+ shapeWhere,
2076
+ table: shape.table,
2077
+ tablePublic: (schema as unknown as { tables: Record<string, { isPublic?: boolean }> }).tables[shape.table]?.isPublic === true,
2078
+ userId: identity?.userId ?? null,
2079
+ });
2080
+
2081
+ // A live shape pokes only from its OWN shard's op-log, so a \`where()\`
2082
+ // that joins to a \`.shardBy()\` table (rows in another DO) is rejected
2083
+ // here — the first point the compiled predicate + shard modes are both
2084
+ // known. Remedy: denormalize, or move the joined table to \`.global()\`.
2085
+ assertShapeShardable(effectiveWhere, schema as unknown as SchemaLike, shape.table);
2086
+
2087
+ // A \`.global()\` table lives in D1 (no per-DO op-log): flag it so the
2088
+ // base serves it through the latency-tiered poll path (\`readGlobalShapeRows\`)
2089
+ // instead of the CDC poke path.
2090
+ const isGlobal = (schema as unknown as SchemaLike).tables[shape.table]?.shardMode?.kind === "global";
2091
+
2092
+ return { columns: shape.columns, effectiveWhere, global: isGlobal, table: shape.table };
2093
+ }
2094
+ ` : "";
2095
+ const customMutatorOverride = hasMutators ? `
2096
+ protected override isCustomMutator(functionPath: string): boolean {
2097
+ return LUNORA_MUTATOR_PATHS.has(functionPath);
2098
+ }
2099
+ ` : "";
2100
+ const shapeReadRegistryConst = hasShapes ? `
2101
+ /** Per-table RLS read policies (hoisted from \`.use(rls(...))\` chains) the shape resolver AND-merges into each \`defineShape\` predicate so partial replication honours read policies. */
2102
+ const LUNORA_RLS_READ_REGISTRY = buildRlsReadRegistry(Object.values(LUNORA_FUNCTIONS));
2103
+ ` : "";
2104
+ const shapeGuardImport = hasShapes ? "assertShapeShardable, " : "";
1984
2105
  const importLines = [
1985
2106
  `import type { ${doTypeImports.join(", ")} } from "${base.do}";`,
1986
- `import { applyCdcChanges, createShardCtxDb, runDataMigration, runShardMigrations, ${relationFanout.importFragment}ShardDO as ShardDOBase } from "${base.do}";`,
2107
+ `import { applyCdcChanges, ${shapeGuardImport}createShardCtxDb, runDataMigration, runShardMigrations, ${relationFanout.importFragment}ShardDO as ShardDOBase } from "${base.do}";`,
1987
2108
  // `asBucketStorage` (the bucket-aware `ctx.storage` wrapper) and
1988
2109
  // `createSecrets` (the `ctx.secrets` core built-in) live in
1989
2110
  // `@lunora/server`, the single source — imported here rather than stamped
1990
- // inline into every generated shard.
1991
- `import { asBucketStorage, createSecrets } from "${base.server}";`
2111
+ // inline into every generated shard. With shapes, also pull the RLS
2112
+ // read-registry builder + `composeShapeReadWhere` so `resolveShape` can
2113
+ // AND-merge a shape's predicate with the table's read base-where.
2114
+ hasShapes ? `import { asBucketStorage, buildRlsReadRegistry, composeShapeReadWhere, createSecrets } from "${base.server}";` : `import { asBucketStorage, createSecrets } from "${base.server}";`
1992
2115
  ];
1993
2116
  if (hasTables) {
1994
2117
  importLines.push(`import { bindOrm, bindTableFacade } from "${base.server}";`);
@@ -2017,7 +2140,9 @@ const emitShard = ({
2017
2140
  ...paymentsImports,
2018
2141
  ``,
2019
2142
  `import schema from "../schema.js";`,
2020
- `import { LUNORA_FUNCTIONS, LUNORA_LIFECYCLE_HOOKS, LUNORA_MIGRATIONS } from "./functions.js";`
2143
+ // Local-first sync registries are pulled in alongside the function table
2144
+ // only when the project declares them, so the import list stays minimal.
2145
+ `import { ${["LUNORA_FUNCTIONS", "LUNORA_LIFECYCLE_HOOKS", "LUNORA_MIGRATIONS", ...hasMutators ? ["LUNORA_MUTATOR_PATHS"] : [], ...hasShapes ? ["LUNORA_SHAPES"] : []].join(", ")} } from "./functions.js";`
2021
2146
  );
2022
2147
  const vectorsConfigField = hasVectors ? `
2023
2148
  vectors?: (env: Record<string, unknown>) => Record<string, VectorizeIndexLike>;` : "";
@@ -2142,6 +2267,37 @@ const vectorsStub: VectorSearchLike = {
2142
2267
  const bindTableHelper = "";
2143
2268
  const globalDatabaseThunk = hasHyperdriveGlobal ? "config.hyperdriveGlobal" : "config.d1";
2144
2269
  const globalDatabaseLine = hasGlobalTables ? ` const globalDb: DatabaseWriterLike = ${globalDatabaseThunk}?.(env, { identity, userId }) ?? globalDbStub;
2270
+ ` : "";
2271
+ const globalShapeReaderOverride = hasShapes && hasGlobalTables ? `
2272
+ protected override async readGlobalShapeRows(resolved: { columns?: readonly string[]; effectiveWhere?: WhereInput; global?: boolean; table: string }, identity?: { identity?: Record<string, unknown>; userId?: string }): Promise<Array<{ doc: Record<string, unknown>; id: string }>> {
2273
+ const env = this.env as Record<string, unknown>;
2274
+ const globalDb: DatabaseWriterLike = ${globalDatabaseThunk}?.(env, identity) ?? globalDbStub;
2275
+ const rows: Array<{ doc: Record<string, unknown>; id: string }> = [];
2276
+
2277
+ let cursor: null | string = null;
2278
+
2279
+ // Drain every page of the global membership so the seed/diff sees the
2280
+ // full rowset (D1 \`findMany\` is paginated). Stop one row past the cap:
2281
+ // a broad \`.global()\` shape would otherwise materialize an unbounded
2282
+ // array before the caller's \`withinGlobalShapeBound\` check rejects it,
2283
+ // so bail early and let the caller fail it closed.
2284
+ do {
2285
+ // eslint-disable-next-line no-await-in-loop -- sequential page drain to assemble the full membership
2286
+ const page = await globalDb.findMany(resolved.table, { cursor, where: resolved.effectiveWhere });
2287
+
2288
+ for (const doc of page.page) {
2289
+ rows.push({ doc, id: String((doc as { _id?: unknown })._id) });
2290
+
2291
+ if (rows.length > ShardDOBase.GLOBAL_SHAPE_MAX_ROWS) {
2292
+ return rows;
2293
+ }
2294
+ }
2295
+
2296
+ cursor = page.isDone ? null : page.continueCursor;
2297
+ } while (cursor !== null);
2298
+
2299
+ return rows;
2300
+ }
2145
2301
  ` : "";
2146
2302
  const facadeBlock = hasTables ? `
2147
2303
  const facade = db as unknown as Record<string, ReturnType<typeof bindTableFacade>>;
@@ -2211,7 +2367,7 @@ const LUNORA_STORAGE_RULES: StorageRulesResult = ${JSON.stringify(storageRulesDa
2211
2367
 
2212
2368
  /** Which optional package-backed features this app wires up (discovered from imports / \`ctx.*\` reads / schema signals) served via \`__lunora_admin__:studioFeatures\` so the studio hides nav pages whose package isn't enabled. */
2213
2369
  const LUNORA_STUDIO_FEATURES: StudioFeaturesResult = ${JSON.stringify(studioFeaturesData, void 0, 4)};
2214
- ${flagsOverrides.constant}${workflowsMetadataConst}${queuesMetadataConst}${containerSpecs}${workflowSpecs}${queueSpecs}
2370
+ ${flagsOverrides.constant}${shapeReadRegistryConst}${workflowsMetadataConst}${queuesMetadataConst}${containerSpecs}${workflowSpecs}${queueSpecs}
2215
2371
  export interface ShardDOConfig {
2216
2372
  /** Opt into change-data-capture: records a post-image to \`__cdc_log\` on every write (backs streaming export + replay-PITR). */
2217
2373
  cdc?: boolean;
@@ -2326,8 +2482,20 @@ export const createShardDO = (config: ShardDOConfig = {}): new (state: ShardDOSt
2326
2482
  // do external I/O that can't be rolled back, so both dispatch directly.
2327
2483
  // \`ctx.run*\` composition runs inside this span (it never re-enters
2328
2484
  // handleRpc); runInTransaction's own guard rejects accidental nesting.
2485
+ //
2486
+ // The replay bookkeeping (idempotency dedup row + custom-mutator
2487
+ // watermark advance) commits INSIDE this span via
2488
+ // \`commitMutationBookkeeping\`, so the writes, the dedup row, and the
2489
+ // watermark are atomic — a crash can't leave the writes durable without
2490
+ // the replay guard.
2329
2491
  if (registered.kind === "mutation") {
2330
- return this.runInTransaction(() => registered.handler(ctx, args));
2492
+ return this.runInTransaction(async () => {
2493
+ const result = await registered.handler(ctx, args);
2494
+
2495
+ this.commitMutationBookkeeping(result);
2496
+
2497
+ return result;
2498
+ });
2331
2499
  }
2332
2500
 
2333
2501
  return registered.handler(ctx, args);
@@ -2365,7 +2533,7 @@ ${relationFanout.override}
2365
2533
  iterator: (signal) => (registered.handler as (context: unknown, args: Record<string, unknown>, signal: AbortSignal) => AsyncIterable<unknown>)(this.buildCtx({ functionPath }), args, signal),
2366
2534
  };
2367
2535
  }
2368
-
2536
+ ${customMutatorOverride}${shapeResolveOverride}${globalShapeReaderOverride}
2369
2537
  protected override lifecycleHookPaths(event: "connect" | "disconnect"): readonly string[] {
2370
2538
  return LUNORA_LIFECYCLE_HOOKS[event];
2371
2539
  }
@@ -2941,4 +3109,4 @@ const emitWranglerCronTriggers = (crons) => {
2941
3109
  return triggers;
2942
3110
  };
2943
3111
 
2944
- export { GENERATED_HEADER, buildStorageColumns, emitApi, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitQueues, emitSeed, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers };
3112
+ export { GENERATED_HEADER, buildStorageColumns, emitApi, emitCollections, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitQueues, emitSeed, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers };
@@ -0,0 +1,81 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { Node, SyntaxKind } from 'ts-morph';
4
+ import { diagnosticAt } from './CodegenDiagnosticError-DeblMkzO.mjs';
5
+
6
+ const MUTATORS_FILENAME = "mutators.ts";
7
+ const MUTATOR_MODULE_SPECIFIERS = /* @__PURE__ */ new Set(["@lunora/server", "lunorash/server"]);
8
+ const isDefineMutator = (identifier) => {
9
+ const symbol = identifier.getSymbol();
10
+ if (!symbol) {
11
+ return identifier.getText() === "defineMutator";
12
+ }
13
+ for (const declaration of symbol.getDeclarations()) {
14
+ if (!Node.isImportSpecifier(declaration)) {
15
+ continue;
16
+ }
17
+ if (!MUTATOR_MODULE_SPECIFIERS.has(declaration.getImportDeclaration().getModuleSpecifierValue())) {
18
+ return false;
19
+ }
20
+ return declaration.getNameNode().getText() === "defineMutator";
21
+ }
22
+ return false;
23
+ };
24
+ const isMutatorNamespaceImport = (identifier) => {
25
+ const symbol = identifier.getSymbol();
26
+ if (!symbol) {
27
+ return false;
28
+ }
29
+ for (const declaration of symbol.getDeclarations()) {
30
+ if (!Node.isNamespaceImport(declaration)) {
31
+ continue;
32
+ }
33
+ const importDeclaration = declaration.getFirstAncestorByKind(SyntaxKind.ImportDeclaration);
34
+ return importDeclaration !== void 0 && MUTATOR_MODULE_SPECIFIERS.has(importDeclaration.getModuleSpecifierValue());
35
+ }
36
+ return false;
37
+ };
38
+ const isDefineMutatorCallee = (callee) => {
39
+ if (Node.isIdentifier(callee)) {
40
+ return isDefineMutator(callee);
41
+ }
42
+ if (Node.isPropertyAccessExpression(callee)) {
43
+ const object = callee.getExpression();
44
+ return callee.getName() === "defineMutator" && Node.isIdentifier(object) && isMutatorNamespaceImport(object);
45
+ }
46
+ return false;
47
+ };
48
+ const mutatorsFromSource = (source) => {
49
+ const mutators = [];
50
+ for (const declaration of source.getVariableDeclarations()) {
51
+ if (!declaration.isExported()) {
52
+ continue;
53
+ }
54
+ const initializer = declaration.getInitializer();
55
+ if (initializer?.getKind() !== SyntaxKind.CallExpression) {
56
+ continue;
57
+ }
58
+ const callee = initializer.getExpression();
59
+ if (!isDefineMutatorCallee(callee)) {
60
+ continue;
61
+ }
62
+ const nameNode = declaration.getNameNode();
63
+ if (!Node.isIdentifier(nameNode)) {
64
+ throw diagnosticAt(nameNode, "defineMutator exports must be plain named exports (no destructuring)");
65
+ }
66
+ mutators.push({ exportName: nameNode.getText(), filePath: "mutators" });
67
+ }
68
+ return mutators;
69
+ };
70
+ const discoverMutators = (project, lunoraDirectory) => {
71
+ const mutatorsPath = join(lunoraDirectory, MUTATORS_FILENAME);
72
+ if (!existsSync(mutatorsPath)) {
73
+ return [];
74
+ }
75
+ const source = project.getSourceFile(mutatorsPath) ?? project.addSourceFileAtPath(mutatorsPath);
76
+ const mutators = mutatorsFromSource(source);
77
+ mutators.sort((a, b) => a.exportName.localeCompare(b.exportName));
78
+ return mutators;
79
+ };
80
+
81
+ export { MUTATORS_FILENAME, discoverMutators, isDefineMutatorCallee };
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-CkZUgM4_.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-BoVKLcnE.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-8NIv1k0I.mjs';
5
+ import { lintSchema } from './formatAdvisories-LM8-Ixjw.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';
@@ -12,19 +12,21 @@ import discoverHttpRoutes from './discoverHttpRoutes-CfP6cMzt.mjs';
12
12
  import discoverInserts from './discoverInserts-C7zxXkUf.mjs';
13
13
  import discoverMaskProcedures, { discoverMaskMetadata } from './discoverMaskProcedures-Cm81kwrb.mjs';
14
14
  import discoverMigrations from './discoverMigrations-Bi5nJ0mJ.mjs';
15
+ import { MUTATORS_FILENAME, isDefineMutatorCallee, discoverMutators } from './MUTATORS_FILENAME-BhqdPtKp.mjs';
15
16
  import discoverNondeterministicCalls from './discoverNondeterministicCalls-C4M8AXmQ.mjs';
16
17
  import discoverQueries from './discoverQueries-B0wGT-xe.mjs';
17
18
  import { discoverQueues } from './QUEUES_FILENAME-B5_eWCRe.mjs';
18
19
  import discoverR2sqlCalls from './discoverR2sqlCalls-BVNMd428.mjs';
19
20
  import discoverRlsProcedures, { discoverRlsMetadata } from './discoverRlsMetadata-BS9GOGC5.mjs';
20
21
  import discoverSchema from './discoverSchema-BnWHHJ4T.mjs';
22
+ import { discoverShapes } from './SHAPES_FILENAME-ChV7MqgE.mjs';
21
23
  import discoverStorageRulesMetadata from './discoverStorageRulesMetadata-Da8BKXcI.mjs';
22
24
  import { e as enclosingExportName$1 } from './discover-ast-CT6BgBr4.mjs';
23
25
  import { discoverWorkflows } from './WORKFLOWS_FILENAME-D62dcBGg.mjs';
24
- import { buildStorageColumns, emitDataModel, emitApi, emitServer, emitFunctions, emitShard, emitContainers, emitWorkflows, emitQueues, emitCrons, emitVectors, emitDrizzleSchema, emitSeed, emitWranglerCronTriggers } from './GENERATED_HEADER-CkZUgM4_.mjs';
25
- import { emitApp } from './emitApp-DGmlt5fq.mjs';
26
- import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-BSoDzIn8.mjs';
27
- import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-VINwUAxf.mjs';
26
+ import { buildStorageColumns, emitDataModel, emitApi, emitServer, emitFunctions, emitShard, emitCollections, emitContainers, emitWorkflows, emitQueues, emitCrons, emitVectors, emitDrizzleSchema, emitSeed, emitWranglerCronTriggers } from './GENERATED_HEADER-BoVKLcnE.mjs';
27
+ import { emitApp } from './emitApp-Bh0HsQVK.mjs';
28
+ import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-CB2brlbc.mjs';
29
+ import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-BMCOe3B6.mjs';
28
30
  import { buildSchemaSnapshot, serializeSchemaSnapshot } from './SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs';
29
31
 
30
32
  const HTTP_VERBS = /* @__PURE__ */ new Set(["delete", "get", "head", "options", "patch", "post", "put"]);
@@ -329,6 +331,61 @@ const buildStudioFeatures = (usage, signals) => {
329
331
  };
330
332
  };
331
333
 
334
+ const isDatabaseReplaceCall = (call) => {
335
+ const callee = call.getExpression();
336
+ if (!Node.isPropertyAccessExpression(callee) || callee.getName() !== "replace") {
337
+ return false;
338
+ }
339
+ const receiver = callee.getExpression();
340
+ if (Node.isPropertyAccessExpression(receiver)) {
341
+ return receiver.getName() === "db";
342
+ }
343
+ return Node.isIdentifier(receiver) && receiver.getText() === "db";
344
+ };
345
+ const serverImplNode = (call) => {
346
+ const argument = call.getArguments()[0];
347
+ if (argument === void 0 || !Node.isObjectLiteralExpression(argument)) {
348
+ return void 0;
349
+ }
350
+ const property = argument.getProperty("server");
351
+ if (property === void 0) {
352
+ return void 0;
353
+ }
354
+ return Node.isPropertyAssignment(property) ? property.getInitializer() : property;
355
+ };
356
+ const exportedDefineMutatorCall = (declaration) => {
357
+ if (!declaration.isExported()) {
358
+ return void 0;
359
+ }
360
+ const initializer = declaration.getInitializer();
361
+ if (initializer?.getKind() !== SyntaxKind.CallExpression) {
362
+ return void 0;
363
+ }
364
+ const call = initializer;
365
+ return isDefineMutatorCallee(call.getExpression()) ? call : void 0;
366
+ };
367
+ const writesFromDeclaration = (declaration) => {
368
+ const call = exportedDefineMutatorCall(declaration);
369
+ const serverImpl = call === void 0 ? void 0 : serverImplNode(call);
370
+ if (serverImpl === void 0) {
371
+ return [];
372
+ }
373
+ const nameNode = declaration.getNameNode();
374
+ const exportName = Node.isIdentifier(nameNode) ? nameNode.getText() : "";
375
+ return serverImpl.getDescendantsOfKind(SyntaxKind.CallExpression).filter((replaceCall) => isDatabaseReplaceCall(replaceCall)).map((replaceCall) => {
376
+ return { exportName, file: "lunora/mutators.ts", line: replaceCall.getStartLineNumber() };
377
+ });
378
+ };
379
+ const mutatorWritesFromSource = (source) => source.getVariableDeclarations().flatMap((declaration) => writesFromDeclaration(declaration));
380
+ const discoverMutatorWrites = (project, lunoraDirectory) => {
381
+ const mutatorsPath = join(lunoraDirectory, MUTATORS_FILENAME);
382
+ if (!existsSync(mutatorsPath)) {
383
+ return [];
384
+ }
385
+ const source = project.getSourceFile(mutatorsPath) ?? project.addSourceFileAtPath(mutatorsPath);
386
+ return mutatorWritesFromSource(source);
387
+ };
388
+
332
389
  const discoverPackageDependencies = (projectRoot) => {
333
390
  const manifestPath = join(projectRoot, "package.json");
334
391
  if (!existsSync(manifestPath)) {
@@ -743,6 +800,8 @@ const runCodegen = (options) => {
743
800
  const functions = discoverFunctions(project, lunoraDirectory);
744
801
  const httpRoutes = discoverHttpRoutes(project, lunoraDirectory);
745
802
  const migrations = discoverMigrations(project, lunoraDirectory);
803
+ const shapes = discoverShapes(project, lunoraDirectory);
804
+ const mutators = discoverMutators(project, lunoraDirectory);
746
805
  const workflows = discoverWorkflows(project, lunoraDirectory);
747
806
  const queues = discoverQueues(project, lunoraDirectory);
748
807
  const crons = discoverCrons(project, lunoraDirectory, workflows);
@@ -763,7 +822,9 @@ const runCodegen = (options) => {
763
822
  discoverSecrets(project, lunoraDirectory),
764
823
  discoverSqlInterpolation(project, lunoraDirectory),
765
824
  discoverAdminRoutes(project, lunoraDirectory),
766
- discoverR2sqlCalls(project, lunoraDirectory)
825
+ discoverR2sqlCalls(project, lunoraDirectory),
826
+ shapes,
827
+ discoverMutatorWrites(project, lunoraDirectory)
767
828
  );
768
829
  const rlsMetadata = discoverRlsMetadata(project, lunoraDirectory);
769
830
  const maskMetadata = discoverMaskMetadata(project, lunoraDirectory);
@@ -812,7 +873,7 @@ const runCodegen = (options) => {
812
873
  useUmbrella,
813
874
  workflows
814
875
  });
815
- const functionsContent = emitFunctions(functions, migrations, useUmbrella);
876
+ const functionsContent = emitFunctions(functions, migrations, useUmbrella, mutators, shapes);
816
877
  const shardContent = emitShard({
817
878
  advisories,
818
879
  containers,
@@ -828,14 +889,17 @@ const runCodegen = (options) => {
828
889
  hasPipelines,
829
890
  hasR2sql,
830
891
  maskMetadata,
892
+ mutators,
831
893
  queues,
832
894
  rlsMetadata,
833
895
  schema,
896
+ shapes,
834
897
  storageRules: storageRulesMetadata,
835
898
  studioFeatures,
836
899
  useUmbrella,
837
900
  workflows
838
901
  });
902
+ const collectionsContent = emitCollections(shapes, dependencies.has("@lunora/db"), useUmbrella);
839
903
  const containersContent = emitContainers(containers, schema.jurisdiction);
840
904
  const workflowsContent = emitWorkflows(workflows);
841
905
  const queuesContent = emitQueues(queues);
@@ -909,6 +973,7 @@ const runCodegen = (options) => {
909
973
  writeIfPresent(join(outputDirectory, "workflows.ts"), workflowsContent);
910
974
  writeIfPresent(join(outputDirectory, "queues.ts"), queuesContent);
911
975
  writeIfPresent(join(outputDirectory, "seed.ts"), seedContent);
976
+ writeIfPresent(join(outputDirectory, "collections.ts"), collectionsContent);
912
977
  if (wantsOpenApi) {
913
978
  writeIfChanged(join(outputDirectory, "openapi.json"), openApiContent);
914
979
  writeIfChanged(join(outputDirectory, "openapi.ts"), openApiModuleContent);
@@ -935,6 +1000,7 @@ const runCodegen = (options) => {
935
1000
  generated: {
936
1001
  api: apiContent,
937
1002
  app: appContent,
1003
+ collections: collectionsContent,
938
1004
  containers: containersContent,
939
1005
  crons: cronsContent,
940
1006
  dataModel: dataModelContent,
@@ -0,0 +1,94 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { SyntaxKind, Node } from 'ts-morph';
4
+ import { diagnosticAt } from './CodegenDiagnosticError-DeblMkzO.mjs';
5
+
6
+ const SHAPES_FILENAME = "shapes.ts";
7
+ const SHAPE_MODULE_SPECIFIERS = /* @__PURE__ */ new Set(["@lunora/server", "lunorash/server"]);
8
+ const isDefineShape = (identifier) => {
9
+ const symbol = identifier.getSymbol();
10
+ if (!symbol) {
11
+ return identifier.getText() === "defineShape";
12
+ }
13
+ for (const declaration of symbol.getDeclarations()) {
14
+ if (!Node.isImportSpecifier(declaration)) {
15
+ continue;
16
+ }
17
+ if (!SHAPE_MODULE_SPECIFIERS.has(declaration.getImportDeclaration().getModuleSpecifierValue())) {
18
+ return false;
19
+ }
20
+ return declaration.getNameNode().getText() === "defineShape";
21
+ }
22
+ return false;
23
+ };
24
+ const isShapeNamespaceImport = (identifier) => {
25
+ const symbol = identifier.getSymbol();
26
+ if (!symbol) {
27
+ return false;
28
+ }
29
+ for (const declaration of symbol.getDeclarations()) {
30
+ if (!Node.isNamespaceImport(declaration)) {
31
+ continue;
32
+ }
33
+ const importDeclaration = declaration.getFirstAncestorByKind(SyntaxKind.ImportDeclaration);
34
+ return importDeclaration !== void 0 && SHAPE_MODULE_SPECIFIERS.has(importDeclaration.getModuleSpecifierValue());
35
+ }
36
+ return false;
37
+ };
38
+ const isDefineShapeCallee = (callee) => {
39
+ if (Node.isIdentifier(callee)) {
40
+ return isDefineShape(callee);
41
+ }
42
+ if (Node.isPropertyAccessExpression(callee)) {
43
+ const object = callee.getExpression();
44
+ return callee.getName() === "defineShape" && Node.isIdentifier(object) && isShapeNamespaceImport(object);
45
+ }
46
+ return false;
47
+ };
48
+ const tableLiteralFrom = (call) => {
49
+ const [config] = call.getArguments();
50
+ if (!config || !Node.isObjectLiteralExpression(config)) {
51
+ return void 0;
52
+ }
53
+ const tableProperty = config.getProperty("table");
54
+ if (!tableProperty || !Node.isPropertyAssignment(tableProperty)) {
55
+ return void 0;
56
+ }
57
+ const value = tableProperty.getInitializer();
58
+ return value && Node.isStringLiteral(value) ? value.getLiteralValue() : void 0;
59
+ };
60
+ const shapesFromSource = (source) => {
61
+ const shapes = [];
62
+ for (const declaration of source.getVariableDeclarations()) {
63
+ if (!declaration.isExported()) {
64
+ continue;
65
+ }
66
+ const initializer = declaration.getInitializer();
67
+ if (initializer?.getKind() !== SyntaxKind.CallExpression) {
68
+ continue;
69
+ }
70
+ const callExpression = initializer;
71
+ const callee = callExpression.getExpression();
72
+ if (!isDefineShapeCallee(callee)) {
73
+ continue;
74
+ }
75
+ const nameNode = declaration.getNameNode();
76
+ if (!Node.isIdentifier(nameNode)) {
77
+ throw diagnosticAt(nameNode, "defineShape exports must be plain named exports (no destructuring)");
78
+ }
79
+ shapes.push({ exportName: nameNode.getText(), filePath: "shapes", table: tableLiteralFrom(callExpression) });
80
+ }
81
+ return shapes;
82
+ };
83
+ const discoverShapes = (project, lunoraDirectory) => {
84
+ const shapesPath = join(lunoraDirectory, SHAPES_FILENAME);
85
+ if (!existsSync(shapesPath)) {
86
+ return [];
87
+ }
88
+ const source = project.getSourceFile(shapesPath) ?? project.addSourceFileAtPath(shapesPath);
89
+ const shapes = shapesFromSource(source);
90
+ shapes.sort((a, b) => a.exportName.localeCompare(b.exportName));
91
+ return shapes;
92
+ };
93
+
94
+ export { SHAPES_FILENAME, discoverShapes };
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-CkZUgM4_.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-BoVKLcnE.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
 
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-CkZUgM4_.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-BoVKLcnE.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`)."],
@@ -31,12 +31,16 @@ const toAdvisorSchema = (schema) => {
31
31
  references: relation.references,
32
32
  table: relation.table
33
33
  };
34
- })
34
+ }),
35
+ shardKind: typeof table.shardMode === "string" ? table.shardMode : "shardBy"
35
36
  };
36
37
  })
37
38
  };
38
39
  };
39
- const lintSchema = (schema, queries = [], inserts, authApiCalls, rlsProcedures, containers, workflows, workflowCalls, maskProcedures, nondeterministicCalls, procedureProtections, argumentValidators, secretLiterals, sqlInterpolations, adminRoutes, r2sqlCalls) => runAdvisor(
40
+ const toAdvisorShapes = (shapes) => shapes.map((shape) => {
41
+ return { exportName: shape.exportName, file: `lunora/${shape.filePath}.ts`, table: shape.table };
42
+ });
43
+ const lintSchema = (schema, queries = [], inserts, authApiCalls, rlsProcedures, containers, workflows, workflowCalls, maskProcedures, nondeterministicCalls, procedureProtections, argumentValidators, secretLiterals, sqlInterpolations, adminRoutes, r2sqlCalls, shapes, mutatorWrites) => runAdvisor(
40
44
  {
41
45
  adminRoutes,
42
46
  argValidators: argumentValidators,
@@ -44,6 +48,7 @@ const lintSchema = (schema, queries = [], inserts, authApiCalls, rlsProcedures,
44
48
  containers,
45
49
  inserts,
46
50
  maskProcedures,
51
+ mutatorWrites,
47
52
  nondeterministicCalls,
48
53
  procedureProtections,
49
54
  queries,
@@ -51,6 +56,7 @@ const lintSchema = (schema, queries = [], inserts, authApiCalls, rlsProcedures,
51
56
  rlsProcedures,
52
57
  schema: toAdvisorSchema(schema),
53
58
  secretLiterals,
59
+ shapes: shapes === void 0 ? void 0 : toAdvisorShapes(shapes),
54
60
  sqlInterpolations,
55
61
  workflowCalls,
56
62
  workflows
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/codegen",
3
- "version": "1.0.0-alpha.15",
3
+ "version": "1.0.0-alpha.17",
4
4
  "description": "Code generator for Lunora: emits _generated/{api,server,dataModel}.ts from your schema",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,8 +46,8 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/advisor": "1.0.0-alpha.7",
50
- "@lunora/container": "1.0.0-alpha.4",
49
+ "@lunora/advisor": "1.0.0-alpha.8",
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",
53
53
  "@lunora/values": "1.0.0-alpha.3",