@lunora/codegen 1.0.0-alpha.10 → 1.0.0-alpha.12

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
@@ -312,6 +312,37 @@ interface WorkflowIR {
312
312
  name: string;
313
313
  }
314
314
  /**
315
+ * A queue lifted from a `defineQueue()` export in `lunora/queues.ts`. Carries
316
+ * what the emitters and the config layer need to wire the typed `ctx.queues`
317
+ * producer, the generated worker `queue()` dispatch, and the wrangler
318
+ * `queues.producers[]` / `queues.consumers[]` entries. Like workflows, a queue
319
+ * is NOT a Durable Object — wrangler gets only `queues.*` entries. Names are
320
+ * derived via `@lunora/queue`'s shared helpers so codegen and the config layer
321
+ * can never disagree.
322
+ */
323
+ interface QueueIR {
324
+ /** The Cloudflare `Queue` producer binding name, e.g. `QUEUE_EMAIL`. */
325
+ bindingName: string;
326
+ /** The `lunora/queues.ts` export name, e.g. `emailQueue`. */
327
+ exportName: string;
328
+ /** How the queue is consumed: `"push"` (a worker `queue()` handler) or `"pull"` (external HTTP). */
329
+ mode: "pull" | "push";
330
+ /**
331
+ * The stable wrangler queue name (`queues.producers[].queue`). Defaults to
332
+ * the kebab-cased export name (`emailQueue` → `email-queue`); a static
333
+ * `name:` literal in the definition overrides it.
334
+ */
335
+ name: string;
336
+ /** Push-consumer batch/retry tuning, mirrored onto the wrangler `queues.consumers[]` entry. */
337
+ tuning: {
338
+ deadLetterQueue?: string;
339
+ maxBatchSize?: number;
340
+ maxBatchTimeout?: number;
341
+ maxRetries?: number;
342
+ retryDelay?: number;
343
+ };
344
+ }
345
+ /**
315
346
  * A `ctx.workflows.get("name")…` call discovered in a function body — the
316
347
  * use-site analog of {@link WorkflowIR} (which is the declaration side). Feeds
317
348
  * the `workflow_unused` lint (a declared workflow with zero call sites) and the
@@ -851,6 +882,15 @@ declare const discoverNondeterministicCalls: (project: Project, lunoraDirectory:
851
882
  * dropping the rest keeps the lint input small.
852
883
  */
853
884
  declare const discoverQueries: (project: Project, lunoraDirectory: string) => QueryReadIR[];
885
+ /** The only file queues may be declared in — mirrors `lunora/workflows.ts`. */
886
+ declare const QUEUES_FILENAME = "queues.ts";
887
+ /**
888
+ * Discover every queue the project declares: exported `defineQueue()` calls in
889
+ * `lunora/queues.ts`. Returns `[]` when the file doesn't exist. Only the
890
+ * wrangler-relevant literals (`name`/`mode`/batch tuning) are read; the handler
891
+ * body is runtime-only, so codegen never evaluates it.
892
+ */
893
+ declare const discoverQueues: (project: Project, lunoraDirectory: string) => QueueIR[];
854
894
  /**
855
895
  * Discover `ctx.r2sql` accesses lexically inside the handler body of every
856
896
  * exported `query(...)` / `mutation(...)` registration under the lunora source
@@ -925,21 +965,23 @@ declare const emitApi: (functions: ReadonlyArray<FunctionIR>, workflows?: Readon
925
965
  interface EmitServerOptions {
926
966
  containers?: ReadonlyArray<ContainerIR>;
927
967
  hasAi?: boolean;
928
- /** A `lunora/` source uses `@lunora/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
968
+ /** A `lunora/` source uses `@lunora/bindings/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
929
969
  hasAnalytics?: boolean;
930
970
  /** A `lunora/` source uses `@lunora/browser` / `ctx.browser` — wires `ctx.browser` onto ActionCtx only. */
931
971
  hasBrowser?: boolean;
932
972
  /** A `lunora/` source uses `@lunora/hyperdrive` / `ctx.sql` — wires `ctx.sql` onto ActionCtx only. */
933
973
  hasHyperdrive?: boolean;
934
- /** A `lunora/` source uses `@lunora/images` / `ctx.images` — wires `ctx.images` onto ActionCtx only. */
974
+ /** A `lunora/` source uses `@lunora/bindings/images` / `ctx.images` — wires `ctx.images` onto ActionCtx only. */
935
975
  hasImages?: boolean;
936
- /** A `lunora/` source uses `@lunora/kv` / `ctx.kv` — wires `ctx.kv` onto every ctx. */
976
+ /** A `lunora/` source uses `@lunora/bindings/kv` / `ctx.kv` — wires `ctx.kv` onto every ctx. */
937
977
  hasKv?: boolean;
938
978
  hasPayments?: boolean;
939
- /** A `lunora/` source uses `@lunora/pipelines` / `ctx.pipelines` — wires `ctx.pipelines` onto ActionCtx only. */
979
+ /** A `lunora/` source uses `@lunora/bindings/pipelines` / `ctx.pipelines` — wires `ctx.pipelines` onto ActionCtx only. */
940
980
  hasPipelines?: boolean;
941
- /** A `lunora/` source uses `@lunora/r2sql` / `ctx.r2sql` — wires `ctx.r2sql` onto ActionCtx only. */
981
+ /** A `lunora/` source uses `@lunora/bindings/r2sql` / `ctx.r2sql` — wires `ctx.r2sql` onto ActionCtx only. */
942
982
  hasR2sql?: boolean;
983
+ /** Queues declared via `defineQueue` exports — wires the typed `ctx.queues` producers onto Mutation/Action contexts. */
984
+ queues?: ReadonlyArray<QueueIR>;
943
985
  schema?: SchemaIR;
944
986
  storageRuleBuckets?: ReadonlyArray<string>;
945
987
  /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
@@ -957,6 +999,7 @@ declare const emitServer: ({
957
999
  hasPayments,
958
1000
  hasPipelines,
959
1001
  hasR2sql,
1002
+ queues,
960
1003
  schema,
961
1004
  storageRuleBuckets,
962
1005
  useUmbrella,
@@ -990,6 +1033,14 @@ declare const emitContainers: (containers: ReadonlyArray<ContainerIR>, jurisdict
990
1033
  * when the project declares no workflows (the file is not written then).
991
1034
  */
992
1035
  declare const emitWorkflows: (workflows: ReadonlyArray<WorkflowIR>) => string;
1036
+ /**
1037
+ * Emit `_generated/queues.ts` — the push-consumer registry the worker `queue()`
1038
+ * handler dispatches through. Maps each push queue's stable wrangler name (which
1039
+ * `batch.queue` carries) to its `defineQueue` definition + export name. Pull
1040
+ * queues are consumed by an external worker, so they carry no handler and are
1041
+ * omitted here. Returns "" (and the file is not written) when no push queues are
1042
+ * declared — a pull-only or queue-free app keeps a clean `_generated/`.
1043
+ */
993
1044
  interface EmitShardOptions {
994
1045
  advisories?: ReadonlyArray<Finding>;
995
1046
  containers?: ReadonlyArray<ContainerIR>;
@@ -1005,9 +1056,13 @@ interface EmitShardOptions {
1005
1056
  /** A `lunora/` source reads `ctx.kv` — wires `ctx.kv` onto every ctx. */
1006
1057
  hasKv?: boolean;
1007
1058
  hasPayments?: boolean;
1059
+ /** A `lunora/` source reads `ctx.pipelines` — wires `ctx.pipelines` onto the ActionCtx only. */
1060
+ hasPipelines?: boolean;
1008
1061
  /** A `lunora/` source reads `ctx.r2sql` (R2 SQL) — wires `ctx.r2sql` onto the ActionCtx only. */
1009
1062
  hasR2sql?: boolean;
1010
1063
  maskMetadata?: MaskMetadataIR;
1064
+ /** Queues declared via `defineQueue` exports in `lunora/queues.ts` — wires the typed `ctx.queues` producers. */
1065
+ queues?: ReadonlyArray<QueueIR>;
1011
1066
  rlsMetadata?: RlsMetadataIR;
1012
1067
  schema: SchemaIR;
1013
1068
  storageRules?: StorageRulesMetadataIR;
@@ -1026,8 +1081,10 @@ declare const emitShard: ({
1026
1081
  hasImages,
1027
1082
  hasKv,
1028
1083
  hasPayments,
1084
+ hasPipelines,
1029
1085
  hasR2sql,
1030
1086
  maskMetadata,
1087
+ queues,
1031
1088
  rlsMetadata,
1032
1089
  schema,
1033
1090
  storageRules,
@@ -1096,7 +1153,7 @@ declare const emitWranglerCronTriggers: (crons: ReadonlyArray<CronJobIR>) => str
1096
1153
  interface EmitAppOptions {
1097
1154
  /** App uses `@lunora/ai` / `ctx.ai` → emit `.ai()` (override the Workers AI binding backing `ctx.ai`). */
1098
1155
  hasAi: boolean;
1099
- /** App uses `@lunora/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
1156
+ /** App uses `@lunora/bindings/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
1100
1157
  hasAnalytics: boolean;
1101
1158
  /** App depends on `@lunora/auth` → emit `.auth()` + the lazy build/migrate dance. */
1102
1159
  hasAuth: boolean;
@@ -1110,13 +1167,15 @@ interface EmitAppOptions {
1110
1167
  hasHyperdrive: boolean;
1111
1168
  /** Schema declares **Hyperdrive-backed** `.global({ backend: "hyperdrive" })` tables → emit `.hyperdriveGlobal()` (reactive Postgres/MySQL ctx-db over Hyperdrive). */
1112
1169
  hasHyperdriveGlobal: boolean;
1113
- /** App uses `@lunora/images` / `ctx.images` → emit `.images()`. */
1170
+ /** App uses `@lunora/bindings/images` / `ctx.images` → emit `.images()`. */
1114
1171
  hasImages: boolean;
1115
- /** App uses `@lunora/kv` / `ctx.kv` → emit `.kv()`. */
1172
+ /** App uses `@lunora/bindings/kv` / `ctx.kv` → emit `.kv()`. */
1116
1173
  hasKv: boolean;
1117
1174
  /** App uses `@lunora/payment` / `ctx.payments` → emit `.payment()`. */
1118
1175
  hasPayments: boolean;
1119
- /** App uses `@lunora/r2sql` / `ctx.r2sql` → emit `.r2sql()`. */
1176
+ /** App declares push queues (`defineQueue`)wire `LUNORA_QUEUE_REGISTRY` into the worker's `queue()` consumer entry. */
1177
+ hasQueue: boolean;
1178
+ /** App uses `@lunora/bindings/r2sql` / `ctx.r2sql` → emit `.r2sql()`. */
1120
1179
  hasR2sql: boolean;
1121
1180
  /** App imports `@lunora/scheduler` / declares crons → emit `.scheduler()`. */
1122
1181
  hasScheduler: boolean;
@@ -1505,6 +1564,8 @@ interface CodegenResult {
1505
1564
  * `apiSpec` includes `openrpc`.
1506
1565
  */
1507
1566
  openRpcModule: string;
1567
+ /** Push-consumer queue registry (`_generated/queues.ts`); `""` (and not written) when no push queues are declared. */
1568
+ queues: string;
1508
1569
  /** Project-bound seed client (`_generated/seed.ts`); `""` (and not written) when `@lunora/seed` is not a declared dependency. */
1509
1570
  seed: string;
1510
1571
  server: string;
@@ -1515,6 +1576,13 @@ interface CodegenResult {
1515
1576
  };
1516
1577
  outputDirectory: string;
1517
1578
  /**
1579
+ * Queues discovered from `defineQueue` exports in `lunora/queues.ts` — the
1580
+ * list the config layer reconciles into wrangler's `queues.producers[]` /
1581
+ * `queues.consumers[]`. Queues are NOT Durable Objects, so this adds no
1582
+ * binding or migration. Empty when the project declares no queues.
1583
+ */
1584
+ queues: ReadonlyArray<QueueIR>;
1585
+ /**
1518
1586
  * The CURRENT structural schema snapshot computed this run (tables + field
1519
1587
  * kinds/optionality + indexes/relations/shard mode + declared migration ids).
1520
1588
  * The pre-deploy drift gate diffs this against the committed baseline read
@@ -1559,4 +1627,4 @@ declare const validatorIrToJsonSchema: (validator: ValidatorIR) => JsonSchema;
1559
1627
  */
1560
1628
  declare const LUNORA_ERROR_CODES: ReadonlyArray<string>;
1561
1629
  declare const VERSION = "0.0.0";
1562
- export { type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, type FieldSnapshot, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type MaskProcedureIR, type MigrationIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, type QueryReadIR, 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, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverNondeterministicCalls, discoverQueries, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSchema, discoverStorageRulesMetadata, discoverWorkflows, emitApi, emitApp, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, formatAdvisories, lintSchema, parseSchemaSnapshot, refreshCodegenProject, runCodegen, schemaFromIr, serializeSchemaSnapshot, validatorIrToJsonSchema };
1630
+ export { type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, type FieldSnapshot, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, 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, 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, formatAdvisories, lintSchema, parseSchemaSnapshot, refreshCodegenProject, runCodegen, schemaFromIr, serializeSchemaSnapshot, validatorIrToJsonSchema };
package/dist/index.d.ts CHANGED
@@ -312,6 +312,37 @@ interface WorkflowIR {
312
312
  name: string;
313
313
  }
314
314
  /**
315
+ * A queue lifted from a `defineQueue()` export in `lunora/queues.ts`. Carries
316
+ * what the emitters and the config layer need to wire the typed `ctx.queues`
317
+ * producer, the generated worker `queue()` dispatch, and the wrangler
318
+ * `queues.producers[]` / `queues.consumers[]` entries. Like workflows, a queue
319
+ * is NOT a Durable Object — wrangler gets only `queues.*` entries. Names are
320
+ * derived via `@lunora/queue`'s shared helpers so codegen and the config layer
321
+ * can never disagree.
322
+ */
323
+ interface QueueIR {
324
+ /** The Cloudflare `Queue` producer binding name, e.g. `QUEUE_EMAIL`. */
325
+ bindingName: string;
326
+ /** The `lunora/queues.ts` export name, e.g. `emailQueue`. */
327
+ exportName: string;
328
+ /** How the queue is consumed: `"push"` (a worker `queue()` handler) or `"pull"` (external HTTP). */
329
+ mode: "pull" | "push";
330
+ /**
331
+ * The stable wrangler queue name (`queues.producers[].queue`). Defaults to
332
+ * the kebab-cased export name (`emailQueue` → `email-queue`); a static
333
+ * `name:` literal in the definition overrides it.
334
+ */
335
+ name: string;
336
+ /** Push-consumer batch/retry tuning, mirrored onto the wrangler `queues.consumers[]` entry. */
337
+ tuning: {
338
+ deadLetterQueue?: string;
339
+ maxBatchSize?: number;
340
+ maxBatchTimeout?: number;
341
+ maxRetries?: number;
342
+ retryDelay?: number;
343
+ };
344
+ }
345
+ /**
315
346
  * A `ctx.workflows.get("name")…` call discovered in a function body — the
316
347
  * use-site analog of {@link WorkflowIR} (which is the declaration side). Feeds
317
348
  * the `workflow_unused` lint (a declared workflow with zero call sites) and the
@@ -851,6 +882,15 @@ declare const discoverNondeterministicCalls: (project: Project, lunoraDirectory:
851
882
  * dropping the rest keeps the lint input small.
852
883
  */
853
884
  declare const discoverQueries: (project: Project, lunoraDirectory: string) => QueryReadIR[];
885
+ /** The only file queues may be declared in — mirrors `lunora/workflows.ts`. */
886
+ declare const QUEUES_FILENAME = "queues.ts";
887
+ /**
888
+ * Discover every queue the project declares: exported `defineQueue()` calls in
889
+ * `lunora/queues.ts`. Returns `[]` when the file doesn't exist. Only the
890
+ * wrangler-relevant literals (`name`/`mode`/batch tuning) are read; the handler
891
+ * body is runtime-only, so codegen never evaluates it.
892
+ */
893
+ declare const discoverQueues: (project: Project, lunoraDirectory: string) => QueueIR[];
854
894
  /**
855
895
  * Discover `ctx.r2sql` accesses lexically inside the handler body of every
856
896
  * exported `query(...)` / `mutation(...)` registration under the lunora source
@@ -925,21 +965,23 @@ declare const emitApi: (functions: ReadonlyArray<FunctionIR>, workflows?: Readon
925
965
  interface EmitServerOptions {
926
966
  containers?: ReadonlyArray<ContainerIR>;
927
967
  hasAi?: boolean;
928
- /** A `lunora/` source uses `@lunora/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
968
+ /** A `lunora/` source uses `@lunora/bindings/analytics` / `ctx.analytics` — wires the write helper onto every ctx. */
929
969
  hasAnalytics?: boolean;
930
970
  /** A `lunora/` source uses `@lunora/browser` / `ctx.browser` — wires `ctx.browser` onto ActionCtx only. */
931
971
  hasBrowser?: boolean;
932
972
  /** A `lunora/` source uses `@lunora/hyperdrive` / `ctx.sql` — wires `ctx.sql` onto ActionCtx only. */
933
973
  hasHyperdrive?: boolean;
934
- /** A `lunora/` source uses `@lunora/images` / `ctx.images` — wires `ctx.images` onto ActionCtx only. */
974
+ /** A `lunora/` source uses `@lunora/bindings/images` / `ctx.images` — wires `ctx.images` onto ActionCtx only. */
935
975
  hasImages?: boolean;
936
- /** A `lunora/` source uses `@lunora/kv` / `ctx.kv` — wires `ctx.kv` onto every ctx. */
976
+ /** A `lunora/` source uses `@lunora/bindings/kv` / `ctx.kv` — wires `ctx.kv` onto every ctx. */
937
977
  hasKv?: boolean;
938
978
  hasPayments?: boolean;
939
- /** A `lunora/` source uses `@lunora/pipelines` / `ctx.pipelines` — wires `ctx.pipelines` onto ActionCtx only. */
979
+ /** A `lunora/` source uses `@lunora/bindings/pipelines` / `ctx.pipelines` — wires `ctx.pipelines` onto ActionCtx only. */
940
980
  hasPipelines?: boolean;
941
- /** A `lunora/` source uses `@lunora/r2sql` / `ctx.r2sql` — wires `ctx.r2sql` onto ActionCtx only. */
981
+ /** A `lunora/` source uses `@lunora/bindings/r2sql` / `ctx.r2sql` — wires `ctx.r2sql` onto ActionCtx only. */
942
982
  hasR2sql?: boolean;
983
+ /** Queues declared via `defineQueue` exports — wires the typed `ctx.queues` producers onto Mutation/Action contexts. */
984
+ queues?: ReadonlyArray<QueueIR>;
943
985
  schema?: SchemaIR;
944
986
  storageRuleBuckets?: ReadonlyArray<string>;
945
987
  /** The project depends on the `lunora` umbrella — import base packages via its subpaths. */
@@ -957,6 +999,7 @@ declare const emitServer: ({
957
999
  hasPayments,
958
1000
  hasPipelines,
959
1001
  hasR2sql,
1002
+ queues,
960
1003
  schema,
961
1004
  storageRuleBuckets,
962
1005
  useUmbrella,
@@ -990,6 +1033,14 @@ declare const emitContainers: (containers: ReadonlyArray<ContainerIR>, jurisdict
990
1033
  * when the project declares no workflows (the file is not written then).
991
1034
  */
992
1035
  declare const emitWorkflows: (workflows: ReadonlyArray<WorkflowIR>) => string;
1036
+ /**
1037
+ * Emit `_generated/queues.ts` — the push-consumer registry the worker `queue()`
1038
+ * handler dispatches through. Maps each push queue's stable wrangler name (which
1039
+ * `batch.queue` carries) to its `defineQueue` definition + export name. Pull
1040
+ * queues are consumed by an external worker, so they carry no handler and are
1041
+ * omitted here. Returns "" (and the file is not written) when no push queues are
1042
+ * declared — a pull-only or queue-free app keeps a clean `_generated/`.
1043
+ */
993
1044
  interface EmitShardOptions {
994
1045
  advisories?: ReadonlyArray<Finding>;
995
1046
  containers?: ReadonlyArray<ContainerIR>;
@@ -1005,9 +1056,13 @@ interface EmitShardOptions {
1005
1056
  /** A `lunora/` source reads `ctx.kv` — wires `ctx.kv` onto every ctx. */
1006
1057
  hasKv?: boolean;
1007
1058
  hasPayments?: boolean;
1059
+ /** A `lunora/` source reads `ctx.pipelines` — wires `ctx.pipelines` onto the ActionCtx only. */
1060
+ hasPipelines?: boolean;
1008
1061
  /** A `lunora/` source reads `ctx.r2sql` (R2 SQL) — wires `ctx.r2sql` onto the ActionCtx only. */
1009
1062
  hasR2sql?: boolean;
1010
1063
  maskMetadata?: MaskMetadataIR;
1064
+ /** Queues declared via `defineQueue` exports in `lunora/queues.ts` — wires the typed `ctx.queues` producers. */
1065
+ queues?: ReadonlyArray<QueueIR>;
1011
1066
  rlsMetadata?: RlsMetadataIR;
1012
1067
  schema: SchemaIR;
1013
1068
  storageRules?: StorageRulesMetadataIR;
@@ -1026,8 +1081,10 @@ declare const emitShard: ({
1026
1081
  hasImages,
1027
1082
  hasKv,
1028
1083
  hasPayments,
1084
+ hasPipelines,
1029
1085
  hasR2sql,
1030
1086
  maskMetadata,
1087
+ queues,
1031
1088
  rlsMetadata,
1032
1089
  schema,
1033
1090
  storageRules,
@@ -1096,7 +1153,7 @@ declare const emitWranglerCronTriggers: (crons: ReadonlyArray<CronJobIR>) => str
1096
1153
  interface EmitAppOptions {
1097
1154
  /** App uses `@lunora/ai` / `ctx.ai` → emit `.ai()` (override the Workers AI binding backing `ctx.ai`). */
1098
1155
  hasAi: boolean;
1099
- /** App uses `@lunora/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
1156
+ /** App uses `@lunora/bindings/analytics` / `ctx.analytics` → emit `.analytics()` (override the dataset backing `ctx.analytics`). */
1100
1157
  hasAnalytics: boolean;
1101
1158
  /** App depends on `@lunora/auth` → emit `.auth()` + the lazy build/migrate dance. */
1102
1159
  hasAuth: boolean;
@@ -1110,13 +1167,15 @@ interface EmitAppOptions {
1110
1167
  hasHyperdrive: boolean;
1111
1168
  /** Schema declares **Hyperdrive-backed** `.global({ backend: "hyperdrive" })` tables → emit `.hyperdriveGlobal()` (reactive Postgres/MySQL ctx-db over Hyperdrive). */
1112
1169
  hasHyperdriveGlobal: boolean;
1113
- /** App uses `@lunora/images` / `ctx.images` → emit `.images()`. */
1170
+ /** App uses `@lunora/bindings/images` / `ctx.images` → emit `.images()`. */
1114
1171
  hasImages: boolean;
1115
- /** App uses `@lunora/kv` / `ctx.kv` → emit `.kv()`. */
1172
+ /** App uses `@lunora/bindings/kv` / `ctx.kv` → emit `.kv()`. */
1116
1173
  hasKv: boolean;
1117
1174
  /** App uses `@lunora/payment` / `ctx.payments` → emit `.payment()`. */
1118
1175
  hasPayments: boolean;
1119
- /** App uses `@lunora/r2sql` / `ctx.r2sql` → emit `.r2sql()`. */
1176
+ /** App declares push queues (`defineQueue`)wire `LUNORA_QUEUE_REGISTRY` into the worker's `queue()` consumer entry. */
1177
+ hasQueue: boolean;
1178
+ /** App uses `@lunora/bindings/r2sql` / `ctx.r2sql` → emit `.r2sql()`. */
1120
1179
  hasR2sql: boolean;
1121
1180
  /** App imports `@lunora/scheduler` / declares crons → emit `.scheduler()`. */
1122
1181
  hasScheduler: boolean;
@@ -1505,6 +1564,8 @@ interface CodegenResult {
1505
1564
  * `apiSpec` includes `openrpc`.
1506
1565
  */
1507
1566
  openRpcModule: string;
1567
+ /** Push-consumer queue registry (`_generated/queues.ts`); `""` (and not written) when no push queues are declared. */
1568
+ queues: string;
1508
1569
  /** Project-bound seed client (`_generated/seed.ts`); `""` (and not written) when `@lunora/seed` is not a declared dependency. */
1509
1570
  seed: string;
1510
1571
  server: string;
@@ -1515,6 +1576,13 @@ interface CodegenResult {
1515
1576
  };
1516
1577
  outputDirectory: string;
1517
1578
  /**
1579
+ * Queues discovered from `defineQueue` exports in `lunora/queues.ts` — the
1580
+ * list the config layer reconciles into wrangler's `queues.producers[]` /
1581
+ * `queues.consumers[]`. Queues are NOT Durable Objects, so this adds no
1582
+ * binding or migration. Empty when the project declares no queues.
1583
+ */
1584
+ queues: ReadonlyArray<QueueIR>;
1585
+ /**
1518
1586
  * The CURRENT structural schema snapshot computed this run (tables + field
1519
1587
  * kinds/optionality + indexes/relations/shard mode + declared migration ids).
1520
1588
  * The pre-deploy drift gate diffs this against the committed baseline read
@@ -1559,4 +1627,4 @@ declare const validatorIrToJsonSchema: (validator: ValidatorIR) => JsonSchema;
1559
1627
  */
1560
1628
  declare const LUNORA_ERROR_CODES: ReadonlyArray<string>;
1561
1629
  declare const VERSION = "0.0.0";
1562
- export { type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, type FieldSnapshot, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, type MaskProcedureIR, type MigrationIR, OPENRPC_VERSION, type OpenApiEmitInput, type OpenRpcEmitInput, type ProjectIR, type QueryReadIR, 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, discoverFunctions, discoverHttpRoutes, discoverInserts, discoverMaskProcedures, discoverMigrations, discoverNondeterministicCalls, discoverQueries, discoverR2sqlCalls, discoverRlsMetadata, discoverRlsProcedures, discoverSchema, discoverStorageRulesMetadata, discoverWorkflows, emitApi, emitApp, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitOpenApi, emitOpenApiModule, emitOpenRpc, emitOpenRpcModule, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers, evaluateSchemaDrift, formatAdvisories, lintSchema, parseSchemaSnapshot, refreshCodegenProject, runCodegen, schemaFromIr, serializeSchemaSnapshot, validatorIrToJsonSchema };
1630
+ export { type AuthApiCallIR, CONTAINERS_FILENAME, CodegenDiagnosticError, type CodegenOptions, type CodegenResult, type ContainerIR, type CronJobIR, type DriftChange, type EmitAppOptions, type FieldSnapshot, type FunctionIR, GENERATED_HEADER, type HttpRouteIR, type IndexIR, type IndexSnapshot, type InsertWriteIR, LUNORA_ERROR_CODES, 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, 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, formatAdvisories, lintSchema, parseSchemaSnapshot, refreshCodegenProject, runCodegen, schemaFromIr, serializeSchemaSnapshot, validatorIrToJsonSchema };
package/dist/index.mjs CHANGED
@@ -10,16 +10,17 @@ export { default as discoverMaskProcedures } from './packem_shared/discoverMaskP
10
10
  export { default as discoverMigrations } from './packem_shared/discoverMigrations-Bi5nJ0mJ.mjs';
11
11
  export { default as discoverNondeterministicCalls } from './packem_shared/discoverNondeterministicCalls-C4M8AXmQ.mjs';
12
12
  export { default as discoverQueries } from './packem_shared/discoverQueries-B0wGT-xe.mjs';
13
+ export { QUEUES_FILENAME, discoverQueues } from './packem_shared/QUEUES_FILENAME-B5_eWCRe.mjs';
13
14
  export { default as discoverR2sqlCalls } from './packem_shared/discoverR2sqlCalls-BVNMd428.mjs';
14
15
  export { discoverRlsMetadata, default as discoverRlsProcedures } from './packem_shared/discoverRlsMetadata-BS9GOGC5.mjs';
15
16
  export { default as discoverSchema } from './packem_shared/discoverSchema-BnWHHJ4T.mjs';
16
17
  export { default as discoverStorageRulesMetadata } from './packem_shared/discoverStorageRulesMetadata-Da8BKXcI.mjs';
17
18
  export { WORKFLOWS_FILENAME, discoverWorkflows } from './packem_shared/WORKFLOWS_FILENAME-D62dcBGg.mjs';
18
- export { GENERATED_HEADER, emitApi, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers } from './packem_shared/GENERATED_HEADER-BiFXNUvo.mjs';
19
- export { emitApp } from './packem_shared/emitApp-KfMaKXWe.mjs';
20
- export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-Cu3mZl-8.mjs';
21
- export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-B4i9Qp3v.mjs';
22
- export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-Bvv7qa_u.mjs';
19
+ export { GENERATED_HEADER, emitApi, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers } from './packem_shared/GENERATED_HEADER-BEA7oWGC.mjs';
20
+ export { emitApp } from './packem_shared/emitApp-DiK0QSBn.mjs';
21
+ export { buildOpenApiDocument, emitOpenApi, emitOpenApiModule } from './packem_shared/buildOpenApiDocument-699G4RP4.mjs';
22
+ export { OPENRPC_VERSION, buildOpenRpcDocument, emitOpenRpc, emitOpenRpcModule } from './packem_shared/OPENRPC_VERSION-CQ3f8YQ-.mjs';
23
+ export { SCHEMA_SNAPSHOT_FILENAME, createCodegenProject, refreshCodegenProject, runCodegen } from './packem_shared/SCHEMA_SNAPSHOT_FILENAME-BXtduJFE.mjs';
23
24
  export { SCHEMA_SNAPSHOT_VERSION, SchemaSnapshotParseError, buildSchemaSnapshot, diffSchemaSnapshots, evaluateSchemaDrift, parseSchemaSnapshot, serializeSchemaSnapshot } from './packem_shared/SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs';
24
25
  export { schemaFromIr } from './packem_shared/schemaFromIr-DTYsLBaA.mjs';
25
26
  export { LUNORA_ERROR_CODES, validatorIrToJsonSchema } from './packem_shared/LUNORA_ERROR_CODES-CySpQPD3.mjs';
@@ -722,6 +722,7 @@ const emitServer = ({
722
722
  hasPayments = false,
723
723
  hasPipelines = false,
724
724
  hasR2sql = false,
725
+ queues = [],
725
726
  schema,
726
727
  storageRuleBuckets = [],
727
728
  useUmbrella = false,
@@ -755,6 +756,11 @@ const emitServer = ({
755
756
  assertIdentifier(workflow.bindingName, `workflow binding "${workflow.bindingName}"`);
756
757
  return ` /** Workflow binding for the \`${workflow.exportName}\` workflow. */
757
758
  readonly ${workflow.bindingName}?: unknown;`;
759
+ }),
760
+ ...queues.map((queue) => {
761
+ assertIdentifier(queue.bindingName, `queue binding "${queue.bindingName}"`);
762
+ return ` /** Queue producer binding for the \`${queue.exportName}\` queue. */
763
+ readonly ${queue.bindingName}?: unknown;`;
758
764
  })
759
765
  ].join("\n");
760
766
  const envBlock = `
@@ -777,7 +783,7 @@ ${envBindingFields}` : ""}
777
783
  /** Alias for {@link CloudflareBindings} — the typed shape of \`env\`. */
778
784
  export type Env = CloudflareBindings;`;
779
785
  const kvContextField = hasKv ? `
780
- readonly kv: import("@lunora/kv").Kv;` : "";
786
+ readonly kv: import("@lunora/bindings/kv").Kv;` : "";
781
787
  const hyperdriveActionField = hasHyperdrive ? `
782
788
  /**
783
789
  * External database access via Hyperdrive. Non-deterministic — available only in actions. Writes here are NOT tracked by Lunora live queries; subscriptions will not re-run on external DB changes.
@@ -788,18 +794,18 @@ export type Env = CloudflareBindings;`;
788
794
  readonly browser: import("@lunora/browser").Browser;` : "";
789
795
  const imagesActionField = hasImages ? `
790
796
  /** Cloudflare Images transforms (resize/format/optimize). Non-deterministic — available only in actions. */
791
- readonly images: import("@lunora/images").Images;` : "";
797
+ readonly images: import("@lunora/bindings/images").Images;` : "";
792
798
  const analyticsContextField = hasAnalytics ? `
793
799
  /** Analytics Engine telemetry sink. Fire-and-forget and sampled; do not read it back in-handler. */
794
- readonly analytics: import("@lunora/analytics").AnalyticsClient;` : "";
800
+ readonly analytics: import("@lunora/bindings/analytics").AnalyticsClient;` : "";
795
801
  const pipelinesActionField = hasPipelines ? `
796
802
  /** Pipelines ingestion sink (durable, R2-backed). Fire-and-forget and batched; do not read it back in-handler. */
797
- readonly pipelines: import("@lunora/pipelines").PipelineClient;` : "";
803
+ readonly pipelines: import("@lunora/bindings/pipelines").PipelineClient;` : "";
798
804
  const r2sqlActionField = hasR2sql ? `
799
805
  /**
800
806
  * R2 SQL over Apache Iceberg tables (window functions, DISTINCT, set operations). Non-deterministic — available only in actions. Reads here are NOT tracked by Lunora live queries.
801
807
  */
802
- readonly r2sql: import("@lunora/r2sql").R2SqlClient;` : "";
808
+ readonly r2sql: import("@lunora/bindings/r2sql").R2SqlClient;` : "";
803
809
  const hasWorkflows = workflows.length > 0;
804
810
  const workflowsTypeImport = hasWorkflows ? `import type { WorkflowHandle } from "@lunora/workflow";
805
811
  import type * as lunoraWorkflowDefinitions from "../workflows.js";
@@ -820,6 +826,21 @@ ${workflows.map((workflow) => ` get(name: ${JSON.stringify(workflow.exportNam
820
826
  const workflowsOmit = hasWorkflows ? ` | "workflows"` : "";
821
827
  const workflowsContextField = hasWorkflows ? `
822
828
  readonly workflows: LunoraWorkflows;` : "";
829
+ const hasQueues = queues.length > 0;
830
+ const queuesTypeImport = hasQueues ? `import type { QueueProducer } from "@lunora/queue";
831
+ import type * as lunoraQueueDefinitions from "../queues.js";
832
+ ` : "";
833
+ const queuesTypeBlock = hasQueues ? `
834
+
835
+ /** Message body type carried by a \`defineQueue\` definition (its phantom \`__lunoraBody\`). */
836
+ type QueueBodyOf<Definition> = Definition extends { __lunoraBody?: infer Body } ? (unknown extends Body ? unknown : NonNullable<Body>) : unknown;
837
+
838
+ /** This project's declared queues, addressable from \`ctx.queues\` by their \`lunora/queues.ts\` export name. */
839
+ export interface LunoraQueues {
840
+ ${queues.map((queue) => ` readonly ${queue.exportName}: QueueProducer<QueueBodyOf<typeof lunoraQueueDefinitions.${queue.exportName}>>;`).join("\n")}
841
+ }` : "";
842
+ const queuesContextField = hasQueues ? `
843
+ readonly queues: LunoraQueues;` : "";
823
844
  const server = `${GENERATED_HEADER}import { createPolicyDsl, initLunora, v as vBase } from "${base.server}";
824
845
  import type {
825
846
  ActionBuilder,
@@ -841,11 +862,11 @@ import type {
841
862
  } from "${base.server}";
842
863
 
843
864
  import type { DataModel, DatabaseReaderFacade, DatabaseWriterFacade, Doc, Id as IdOfTable, OrmReader, OrmWriter, Relations, TableName } from "./dataModel.js";
844
- ${aiTypeImport}${paymentsTypeImport}${containersTypeImport}${workflowsTypeImport}
865
+ ${aiTypeImport}${paymentsTypeImport}${containersTypeImport}${workflowsTypeImport}${queuesTypeImport}
845
866
  export type { DataModel, Doc, Id, TableName } from "./dataModel.js";
846
867
 
847
868
  /** Storage buckets this schema declares (\`v.storage("name")\`), narrowing \`ctx.storage.bucket(name)\`. */
848
- export type StorageBucketName = ${storageBucketUnion};${envBlock}${workflowsTypeBlock}
869
+ export type StorageBucketName = ${storageBucketUnion};${envBlock}${workflowsTypeBlock}${queuesTypeBlock}
849
870
 
850
871
  /**
851
872
  * Project-typed contexts. The base contexts from \`@lunora/server\` are
@@ -883,13 +904,13 @@ export interface QueryCtx extends Omit<QueryCtxBase, "db" | "storage"> {
883
904
  export interface MutationCtx extends Omit<MutationCtxBase, "db" | "storage"${workflowsOmit}> {
884
905
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
885
906
  readonly orm: OrmWriter;
886
- readonly storage: ReadOnlyStorage<StorageBucketName>;${kvContextField}${analyticsContextField}${workflowsContextField}
907
+ readonly storage: ReadOnlyStorage<StorageBucketName>;${kvContextField}${analyticsContextField}${workflowsContextField}${queuesContextField}
887
908
  }
888
909
 
889
910
  export interface ActionCtx extends Omit<ActionCtxBase, "db" | "storage"${workflowsOmit}> {
890
911
  readonly db: Omit<DatabaseWriter, "query" | "get"> & DatabaseWriterFacade & { query: TypedTableQuery; get: TypedTableGet };
891
912
  readonly orm: OrmWriter;
892
- readonly storage: StorageBase<StorageBucketName>;${aiActionField}${paymentsActionField}${containersActionField}${kvContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${r2sqlActionField}${workflowsContextField}
913
+ readonly storage: StorageBase<StorageBucketName>;${aiActionField}${paymentsActionField}${containersActionField}${kvContextField}${hyperdriveActionField}${browserActionField}${imagesActionField}${analyticsContextField}${pipelinesActionField}${r2sqlActionField}${workflowsContextField}${queuesContextField}
893
914
  }
894
915
 
895
916
  /**
@@ -1235,7 +1256,7 @@ const emitKvFragments = (hasKv) => {
1235
1256
  kv?: (env: Record<string, unknown>) => KVNamespaceLike;`,
1236
1257
  contextField: `
1237
1258
  kv,`,
1238
- importLines: [`import type { Kv, KVNamespaceLike } from "@lunora/kv";`, `import { createKv } from "@lunora/kv";`],
1259
+ importLines: [`import type { Kv, KVNamespaceLike } from "@lunora/bindings/kv";`, `import { createKv } from "@lunora/bindings/kv";`],
1239
1260
  stub: `
1240
1261
  const kvStub: Kv = {
1241
1262
  delete: async () => {
@@ -1275,8 +1296,8 @@ const emitAnalyticsFragments = (hasAnalytics) => {
1275
1296
  contextField: `
1276
1297
  analytics,`,
1277
1298
  importLines: [
1278
- `import type { AnalyticsClient, AnalyticsEngineDatasetLike } from "@lunora/analytics";`,
1279
- `import { createAnalytics } from "@lunora/analytics";`
1299
+ `import type { AnalyticsClient, AnalyticsEngineDatasetLike } from "@lunora/bindings/analytics";`,
1300
+ `import { createAnalytics } from "@lunora/bindings/analytics";`
1280
1301
  ],
1281
1302
  stub: `
1282
1303
  const analyticsStub: AnalyticsClient = {
@@ -1305,7 +1326,7 @@ const emitImagesFragments = (hasImages) => {
1305
1326
  // ActionCtx-only: woven onto the action ctx object, never query/mutation.
1306
1327
  contextField: `
1307
1328
  images,`,
1308
- importLines: [`import type { Images, ImagesBindingLike } from "@lunora/images";`, `import { createImages } from "@lunora/images";`],
1329
+ importLines: [`import type { Images, ImagesBindingLike } from "@lunora/bindings/images";`, `import { createImages } from "@lunora/bindings/images";`],
1309
1330
  stub: `
1310
1331
  const imagesStub: Images = {
1311
1332
  info: async () => {
@@ -1404,10 +1425,10 @@ const emitR2sqlFragments = (hasR2sql) => {
1404
1425
  // ActionCtx-only: attached via the \`ctx.r2sql = r2sql\` assignment in the
1405
1426
  // \`isAction\` block, never the every-ctx object literal.
1406
1427
  contextField: "",
1407
- importLines: [`import type { R2SqlClient } from "@lunora/r2sql";`, `import { createR2Sql } from "@lunora/r2sql";`],
1428
+ importLines: [`import type { R2SqlClient } from "@lunora/bindings/r2sql";`, `import { createR2Sql } from "@lunora/bindings/r2sql";`],
1408
1429
  // The stub is typed `R2SqlClient`, so TS flags a missing method at build
1409
1430
  // time — but it must stay structurally in sync with that interface
1410
- // (`@lunora/r2sql` client.ts) when a method is added there.
1431
+ // (`@lunora/bindings/r2sql` client.ts) when a method is added there.
1411
1432
  stub: `
1412
1433
  const r2sqlStub: R2SqlClient = {
1413
1434
  describe: async () => {
@@ -1432,6 +1453,34 @@ const r2sqlStub: R2SqlClient = {
1432
1453
  `
1433
1454
  };
1434
1455
  };
1456
+ const emitPipelinesFragments = (hasPipelines) => {
1457
+ if (!hasPipelines) {
1458
+ return EMPTY_HELPER_FRAGMENTS;
1459
+ }
1460
+ const pipelinesMissing = `throw new Error("ctx.pipelines: no Pipelines binding found. Add a \\\`pipelines\\\` binding (env.PIPELINES) to wrangler.jsonc, or pass \\\`pipelines\\\` to createShardDO().");`;
1461
+ return {
1462
+ build: `
1463
+ const pipelinesBinding = config.pipelines?.(env) ?? (env as Record<string, unknown>).PIPELINES;
1464
+ const pipelines: PipelineClient = pipelinesBinding ? createPipelines({ binding: pipelinesBinding as PipelineBindingLike }) : pipelinesStub;
1465
+ `,
1466
+ configField: `
1467
+ pipelines?: (env: Record<string, unknown>) => PipelineBindingLike;`,
1468
+ // ActionCtx-only: attached via the \`ctx.pipelines = pipelines\` assignment
1469
+ // in the \`isAction\` block, never the every-ctx object literal.
1470
+ contextField: "",
1471
+ importLines: [
1472
+ `import type { PipelineBindingLike, PipelineClient } from "@lunora/bindings/pipelines";`,
1473
+ `import { createPipelines } from "@lunora/bindings/pipelines";`
1474
+ ],
1475
+ stub: `
1476
+ const pipelinesStub: PipelineClient = {
1477
+ send: async () => {
1478
+ ${pipelinesMissing}
1479
+ },
1480
+ };
1481
+ `
1482
+ };
1483
+ };
1435
1484
  const emitContainers = (containers, jurisdiction) => {
1436
1485
  if (containers.length === 0) {
1437
1486
  return "";
@@ -1455,11 +1504,18 @@ export class ${container.className} extends LunoraContainer {
1455
1504
  * requires each \`containers[].class_name\` to be exported by the worker:
1456
1505
  *
1457
1506
  * \`export * from "./lunora/_generated/containers.js";\`
1507
+ *
1508
+ * \`ContainerProxy\` is re-exported alongside them: the egress-interception path
1509
+ * (\`allowedHosts\`/\`deniedHosts\`/\`interceptHttps\` and the runtime
1510
+ * \`handle.egress\` controls) routes container outbound traffic through this
1511
+ * WorkerEntrypoint, so it too must be exported by the deployed worker.
1458
1512
  */
1459
- import LunoraContainer from "@lunora/container/do";
1513
+ import { LunoraContainer } from "@lunora/container/do";
1460
1514
 
1461
1515
  import { ${imports} } from "../containers.js";
1462
1516
 
1517
+ export { ContainerProxy } from "@lunora/container/do";
1518
+
1463
1519
  ${classes}`;
1464
1520
  };
1465
1521
  const emitContainerFragments = (containers, jurisdiction) => {
@@ -1532,6 +1588,32 @@ type WorkflowOutputOf<Definition> = Definition extends { __output?: infer Output
1532
1588
 
1533
1589
  ${classes}`;
1534
1590
  };
1591
+ const emitQueues = (queues) => {
1592
+ const pushQueues = queues.filter((queue) => queue.mode === "push");
1593
+ if (pushQueues.length === 0) {
1594
+ return "";
1595
+ }
1596
+ for (const queue of pushQueues) {
1597
+ assertIdentifier(queue.exportName, `queue export "${queue.exportName}"`);
1598
+ }
1599
+ const imports = pushQueues.map((queue) => queue.exportName).join(", ");
1600
+ const entries = pushQueues.map((queue) => ` ${JSON.stringify(queue.name)}: { definition: ${queue.exportName}, exportName: ${JSON.stringify(queue.exportName)} },`).join("\n");
1601
+ return `${GENERATED_HEADER}/**
1602
+ * Push-consumer registry for the queues declared in \`lunora/queues.ts\`. The
1603
+ * composed worker's \`queue(batch, env, ctx)\` entry routes each delivered batch
1604
+ * by \`batch.queue\` to the matching \`defineQueue\` handler. Wired automatically
1605
+ * by \`defineApp\` — you don't import this directly.
1606
+ */
1607
+ import type { QueueRegistry } from "@lunora/queue";
1608
+
1609
+ import { ${imports} } from "../queues.js";
1610
+
1611
+ /** Stable wrangler queue name → { definition, exportName } for batch routing. */
1612
+ export const LUNORA_QUEUE_REGISTRY: QueueRegistry = {
1613
+ ${entries}
1614
+ };
1615
+ `;
1616
+ };
1535
1617
  const emitWorkflowFragments = (workflows) => {
1536
1618
  if (workflows.length === 0) {
1537
1619
  return { build: "", contextField: "", importLines: [], specs: "" };
@@ -1557,6 +1639,31 @@ ${specEntries}
1557
1639
  `
1558
1640
  };
1559
1641
  };
1642
+ const emitQueueFragments = (queues) => {
1643
+ if (queues.length === 0) {
1644
+ return { build: "", contextField: "", importLines: [], specs: "" };
1645
+ }
1646
+ for (const queue of queues) {
1647
+ assertIdentifier(queue.exportName, `queue export "${queue.exportName}"`);
1648
+ assertIdentifier(queue.bindingName, `queue binding "${queue.bindingName}"`);
1649
+ }
1650
+ const specEntries = queues.map((queue) => ` { binding: "${queue.bindingName}", exportName: "${queue.exportName}", name: ${JSON.stringify(queue.name)} },`).join("\n");
1651
+ return {
1652
+ build: `
1653
+ const queues = createQueueContext(env, LUNORA_QUEUES);
1654
+ `,
1655
+ contextField: `
1656
+ queues,`,
1657
+ importLines: [`import type { QueueBindingSpec } from "@lunora/queue";`, `import { createQueueContext } from "@lunora/queue";`],
1658
+ // eslint-disable-next-line no-secrets/no-secrets -- the emitted readonly-array type annotation is dense generated TS, not a credential
1659
+ specs: `
1660
+ /** Wiring specs for \`ctx.queues\` (codegen-derived from \`lunora/queues.ts\`). */
1661
+ const LUNORA_QUEUES: ReadonlyArray<QueueBindingSpec> = [
1662
+ ${specEntries}
1663
+ ];
1664
+ `
1665
+ };
1666
+ };
1560
1667
  const emitWorkflowsMetadataFragments = (workflows) => {
1561
1668
  if (workflows.length === 0) {
1562
1669
  return { constant: "", override: "" };
@@ -1583,6 +1690,33 @@ const LUNORA_WORKFLOWS_INFO: WorkflowsResult = ${JSON.stringify(metadata, void 0
1583
1690
  `
1584
1691
  };
1585
1692
  };
1693
+ const emitQueuesMetadataFragments = (queues) => {
1694
+ if (queues.length === 0) {
1695
+ return { constant: "", override: "" };
1696
+ }
1697
+ const metadata = {
1698
+ queues: queues.map((queue) => {
1699
+ return {
1700
+ binding: queue.bindingName,
1701
+ ...queue.tuning.deadLetterQueue === void 0 ? {} : { deadLetterQueue: queue.tuning.deadLetterQueue },
1702
+ exportName: queue.exportName,
1703
+ mode: queue.mode,
1704
+ name: queue.name
1705
+ };
1706
+ })
1707
+ };
1708
+ return {
1709
+ constant: `
1710
+ /** Read-only declared-queue metadata (discovered from \`lunora/queues.ts\`) served via \`__lunora_admin__:listQueues\` for the studio's queues view. */
1711
+ const LUNORA_QUEUES_INFO: QueuesResult = ${JSON.stringify(metadata, void 0, 4)};
1712
+ `,
1713
+ override: `
1714
+ protected override queuesMetadata(): QueuesResult {
1715
+ return LUNORA_QUEUES_INFO;
1716
+ }
1717
+ `
1718
+ };
1719
+ };
1586
1720
  const emitPaymentFragments = (hasPayments) => {
1587
1721
  if (!hasPayments) {
1588
1722
  return { build: "", configField: "", contextField: "", imports: [], stub: "" };
@@ -1638,13 +1772,14 @@ const paymentStub: LunoraPayment = {
1638
1772
  `
1639
1773
  };
1640
1774
  };
1641
- const buildDoTypeImports = (hasVectors, hasWorkflows) => [
1775
+ const buildDoTypeImports = (hasVectors, hasWorkflows, hasQueues) => [
1642
1776
  "AdvisoryFinding",
1643
1777
  "DatabaseWriterLike",
1644
1778
  "DataMigrationLike",
1645
1779
  "LogSink",
1646
1780
  "MaskPoliciesResult",
1647
1781
  "MigrationRunResult",
1782
+ ...hasQueues ? ["QueuesResult"] : [],
1648
1783
  "RunShardApplyCdcArgs",
1649
1784
  "RunShardMigrationArgs",
1650
1785
  "RlsPoliciesResult",
@@ -1673,8 +1808,10 @@ const emitShard = ({
1673
1808
  hasImages = false,
1674
1809
  hasKv = false,
1675
1810
  hasPayments = false,
1811
+ hasPipelines = false,
1676
1812
  hasR2sql = false,
1677
1813
  maskMetadata,
1814
+ queues = [],
1678
1815
  rlsMetadata,
1679
1816
  schema,
1680
1817
  storageRules,
@@ -1690,6 +1827,8 @@ const emitShard = ({
1690
1827
  const hyperdriveFragments = emitHyperdriveFragments(hasHyperdrive);
1691
1828
  const browserFragments = emitBrowserFragments(hasBrowser);
1692
1829
  const r2sqlFragments = emitR2sqlFragments(hasR2sql);
1830
+ const pipelinesFragments = emitPipelinesFragments(hasPipelines);
1831
+ const { build: queuesBuild, contextField: queuesContextField, importLines: queueImportLines, specs: queueSpecs } = emitQueueFragments(queues);
1693
1832
  const {
1694
1833
  build: containersBuild,
1695
1834
  contextField: containersContextField,
@@ -1716,12 +1855,14 @@ const emitShard = ({
1716
1855
  const studioFeaturesData = studioFeatures ?? {
1717
1856
  mail: false,
1718
1857
  payments: false,
1858
+ queues: false,
1719
1859
  scheduler: false,
1720
1860
  storage: false,
1721
1861
  vectors: false,
1722
1862
  workflows: false
1723
1863
  };
1724
1864
  const { constant: workflowsMetadataConst, override: workflowsMetadataOverride } = emitWorkflowsMetadataFragments(workflows);
1865
+ const { constant: queuesMetadataConst, override: queuesMetadataOverride } = emitQueuesMetadataFragments(queues);
1725
1866
  const hasVectors = schema.vectorIndexes.length > 0;
1726
1867
  const hasGlobalTables = schema.tables.some((table) => table.shardMode === "global");
1727
1868
  const hasHyperdriveGlobal = schema.tables.some((table) => table.shardMode === "global" && table.globalBackend === "hyperdrive");
@@ -1736,23 +1877,24 @@ const emitShard = ({
1736
1877
  const tableIndexes = buildTableIndexes(schema);
1737
1878
  const tableColumns = buildTableColumns(schema);
1738
1879
  const storageColumns = buildStorageColumns(schema);
1739
- const doTypeImports = buildDoTypeImports(hasVectors, workflows.length > 0);
1880
+ const doTypeImports = buildDoTypeImports(hasVectors, workflows.length > 0, queues.length > 0);
1740
1881
  const relationFanout = emitRelationFanout(hasGlobalTables);
1741
1882
  const importLines = [
1742
1883
  `import type { ${doTypeImports.join(", ")} } from "${base.do}";`,
1743
1884
  `import { applyCdcChanges, createShardCtxDb, runDataMigration, runShardMigrations, ${relationFanout.importFragment}ShardDO as ShardDOBase } from "${base.do}";`,
1744
- // `asBucketStorage` (the bucket-aware `ctx.storage` wrapper) lives in
1885
+ // `asBucketStorage` (the bucket-aware `ctx.storage` wrapper) and
1886
+ // `createSecrets` (the `ctx.secrets` core built-in) live in
1745
1887
  // `@lunora/server`, the single source — imported here rather than stamped
1746
1888
  // inline into every generated shard.
1747
- `import { asBucketStorage } from "${base.server}";`
1889
+ `import { asBucketStorage, createSecrets } from "${base.server}";`
1748
1890
  ];
1749
1891
  if (hasTables) {
1750
1892
  importLines.push(`import { bindOrm, bindTableFacade } from "${base.server}";`);
1751
1893
  }
1752
1894
  if (hasVectors) {
1753
1895
  importLines.push(
1754
- `import type { SchemaLike as VectorSchemaLike, VectorizeIndexLike, VectorSearchLike } from "@lunora/vectors";`,
1755
- `import { createContextVectors, createVectors, createVectorSyncHook } from "@lunora/vectors";`
1896
+ `import type { SchemaLike as VectorSchemaLike, VectorizeIndexLike, VectorSearchLike } from "@lunora/bindings/vectors";`,
1897
+ `import { createContextVectors, createVectors, createVectorSyncHook } from "@lunora/bindings/vectors";`
1756
1898
  );
1757
1899
  }
1758
1900
  if (hasAi) {
@@ -1765,8 +1907,10 @@ const emitShard = ({
1765
1907
  ...hyperdriveFragments.importLines,
1766
1908
  ...browserFragments.importLines,
1767
1909
  ...r2sqlFragments.importLines,
1910
+ ...pipelinesFragments.importLines,
1768
1911
  ...containerImportLines,
1769
1912
  ...workflowImportLines,
1913
+ ...queueImportLines,
1770
1914
  ...paymentsImports,
1771
1915
  ``,
1772
1916
  `import schema from "../schema.js";`,
@@ -1908,18 +2052,23 @@ ${schema.tables.map(
1908
2052
  (table) => ` facade[${JSON.stringify(table.name)}] = bindTableFacade(db, ${JSON.stringify(table.name)});`
1909
2053
  ).join("\n")}
1910
2054
  ` : "";
1911
- const everyContextBuild = `${kvFragments.build}${analyticsFragments.build}`;
1912
- const everyContextField = `${kvFragments.contextField}${analyticsFragments.contextField}`;
1913
- const actionOnlyHasAny = hasImages || hasHyperdrive || hasBrowser || hasR2sql;
2055
+ const secretsBuild = `
2056
+ const secrets = createSecrets(env);
2057
+ `;
2058
+ const everyContextBuild = `${kvFragments.build}${analyticsFragments.build}${secretsBuild}`;
2059
+ const everyContextField = `${kvFragments.contextField}${analyticsFragments.contextField}
2060
+ secrets,`;
2061
+ const actionOnlyHasAny = hasImages || hasHyperdrive || hasBrowser || hasR2sql || hasPipelines;
1914
2062
  const actionOnlyBlock = actionOnlyHasAny ? `
1915
2063
  // ActionCtx-only helpers (external, non-deterministic I/O): constructed
1916
2064
  // and attached only for an \`action\` so query/mutation ctx never carry them.
1917
2065
  if (isAction) {
1918
- ${imagesFragments.build}${hyperdriveFragments.build}${browserFragments.build}${r2sqlFragments.build}${[
2066
+ ${imagesFragments.build}${hyperdriveFragments.build}${browserFragments.build}${r2sqlFragments.build}${pipelinesFragments.build}${[
1919
2067
  ...hasImages ? [" ctx.images = images;"] : [],
1920
2068
  ...hasHyperdrive ? [" ctx.sql = sql;"] : [],
1921
2069
  ...hasBrowser ? [" ctx.browser = browser;"] : [],
1922
- ...hasR2sql ? [" ctx.r2sql = r2sql;"] : []
2070
+ ...hasR2sql ? [" ctx.r2sql = r2sql;"] : [],
2071
+ ...hasPipelines ? [" ctx.pipelines = pipelines;"] : []
1923
2072
  ].join("\n")}
1924
2073
  }
1925
2074
  ` : "";
@@ -1959,14 +2108,14 @@ const LUNORA_STORAGE_RULES: StorageRulesResult = ${JSON.stringify(storageRulesDa
1959
2108
 
1960
2109
  /** 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. */
1961
2110
  const LUNORA_STUDIO_FEATURES: StudioFeaturesResult = ${JSON.stringify(studioFeaturesData, void 0, 4)};
1962
- ${workflowsMetadataConst}${containerSpecs}${workflowSpecs}
2111
+ ${workflowsMetadataConst}${queuesMetadataConst}${containerSpecs}${workflowSpecs}${queueSpecs}
1963
2112
  export interface ShardDOConfig {
1964
2113
  /** Opt into change-data-capture: records a post-image to \`__cdc_log\` on every write (backs streaming export + replay-PITR). */
1965
2114
  cdc?: boolean;
1966
2115
  /** 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. */
1967
2116
  observability?: (env: Record<string, unknown>) => LogSink | undefined;
1968
2117
  scheduler?: (env: Record<string, unknown>) => unknown;
1969
- storage?: (env: Record<string, unknown>) => unknown;${vectorsConfigField}${aiConfigField}${kvFragments.configField}${analyticsFragments.configField}${imagesFragments.configField}${hyperdriveFragments.configField}${browserFragments.configField}${r2sqlFragments.configField}${paymentsConfigField}${d1ConfigField}${hyperdriveGlobalConfigField}
2118
+ storage?: (env: Record<string, unknown>) => unknown;${vectorsConfigField}${aiConfigField}${kvFragments.configField}${analyticsFragments.configField}${imagesFragments.configField}${hyperdriveFragments.configField}${browserFragments.configField}${r2sqlFragments.configField}${pipelinesFragments.configField}${paymentsConfigField}${d1ConfigField}${hyperdriveGlobalConfigField}
1970
2119
  }
1971
2120
 
1972
2121
  const schedulerStub = {
@@ -2004,7 +2153,7 @@ const storageStub = {
2004
2153
  throw new Error("ctx.storage: no storage configured. Pass \`storage\` to createShardDO().");
2005
2154
  },
2006
2155
  };
2007
- ${globalDatabaseStub}${vectorsStub}${aiStub}${kvFragments.stub}${analyticsFragments.stub}${imagesFragments.stub}${hyperdriveFragments.stub}${browserFragments.stub}${r2sqlFragments.stub}${paymentStub}${bindTableHelper}
2156
+ ${globalDatabaseStub}${vectorsStub}${aiStub}${kvFragments.stub}${analyticsFragments.stub}${imagesFragments.stub}${hyperdriveFragments.stub}${browserFragments.stub}${r2sqlFragments.stub}${pipelinesFragments.stub}${paymentStub}${bindTableHelper}
2008
2157
  // Bound in-process \`ctx.run*\` composition depth so a self- or cyclically-
2009
2158
  // referencing call fails loudly with a clear error instead of overflowing the
2010
2159
  // stack. Tracked across the awaited handler chain (one DO invocation is
@@ -2149,7 +2298,7 @@ ${relationFanout.override}
2149
2298
  protected override studioFeatures(): StudioFeaturesResult {
2150
2299
  return LUNORA_STUDIO_FEATURES;
2151
2300
  }
2152
- ${workflowsMetadataOverride}
2301
+ ${workflowsMetadataOverride}${queuesMetadataOverride}
2153
2302
  protected override advisories(): AdvisoryFinding[] {
2154
2303
  return LUNORA_ADVISORIES;
2155
2304
  }
@@ -2383,7 +2532,7 @@ ${workflowsMetadataOverride}
2383
2532
  // dispatch path) fall back to the per-request fields as before.
2384
2533
  const userId = options.identity ? options.identity.userId : this.getCurrentUserId();
2385
2534
  const identity = options.identity ? options.identity.identity : this.getCurrentIdentity();
2386
- ${vectorsBuild}${aiBuild}${everyContextBuild}${containersBuild}${workflowsBuild}
2535
+ ${vectorsBuild}${aiBuild}${everyContextBuild}${containersBuild}${workflowsBuild}${queuesBuild}
2387
2536
  const scheduler = (config.scheduler?.(env) ?? schedulerStub) as SchedulerLike;
2388
2537
  // Build the storage adapter once and share it between \`ctx.storage\`
2389
2538
  // and \`ctx.db.system._storage\` so both read the same R2 binding. The
@@ -2425,7 +2574,7 @@ ${facadeBlock}${paymentsBuild}
2425
2574
  log,
2426
2575
  now,${ormContextField}
2427
2576
  scheduler,
2428
- storage,${vectorsContextField}${aiContextField}${everyContextField}${paymentsContextField}${containersContextField}${workflowsContextField}
2577
+ storage,${vectorsContextField}${aiContextField}${everyContextField}${paymentsContextField}${containersContextField}${workflowsContextField}${queuesContextField}
2429
2578
  };
2430
2579
  ${isActionLine}${actionOnlyBlock}
2431
2580
  ctx.runAction = (reference: FunctionReference, fnArgs: Record<string, unknown>) => dispatchRun("action", reference.__lunoraRef, fnArgs, ctx);
@@ -2689,4 +2838,4 @@ const emitWranglerCronTriggers = (crons) => {
2689
2838
  return triggers;
2690
2839
  };
2691
2840
 
2692
- export { GENERATED_HEADER, buildStorageColumns, emitApi, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitSeed, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers };
2841
+ export { GENERATED_HEADER, buildStorageColumns, emitApi, emitContainers, emitCrons, emitDataModel, emitDrizzleSchema, emitFunctions, emitQueues, emitSeed, emitServer, emitShard, emitVectors, emitWorkflows, emitWranglerCronTriggers };
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-BiFXNUvo.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-BEA7oWGC.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
 
@@ -0,0 +1,119 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { queueDefaultName, queueBindingName } from '@lunora/queue';
4
+ import { SyntaxKind, Node } from 'ts-morph';
5
+ import { diagnosticAt } from './CodegenDiagnosticError-DeblMkzO.mjs';
6
+
7
+ const QUEUES_FILENAME = "queues.ts";
8
+ const isDefineQueue = (identifier) => {
9
+ const symbol = identifier.getSymbol();
10
+ if (!symbol) {
11
+ return identifier.getText() === "defineQueue";
12
+ }
13
+ for (const declaration of symbol.getDeclarations()) {
14
+ if (!Node.isImportSpecifier(declaration)) {
15
+ continue;
16
+ }
17
+ if (declaration.getImportDeclaration().getModuleSpecifierValue() !== "@lunora/queue") {
18
+ return false;
19
+ }
20
+ return declaration.getNameNode().getText() === "defineQueue";
21
+ }
22
+ return false;
23
+ };
24
+ const stringProperty = (expression, exportName, property) => {
25
+ if (Node.isStringLiteral(expression) || Node.isNoSubstitutionTemplateLiteral(expression)) {
26
+ return expression.getLiteralValue();
27
+ }
28
+ throw diagnosticAt(
29
+ expression,
30
+ `queue "${exportName}": \`${property}\` must be a static string literal — it is deploy configuration codegen writes into wrangler.jsonc`
31
+ );
32
+ };
33
+ const numberProperty = (expression, exportName, property) => {
34
+ if (Node.isNumericLiteral(expression)) {
35
+ return expression.getLiteralValue();
36
+ }
37
+ throw diagnosticAt(
38
+ expression,
39
+ `queue "${exportName}": \`${property}\` must be a static numeric literal — it is deploy configuration codegen writes into wrangler.jsonc`
40
+ );
41
+ };
42
+ const queueNameOverride = (argument, exportName) => {
43
+ const nameProperty = argument.getProperty("name");
44
+ if (!nameProperty || !Node.isPropertyAssignment(nameProperty)) {
45
+ return void 0;
46
+ }
47
+ const name = stringProperty(nameProperty.getInitializerOrThrow(), exportName, "name");
48
+ if (name.length === 0) {
49
+ throw diagnosticAt(nameProperty, `queue "${exportName}": \`name\` must be a non-empty string when provided`);
50
+ }
51
+ return name;
52
+ };
53
+ const queueFromCall = (call, exportName) => {
54
+ const argument = call.getArguments()[0];
55
+ if (!argument || !Node.isObjectLiteralExpression(argument)) {
56
+ throw diagnosticAt(call, `queue "${exportName}": defineQueue must be passed an inline object literal`);
57
+ }
58
+ const ir = {
59
+ bindingName: queueBindingName(exportName),
60
+ exportName,
61
+ mode: "push",
62
+ name: queueNameOverride(argument, exportName) ?? queueDefaultName(exportName),
63
+ tuning: {}
64
+ };
65
+ const modeProperty = argument.getProperty("mode");
66
+ if (modeProperty && Node.isPropertyAssignment(modeProperty)) {
67
+ const mode = stringProperty(modeProperty.getInitializerOrThrow(), exportName, "mode");
68
+ if (mode !== "push" && mode !== "pull") {
69
+ throw diagnosticAt(modeProperty, `queue "${exportName}": \`mode\` must be "push" or "pull" (got ${JSON.stringify(mode)})`);
70
+ }
71
+ ir.mode = mode;
72
+ }
73
+ const dlqProperty = argument.getProperty("deadLetterQueue");
74
+ if (dlqProperty && Node.isPropertyAssignment(dlqProperty)) {
75
+ ir.tuning.deadLetterQueue = stringProperty(dlqProperty.getInitializerOrThrow(), exportName, "deadLetterQueue");
76
+ }
77
+ for (const property of ["maxBatchSize", "maxBatchTimeout", "maxRetries", "retryDelay"]) {
78
+ const node = argument.getProperty(property);
79
+ if (node && Node.isPropertyAssignment(node)) {
80
+ ir.tuning[property] = numberProperty(node.getInitializerOrThrow(), exportName, property);
81
+ }
82
+ }
83
+ return ir;
84
+ };
85
+ const queuesFromSource = (source) => {
86
+ const queues = [];
87
+ for (const declaration of source.getVariableDeclarations()) {
88
+ if (!declaration.isExported()) {
89
+ continue;
90
+ }
91
+ const initializer = declaration.getInitializer();
92
+ if (initializer?.getKind() !== SyntaxKind.CallExpression) {
93
+ continue;
94
+ }
95
+ const call = initializer;
96
+ const callee = call.getExpression();
97
+ if (!Node.isIdentifier(callee) || !isDefineQueue(callee)) {
98
+ continue;
99
+ }
100
+ const nameNode = declaration.getNameNode();
101
+ if (!Node.isIdentifier(nameNode)) {
102
+ throw diagnosticAt(nameNode, "defineQueue exports must be plain named exports (no destructuring)");
103
+ }
104
+ queues.push(queueFromCall(call, nameNode.getText()));
105
+ }
106
+ return queues;
107
+ };
108
+ const discoverQueues = (project, lunoraDirectory) => {
109
+ const queuesPath = join(lunoraDirectory, QUEUES_FILENAME);
110
+ if (!existsSync(queuesPath)) {
111
+ return [];
112
+ }
113
+ const source = project.getSourceFile(queuesPath) ?? project.addSourceFileAtPath(queuesPath);
114
+ const queues = queuesFromSource(source);
115
+ queues.sort((a, b) => a.exportName.localeCompare(b.exportName));
116
+ return queues;
117
+ };
118
+
119
+ export { QUEUES_FILENAME, discoverQueues };
@@ -13,16 +13,17 @@ import discoverMaskProcedures, { discoverMaskMetadata } from './discoverMaskProc
13
13
  import discoverMigrations from './discoverMigrations-Bi5nJ0mJ.mjs';
14
14
  import discoverNondeterministicCalls from './discoverNondeterministicCalls-C4M8AXmQ.mjs';
15
15
  import discoverQueries from './discoverQueries-B0wGT-xe.mjs';
16
+ import { discoverQueues } from './QUEUES_FILENAME-B5_eWCRe.mjs';
16
17
  import discoverR2sqlCalls from './discoverR2sqlCalls-BVNMd428.mjs';
17
18
  import discoverRlsProcedures, { discoverRlsMetadata } from './discoverRlsMetadata-BS9GOGC5.mjs';
18
19
  import discoverSchema from './discoverSchema-BnWHHJ4T.mjs';
19
20
  import discoverStorageRulesMetadata from './discoverStorageRulesMetadata-Da8BKXcI.mjs';
20
21
  import { e as enclosingExportName$1 } from './discover-ast-CT6BgBr4.mjs';
21
22
  import { discoverWorkflows } from './WORKFLOWS_FILENAME-D62dcBGg.mjs';
22
- import { buildStorageColumns, emitDataModel, emitApi, emitServer, emitFunctions, emitShard, emitContainers, emitWorkflows, emitCrons, emitVectors, emitDrizzleSchema, emitSeed, emitWranglerCronTriggers } from './GENERATED_HEADER-BiFXNUvo.mjs';
23
- import { emitApp } from './emitApp-KfMaKXWe.mjs';
24
- import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-Cu3mZl-8.mjs';
25
- import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-B4i9Qp3v.mjs';
23
+ import { buildStorageColumns, emitDataModel, emitApi, emitServer, emitFunctions, emitShard, emitContainers, emitWorkflows, emitQueues, emitCrons, emitVectors, emitDrizzleSchema, emitSeed, emitWranglerCronTriggers } from './GENERATED_HEADER-BEA7oWGC.mjs';
24
+ import { emitApp } from './emitApp-DiK0QSBn.mjs';
25
+ import { buildOpenApiDocument, emitOpenApiModule } from './buildOpenApiDocument-699G4RP4.mjs';
26
+ import { buildOpenRpcDocument, emitOpenRpcModule } from './OPENRPC_VERSION-CQ3f8YQ-.mjs';
26
27
  import { buildSchemaSnapshot, serializeSchemaSnapshot } from './SCHEMA_SNAPSHOT_VERSION-wWGP2_5E.mjs';
27
28
 
28
29
  const HTTP_VERBS = /* @__PURE__ */ new Set(["delete", "get", "head", "options", "patch", "post", "put"]);
@@ -240,18 +241,21 @@ const discoverArgumentValidators = (project, lunoraDirectory) => {
240
241
 
241
242
  const PROBES = {
242
243
  ai: { contextProperty: "ai", moduleSpecifier: "@lunora/ai" },
243
- analytics: { contextProperty: "analytics", moduleSpecifier: "@lunora/analytics" },
244
+ analytics: { contextProperty: "analytics", moduleSpecifier: "@lunora/bindings/analytics" },
244
245
  browser: { contextProperty: "browser", moduleSpecifier: "@lunora/browser" },
245
246
  hyperdrive: { contextProperty: "sql", moduleSpecifier: "@lunora/hyperdrive" },
246
- images: { contextProperty: "images", moduleSpecifier: "@lunora/images" },
247
- kv: { contextProperty: "kv", moduleSpecifier: "@lunora/kv" },
247
+ images: { contextProperty: "images", moduleSpecifier: "@lunora/bindings/images" },
248
+ kv: { contextProperty: "kv", moduleSpecifier: "@lunora/bindings/kv" },
248
249
  mail: { moduleSpecifier: "@lunora/mail" },
249
250
  payments: { contextProperty: "payments", moduleSpecifier: "@lunora/payment" },
250
- pipelines: { contextProperty: "pipelines", moduleSpecifier: "@lunora/pipelines" },
251
- r2sql: { contextProperty: "r2sql", moduleSpecifier: "@lunora/r2sql" },
251
+ // Pipelines is its own `@lunora/bindings/pipelines` subpath (distinct from
252
+ // `/analytics`), so a real import is a clean signal that won't be flipped by a
253
+ // plain analytics import; `ctx.pipelines` reads flip it too.
254
+ pipelines: { contextProperty: "pipelines", moduleSpecifier: "@lunora/bindings/pipelines" },
255
+ r2sql: { contextProperty: "r2sql", moduleSpecifier: "@lunora/bindings/r2sql" },
252
256
  scheduler: { contextProperty: "scheduler", moduleSpecifier: "@lunora/scheduler" },
253
257
  storage: { contextProperty: "storage", moduleSpecifier: "@lunora/storage" },
254
- vectors: { contextProperty: "vectors", moduleSpecifier: "@lunora/vectors" },
258
+ vectors: { contextProperty: "vectors", moduleSpecifier: "@lunora/bindings/vectors" },
255
259
  workflows: { contextProperty: "workflows", moduleSpecifier: "@lunora/workflow" }
256
260
  };
257
261
  const readsContextProperty = (sourceFile, property) => {
@@ -313,9 +317,10 @@ const buildStudioFeatures = (usage, signals) => {
313
317
  return {
314
318
  mail: usage.mail || signals.dependencies.has("@lunora/mail"),
315
319
  payments: usage.payments || signals.dependencies.has("@lunora/payment"),
320
+ queues: signals.queueCount > 0 || signals.dependencies.has("@lunora/queue"),
316
321
  scheduler: usage.scheduler || signals.cronCount > 0 || signals.dependencies.has("@lunora/scheduler"),
317
322
  storage: usage.storage || signals.storageRuleCount > 0 || signals.storageColumnCount > 0 || signals.dependencies.has("@lunora/storage"),
318
- vectors: usage.vectors || signals.vectorIndexCount > 0 || signals.dependencies.has("@lunora/vectors"),
323
+ vectors: usage.vectors || signals.vectorIndexCount > 0 || signals.dependencies.has("@lunora/bindings/vectors"),
319
324
  workflows: usage.workflows || signals.workflowCount > 0 || signals.dependencies.has("@lunora/workflow")
320
325
  };
321
326
  };
@@ -735,6 +740,7 @@ const runCodegen = (options) => {
735
740
  const httpRoutes = discoverHttpRoutes(project, lunoraDirectory);
736
741
  const migrations = discoverMigrations(project, lunoraDirectory);
737
742
  const workflows = discoverWorkflows(project, lunoraDirectory);
743
+ const queues = discoverQueues(project, lunoraDirectory);
738
744
  const crons = discoverCrons(project, lunoraDirectory, workflows);
739
745
  const containers = discoverContainers(project, lunoraDirectory);
740
746
  const advisories = options.lint === false ? [] : lintSchema(
@@ -772,6 +778,7 @@ const runCodegen = (options) => {
772
778
  const studioFeatures = buildStudioFeatures(featureUsage, {
773
779
  cronCount: crons.length,
774
780
  dependencies,
781
+ queueCount: queues.length,
775
782
  storageColumnCount: Object.keys(buildStorageColumns(schema)).length,
776
783
  storageRuleCount: storageRulesMetadata.rules.length,
777
784
  vectorIndexCount: schema.vectorIndexes.length,
@@ -792,6 +799,7 @@ const runCodegen = (options) => {
792
799
  hasPayments,
793
800
  hasPipelines,
794
801
  hasR2sql,
802
+ queues,
795
803
  schema,
796
804
  storageRuleBuckets: storageRulesMetadata.rules.map((rule) => rule.bucket),
797
805
  useUmbrella,
@@ -808,8 +816,10 @@ const runCodegen = (options) => {
808
816
  hasImages,
809
817
  hasKv,
810
818
  hasPayments,
819
+ hasPipelines,
811
820
  hasR2sql,
812
821
  maskMetadata,
822
+ queues,
813
823
  rlsMetadata,
814
824
  schema,
815
825
  storageRules: storageRulesMetadata,
@@ -819,6 +829,7 @@ const runCodegen = (options) => {
819
829
  });
820
830
  const containersContent = emitContainers(containers, schema.jurisdiction);
821
831
  const workflowsContent = emitWorkflows(workflows);
832
+ const queuesContent = emitQueues(queues);
822
833
  const cronsContent = emitCrons(crons);
823
834
  const vectorsContent = emitVectors(schema.vectorIndexes);
824
835
  const drizzleFiles = emitDrizzleSchema(schema, useUmbrella);
@@ -844,6 +855,7 @@ const runCodegen = (options) => {
844
855
  hasKv,
845
856
  hasPayments,
846
857
  hasR2sql,
858
+ hasQueue: queues.some((queue) => queue.mode === "push"),
847
859
  hasScheduler: studioFeatures.scheduler,
848
860
  hasStorage: studioFeatures.storage,
849
861
  hasVectors: schema.vectorIndexes.length > 0,
@@ -886,6 +898,7 @@ const runCodegen = (options) => {
886
898
  writeIfChanged(join(outputDirectory, "drizzle.shard.ts"), drizzleFiles.shard);
887
899
  writeIfPresent(join(outputDirectory, "containers.ts"), containersContent);
888
900
  writeIfPresent(join(outputDirectory, "workflows.ts"), workflowsContent);
901
+ writeIfPresent(join(outputDirectory, "queues.ts"), queuesContent);
889
902
  writeIfPresent(join(outputDirectory, "seed.ts"), seedContent);
890
903
  if (wantsOpenApi) {
891
904
  writeIfChanged(join(outputDirectory, "openapi.json"), openApiContent);
@@ -923,6 +936,7 @@ const runCodegen = (options) => {
923
936
  openApiModule: openApiModuleContent,
924
937
  openRpc: openRpcContent,
925
938
  openRpcModule: openRpcModuleContent,
939
+ queues: queuesContent,
926
940
  seed: seedContent,
927
941
  server: serverContent,
928
942
  shard: shardContent,
@@ -930,6 +944,7 @@ const runCodegen = (options) => {
930
944
  workflows: workflowsContent
931
945
  },
932
946
  outputDirectory,
947
+ queues,
933
948
  schemaSnapshot,
934
949
  schemaSnapshotPath,
935
950
  workflows
@@ -1,4 +1,4 @@
1
- import { GENERATED_HEADER } from './GENERATED_HEADER-BiFXNUvo.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-BEA7oWGC.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-BiFXNUvo.mjs';
1
+ import { GENERATED_HEADER } from './GENERATED_HEADER-BEA7oWGC.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`)."],
@@ -23,7 +23,7 @@ const LONG_TAIL = [
23
23
  ];
24
24
  const hasAnyLongTail = (options) => LONG_TAIL.some(([flag]) => options[flag]);
25
25
  const buildImportLines = (options) => {
26
- const { hasAuth, hasFramework, hasGlobal, hasHyperdriveGlobal, hasScheduler, hasStorage, hasWorkflow, useUmbrella, wantsOpenApi, wantsOpenRpc } = options;
26
+ const { hasAuth, hasFramework, hasGlobal, hasHyperdriveGlobal, hasQueue, hasScheduler, hasStorage, hasWorkflow, useUmbrella, wantsOpenApi, wantsOpenRpc } = options;
27
27
  const runtimeModule = useUmbrella ? "lunorash/runtime" : "@lunora/runtime";
28
28
  const runtimeTypeImports = ["ExecutionContextLike", "LunoraWorker", "Route", "ScheduledControllerLike", "ShardNamespaceLike", "WorkerOptions"];
29
29
  if (hasGlobal) {
@@ -60,6 +60,7 @@ const buildImportLines = (options) => {
60
60
  ...hasGlobal || hasHyperdriveGlobal ? [`import schema from "../schema.js";`] : [],
61
61
  `import { LUNORA_CRONS } from "./crons.js";`,
62
62
  `import { LUNORA_FUNCTIONS } from "./functions.js";`,
63
+ ...hasQueue ? [`import { dispatchQueueBatch } from "@lunora/queue";`, `import { LUNORA_QUEUE_REGISTRY } from "./queues.js";`] : [],
63
64
  ...wantsOpenApi ? [`import { openApiSpec } from "./openapi.js";`] : [],
64
65
  ...wantsOpenRpc ? [`import { openRpcSpec } from "./openrpc.js";`] : [],
65
66
  `import { createShardDO } from "./shard.js";`
@@ -354,6 +355,14 @@ const buildBaseWorkerOptions = (options) => [
354
355
  ...options.jurisdiction ? [` jurisdiction: ${JSON.stringify(options.jurisdiction)},`] : [],
355
356
  ...options.wantsOpenApi ? [` openApiSpec,`] : [],
356
357
  ...options.wantsOpenRpc ? [` openRpcSpec,`] : [],
358
+ // The push-consumer handler backing the worker's `queue(batch, …)` entry:
359
+ // routes each delivered batch to its `defineQueue` handler. Built from
360
+ // `@lunora/queue` here (keeping the runtime decoupled) and wired only when the
361
+ // app declares push queues in `lunora/queues.ts`.
362
+ ...options.hasQueue ? [
363
+ ` queue: (batch: unknown, queueEnv: unknown, _context: ExecutionContextLike): Promise<void> =>`,
364
+ ` dispatchQueueBatch(batch as Parameters<typeof dispatchQueueBatch>[0], LUNORA_QUEUE_REGISTRY, { env: queueEnv as Record<string, unknown> }),`
365
+ ] : [],
357
366
  ` routes: this.routeMap,`,
358
367
  ` shardDO: this.shardSelector?.(env) ?? (undefined as unknown as ShardNamespaceLike),`
359
368
  ];
@@ -562,7 +571,12 @@ ${buildWorkerLine}
562
571
  worker ??= buildWorker(rawEnv as Env);
563
572
 
564
573
  return worker.serverQuery(request, rawEnv, reference, args, options);
565
- },
574
+ },${options.hasQueue ? `
575
+ queue: async (batch: unknown, rawEnv: unknown, context: ExecutionContextLike): Promise<void> => {
576
+ worker ??= buildWorker(rawEnv as Env);
577
+
578
+ return worker.queue?.(batch, rawEnv, context);
579
+ },` : ""}
566
580
  };
567
581
 
568
582
  if (this.emailHandler) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/codegen",
3
- "version": "1.0.0-alpha.10",
3
+ "version": "1.0.0-alpha.12",
4
4
  "description": "Code generator for Lunora: emits _generated/{api,server,dataModel}.ts from your schema",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -25,7 +25,7 @@
25
25
  "directory": "packages/codegen"
26
26
  },
27
27
  "files": [
28
- "dist",
28
+ "./dist",
29
29
  "__assets__",
30
30
  "README.md",
31
31
  "LICENSE.md"
@@ -46,11 +46,12 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/advisor": "1.0.0-alpha.5",
50
- "@lunora/container": "1.0.0-alpha.2",
51
- "@lunora/scheduler": "1.0.0-alpha.2",
52
- "@lunora/values": "1.0.0-alpha.2",
53
- "@lunora/workflow": "1.0.0-alpha.2",
49
+ "@lunora/advisor": "1.0.0-alpha.6",
50
+ "@lunora/container": "1.0.0-alpha.4",
51
+ "@lunora/queue": "1.0.0-alpha.1",
52
+ "@lunora/scheduler": "1.0.0-alpha.3",
53
+ "@lunora/values": "1.0.0-alpha.3",
54
+ "@lunora/workflow": "1.0.0-alpha.3",
54
55
  "ts-morph": "^28.0.0"
55
56
  },
56
57
  "engines": {