@pattern-stack/codegen 0.9.2 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +5 -0
  3. package/consumer-skills/bridge/SKILL.md +265 -0
  4. package/consumer-skills/codegen/SKILL.md +115 -0
  5. package/consumer-skills/entities/SKILL.md +111 -0
  6. package/consumer-skills/entities/families-and-queries.md +82 -0
  7. package/consumer-skills/entities/yaml-reference.md +118 -0
  8. package/consumer-skills/events/SKILL.md +71 -0
  9. package/consumer-skills/events/authoring-events.md +164 -0
  10. package/consumer-skills/events/typed-bus-and-outbox.md +163 -0
  11. package/consumer-skills/jobs/SKILL.md +66 -0
  12. package/consumer-skills/jobs/handler-authoring.md +236 -0
  13. package/consumer-skills/jobs/pools-and-ordering.md +161 -0
  14. package/consumer-skills/subsystems/SKILL.md +161 -0
  15. package/consumer-skills/subsystems/wiring-and-order.md +120 -0
  16. package/consumer-skills/sync/SKILL.md +134 -0
  17. package/consumer-skills/sync/audit-and-detection.md +302 -0
  18. package/consumer-skills/sync/change-sources-and-sinks.md +442 -0
  19. package/dist/runtime/subsystems/bridge/bridge.module.d.ts +0 -1
  20. package/dist/runtime/subsystems/bridge/bridge.module.js +294 -710
  21. package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
  22. package/dist/runtime/subsystems/bridge/index.d.ts +0 -1
  23. package/dist/runtime/subsystems/bridge/index.js +248 -664
  24. package/dist/runtime/subsystems/bridge/index.js.map +1 -1
  25. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +18 -10
  26. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
  27. package/dist/runtime/subsystems/events/events.module.js +43 -244
  28. package/dist/runtime/subsystems/events/events.module.js.map +1 -1
  29. package/dist/runtime/subsystems/events/index.d.ts +0 -1
  30. package/dist/runtime/subsystems/events/index.js +39 -241
  31. package/dist/runtime/subsystems/events/index.js.map +1 -1
  32. package/dist/runtime/subsystems/index.js +174 -791
  33. package/dist/runtime/subsystems/index.js.map +1 -1
  34. package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +22 -3
  35. package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
  36. package/dist/runtime/subsystems/jobs/index.d.ts +1 -4
  37. package/dist/runtime/subsystems/jobs/index.js +87 -506
  38. package/dist/runtime/subsystems/jobs/index.js.map +1 -1
  39. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
  40. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +3 -0
  41. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
  42. package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +11 -4
  43. package/dist/runtime/subsystems/jobs/job-worker.module.js +248 -664
  44. package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
  45. package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +0 -1
  46. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +89 -391
  47. package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
  48. package/dist/src/cli/index.js +1065 -440
  49. package/dist/src/cli/index.js.map +1 -1
  50. package/dist/src/index.js +26 -4
  51. package/dist/src/index.js.map +1 -1
  52. package/package.json +2 -1
  53. package/runtime/subsystems/events/event-bus.drizzle-backend.ts +32 -10
  54. package/runtime/subsystems/events/events.module.ts +38 -6
  55. package/runtime/subsystems/events/index.ts +7 -1
  56. package/runtime/subsystems/jobs/bullmq.config.ts +23 -3
  57. package/runtime/subsystems/jobs/index.ts +13 -8
  58. package/runtime/subsystems/jobs/job-worker.bullmq-backend.ts +5 -2
  59. package/runtime/subsystems/jobs/job-worker.module.ts +27 -7
  60. package/runtime/subsystems/jobs/jobs-domain.module.ts +27 -2
  61. package/templates/subsystem/events/domain-events.schema.ejs.t +43 -2
@@ -12,18 +12,18 @@ var __decorateParam = (index4, decorator) => (target, key2) => decorator(target,
12
12
 
13
13
  // runtime/subsystems/bridge/bridge.module.ts
14
14
  import {
15
- Inject as Inject13,
15
+ Inject as Inject12,
16
16
  Module as Module3,
17
- Optional as Optional8
17
+ Optional as Optional7
18
18
  } from "@nestjs/common";
19
19
 
20
20
  // runtime/subsystems/jobs/job-worker.module.ts
21
21
  import {
22
- Inject as Inject8,
23
- Injectable as Injectable9,
24
- Logger as Logger6,
22
+ Inject as Inject7,
23
+ Injectable as Injectable8,
24
+ Logger as Logger4,
25
25
  Module as Module2,
26
- Optional as Optional3
26
+ Optional as Optional2
27
27
  } from "@nestjs/common";
28
28
 
29
29
  // runtime/constants/tokens.ts
@@ -911,394 +911,9 @@ DrizzleJobStepService = __decorateClass([
911
911
  __decorateParam(0, Inject3(DRIZZLE))
912
912
  ], DrizzleJobStepService);
913
913
 
914
- // runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts
915
- import { createHash } from "crypto";
916
- import { Inject as Inject4, Injectable as Injectable4, Logger as Logger2, Optional } from "@nestjs/common";
917
- import { eq as eq4 } from "drizzle-orm";
918
-
919
- // runtime/subsystems/jobs/pool-config.loader.ts
920
- import { existsSync, readFileSync } from "fs";
921
- import { resolve } from "path";
922
- import { parse as parseYaml } from "yaml";
923
- var FRAMEWORK_POOLS = Object.freeze({
924
- events_inbound: Object.freeze({
925
- queue: "jobs-events-inbound",
926
- concurrency: 20,
927
- reserved: true,
928
- description: "Inbound events drain (events subsystem outbox)."
929
- }),
930
- events_change: Object.freeze({
931
- queue: "jobs-events-change",
932
- concurrency: 30,
933
- reserved: true,
934
- description: "Change events drain (events subsystem outbox)."
935
- }),
936
- events_outbound: Object.freeze({
937
- queue: "jobs-events-outbound",
938
- concurrency: 10,
939
- reserved: true,
940
- description: "Outbound events drain (events subsystem outbox)."
941
- }),
942
- interactive: Object.freeze({
943
- queue: "jobs-interactive",
944
- concurrency: 20,
945
- reserved: false,
946
- description: "User-facing latency-sensitive jobs."
947
- }),
948
- batch: Object.freeze({
949
- queue: "jobs-batch",
950
- concurrency: 5,
951
- reserved: false,
952
- description: "Default pool for background jobs."
953
- })
954
- });
955
- var RESERVED_POOL_NAMES = new Set(
956
- Object.entries(FRAMEWORK_POOLS).filter(([, def]) => def.reserved).map(([name]) => name)
957
- );
958
- var cache = /* @__PURE__ */ new Map();
959
- function loadPoolConfig(configPath) {
960
- const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);
961
- const cached = cache.get(resolved);
962
- if (cached) return cached;
963
- const merged = /* @__PURE__ */ new Map();
964
- for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {
965
- merged.set(name, { ...def });
966
- }
967
- if (!existsSync(resolved)) {
968
- cache.set(resolved, merged);
969
- return merged;
970
- }
971
- let raw;
972
- try {
973
- raw = parseYaml(readFileSync(resolved, "utf8"));
974
- } catch (err) {
975
- throw new Error(
976
- `pool-config.loader: failed to parse YAML at ${resolved}: ${err.message}`
977
- );
978
- }
979
- const userPools = extractUserPools(raw);
980
- for (const [name, userDef] of Object.entries(userPools)) {
981
- const existing = merged.get(name);
982
- if (existing) {
983
- const next = {
984
- queue: existing.queue,
985
- concurrency: typeof userDef.concurrency === "number" ? userDef.concurrency : existing.concurrency,
986
- reserved: existing.reserved,
987
- description: userDef.description ?? existing.description
988
- };
989
- merged.set(name, next);
990
- continue;
991
- }
992
- if (typeof userDef.queue !== "string" || userDef.queue.length === 0) {
993
- throw new Error(
994
- `pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`
995
- );
996
- }
997
- if (typeof userDef.concurrency !== "number" || userDef.concurrency <= 0) {
998
- throw new Error(
999
- `pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`
1000
- );
1001
- }
1002
- if (userDef.reserved === true) {
1003
- throw new Error(
1004
- `pool-config.loader: user-defined pool '${name}' cannot set 'reserved: true' \u2014 reserved is framework-only.`
1005
- );
1006
- }
1007
- merged.set(name, {
1008
- queue: userDef.queue,
1009
- concurrency: userDef.concurrency,
1010
- reserved: false,
1011
- description: userDef.description
1012
- });
1013
- }
1014
- cache.set(resolved, merged);
1015
- return merged;
1016
- }
1017
- function allNonReservedPoolNames(config) {
1018
- const out = [];
1019
- for (const [name, def] of config) {
1020
- if (!def.reserved) out.push(name);
1021
- }
1022
- return out;
1023
- }
1024
- function allPoolNames(config) {
1025
- return [...config.keys()];
1026
- }
1027
- function extractUserPools(raw) {
1028
- if (!raw || typeof raw !== "object") return {};
1029
- const jobs2 = raw.jobs;
1030
- if (!jobs2 || typeof jobs2 !== "object") return {};
1031
- const pools = jobs2.pools;
1032
- if (!pools || typeof pools !== "object") return {};
1033
- const out = {};
1034
- for (const [name, def] of Object.entries(pools)) {
1035
- if (!def || typeof def !== "object") continue;
1036
- out[name] = def;
1037
- }
1038
- return out;
1039
- }
1040
-
1041
- // runtime/subsystems/jobs/bullmq.config.ts
1042
- var BULLMQ_CONNECTION = /* @__PURE__ */ Symbol("BULLMQ_CONNECTION");
1043
- var BULLMQ_RESOLVED_CONFIG = /* @__PURE__ */ Symbol("BULLMQ_RESOLVED_CONFIG");
1044
- var DEFAULT_REDIS_URL = "redis://localhost:6379";
1045
- var DEFAULT_BULL_BOARD_MOUNT = "/admin/queues";
1046
- function resolveBullMqConfig(ext) {
1047
- const url = ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;
1048
- const resolved = {
1049
- connection: { url },
1050
- queuePrefix: ext?.queue_prefix
1051
- };
1052
- if (ext?.bull_board?.enabled) {
1053
- resolved.bullBoard = {
1054
- enabled: true,
1055
- mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT
1056
- };
1057
- }
1058
- return resolved;
1059
- }
1060
- function resolvePoolQueueName(pool, config, poolConfig = loadPoolConfig()) {
1061
- const alias = poolConfig.get(pool)?.queue ?? pool;
1062
- const prefix = config?.queuePrefix;
1063
- return prefix ? `${prefix}:${alias}` : alias;
1064
- }
1065
-
1066
- // runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts
1067
- function sha1JobId(idempotencyKey) {
1068
- return createHash("sha1").update(idempotencyKey).digest("hex");
1069
- }
1070
- var BullMQJobOrchestrator = class extends DrizzleJobOrchestrator {
1071
- constructor(db, multiTenant, connection, bullConfig = null) {
1072
- super(db, multiTenant);
1073
- this.connection = connection;
1074
- this.bullConfig = bullConfig;
1075
- this.bullDb = db;
1076
- }
1077
- connection;
1078
- bullConfig;
1079
- // TODO(logging-subsystem): swap to ILogger once ADR-028 lands
1080
- bullLogger = new Logger2(BullMQJobOrchestrator.name);
1081
- /** Lazily-opened `Queue` handles, one per pool. */
1082
- queues = /* @__PURE__ */ new Map();
1083
- /** Single FlowProducer for parent/child hierarchies. Lazily opened. */
1084
- _flow = null;
1085
- /**
1086
- * Cached `bullmq` value constructors, populated by `loadBullMq()` on first
1087
- * use (the `start`/`cancel`/`replay` entrypoints `await` it before touching
1088
- * a queue). Kept off the import graph so a `drizzle`-only consumer never
1089
- * resolves the optional `'bullmq'` package.
1090
- */
1091
- QueueCtor = null;
1092
- FlowProducerCtor = null;
1093
- bullMqLoad = null;
1094
- /**
1095
- * Own reference to the Drizzle client. `DrizzleJobOrchestrator.db` is
1096
- * `private` (can't be redeclared even privately in a subclass), and the
1097
- * spec forbids touching that file — so the subclass keeps its own handle
1098
- * under a distinct name (same instance, passed through to `super`) for the
1099
- * cancel-cascade snapshot + definition/run loads below.
1100
- */
1101
- bullDb;
1102
- /**
1103
- * Lazily load the optional `bullmq` package and cache its value
1104
- * constructors. Idempotent (single in-flight promise). Throws a friendly,
1105
- * actionable error when the consumer selected `backend: 'bullmq'` but did
1106
- * not install the package — mirrors `createRedisClient` in the redis event
1107
- * backend. Must be `await`ed before any `queueFor`/`flow` access.
1108
- */
1109
- async loadBullMq() {
1110
- if (this.QueueCtor && this.FlowProducerCtor) return;
1111
- if (!this.bullMqLoad) {
1112
- this.bullMqLoad = (async () => {
1113
- try {
1114
- const mod = await import("bullmq");
1115
- this.QueueCtor = mod.Queue;
1116
- this.FlowProducerCtor = mod.FlowProducer;
1117
- } catch {
1118
- throw new Error(
1119
- 'BullMQ backend requires the "bullmq" package. Install it with: npm install bullmq'
1120
- );
1121
- }
1122
- })();
1123
- }
1124
- await this.bullMqLoad;
1125
- }
1126
- /**
1127
- * Open (or reuse) the `Queue` for a pool. Synchronous — callers `await
1128
- * loadBullMq()` first so `QueueCtor` is populated.
1129
- */
1130
- queueFor(pool) {
1131
- if (!this.QueueCtor) {
1132
- throw new Error("BullMQJobOrchestrator: queueFor called before loadBullMq()");
1133
- }
1134
- const name = resolvePoolQueueName(pool, this.bullConfig);
1135
- let q = this.queues.get(name);
1136
- if (!q) {
1137
- q = new this.QueueCtor(name, { connection: this.connection });
1138
- this.queues.set(name, q);
1139
- }
1140
- return q;
1141
- }
1142
- flow() {
1143
- if (!this.FlowProducerCtor) {
1144
- throw new Error("BullMQJobOrchestrator: flow called before loadBullMq()");
1145
- }
1146
- if (!this._flow) {
1147
- this._flow = new this.FlowProducerCtor({ connection: this.connection });
1148
- }
1149
- return this._flow;
1150
- }
1151
- // ==========================================================================
1152
- // start — Postgres insert (super) + BullMQ dispatch
1153
- // ==========================================================================
1154
- async start(type, input, opts = {}, tx) {
1155
- const run = await super.start(type, input, opts, tx);
1156
- await this.dispatch(run, type);
1157
- return run;
1158
- }
1159
- /**
1160
- * Map a `job_run` row onto a BullMQ job via `queue.add`. When the run has a
1161
- * `parentRunId` we attach it to the parent's existing BullMQ job through the
1162
- * `parent: { id, queue }` opt — BullMQ then tracks the parent/child link in
1163
- * its own graph. (The FlowProducer is reserved for whole-tree atomic
1164
- * submits, exposed as an opt-in extension via `flowProducer()`; runtime
1165
- * `ctx.spawnChild` is incremental, so `queue.add` with a parent ref is the
1166
- * correct primitive here.)
1167
- *
1168
- * The `jobId` is colon-safe + stable: `sha1(dedupeKey)` when a dedupe key is
1169
- * present (so the same logical key dedups), else the `job_run.id` UUID
1170
- * (already colon-free).
1171
- *
1172
- * The domain `parentClosePolicy` cascade is still enforced in Postgres by
1173
- * the shared `cancel` path — BullMQ's parent link is dispatch bookkeeping,
1174
- * not the authority.
1175
- */
1176
- async dispatch(run, type) {
1177
- await this.loadBullMq();
1178
- const def = await this.loadDefinition(type);
1179
- const jobId = run.dedupeKey ? sha1JobId(run.dedupeKey) : run.id;
1180
- const jobOpts = {
1181
- jobId,
1182
- ...this.retryOpts(def),
1183
- ...this.dedupeOpts(run, def)
1184
- };
1185
- if (run.parentRunId) {
1186
- const parentRow = await this.loadRun(run.parentRunId);
1187
- if (parentRow) {
1188
- const parentJobId = parentRow.dedupeKey ? sha1JobId(parentRow.dedupeKey) : parentRow.id;
1189
- jobOpts.parent = {
1190
- id: parentJobId,
1191
- queue: resolvePoolQueueName(parentRow.pool, this.bullConfig)
1192
- };
1193
- }
1194
- }
1195
- const payload = { runId: run.id, type, input: run.input };
1196
- await this.queueFor(run.pool).add(type, payload, jobOpts);
1197
- }
1198
- /**
1199
- * Opt-in extension (spec §Extensions): expose the FlowProducer for
1200
- * consumers that want to submit a whole parent/child DAG atomically up
1201
- * front, rather than incrementally via `ctx.spawnChild`. Backend-specific —
1202
- * code using it is not portable to the Drizzle backend. Async because it
1203
- * lazily loads the optional `bullmq` package on first use.
1204
- */
1205
- async flowProducer() {
1206
- await this.loadBullMq();
1207
- return this.flow();
1208
- }
1209
- retryOpts(def) {
1210
- const policy = def.retryPolicy;
1211
- if (!policy) return {};
1212
- return {
1213
- attempts: policy.attempts,
1214
- backoff: {
1215
- type: policy.backoff === "exponential" ? "exponential" : "fixed",
1216
- delay: policy.baseMs
1217
- }
1218
- };
1219
- }
1220
- dedupeOpts(run, def) {
1221
- if (!run.dedupeKey || !def.dedupeWindowMs) return {};
1222
- return {
1223
- deduplication: {
1224
- id: sha1JobId(run.dedupeKey),
1225
- ttl: def.dedupeWindowMs
1226
- }
1227
- };
1228
- }
1229
- // ==========================================================================
1230
- // cancel — Postgres cascade (super) + remove from queue
1231
- // ==========================================================================
1232
- async cancel(runId, opts = {}) {
1233
- const target = await this.loadRun(runId);
1234
- await super.cancel(runId, opts);
1235
- if (!target) return;
1236
- await this.loadBullMq();
1237
- await this.removeFromQueue(target);
1238
- if (opts.cascade === false) return;
1239
- const descendants = await this.bullDb.select().from(jobRuns).where(eq4(jobRuns.rootRunId, target.rootRunId));
1240
- for (const child of descendants) {
1241
- if (child.id === runId) continue;
1242
- await this.removeFromQueue(child);
1243
- }
1244
- }
1245
- async removeFromQueue(run) {
1246
- const jobId = run.dedupeKey ? sha1JobId(run.dedupeKey) : run.id;
1247
- try {
1248
- const job = await this.queueFor(run.pool).getJob(jobId);
1249
- if (job) await job.remove();
1250
- } catch (err) {
1251
- this.bullLogger.warn(
1252
- `cancel: could not remove BullMQ job ${jobId} (pool=${run.pool}): ${err.message}`
1253
- );
1254
- }
1255
- }
1256
- // ==========================================================================
1257
- // replay — Postgres reset (super) + re-enqueue
1258
- // ==========================================================================
1259
- async replay(runId) {
1260
- const run = await super.replay(runId);
1261
- await this.dispatch(run, run.jobType);
1262
- return run;
1263
- }
1264
- // ==========================================================================
1265
- // Internals
1266
- // ==========================================================================
1267
- async loadDefinition(type) {
1268
- const [def] = await this.bullDb.select().from(jobs).where(eq4(jobs.type, type)).limit(1);
1269
- if (!def) {
1270
- throw new Error(`BullMQJobOrchestrator: no job definition for '${type}'`);
1271
- }
1272
- return def;
1273
- }
1274
- async loadRun(id) {
1275
- const [row] = await this.bullDb.select().from(jobRuns).where(eq4(jobRuns.id, id)).limit(1);
1276
- return row ?? null;
1277
- }
1278
- /** Close all open queue + flow connections. Called on module destroy. */
1279
- async closeConnections() {
1280
- for (const q of this.queues.values()) {
1281
- await q.close().catch(() => void 0);
1282
- }
1283
- this.queues.clear();
1284
- if (this._flow) {
1285
- await this._flow.close().catch(() => void 0);
1286
- this._flow = null;
1287
- }
1288
- }
1289
- };
1290
- BullMQJobOrchestrator = __decorateClass([
1291
- Injectable4(),
1292
- __decorateParam(0, Inject4(DRIZZLE)),
1293
- __decorateParam(1, Inject4(JOBS_MULTI_TENANT)),
1294
- __decorateParam(2, Inject4(BULLMQ_CONNECTION)),
1295
- __decorateParam(3, Optional()),
1296
- __decorateParam(3, Inject4(BULLMQ_RESOLVED_CONFIG))
1297
- ], BullMQJobOrchestrator);
1298
-
1299
914
  // runtime/subsystems/jobs/job-orchestrator.memory-backend.ts
1300
915
  import { randomUUID as randomUUID2 } from "crypto";
1301
- import { Inject as Inject5, Injectable as Injectable5, Logger as Logger3, Optional as Optional2 } from "@nestjs/common";
916
+ import { Inject as Inject4, Injectable as Injectable4, Logger as Logger2, Optional } from "@nestjs/common";
1302
917
  var QUEUED_RUN_AT = /* @__PURE__ */ new Date(864e13);
1303
918
  var TERMINAL_STATUSES2 = [
1304
919
  "completed",
@@ -1345,7 +960,7 @@ var MemoryJobOrchestrator = class {
1345
960
  stepService;
1346
961
  multiTenant;
1347
962
  moduleRef;
1348
- logger = new Logger3(MemoryJobOrchestrator.name);
963
+ logger = new Logger2(MemoryJobOrchestrator.name);
1349
964
  mutex = new PromiseMutex();
1350
965
  handlerRegistry = /* @__PURE__ */ new Map();
1351
966
  /**
@@ -1694,7 +1309,7 @@ var MemoryJobOrchestrator = class {
1694
1309
  run,
1695
1310
  step: this.makeStepFn(run),
1696
1311
  spawnChild: this.makeSpawnFn(run),
1697
- logger: new Logger3(`JobRun:${run.id}`)
1312
+ logger: new Logger2(`JobRun:${run.id}`)
1698
1313
  };
1699
1314
  const attemptsBefore = run.attempts ?? 0;
1700
1315
  try {
@@ -1867,9 +1482,9 @@ var MemoryJobOrchestrator = class {
1867
1482
  }
1868
1483
  };
1869
1484
  MemoryJobOrchestrator = __decorateClass([
1870
- Injectable5(),
1871
- __decorateParam(2, Inject5(JOBS_MULTI_TENANT)),
1872
- __decorateParam(3, Optional2())
1485
+ Injectable4(),
1486
+ __decorateParam(2, Inject4(JOBS_MULTI_TENANT)),
1487
+ __decorateParam(3, Optional())
1873
1488
  ], MemoryJobOrchestrator);
1874
1489
  function classifyError(err, policy, currentAttempts) {
1875
1490
  if (!policy) return "fail";
@@ -1903,7 +1518,7 @@ function serialiseError(err, attempt, retryable) {
1903
1518
  }
1904
1519
 
1905
1520
  // runtime/subsystems/jobs/job-run-service.memory-backend.ts
1906
- import { Inject as Inject6, Injectable as Injectable6 } from "@nestjs/common";
1521
+ import { Inject as Inject5, Injectable as Injectable5 } from "@nestjs/common";
1907
1522
  var NON_TERMINAL_STATUSES2 = [
1908
1523
  "pending",
1909
1524
  "running",
@@ -2070,9 +1685,9 @@ var MemoryJobRunService = class {
2070
1685
  }
2071
1686
  };
2072
1687
  MemoryJobRunService = __decorateClass([
2073
- Injectable6(),
2074
- __decorateParam(1, Inject6(JOB_ORCHESTRATOR)),
2075
- __decorateParam(2, Inject6(JOBS_MULTI_TENANT))
1688
+ Injectable5(),
1689
+ __decorateParam(1, Inject5(JOB_ORCHESTRATOR)),
1690
+ __decorateParam(2, Inject5(JOBS_MULTI_TENANT))
2076
1691
  ], MemoryJobRunService);
2077
1692
  function compareBy(a, b, order) {
2078
1693
  switch (order) {
@@ -2090,7 +1705,7 @@ function compareBy(a, b, order) {
2090
1705
 
2091
1706
  // runtime/subsystems/jobs/job-step-service.memory-backend.ts
2092
1707
  import { randomUUID as randomUUID3 } from "crypto";
2093
- import { Injectable as Injectable7 } from "@nestjs/common";
1708
+ import { Injectable as Injectable6 } from "@nestjs/common";
2094
1709
  var MemoryJobStepService = class {
2095
1710
  constructor(store) {
2096
1711
  this.store = store;
@@ -2165,42 +1780,189 @@ var MemoryJobStepService = class {
2165
1780
  } else {
2166
1781
  this.store.steps.set(runId, kept);
2167
1782
  }
2168
- }
2169
- getOrCreateRows(runId) {
2170
- let rows = this.store.steps.get(runId);
2171
- if (!rows) {
2172
- rows = [];
2173
- this.store.steps.set(runId, rows);
1783
+ }
1784
+ getOrCreateRows(runId) {
1785
+ let rows = this.store.steps.get(runId);
1786
+ if (!rows) {
1787
+ rows = [];
1788
+ this.store.steps.set(runId, rows);
1789
+ }
1790
+ return rows;
1791
+ }
1792
+ nextSeq(rows) {
1793
+ let max = 0;
1794
+ for (const r of rows) {
1795
+ if (r.seq > max) max = r.seq;
1796
+ }
1797
+ return max + 1;
1798
+ }
1799
+ };
1800
+ MemoryJobStepService = __decorateClass([
1801
+ Injectable6()
1802
+ ], MemoryJobStepService);
1803
+
1804
+ // runtime/subsystems/jobs/memory-job-store.ts
1805
+ var MemoryJobStore = class {
1806
+ /** Runs keyed by `id` (single source of truth for status/scope/lineage). */
1807
+ runs = /* @__PURE__ */ new Map();
1808
+ /** Steps keyed by `job_run_id`; array order matches insertion order. */
1809
+ steps = /* @__PURE__ */ new Map();
1810
+ /** Job definitions keyed by `type` — memory mirror of the `job` table. */
1811
+ jobs = /* @__PURE__ */ new Map();
1812
+ /** Reset everything. Tests call this in `beforeEach`. */
1813
+ clear() {
1814
+ this.runs.clear();
1815
+ this.steps.clear();
1816
+ this.jobs.clear();
1817
+ }
1818
+ };
1819
+
1820
+ // runtime/subsystems/jobs/pool-config.loader.ts
1821
+ import { existsSync, readFileSync } from "fs";
1822
+ import { resolve } from "path";
1823
+ import { parse as parseYaml } from "yaml";
1824
+ var FRAMEWORK_POOLS = Object.freeze({
1825
+ events_inbound: Object.freeze({
1826
+ queue: "jobs-events-inbound",
1827
+ concurrency: 20,
1828
+ reserved: true,
1829
+ description: "Inbound events drain (events subsystem outbox)."
1830
+ }),
1831
+ events_change: Object.freeze({
1832
+ queue: "jobs-events-change",
1833
+ concurrency: 30,
1834
+ reserved: true,
1835
+ description: "Change events drain (events subsystem outbox)."
1836
+ }),
1837
+ events_outbound: Object.freeze({
1838
+ queue: "jobs-events-outbound",
1839
+ concurrency: 10,
1840
+ reserved: true,
1841
+ description: "Outbound events drain (events subsystem outbox)."
1842
+ }),
1843
+ interactive: Object.freeze({
1844
+ queue: "jobs-interactive",
1845
+ concurrency: 20,
1846
+ reserved: false,
1847
+ description: "User-facing latency-sensitive jobs."
1848
+ }),
1849
+ batch: Object.freeze({
1850
+ queue: "jobs-batch",
1851
+ concurrency: 5,
1852
+ reserved: false,
1853
+ description: "Default pool for background jobs."
1854
+ })
1855
+ });
1856
+ var RESERVED_POOL_NAMES = new Set(
1857
+ Object.entries(FRAMEWORK_POOLS).filter(([, def]) => def.reserved).map(([name]) => name)
1858
+ );
1859
+ var cache = /* @__PURE__ */ new Map();
1860
+ function loadPoolConfig(configPath) {
1861
+ const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);
1862
+ const cached = cache.get(resolved);
1863
+ if (cached) return cached;
1864
+ const merged = /* @__PURE__ */ new Map();
1865
+ for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {
1866
+ merged.set(name, { ...def });
1867
+ }
1868
+ if (!existsSync(resolved)) {
1869
+ cache.set(resolved, merged);
1870
+ return merged;
1871
+ }
1872
+ let raw;
1873
+ try {
1874
+ raw = parseYaml(readFileSync(resolved, "utf8"));
1875
+ } catch (err) {
1876
+ throw new Error(
1877
+ `pool-config.loader: failed to parse YAML at ${resolved}: ${err.message}`
1878
+ );
1879
+ }
1880
+ const userPools = extractUserPools(raw);
1881
+ for (const [name, userDef] of Object.entries(userPools)) {
1882
+ const existing = merged.get(name);
1883
+ if (existing) {
1884
+ const next = {
1885
+ queue: existing.queue,
1886
+ concurrency: typeof userDef.concurrency === "number" ? userDef.concurrency : existing.concurrency,
1887
+ reserved: existing.reserved,
1888
+ description: userDef.description ?? existing.description
1889
+ };
1890
+ merged.set(name, next);
1891
+ continue;
1892
+ }
1893
+ if (typeof userDef.queue !== "string" || userDef.queue.length === 0) {
1894
+ throw new Error(
1895
+ `pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`
1896
+ );
2174
1897
  }
2175
- return rows;
2176
- }
2177
- nextSeq(rows) {
2178
- let max = 0;
2179
- for (const r of rows) {
2180
- if (r.seq > max) max = r.seq;
1898
+ if (typeof userDef.concurrency !== "number" || userDef.concurrency <= 0) {
1899
+ throw new Error(
1900
+ `pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`
1901
+ );
2181
1902
  }
2182
- return max + 1;
1903
+ if (userDef.reserved === true) {
1904
+ throw new Error(
1905
+ `pool-config.loader: user-defined pool '${name}' cannot set 'reserved: true' \u2014 reserved is framework-only.`
1906
+ );
1907
+ }
1908
+ merged.set(name, {
1909
+ queue: userDef.queue,
1910
+ concurrency: userDef.concurrency,
1911
+ reserved: false,
1912
+ description: userDef.description
1913
+ });
2183
1914
  }
2184
- };
2185
- MemoryJobStepService = __decorateClass([
2186
- Injectable7()
2187
- ], MemoryJobStepService);
1915
+ cache.set(resolved, merged);
1916
+ return merged;
1917
+ }
1918
+ function allNonReservedPoolNames(config) {
1919
+ const out = [];
1920
+ for (const [name, def] of config) {
1921
+ if (!def.reserved) out.push(name);
1922
+ }
1923
+ return out;
1924
+ }
1925
+ function allPoolNames(config) {
1926
+ return [...config.keys()];
1927
+ }
1928
+ function extractUserPools(raw) {
1929
+ if (!raw || typeof raw !== "object") return {};
1930
+ const jobs2 = raw.jobs;
1931
+ if (!jobs2 || typeof jobs2 !== "object") return {};
1932
+ const pools = jobs2.pools;
1933
+ if (!pools || typeof pools !== "object") return {};
1934
+ const out = {};
1935
+ for (const [name, def] of Object.entries(pools)) {
1936
+ if (!def || typeof def !== "object") continue;
1937
+ out[name] = def;
1938
+ }
1939
+ return out;
1940
+ }
2188
1941
 
2189
- // runtime/subsystems/jobs/memory-job-store.ts
2190
- var MemoryJobStore = class {
2191
- /** Runs keyed by `id` (single source of truth for status/scope/lineage). */
2192
- runs = /* @__PURE__ */ new Map();
2193
- /** Steps keyed by `job_run_id`; array order matches insertion order. */
2194
- steps = /* @__PURE__ */ new Map();
2195
- /** Job definitions keyed by `type` memory mirror of the `job` table. */
2196
- jobs = /* @__PURE__ */ new Map();
2197
- /** Reset everything. Tests call this in `beforeEach`. */
2198
- clear() {
2199
- this.runs.clear();
2200
- this.steps.clear();
2201
- this.jobs.clear();
1942
+ // runtime/subsystems/jobs/bullmq.config.ts
1943
+ var BULLMQ_CONNECTION = /* @__PURE__ */ Symbol("BULLMQ_CONNECTION");
1944
+ var BULLMQ_RESOLVED_CONFIG = /* @__PURE__ */ Symbol("BULLMQ_RESOLVED_CONFIG");
1945
+ var DEFAULT_REDIS_URL = "redis://localhost:6379";
1946
+ var DEFAULT_BULL_BOARD_MOUNT = "/admin/queues";
1947
+ function resolveBullMqConfig(ext) {
1948
+ const url = ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;
1949
+ const resolved = {
1950
+ connection: { url },
1951
+ queuePrefix: ext?.queue_prefix
1952
+ };
1953
+ if (ext?.bull_board?.enabled) {
1954
+ resolved.bullBoard = {
1955
+ enabled: true,
1956
+ mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT
1957
+ };
2202
1958
  }
2203
- };
1959
+ return resolved;
1960
+ }
1961
+ function resolvePoolQueueName(pool, config, poolConfig = loadPoolConfig()) {
1962
+ const alias = poolConfig.get(pool)?.queue ?? pool;
1963
+ const prefix = config?.queuePrefix;
1964
+ return prefix ? `${prefix}:${alias}` : alias;
1965
+ }
2204
1966
 
2205
1967
  // runtime/subsystems/jobs/jobs-domain.module.ts
2206
1968
  var JobsDomainModule = class {
@@ -2227,7 +1989,20 @@ var JobsDomainModule = class {
2227
1989
  const resolved = resolveBullMqConfig(opts.extensions?.bullmq);
2228
1990
  providers.push({ provide: BULLMQ_CONNECTION, useValue: resolved.connection });
2229
1991
  providers.push({ provide: BULLMQ_RESOLVED_CONFIG, useValue: resolved });
2230
- providers.push({ provide: JOB_ORCHESTRATOR, useClass: BullMQJobOrchestrator });
1992
+ providers.push({
1993
+ provide: JOB_ORCHESTRATOR,
1994
+ useFactory: async (...args) => {
1995
+ const specifier = "./job-orchestrator.bullmq-backend";
1996
+ const mod = await import(specifier);
1997
+ return new mod.BullMQJobOrchestrator(...args);
1998
+ },
1999
+ // The bullmq orchestrator constructor mirrors DrizzleJobOrchestrator's
2000
+ // injection list: DRIZZLE + JOBS_MULTI_TENANT + the resolved BullMQ
2001
+ // tokens. Importing token references would force a static dep on the
2002
+ // tokens file in this module's import graph; using the existing
2003
+ // symbols already in scope is sufficient.
2004
+ inject: [DRIZZLE, JOBS_MULTI_TENANT, BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG]
2005
+ });
2231
2006
  providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });
2232
2007
  providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });
2233
2008
  } else {
@@ -2257,8 +2032,8 @@ JobsDomainModule = __decorateClass([
2257
2032
  ], JobsDomainModule);
2258
2033
 
2259
2034
  // runtime/subsystems/jobs/job-worker.ts
2260
- import { Inject as Inject7, Injectable as Injectable8, Logger as Logger4 } from "@nestjs/common";
2261
- import { and as and4, asc as asc2, desc as desc3, eq as eq5, inArray as inArray3, lt as lt2, lte, sql as sql4 } from "drizzle-orm";
2035
+ import { Inject as Inject6, Injectable as Injectable7, Logger as Logger3 } from "@nestjs/common";
2036
+ import { and as and4, asc as asc2, desc as desc3, eq as eq4, inArray as inArray3, lt as lt2, lte, sql as sql4 } from "drizzle-orm";
2262
2037
  var JOB_WORKER_OPTIONS = /* @__PURE__ */ Symbol("JOB_WORKER_OPTIONS");
2263
2038
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
2264
2039
  var DEFAULT_STALE_SWEEPER_INTERVAL_MS = 6e4;
@@ -2321,7 +2096,7 @@ var JobWorker = class {
2321
2096
  stepService;
2322
2097
  options;
2323
2098
  moduleRef;
2324
- logger = new Logger4(JobWorker.name);
2099
+ logger = new Logger3(JobWorker.name);
2325
2100
  shuttingDown = false;
2326
2101
  inFlight = /* @__PURE__ */ new Set();
2327
2102
  pollTimer = null;
@@ -2362,7 +2137,7 @@ var JobWorker = class {
2362
2137
  await this.drainInFlight();
2363
2138
  try {
2364
2139
  await this.db.update(jobRuns).set({ status: "pending", claimedAt: null, startedAt: null }).where(
2365
- and4(eq5(jobRuns.status, "running"), eq5(jobRuns.pool, this.options.pool))
2140
+ and4(eq4(jobRuns.status, "running"), eq4(jobRuns.pool, this.options.pool))
2366
2141
  );
2367
2142
  } catch (err) {
2368
2143
  this.logger.error(`shutdown reset failed: ${err.message}`);
@@ -2412,8 +2187,8 @@ var JobWorker = class {
2412
2187
  return this.db.transaction(async (tx) => {
2413
2188
  const candidates = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
2414
2189
  and4(
2415
- eq5(jobRuns.status, "pending"),
2416
- eq5(jobRuns.pool, pool),
2190
+ eq4(jobRuns.status, "pending"),
2191
+ eq4(jobRuns.pool, pool),
2417
2192
  lte(jobRuns.runAt, /* @__PURE__ */ new Date())
2418
2193
  )
2419
2194
  ).orderBy(desc3(jobRuns.priority), asc2(jobRuns.runAt)).limit(1).for("update", { skipLocked: true });
@@ -2424,7 +2199,7 @@ var JobWorker = class {
2424
2199
  claimedAt: /* @__PURE__ */ new Date(),
2425
2200
  startedAt: /* @__PURE__ */ new Date(),
2426
2201
  updatedAt: /* @__PURE__ */ new Date()
2427
- }).where(eq5(jobRuns.id, candidate.id)).returning();
2202
+ }).where(eq4(jobRuns.id, candidate.id)).returning();
2428
2203
  return claimed ?? null;
2429
2204
  });
2430
2205
  }
@@ -2442,7 +2217,7 @@ var JobWorker = class {
2442
2217
  await this.db.transaction(async (tx) => {
2443
2218
  const threshold = new Date(Date.now() - this.staleThresholdMs);
2444
2219
  const stale = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
2445
- and4(eq5(jobRuns.status, "running"), lt2(jobRuns.claimedAt, threshold))
2220
+ and4(eq4(jobRuns.status, "running"), lt2(jobRuns.claimedAt, threshold))
2446
2221
  ).for("update", { skipLocked: true });
2447
2222
  if (stale.length === 0) return;
2448
2223
  const ids = stale.map((r) => r.id);
@@ -2475,8 +2250,8 @@ var JobWorker = class {
2475
2250
  if (claimed.concurrencyKey) {
2476
2251
  const inflight = await this.db.select({ id: jobRuns.id }).from(jobRuns).where(
2477
2252
  and4(
2478
- eq5(jobRuns.concurrencyKey, claimed.concurrencyKey),
2479
- eq5(jobRuns.status, "running")
2253
+ eq4(jobRuns.concurrencyKey, claimed.concurrencyKey),
2254
+ eq4(jobRuns.status, "running")
2480
2255
  )
2481
2256
  );
2482
2257
  const other = inflight.find((r) => r.id !== claimed.id);
@@ -2486,7 +2261,7 @@ var JobWorker = class {
2486
2261
  claimedAt: null,
2487
2262
  startedAt: null,
2488
2263
  updatedAt: /* @__PURE__ */ new Date()
2489
- }).where(eq5(jobRuns.id, claimed.id));
2264
+ }).where(eq4(jobRuns.id, claimed.id));
2490
2265
  return;
2491
2266
  }
2492
2267
  }
@@ -2501,7 +2276,7 @@ var JobWorker = class {
2501
2276
  run: claimed,
2502
2277
  step: this.makeStepFn(claimed),
2503
2278
  spawnChild: this.makeSpawnFn(claimed),
2504
- logger: new Logger4(`JobRun:${claimed.id}`)
2279
+ logger: new Logger3(`JobRun:${claimed.id}`)
2505
2280
  };
2506
2281
  const attemptsBefore = claimed.attempts ?? 0;
2507
2282
  try {
@@ -2512,7 +2287,7 @@ var JobWorker = class {
2512
2287
  finishedAt: /* @__PURE__ */ new Date(),
2513
2288
  updatedAt: /* @__PURE__ */ new Date(),
2514
2289
  attempts: attemptsBefore + 1
2515
- }).where(eq5(jobRuns.id, claimed.id));
2290
+ }).where(eq4(jobRuns.id, claimed.id));
2516
2291
  } catch (err) {
2517
2292
  const policy = meta.retry;
2518
2293
  const decision = classifyError2(err, policy, attemptsBefore);
@@ -2527,7 +2302,7 @@ var JobWorker = class {
2527
2302
  claimedAt: null,
2528
2303
  error: serialiseError2(err, nextAttempts, true),
2529
2304
  updatedAt: /* @__PURE__ */ new Date()
2530
- }).where(eq5(jobRuns.id, claimed.id));
2305
+ }).where(eq4(jobRuns.id, claimed.id));
2531
2306
  } else {
2532
2307
  await this.markFailed(claimed, err, nextAttempts);
2533
2308
  }
@@ -2540,7 +2315,7 @@ var JobWorker = class {
2540
2315
  finishedAt: /* @__PURE__ */ new Date(),
2541
2316
  error: serialiseError2(err, finalAttempts, false),
2542
2317
  updatedAt: /* @__PURE__ */ new Date()
2543
- }).where(eq5(jobRuns.id, claimed.id));
2318
+ }).where(eq4(jobRuns.id, claimed.id));
2544
2319
  if (claimed.parentClosePolicy === "terminate") {
2545
2320
  try {
2546
2321
  await this.orchestrator.cancel(claimed.id, {
@@ -2643,215 +2418,14 @@ var JobWorker = class {
2643
2418
  // ============================================================================
2644
2419
  };
2645
2420
  JobWorker = __decorateClass([
2646
- Injectable8(),
2647
- __decorateParam(0, Inject7(DRIZZLE)),
2648
- __decorateParam(1, Inject7(JOB_ORCHESTRATOR)),
2649
- __decorateParam(2, Inject7(JOB_RUN_SERVICE)),
2650
- __decorateParam(3, Inject7(JOB_STEP_SERVICE)),
2651
- __decorateParam(4, Inject7(JOB_WORKER_OPTIONS))
2421
+ Injectable7(),
2422
+ __decorateParam(0, Inject6(DRIZZLE)),
2423
+ __decorateParam(1, Inject6(JOB_ORCHESTRATOR)),
2424
+ __decorateParam(2, Inject6(JOB_RUN_SERVICE)),
2425
+ __decorateParam(3, Inject6(JOB_STEP_SERVICE)),
2426
+ __decorateParam(4, Inject6(JOB_WORKER_OPTIONS))
2652
2427
  ], JobWorker);
2653
2428
 
2654
- // runtime/subsystems/jobs/job-worker.bullmq-backend.ts
2655
- import { Logger as Logger5 } from "@nestjs/common";
2656
- import { eq as eq6 } from "drizzle-orm";
2657
- function serialiseError3(err, attempt, retryable) {
2658
- const e = err;
2659
- return {
2660
- message: e?.message ?? String(err),
2661
- stack: e?.stack,
2662
- retryable,
2663
- attempt
2664
- };
2665
- }
2666
- var BullMQJobWorker = class _BullMQJobWorker {
2667
- constructor(db, orchestrator, stepService, options, moduleRef) {
2668
- this.db = db;
2669
- this.orchestrator = orchestrator;
2670
- this.stepService = stepService;
2671
- this.options = options;
2672
- this.moduleRef = moduleRef;
2673
- }
2674
- db;
2675
- orchestrator;
2676
- stepService;
2677
- options;
2678
- moduleRef;
2679
- logger = new Logger5(_BullMQJobWorker.name);
2680
- worker = null;
2681
- async onModuleInit() {
2682
- let WorkerCtor;
2683
- try {
2684
- const mod = await import("bullmq");
2685
- WorkerCtor = mod.Worker;
2686
- } catch {
2687
- throw new Error(
2688
- 'BullMQ backend requires the "bullmq" package. Install it with: npm install bullmq'
2689
- );
2690
- }
2691
- this.worker = new WorkerCtor(
2692
- this.options.queueName,
2693
- (job) => this.process(job),
2694
- {
2695
- connection: this.options.connection,
2696
- concurrency: this.options.concurrency
2697
- }
2698
- );
2699
- this.worker.on("failed", (job, err) => {
2700
- if (!job) return;
2701
- const attemptsMade = job.attemptsMade;
2702
- const maxAttempts = job.opts.attempts ?? 1;
2703
- if (attemptsMade >= maxAttempts) {
2704
- void this.markFailed(job.data.runId, err, attemptsMade);
2705
- }
2706
- });
2707
- this.logger.log(
2708
- `BullMQ worker started: pool='${this.options.pool}' queue='${this.options.queueName}' concurrency=${this.options.concurrency}`
2709
- );
2710
- }
2711
- async onModuleDestroy() {
2712
- if (this.worker) {
2713
- await this.worker.close();
2714
- this.worker = null;
2715
- }
2716
- }
2717
- /**
2718
- * Process one BullMQ job. Returns the handler output (stored by BullMQ as
2719
- * the job return value AND written to `job_run.output`). Throws on handler
2720
- * failure so BullMQ applies the retry policy.
2721
- */
2722
- async process(job) {
2723
- const { runId } = job.data;
2724
- const [row] = await this.db.select().from(jobRuns).where(eq6(jobRuns.id, runId)).limit(1);
2725
- if (!row) {
2726
- this.logger.warn(`process: job_run ${runId} not found; skipping`);
2727
- return {};
2728
- }
2729
- const run = row;
2730
- if (run.status === "canceled") {
2731
- return {};
2732
- }
2733
- const registryEntry = JOB_HANDLER_REGISTRY.get(run.jobType);
2734
- if (!registryEntry) {
2735
- throw new Error(
2736
- `No handler registered for jobType='${run.jobType}' (run ${run.id})`
2737
- );
2738
- }
2739
- await this.db.update(jobRuns).set({
2740
- status: "running",
2741
- claimedAt: /* @__PURE__ */ new Date(),
2742
- startedAt: /* @__PURE__ */ new Date(),
2743
- attempts: job.attemptsMade + 1,
2744
- updatedAt: /* @__PURE__ */ new Date()
2745
- }).where(eq6(jobRuns.id, run.id));
2746
- const HandlerClass = registryEntry.handlerClass;
2747
- const handler = this.moduleRef.get(
2748
- HandlerClass,
2749
- { strict: false }
2750
- );
2751
- const ctx = {
2752
- input: run.input,
2753
- run,
2754
- step: this.makeStepFn(run),
2755
- spawnChild: this.makeSpawnFn(run),
2756
- logger: new Logger5(`JobRun:${run.id}`)
2757
- };
2758
- const output = await handler.run(ctx);
2759
- await this.db.update(jobRuns).set({
2760
- status: "completed",
2761
- output: output ?? {},
2762
- finishedAt: /* @__PURE__ */ new Date(),
2763
- updatedAt: /* @__PURE__ */ new Date()
2764
- }).where(eq6(jobRuns.id, run.id));
2765
- return output ?? {};
2766
- }
2767
- async markFailed(runId, err, finalAttempts) {
2768
- const [row] = await this.db.select().from(jobRuns).where(eq6(jobRuns.id, runId)).limit(1);
2769
- if (!row) return;
2770
- const run = row;
2771
- await this.db.update(jobRuns).set({
2772
- status: "failed",
2773
- attempts: finalAttempts,
2774
- finishedAt: /* @__PURE__ */ new Date(),
2775
- error: serialiseError3(err, finalAttempts, false),
2776
- updatedAt: /* @__PURE__ */ new Date()
2777
- }).where(eq6(jobRuns.id, runId));
2778
- if (run.parentClosePolicy === "terminate") {
2779
- try {
2780
- await this.orchestrator.cancel(run.id, {
2781
- cascade: true,
2782
- reason: "parent-failed",
2783
- tenantId: run.tenantId
2784
- });
2785
- } catch (cascadeErr) {
2786
- this.logger.warn(
2787
- `cascade on failed run ${run.id}: ${cascadeErr.message}`
2788
- );
2789
- }
2790
- }
2791
- }
2792
- // ── ctx.step / ctx.spawnChild (mirror JobWorker) ──────────────────────────
2793
- makeStepFn(run) {
2794
- return async (stepId, fn, _opts) => {
2795
- void _opts;
2796
- const existing = await this.stepService.findStep(run.id, stepId);
2797
- if (existing?.status === "completed") {
2798
- return existing.output;
2799
- }
2800
- const nextAttempts = (existing?.attempts ?? 0) + 1;
2801
- const seq = nextAttempts;
2802
- await this.stepService.recordStep({
2803
- jobRunId: run.id,
2804
- stepId,
2805
- kind: "task",
2806
- seq,
2807
- status: "running",
2808
- startedAt: /* @__PURE__ */ new Date(),
2809
- attempts: nextAttempts
2810
- });
2811
- try {
2812
- const output = await fn();
2813
- await this.stepService.recordStep({
2814
- jobRunId: run.id,
2815
- stepId,
2816
- kind: "task",
2817
- seq,
2818
- status: "completed",
2819
- output,
2820
- finishedAt: /* @__PURE__ */ new Date(),
2821
- attempts: nextAttempts
2822
- });
2823
- return output;
2824
- } catch (err) {
2825
- await this.stepService.recordStep({
2826
- jobRunId: run.id,
2827
- stepId,
2828
- kind: "task",
2829
- seq,
2830
- status: "failed",
2831
- error: serialiseError3(err, nextAttempts, false),
2832
- finishedAt: /* @__PURE__ */ new Date(),
2833
- attempts: nextAttempts
2834
- });
2835
- throw err;
2836
- }
2837
- };
2838
- }
2839
- makeSpawnFn(run) {
2840
- return async (type, input, opts) => {
2841
- return this.orchestrator.start(type, input, {
2842
- parentRunId: run.id,
2843
- parentClosePolicy: opts?.closePolicy,
2844
- runAt: opts?.runAt,
2845
- priority: opts?.priority,
2846
- tags: opts?.tags,
2847
- triggerSource: "parent",
2848
- triggerRef: run.id,
2849
- tenantId: run.tenantId
2850
- });
2851
- };
2852
- }
2853
- };
2854
-
2855
2429
  // runtime/subsystems/jobs/job-worker.module.ts
2856
2430
  var DEFAULT_SHUTDOWN_TIMEOUT_MS2 = 3e4;
2857
2431
  var JOB_WORKER_MODULE_OPTIONS = /* @__PURE__ */ Symbol("JOB_WORKER_MODULE_OPTIONS");
@@ -2874,7 +2448,7 @@ var JobWorkerOrchestrator = class {
2874
2448
  moduleRef;
2875
2449
  bullConnection;
2876
2450
  bullConfig;
2877
- logger = new Logger6(JobWorkerOrchestrator.name);
2451
+ logger = new Logger4(JobWorkerOrchestrator.name);
2878
2452
  workers = [];
2879
2453
  // ============================================================================
2880
2454
  // Lifecycle
@@ -2904,7 +2478,7 @@ var JobWorkerOrchestrator = class {
2904
2478
  concurrency: def.concurrency,
2905
2479
  shutdownTimeoutMs: this.options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS2
2906
2480
  };
2907
- const worker = this.options.workerFactory ? this.options.workerFactory(workerOptions) : backend === "bullmq" ? this.spawnBullMQWorker(poolName, def.queue, def.concurrency, poolConfig) : this.spawnWorker(workerOptions);
2481
+ const worker = this.options.workerFactory ? this.options.workerFactory(workerOptions) : backend === "bullmq" ? await this.spawnBullMQWorker(poolName, def.queue, def.concurrency, poolConfig) : this.spawnWorker(workerOptions);
2908
2482
  await worker.onModuleInit();
2909
2483
  this.workers.push(worker);
2910
2484
  this.logger.log(
@@ -3004,7 +2578,15 @@ var JobWorkerOrchestrator = class {
3004
2578
  * orchestrator's `dispatch` via `resolvePoolQueueName(pool, …)` so producer
3005
2579
  * and consumer agree.
3006
2580
  */
3007
- spawnBullMQWorker(pool, _queueAlias, concurrency, poolConfig) {
2581
+ /**
2582
+ * #6 — async + dynamic-import. The `job-worker.bullmq-backend.ts` file is
2583
+ * filtered out of the vendor set for drizzle/memory installs (no `bullmq`
2584
+ * peer dep needed). The non-literal import specifier makes TS treat the
2585
+ * module as `any` so the consumer's tsc never tries to resolve an absent
2586
+ * file. This method is only entered when `backend === 'bullmq'` — at which
2587
+ * point the file IS vendored.
2588
+ */
2589
+ async spawnBullMQWorker(pool, _queueAlias, concurrency, poolConfig) {
3008
2590
  if (!this.db) {
3009
2591
  throw new Error(
3010
2592
  `JobWorkerModule: BullMQ worker spawning requires the Drizzle client (no DRIZZLE provider available) \u2014 job_run remains the source of truth.`
@@ -3021,7 +2603,9 @@ var JobWorkerOrchestrator = class {
3021
2603
  );
3022
2604
  }
3023
2605
  const queueName = resolvePoolQueueName(pool, this.bullConfig, poolConfig);
3024
- return new BullMQJobWorker(
2606
+ const specifier = "./job-worker.bullmq-backend";
2607
+ const mod = await import(specifier);
2608
+ return new mod.BullMQJobWorker(
3025
2609
  this.db,
3026
2610
  this.orchestrator,
3027
2611
  this.stepService,
@@ -3036,17 +2620,17 @@ var JobWorkerOrchestrator = class {
3036
2620
  }
3037
2621
  };
3038
2622
  JobWorkerOrchestrator = __decorateClass([
3039
- Injectable9(),
3040
- __decorateParam(0, Inject8(JOB_ORCHESTRATOR)),
3041
- __decorateParam(1, Inject8(JOB_RUN_SERVICE)),
3042
- __decorateParam(2, Inject8(JOB_STEP_SERVICE)),
3043
- __decorateParam(3, Inject8(JOB_WORKER_MODULE_OPTIONS)),
3044
- __decorateParam(4, Optional3()),
3045
- __decorateParam(4, Inject8(DRIZZLE)),
3046
- __decorateParam(6, Optional3()),
3047
- __decorateParam(6, Inject8(BULLMQ_CONNECTION)),
3048
- __decorateParam(7, Optional3()),
3049
- __decorateParam(7, Inject8(BULLMQ_RESOLVED_CONFIG))
2623
+ Injectable8(),
2624
+ __decorateParam(0, Inject7(JOB_ORCHESTRATOR)),
2625
+ __decorateParam(1, Inject7(JOB_RUN_SERVICE)),
2626
+ __decorateParam(2, Inject7(JOB_STEP_SERVICE)),
2627
+ __decorateParam(3, Inject7(JOB_WORKER_MODULE_OPTIONS)),
2628
+ __decorateParam(4, Optional2()),
2629
+ __decorateParam(4, Inject7(DRIZZLE)),
2630
+ __decorateParam(6, Optional2()),
2631
+ __decorateParam(6, Inject7(BULLMQ_CONNECTION)),
2632
+ __decorateParam(7, Optional2()),
2633
+ __decorateParam(7, Inject7(BULLMQ_RESOLVED_CONFIG))
3050
2634
  ], JobWorkerOrchestrator);
3051
2635
  var JobWorkerModule = class {
3052
2636
  static forRoot(opts) {
@@ -3232,8 +2816,8 @@ var MemoryBridgeDeliveryRepo = class {
3232
2816
  };
3233
2817
 
3234
2818
  // runtime/subsystems/bridge/bridge-delivery.drizzle-backend.ts
3235
- import { Inject as Inject9, Injectable as Injectable10, Optional as Optional4 } from "@nestjs/common";
3236
- import { eq as eq7, and as and5, gte as gte2, isNull as isNull2, sql as sql7 } from "drizzle-orm";
2819
+ import { Inject as Inject8, Injectable as Injectable9, Optional as Optional3 } from "@nestjs/common";
2820
+ import { eq as eq5, and as and5, gte as gte2, isNull as isNull2, sql as sql7 } from "drizzle-orm";
3237
2821
 
3238
2822
  // runtime/subsystems/bridge/bridge-delivery.schema.ts
3239
2823
  import {
@@ -3420,14 +3004,14 @@ var DrizzleBridgeDeliveryRepo = class {
3420
3004
  async findDelivery(eventId, triggerId) {
3421
3005
  const rows = await this.db.select().from(bridgeDelivery).where(
3422
3006
  and5(
3423
- eq7(bridgeDelivery.eventId, eventId),
3424
- eq7(bridgeDelivery.triggerId, triggerId)
3007
+ eq5(bridgeDelivery.eventId, eventId),
3008
+ eq5(bridgeDelivery.triggerId, triggerId)
3425
3009
  )
3426
3010
  ).limit(1);
3427
3011
  return rows[0] ?? null;
3428
3012
  }
3429
3013
  async findDeliveryById(id) {
3430
- const rows = await this.db.select().from(bridgeDelivery).where(eq7(bridgeDelivery.id, id)).limit(1);
3014
+ const rows = await this.db.select().from(bridgeDelivery).where(eq5(bridgeDelivery.id, id)).limit(1);
3431
3015
  return rows[0] ?? null;
3432
3016
  }
3433
3017
  async markDelivered(id, userRunId, tx) {
@@ -3436,15 +3020,15 @@ var DrizzleBridgeDeliveryRepo = class {
3436
3020
  status: "delivered",
3437
3021
  userRunId,
3438
3022
  deliveredAt: /* @__PURE__ */ new Date()
3439
- }).where(eq7(bridgeDelivery.id, id));
3023
+ }).where(eq5(bridgeDelivery.id, id));
3440
3024
  }
3441
3025
  async markSkipped(id, reason, tx) {
3442
3026
  const client = tx ?? this.db;
3443
- await client.update(bridgeDelivery).set({ status: "skipped", skipReason: reason }).where(eq7(bridgeDelivery.id, id));
3027
+ await client.update(bridgeDelivery).set({ status: "skipped", skipReason: reason }).where(eq5(bridgeDelivery.id, id));
3444
3028
  }
3445
3029
  async markFailed(id, error, tx) {
3446
3030
  const client = tx ?? this.db;
3447
- await client.update(bridgeDelivery).set({ status: "failed", error }).where(eq7(bridgeDelivery.id, id));
3031
+ await client.update(bridgeDelivery).set({ status: "failed", error }).where(eq5(bridgeDelivery.id, id));
3448
3032
  }
3449
3033
  /**
3450
3034
  * Observability read — see `IJobBridge.getStatusHistogram` JSDoc for the
@@ -3472,7 +3056,7 @@ var DrizzleBridgeDeliveryRepo = class {
3472
3056
  if (tenantId === null) {
3473
3057
  conditions.push(isNull2(bridgeDelivery.tenantId));
3474
3058
  } else if (typeof tenantId === "string") {
3475
- conditions.push(eq7(bridgeDelivery.tenantId, tenantId));
3059
+ conditions.push(eq5(bridgeDelivery.tenantId, tenantId));
3476
3060
  }
3477
3061
  const rows = await this.db.select({
3478
3062
  status: bridgeDelivery.status,
@@ -3491,18 +3075,18 @@ var DrizzleBridgeDeliveryRepo = class {
3491
3075
  }
3492
3076
  };
3493
3077
  DrizzleBridgeDeliveryRepo = __decorateClass([
3494
- Injectable10(),
3495
- __decorateParam(0, Inject9(DRIZZLE)),
3496
- __decorateParam(1, Optional4()),
3497
- __decorateParam(1, Inject9(BRIDGE_MULTI_TENANT))
3078
+ Injectable9(),
3079
+ __decorateParam(0, Inject8(DRIZZLE)),
3080
+ __decorateParam(1, Optional3()),
3081
+ __decorateParam(1, Inject8(BRIDGE_MULTI_TENANT))
3498
3082
  ], DrizzleBridgeDeliveryRepo);
3499
3083
 
3500
3084
  // runtime/subsystems/bridge/bridge-outbox-drain-hook.ts
3501
- import { Inject as Inject11, Injectable as Injectable12, Logger as Logger8, Optional as Optional6 } from "@nestjs/common";
3085
+ import { Inject as Inject10, Injectable as Injectable11, Logger as Logger6, Optional as Optional5 } from "@nestjs/common";
3502
3086
  import { randomUUID as randomUUID5 } from "crypto";
3503
3087
 
3504
3088
  // runtime/subsystems/bridge/bridge-delivery-handler.ts
3505
- import { Inject as Inject10, Injectable as Injectable11, Logger as Logger7, Optional as Optional5 } from "@nestjs/common";
3089
+ import { Inject as Inject9, Injectable as Injectable10, Logger as Logger5, Optional as Optional4 } from "@nestjs/common";
3506
3090
 
3507
3091
  // runtime/subsystems/events/events.tokens.ts
3508
3092
  var EVENT_BUS = "EVENT_BUS";
@@ -3524,7 +3108,7 @@ var BridgeDeliveryHandler = class extends JobHandlerBase {
3524
3108
  events;
3525
3109
  registry;
3526
3110
  multiTenant;
3527
- classLogger = new Logger7(BridgeDeliveryHandler.name);
3111
+ classLogger = new Logger5(BridgeDeliveryHandler.name);
3528
3112
  async run(ctx) {
3529
3113
  const { deliveryId } = ctx.input;
3530
3114
  const delivery = await this.repo.findDeliveryById(deliveryId);
@@ -3585,18 +3169,18 @@ var BridgeDeliveryHandler = class extends JobHandlerBase {
3585
3169
  }
3586
3170
  };
3587
3171
  BridgeDeliveryHandler = __decorateClass([
3588
- Injectable11(),
3172
+ Injectable10(),
3589
3173
  JobHandler(BRIDGE_DELIVERY_JOB_TYPE, {
3590
3174
  pool: "events_change",
3591
3175
  retry: { attempts: 3, backoff: "exponential", baseMs: 250 },
3592
3176
  replayFrom: "last_step"
3593
3177
  }),
3594
- __decorateParam(0, Inject10(BRIDGE_DELIVERY_REPO)),
3595
- __decorateParam(1, Inject10(JOB_ORCHESTRATOR)),
3596
- __decorateParam(2, Inject10(EVENT_BUS)),
3597
- __decorateParam(3, Inject10(BRIDGE_REGISTRY)),
3598
- __decorateParam(4, Optional5()),
3599
- __decorateParam(4, Inject10(BRIDGE_MULTI_TENANT))
3178
+ __decorateParam(0, Inject9(BRIDGE_DELIVERY_REPO)),
3179
+ __decorateParam(1, Inject9(JOB_ORCHESTRATOR)),
3180
+ __decorateParam(2, Inject9(EVENT_BUS)),
3181
+ __decorateParam(3, Inject9(BRIDGE_REGISTRY)),
3182
+ __decorateParam(4, Optional4()),
3183
+ __decorateParam(4, Inject9(BRIDGE_MULTI_TENANT))
3600
3184
  ], BridgeDeliveryHandler);
3601
3185
 
3602
3186
  // runtime/subsystems/bridge/bridge-outbox-drain-hook.ts
@@ -3610,7 +3194,7 @@ var BridgeOutboxDrainHook = class {
3610
3194
  this.registry = registry;
3611
3195
  }
3612
3196
  registry;
3613
- logger = new Logger8(BridgeOutboxDrainHook.name);
3197
+ logger = new Logger6(BridgeOutboxDrainHook.name);
3614
3198
  warnedNullDirection = false;
3615
3199
  warnedAuditTypes = /* @__PURE__ */ new Set();
3616
3200
  async processEvent(event, tx) {
@@ -3703,13 +3287,13 @@ var BridgeOutboxDrainHook = class {
3703
3287
  }
3704
3288
  };
3705
3289
  BridgeOutboxDrainHook = __decorateClass([
3706
- Injectable12(),
3707
- __decorateParam(0, Optional6()),
3708
- __decorateParam(0, Inject11(BRIDGE_REGISTRY))
3290
+ Injectable11(),
3291
+ __decorateParam(0, Optional5()),
3292
+ __decorateParam(0, Inject10(BRIDGE_REGISTRY))
3709
3293
  ], BridgeOutboxDrainHook);
3710
3294
 
3711
3295
  // runtime/subsystems/bridge/event-flow.service.ts
3712
- import { Inject as Inject12, Injectable as Injectable13, Optional as Optional7 } from "@nestjs/common";
3296
+ import { Inject as Inject11, Injectable as Injectable12, Optional as Optional6 } from "@nestjs/common";
3713
3297
  var EventFlowService = class {
3714
3298
  constructor(db, eventBus, orchestrator, bridgeRepo, registry = {}, multiTenant = false) {
3715
3299
  this.db = db;
@@ -3780,15 +3364,15 @@ var EventFlowService = class {
3780
3364
  }
3781
3365
  };
3782
3366
  EventFlowService = __decorateClass([
3783
- Injectable13(),
3784
- __decorateParam(0, Inject12(DRIZZLE)),
3785
- __decorateParam(1, Inject12(EVENT_BUS)),
3786
- __decorateParam(2, Inject12(JOB_ORCHESTRATOR)),
3787
- __decorateParam(3, Inject12(BRIDGE_DELIVERY_REPO)),
3788
- __decorateParam(4, Optional7()),
3789
- __decorateParam(4, Inject12(BRIDGE_REGISTRY)),
3790
- __decorateParam(5, Optional7()),
3791
- __decorateParam(5, Inject12(BRIDGE_MULTI_TENANT))
3367
+ Injectable12(),
3368
+ __decorateParam(0, Inject11(DRIZZLE)),
3369
+ __decorateParam(1, Inject11(EVENT_BUS)),
3370
+ __decorateParam(2, Inject11(JOB_ORCHESTRATOR)),
3371
+ __decorateParam(3, Inject11(BRIDGE_DELIVERY_REPO)),
3372
+ __decorateParam(4, Optional6()),
3373
+ __decorateParam(4, Inject11(BRIDGE_REGISTRY)),
3374
+ __decorateParam(5, Optional6()),
3375
+ __decorateParam(5, Inject11(BRIDGE_MULTI_TENANT))
3792
3376
  ], EventFlowService);
3793
3377
 
3794
3378
  // runtime/subsystems/bridge/generated/registry.ts
@@ -3866,8 +3450,8 @@ var BridgeModule = class {
3866
3450
  };
3867
3451
  BridgeModule = __decorateClass([
3868
3452
  Module3({}),
3869
- __decorateParam(0, Optional8()),
3870
- __decorateParam(0, Inject13(JOB_WORKER_MODULE_OPTIONS))
3453
+ __decorateParam(0, Optional7()),
3454
+ __decorateParam(0, Inject12(JOB_WORKER_MODULE_OPTIONS))
3871
3455
  ], BridgeModule);
3872
3456
  export {
3873
3457
  BridgeModule