@pattern-stack/codegen 0.8.1 → 0.9.0

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 (107) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js.map +1 -1
  3. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
  4. package/dist/runtime/subsystems/bridge/bridge.module.d.ts +3 -0
  5. package/dist/runtime/subsystems/bridge/bridge.module.js +930 -275
  6. package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
  7. package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
  8. package/dist/runtime/subsystems/bridge/index.d.ts +3 -0
  9. package/dist/runtime/subsystems/bridge/index.js +837 -182
  10. package/dist/runtime/subsystems/bridge/index.js.map +1 -1
  11. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.d.ts +3 -1
  12. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +92 -1
  13. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
  14. package/dist/runtime/subsystems/events/event-bus.memory-backend.d.ts +3 -1
  15. package/dist/runtime/subsystems/events/event-bus.memory-backend.js +99 -0
  16. package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
  17. package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
  18. package/dist/runtime/subsystems/events/event-keyset-cursor.d.ts +32 -0
  19. package/dist/runtime/subsystems/events/event-keyset-cursor.js +38 -0
  20. package/dist/runtime/subsystems/events/event-keyset-cursor.js.map +1 -0
  21. package/dist/runtime/subsystems/events/event-read.protocol.d.ts +94 -0
  22. package/dist/runtime/subsystems/events/event-read.protocol.js +9 -0
  23. package/dist/runtime/subsystems/events/event-read.protocol.js.map +1 -0
  24. package/dist/runtime/subsystems/events/events.module.js +177 -3
  25. package/dist/runtime/subsystems/events/events.module.js.map +1 -1
  26. package/dist/runtime/subsystems/events/events.tokens.d.ts +16 -1
  27. package/dist/runtime/subsystems/events/events.tokens.js +2 -0
  28. package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
  29. package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
  30. package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
  31. package/dist/runtime/subsystems/events/index.d.ts +2 -1
  32. package/dist/runtime/subsystems/events/index.js +178 -3
  33. package/dist/runtime/subsystems/events/index.js.map +1 -1
  34. package/dist/runtime/subsystems/index.d.ts +1 -0
  35. package/dist/runtime/subsystems/index.js +1194 -264
  36. package/dist/runtime/subsystems/index.js.map +1 -1
  37. package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +98 -0
  38. package/dist/runtime/subsystems/jobs/bullmq.config.js +143 -0
  39. package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -0
  40. package/dist/runtime/subsystems/jobs/index.d.ts +6 -2
  41. package/dist/runtime/subsystems/jobs/index.js +861 -201
  42. package/dist/runtime/subsystems/jobs/index.js.map +1 -1
  43. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.d.ts +107 -0
  44. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +922 -0
  45. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -0
  46. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.d.ts +52 -0
  47. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js +57 -0
  48. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js.map +1 -0
  49. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.d.ts +2 -1
  50. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +81 -1
  51. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js.map +1 -1
  52. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.d.ts +2 -1
  53. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +81 -0
  54. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
  55. package/dist/runtime/subsystems/jobs/job-run-service.protocol.d.ts +74 -1
  56. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.d.ts +48 -0
  57. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +374 -0
  58. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -0
  59. package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +42 -4
  60. package/dist/runtime/subsystems/jobs/job-worker.module.js +832 -178
  61. package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
  62. package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +10 -1
  63. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +519 -20
  64. package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
  65. package/dist/runtime/subsystems/jobs/pool-config.loader.d.ts +9 -1
  66. package/dist/runtime/subsystems/jobs/pool-config.loader.js +4 -0
  67. package/dist/runtime/subsystems/jobs/pool-config.loader.js.map +1 -1
  68. package/dist/runtime/subsystems/observability/index.d.ts +4 -3
  69. package/dist/runtime/subsystems/observability/index.js +109 -2
  70. package/dist/runtime/subsystems/observability/index.js.map +1 -1
  71. package/dist/runtime/subsystems/observability/observability.module.js +109 -2
  72. package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
  73. package/dist/runtime/subsystems/observability/observability.protocol.d.ts +63 -2
  74. package/dist/runtime/subsystems/observability/observability.service.d.ts +21 -3
  75. package/dist/runtime/subsystems/observability/observability.service.js +109 -2
  76. package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
  77. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +1 -0
  78. package/dist/runtime/subsystems/observability/reporters/index.d.ts +1 -0
  79. package/dist/src/cli/index.js +30 -6
  80. package/dist/src/cli/index.js.map +1 -1
  81. package/package.json +1 -1
  82. package/runtime/subsystems/bridge/bridge.module.ts +5 -0
  83. package/runtime/subsystems/events/event-bus.drizzle-backend.ts +109 -3
  84. package/runtime/subsystems/events/event-bus.memory-backend.ts +103 -1
  85. package/runtime/subsystems/events/event-keyset-cursor.ts +59 -0
  86. package/runtime/subsystems/events/event-read.protocol.ts +97 -0
  87. package/runtime/subsystems/events/events.module.ts +18 -2
  88. package/runtime/subsystems/events/events.tokens.ts +16 -0
  89. package/runtime/subsystems/events/index.ts +7 -0
  90. package/runtime/subsystems/jobs/bullmq.config.ts +125 -0
  91. package/runtime/subsystems/jobs/index.ts +22 -0
  92. package/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts +381 -0
  93. package/runtime/subsystems/jobs/job-run-keyset-cursor.ts +88 -0
  94. package/runtime/subsystems/jobs/job-run-service.drizzle-backend.ts +59 -1
  95. package/runtime/subsystems/jobs/job-run-service.memory-backend.ts +53 -0
  96. package/runtime/subsystems/jobs/job-run-service.protocol.ts +77 -0
  97. package/runtime/subsystems/jobs/job-worker.bullmq-backend.ts +311 -0
  98. package/runtime/subsystems/jobs/job-worker.module.ts +124 -10
  99. package/runtime/subsystems/jobs/jobs-domain.module.ts +40 -21
  100. package/runtime/subsystems/jobs/pool-config.loader.ts +11 -0
  101. package/runtime/subsystems/observability/index.ts +8 -0
  102. package/runtime/subsystems/observability/observability.protocol.ts +76 -0
  103. package/runtime/subsystems/observability/observability.service.ts +148 -1
  104. package/templates/entity/new/clean-lite-ps/prompt-extension.js +14 -12
  105. package/templates/relationship/new/prompt.js +8 -5
  106. package/templates/subsystem/jobs/worker.ejs.t +30 -7
  107. package/templates/subsystem/sync/sync-audit.schema.ejs.t +12 -16
@@ -629,7 +629,58 @@ function notInStatus(statuses) {
629
629
 
630
630
  // runtime/subsystems/jobs/job-run-service.drizzle-backend.ts
631
631
  import { Inject as Inject2, Injectable as Injectable2 } from "@nestjs/common";
632
- import { and as and2, asc, desc as desc2, eq as eq2, inArray as inArray2, isNull, sql as sql3 } from "drizzle-orm";
632
+ import { and as and2, asc, desc as desc2, eq as eq2, gte, inArray as inArray2, isNull, lt, or, sql as sql3 } from "drizzle-orm";
633
+
634
+ // runtime/subsystems/jobs/job-run-keyset-cursor.ts
635
+ var DEFAULT_LIST_LIMIT = 50;
636
+ var MAX_LIST_LIMIT = 200;
637
+ function clampLimit(limit) {
638
+ if (typeof limit !== "number" || !Number.isFinite(limit)) {
639
+ return DEFAULT_LIST_LIMIT;
640
+ }
641
+ const floored = Math.floor(limit);
642
+ if (floored < 1) return 1;
643
+ if (floored > MAX_LIST_LIMIT) return MAX_LIST_LIMIT;
644
+ return floored;
645
+ }
646
+ function encodeKeysetCursor(keyset) {
647
+ const tuple = [keyset.createdAt.toISOString(), keyset.id];
648
+ return Buffer.from(JSON.stringify(tuple), "utf8").toString("base64url");
649
+ }
650
+ function decodeKeysetCursor(cursor) {
651
+ try {
652
+ const json = Buffer.from(cursor, "base64url").toString("utf8");
653
+ const parsed = JSON.parse(json);
654
+ if (!Array.isArray(parsed) || parsed.length !== 2) return null;
655
+ const [iso, id] = parsed;
656
+ if (typeof iso !== "string" || typeof id !== "string") return null;
657
+ const createdAt = new Date(iso);
658
+ if (Number.isNaN(createdAt.getTime())) return null;
659
+ return { createdAt, id };
660
+ } catch {
661
+ return null;
662
+ }
663
+ }
664
+ function toJobRunSummary(r) {
665
+ return {
666
+ runId: r.id,
667
+ rootRunId: r.rootRunId,
668
+ jobType: r.jobType,
669
+ pool: r.pool,
670
+ status: r.status,
671
+ scopeEntityType: r.scopeEntityType,
672
+ scopeEntityId: r.scopeEntityId,
673
+ tenantId: r.tenantId,
674
+ attempts: r.attempts,
675
+ errorMessage: r.error?.message ?? null,
676
+ runAt: r.runAt,
677
+ startedAt: r.startedAt,
678
+ finishedAt: r.finishedAt,
679
+ createdAt: r.createdAt
680
+ };
681
+ }
682
+
683
+ // runtime/subsystems/jobs/job-run-service.drizzle-backend.ts
633
684
  var NON_TERMINAL_STATUSES = [
634
685
  "pending",
635
686
  "running",
@@ -752,6 +803,37 @@ var DrizzleJobRunService = class {
752
803
  createdAt: r.createdAt
753
804
  }));
754
805
  }
806
+ async listJobRuns(query = {}) {
807
+ const limit = clampLimit(query.limit);
808
+ const conditions = [];
809
+ const tenantCond = this.tenantCondition("listJobRuns", query.tenantId);
810
+ if (tenantCond) conditions.push(tenantCond);
811
+ if (query.poolId) conditions.push(eq2(jobRuns.pool, query.poolId));
812
+ if (query.rootRunId) conditions.push(eq2(jobRuns.rootRunId, query.rootRunId));
813
+ if (query.status) conditions.push(eq2(jobRuns.status, query.status));
814
+ if (query.since) conditions.push(gte(jobRuns.createdAt, query.since));
815
+ if (query.cursor) {
816
+ const keyset = decodeKeysetCursor(query.cursor);
817
+ if (keyset) {
818
+ conditions.push(
819
+ or(
820
+ lt(jobRuns.createdAt, keyset.createdAt),
821
+ and2(
822
+ eq2(jobRuns.createdAt, keyset.createdAt),
823
+ lt(jobRuns.id, keyset.id)
824
+ )
825
+ )
826
+ );
827
+ }
828
+ }
829
+ const rows = await this.db.select().from(jobRuns).where(conditions.length > 0 ? and2(...conditions) : void 0).orderBy(desc2(jobRuns.createdAt), desc2(jobRuns.id)).limit(limit + 1);
830
+ const hasMore = rows.length > limit;
831
+ const page = hasMore ? rows.slice(0, limit) : rows;
832
+ const items = page.map(toJobRunSummary);
833
+ const last = page[page.length - 1];
834
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id }) : null;
835
+ return { items, nextCursor };
836
+ }
755
837
  /**
756
838
  * Internal helper used by cascade paths (not on the public protocol).
757
839
  * Exposed as a public method on the concrete class so infrastructure
@@ -800,22 +882,608 @@ var DrizzleJobStepService = class {
800
882
  finishedAt: values.finishedAt,
801
883
  attempts: values.attempts
802
884
  }
803
- }).returning();
804
- return row;
885
+ }).returning();
886
+ return row;
887
+ }
888
+ async findStep(runId, stepId) {
889
+ const [row] = await this.db.select().from(jobSteps).where(and3(eq3(jobSteps.jobRunId, runId), eq3(jobSteps.stepId, stepId))).limit(1);
890
+ return row ?? null;
891
+ }
892
+ };
893
+ DrizzleJobStepService = __decorateClass([
894
+ Injectable3(),
895
+ __decorateParam(0, Inject3(DRIZZLE))
896
+ ], DrizzleJobStepService);
897
+
898
+ // runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts
899
+ import { createHash } from "crypto";
900
+ import { Inject as Inject4, Injectable as Injectable4, Logger as Logger2, Optional } from "@nestjs/common";
901
+ import { eq as eq4 } from "drizzle-orm";
902
+
903
+ // runtime/subsystems/jobs/pool-config.loader.ts
904
+ import { existsSync, readFileSync } from "fs";
905
+ import { resolve } from "path";
906
+ import { parse as parseYaml } from "yaml";
907
+ var FRAMEWORK_POOLS = Object.freeze({
908
+ events_inbound: Object.freeze({
909
+ queue: "jobs-events-inbound",
910
+ concurrency: 20,
911
+ reserved: true,
912
+ description: "Inbound events drain (events subsystem outbox)."
913
+ }),
914
+ events_change: Object.freeze({
915
+ queue: "jobs-events-change",
916
+ concurrency: 30,
917
+ reserved: true,
918
+ description: "Change events drain (events subsystem outbox)."
919
+ }),
920
+ events_outbound: Object.freeze({
921
+ queue: "jobs-events-outbound",
922
+ concurrency: 10,
923
+ reserved: true,
924
+ description: "Outbound events drain (events subsystem outbox)."
925
+ }),
926
+ interactive: Object.freeze({
927
+ queue: "jobs-interactive",
928
+ concurrency: 20,
929
+ reserved: false,
930
+ description: "User-facing latency-sensitive jobs."
931
+ }),
932
+ batch: Object.freeze({
933
+ queue: "jobs-batch",
934
+ concurrency: 5,
935
+ reserved: false,
936
+ description: "Default pool for background jobs."
937
+ })
938
+ });
939
+ var RESERVED_POOL_NAMES = new Set(
940
+ Object.entries(FRAMEWORK_POOLS).filter(([, def]) => def.reserved).map(([name]) => name)
941
+ );
942
+ var cache = /* @__PURE__ */ new Map();
943
+ function loadPoolConfig(configPath) {
944
+ const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);
945
+ const cached = cache.get(resolved);
946
+ if (cached) return cached;
947
+ const merged = /* @__PURE__ */ new Map();
948
+ for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {
949
+ merged.set(name, { ...def });
950
+ }
951
+ if (!existsSync(resolved)) {
952
+ cache.set(resolved, merged);
953
+ return merged;
954
+ }
955
+ let raw;
956
+ try {
957
+ raw = parseYaml(readFileSync(resolved, "utf8"));
958
+ } catch (err) {
959
+ throw new Error(
960
+ `pool-config.loader: failed to parse YAML at ${resolved}: ${err.message}`
961
+ );
962
+ }
963
+ const userPools = extractUserPools(raw);
964
+ for (const [name, userDef] of Object.entries(userPools)) {
965
+ const existing = merged.get(name);
966
+ if (existing) {
967
+ const next = {
968
+ queue: existing.queue,
969
+ concurrency: typeof userDef.concurrency === "number" ? userDef.concurrency : existing.concurrency,
970
+ reserved: existing.reserved,
971
+ description: userDef.description ?? existing.description
972
+ };
973
+ merged.set(name, next);
974
+ continue;
975
+ }
976
+ if (typeof userDef.queue !== "string" || userDef.queue.length === 0) {
977
+ throw new Error(
978
+ `pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`
979
+ );
980
+ }
981
+ if (typeof userDef.concurrency !== "number" || userDef.concurrency <= 0) {
982
+ throw new Error(
983
+ `pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`
984
+ );
985
+ }
986
+ if (userDef.reserved === true) {
987
+ throw new Error(
988
+ `pool-config.loader: user-defined pool '${name}' cannot set 'reserved: true' \u2014 reserved is framework-only.`
989
+ );
990
+ }
991
+ merged.set(name, {
992
+ queue: userDef.queue,
993
+ concurrency: userDef.concurrency,
994
+ reserved: false,
995
+ description: userDef.description
996
+ });
997
+ }
998
+ cache.set(resolved, merged);
999
+ return merged;
1000
+ }
1001
+ function allNonReservedPoolNames(config) {
1002
+ const out = [];
1003
+ for (const [name, def] of config) {
1004
+ if (!def.reserved) out.push(name);
1005
+ }
1006
+ return out;
1007
+ }
1008
+ function allPoolNames(config) {
1009
+ return [...config.keys()];
1010
+ }
1011
+ function extractUserPools(raw) {
1012
+ if (!raw || typeof raw !== "object") return {};
1013
+ const jobs2 = raw.jobs;
1014
+ if (!jobs2 || typeof jobs2 !== "object") return {};
1015
+ const pools = jobs2.pools;
1016
+ if (!pools || typeof pools !== "object") return {};
1017
+ const out = {};
1018
+ for (const [name, def] of Object.entries(pools)) {
1019
+ if (!def || typeof def !== "object") continue;
1020
+ out[name] = def;
1021
+ }
1022
+ return out;
1023
+ }
1024
+
1025
+ // runtime/subsystems/jobs/bullmq.config.ts
1026
+ var BULLMQ_CONNECTION = /* @__PURE__ */ Symbol("BULLMQ_CONNECTION");
1027
+ var BULLMQ_RESOLVED_CONFIG = /* @__PURE__ */ Symbol("BULLMQ_RESOLVED_CONFIG");
1028
+ var DEFAULT_REDIS_URL = "redis://localhost:6379";
1029
+ var DEFAULT_BULL_BOARD_MOUNT = "/admin/queues";
1030
+ function resolveBullMqConfig(ext) {
1031
+ const url = ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;
1032
+ const resolved = {
1033
+ connection: { url },
1034
+ queuePrefix: ext?.queue_prefix
1035
+ };
1036
+ if (ext?.bull_board?.enabled) {
1037
+ resolved.bullBoard = {
1038
+ enabled: true,
1039
+ mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT
1040
+ };
1041
+ }
1042
+ return resolved;
1043
+ }
1044
+ function resolvePoolQueueName(pool, config, poolConfig = loadPoolConfig()) {
1045
+ const alias = poolConfig.get(pool)?.queue ?? pool;
1046
+ const prefix = config?.queuePrefix;
1047
+ return prefix ? `${prefix}:${alias}` : alias;
1048
+ }
1049
+
1050
+ // runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts
1051
+ function sha1JobId(idempotencyKey) {
1052
+ return createHash("sha1").update(idempotencyKey).digest("hex");
1053
+ }
1054
+ var BullMQJobOrchestrator = class extends DrizzleJobOrchestrator {
1055
+ constructor(db, multiTenant, connection, bullConfig = null) {
1056
+ super(db, multiTenant);
1057
+ this.connection = connection;
1058
+ this.bullConfig = bullConfig;
1059
+ this.bullDb = db;
1060
+ }
1061
+ connection;
1062
+ bullConfig;
1063
+ // TODO(logging-subsystem): swap to ILogger once ADR-028 lands
1064
+ bullLogger = new Logger2(BullMQJobOrchestrator.name);
1065
+ /** Lazily-opened `Queue` handles, one per pool. */
1066
+ queues = /* @__PURE__ */ new Map();
1067
+ /** Single FlowProducer for parent/child hierarchies. Lazily opened. */
1068
+ _flow = null;
1069
+ /**
1070
+ * Cached `bullmq` value constructors, populated by `loadBullMq()` on first
1071
+ * use (the `start`/`cancel`/`replay` entrypoints `await` it before touching
1072
+ * a queue). Kept off the import graph so a `drizzle`-only consumer never
1073
+ * resolves the optional `'bullmq'` package.
1074
+ */
1075
+ QueueCtor = null;
1076
+ FlowProducerCtor = null;
1077
+ bullMqLoad = null;
1078
+ /**
1079
+ * Own reference to the Drizzle client. `DrizzleJobOrchestrator.db` is
1080
+ * `private` (can't be redeclared even privately in a subclass), and the
1081
+ * spec forbids touching that file — so the subclass keeps its own handle
1082
+ * under a distinct name (same instance, passed through to `super`) for the
1083
+ * cancel-cascade snapshot + definition/run loads below.
1084
+ */
1085
+ bullDb;
1086
+ /**
1087
+ * Lazily load the optional `bullmq` package and cache its value
1088
+ * constructors. Idempotent (single in-flight promise). Throws a friendly,
1089
+ * actionable error when the consumer selected `backend: 'bullmq'` but did
1090
+ * not install the package — mirrors `createRedisClient` in the redis event
1091
+ * backend. Must be `await`ed before any `queueFor`/`flow` access.
1092
+ */
1093
+ async loadBullMq() {
1094
+ if (this.QueueCtor && this.FlowProducerCtor) return;
1095
+ if (!this.bullMqLoad) {
1096
+ this.bullMqLoad = (async () => {
1097
+ try {
1098
+ const mod = await import("bullmq");
1099
+ this.QueueCtor = mod.Queue;
1100
+ this.FlowProducerCtor = mod.FlowProducer;
1101
+ } catch {
1102
+ throw new Error(
1103
+ 'BullMQ backend requires the "bullmq" package. Install it with: npm install bullmq'
1104
+ );
1105
+ }
1106
+ })();
1107
+ }
1108
+ await this.bullMqLoad;
1109
+ }
1110
+ /**
1111
+ * Open (or reuse) the `Queue` for a pool. Synchronous — callers `await
1112
+ * loadBullMq()` first so `QueueCtor` is populated.
1113
+ */
1114
+ queueFor(pool) {
1115
+ if (!this.QueueCtor) {
1116
+ throw new Error("BullMQJobOrchestrator: queueFor called before loadBullMq()");
1117
+ }
1118
+ const name = resolvePoolQueueName(pool, this.bullConfig);
1119
+ let q = this.queues.get(name);
1120
+ if (!q) {
1121
+ q = new this.QueueCtor(name, { connection: this.connection });
1122
+ this.queues.set(name, q);
1123
+ }
1124
+ return q;
1125
+ }
1126
+ flow() {
1127
+ if (!this.FlowProducerCtor) {
1128
+ throw new Error("BullMQJobOrchestrator: flow called before loadBullMq()");
1129
+ }
1130
+ if (!this._flow) {
1131
+ this._flow = new this.FlowProducerCtor({ connection: this.connection });
1132
+ }
1133
+ return this._flow;
1134
+ }
1135
+ // ==========================================================================
1136
+ // start — Postgres insert (super) + BullMQ dispatch
1137
+ // ==========================================================================
1138
+ async start(type, input, opts = {}, tx) {
1139
+ const run = await super.start(type, input, opts, tx);
1140
+ await this.dispatch(run, type);
1141
+ return run;
1142
+ }
1143
+ /**
1144
+ * Map a `job_run` row onto a BullMQ job via `queue.add`. When the run has a
1145
+ * `parentRunId` we attach it to the parent's existing BullMQ job through the
1146
+ * `parent: { id, queue }` opt — BullMQ then tracks the parent/child link in
1147
+ * its own graph. (The FlowProducer is reserved for whole-tree atomic
1148
+ * submits, exposed as an opt-in extension via `flowProducer()`; runtime
1149
+ * `ctx.spawnChild` is incremental, so `queue.add` with a parent ref is the
1150
+ * correct primitive here.)
1151
+ *
1152
+ * The `jobId` is colon-safe + stable: `sha1(dedupeKey)` when a dedupe key is
1153
+ * present (so the same logical key dedups), else the `job_run.id` UUID
1154
+ * (already colon-free).
1155
+ *
1156
+ * The domain `parentClosePolicy` cascade is still enforced in Postgres by
1157
+ * the shared `cancel` path — BullMQ's parent link is dispatch bookkeeping,
1158
+ * not the authority.
1159
+ */
1160
+ async dispatch(run, type) {
1161
+ await this.loadBullMq();
1162
+ const def = await this.loadDefinition(type);
1163
+ const jobId = run.dedupeKey ? sha1JobId(run.dedupeKey) : run.id;
1164
+ const jobOpts = {
1165
+ jobId,
1166
+ ...this.retryOpts(def),
1167
+ ...this.dedupeOpts(run, def)
1168
+ };
1169
+ if (run.parentRunId) {
1170
+ const parentRow = await this.loadRun(run.parentRunId);
1171
+ if (parentRow) {
1172
+ const parentJobId = parentRow.dedupeKey ? sha1JobId(parentRow.dedupeKey) : parentRow.id;
1173
+ jobOpts.parent = {
1174
+ id: parentJobId,
1175
+ queue: resolvePoolQueueName(parentRow.pool, this.bullConfig)
1176
+ };
1177
+ }
1178
+ }
1179
+ const payload = { runId: run.id, type, input: run.input };
1180
+ await this.queueFor(run.pool).add(type, payload, jobOpts);
1181
+ }
1182
+ /**
1183
+ * Opt-in extension (spec §Extensions): expose the FlowProducer for
1184
+ * consumers that want to submit a whole parent/child DAG atomically up
1185
+ * front, rather than incrementally via `ctx.spawnChild`. Backend-specific —
1186
+ * code using it is not portable to the Drizzle backend. Async because it
1187
+ * lazily loads the optional `bullmq` package on first use.
1188
+ */
1189
+ async flowProducer() {
1190
+ await this.loadBullMq();
1191
+ return this.flow();
1192
+ }
1193
+ retryOpts(def) {
1194
+ const policy = def.retryPolicy;
1195
+ if (!policy) return {};
1196
+ return {
1197
+ attempts: policy.attempts,
1198
+ backoff: {
1199
+ type: policy.backoff === "exponential" ? "exponential" : "fixed",
1200
+ delay: policy.baseMs
1201
+ }
1202
+ };
1203
+ }
1204
+ dedupeOpts(run, def) {
1205
+ if (!run.dedupeKey || !def.dedupeWindowMs) return {};
1206
+ return {
1207
+ deduplication: {
1208
+ id: sha1JobId(run.dedupeKey),
1209
+ ttl: def.dedupeWindowMs
1210
+ }
1211
+ };
1212
+ }
1213
+ // ==========================================================================
1214
+ // cancel — Postgres cascade (super) + remove from queue
1215
+ // ==========================================================================
1216
+ async cancel(runId, opts = {}) {
1217
+ const target = await this.loadRun(runId);
1218
+ await super.cancel(runId, opts);
1219
+ if (!target) return;
1220
+ await this.loadBullMq();
1221
+ await this.removeFromQueue(target);
1222
+ if (opts.cascade === false) return;
1223
+ const descendants = await this.bullDb.select().from(jobRuns).where(eq4(jobRuns.rootRunId, target.rootRunId));
1224
+ for (const child of descendants) {
1225
+ if (child.id === runId) continue;
1226
+ await this.removeFromQueue(child);
1227
+ }
1228
+ }
1229
+ async removeFromQueue(run) {
1230
+ const jobId = run.dedupeKey ? sha1JobId(run.dedupeKey) : run.id;
1231
+ try {
1232
+ const job = await this.queueFor(run.pool).getJob(jobId);
1233
+ if (job) await job.remove();
1234
+ } catch (err) {
1235
+ this.bullLogger.warn(
1236
+ `cancel: could not remove BullMQ job ${jobId} (pool=${run.pool}): ${err.message}`
1237
+ );
1238
+ }
1239
+ }
1240
+ // ==========================================================================
1241
+ // replay — Postgres reset (super) + re-enqueue
1242
+ // ==========================================================================
1243
+ async replay(runId) {
1244
+ const run = await super.replay(runId);
1245
+ await this.dispatch(run, run.jobType);
1246
+ return run;
1247
+ }
1248
+ // ==========================================================================
1249
+ // Internals
1250
+ // ==========================================================================
1251
+ async loadDefinition(type) {
1252
+ const [def] = await this.bullDb.select().from(jobs).where(eq4(jobs.type, type)).limit(1);
1253
+ if (!def) {
1254
+ throw new Error(`BullMQJobOrchestrator: no job definition for '${type}'`);
1255
+ }
1256
+ return def;
1257
+ }
1258
+ async loadRun(id) {
1259
+ const [row] = await this.bullDb.select().from(jobRuns).where(eq4(jobRuns.id, id)).limit(1);
1260
+ return row ?? null;
1261
+ }
1262
+ /** Close all open queue + flow connections. Called on module destroy. */
1263
+ async closeConnections() {
1264
+ for (const q of this.queues.values()) {
1265
+ await q.close().catch(() => void 0);
1266
+ }
1267
+ this.queues.clear();
1268
+ if (this._flow) {
1269
+ await this._flow.close().catch(() => void 0);
1270
+ this._flow = null;
1271
+ }
1272
+ }
1273
+ };
1274
+ BullMQJobOrchestrator = __decorateClass([
1275
+ Injectable4(),
1276
+ __decorateParam(0, Inject4(DRIZZLE)),
1277
+ __decorateParam(1, Inject4(JOBS_MULTI_TENANT)),
1278
+ __decorateParam(2, Inject4(BULLMQ_CONNECTION)),
1279
+ __decorateParam(3, Optional()),
1280
+ __decorateParam(3, Inject4(BULLMQ_RESOLVED_CONFIG))
1281
+ ], BullMQJobOrchestrator);
1282
+
1283
+ // runtime/subsystems/jobs/job-worker.bullmq-backend.ts
1284
+ import { Logger as Logger3 } from "@nestjs/common";
1285
+ import { eq as eq5 } from "drizzle-orm";
1286
+ function serialiseError(err, attempt, retryable) {
1287
+ const e = err;
1288
+ return {
1289
+ message: e?.message ?? String(err),
1290
+ stack: e?.stack,
1291
+ retryable,
1292
+ attempt
1293
+ };
1294
+ }
1295
+ var BullMQJobWorker = class _BullMQJobWorker {
1296
+ constructor(db, orchestrator, stepService, options, moduleRef) {
1297
+ this.db = db;
1298
+ this.orchestrator = orchestrator;
1299
+ this.stepService = stepService;
1300
+ this.options = options;
1301
+ this.moduleRef = moduleRef;
1302
+ }
1303
+ db;
1304
+ orchestrator;
1305
+ stepService;
1306
+ options;
1307
+ moduleRef;
1308
+ logger = new Logger3(_BullMQJobWorker.name);
1309
+ worker = null;
1310
+ async onModuleInit() {
1311
+ let WorkerCtor;
1312
+ try {
1313
+ const mod = await import("bullmq");
1314
+ WorkerCtor = mod.Worker;
1315
+ } catch {
1316
+ throw new Error(
1317
+ 'BullMQ backend requires the "bullmq" package. Install it with: npm install bullmq'
1318
+ );
1319
+ }
1320
+ this.worker = new WorkerCtor(
1321
+ this.options.queueName,
1322
+ (job) => this.process(job),
1323
+ {
1324
+ connection: this.options.connection,
1325
+ concurrency: this.options.concurrency
1326
+ }
1327
+ );
1328
+ this.worker.on("failed", (job, err) => {
1329
+ if (!job) return;
1330
+ const attemptsMade = job.attemptsMade;
1331
+ const maxAttempts = job.opts.attempts ?? 1;
1332
+ if (attemptsMade >= maxAttempts) {
1333
+ void this.markFailed(job.data.runId, err, attemptsMade);
1334
+ }
1335
+ });
1336
+ this.logger.log(
1337
+ `BullMQ worker started: pool='${this.options.pool}' queue='${this.options.queueName}' concurrency=${this.options.concurrency}`
1338
+ );
1339
+ }
1340
+ async onModuleDestroy() {
1341
+ if (this.worker) {
1342
+ await this.worker.close();
1343
+ this.worker = null;
1344
+ }
1345
+ }
1346
+ /**
1347
+ * Process one BullMQ job. Returns the handler output (stored by BullMQ as
1348
+ * the job return value AND written to `job_run.output`). Throws on handler
1349
+ * failure so BullMQ applies the retry policy.
1350
+ */
1351
+ async process(job) {
1352
+ const { runId } = job.data;
1353
+ const [row] = await this.db.select().from(jobRuns).where(eq5(jobRuns.id, runId)).limit(1);
1354
+ if (!row) {
1355
+ this.logger.warn(`process: job_run ${runId} not found; skipping`);
1356
+ return {};
1357
+ }
1358
+ const run = row;
1359
+ if (run.status === "canceled") {
1360
+ return {};
1361
+ }
1362
+ const registryEntry = JOB_HANDLER_REGISTRY.get(run.jobType);
1363
+ if (!registryEntry) {
1364
+ throw new Error(
1365
+ `No handler registered for jobType='${run.jobType}' (run ${run.id})`
1366
+ );
1367
+ }
1368
+ await this.db.update(jobRuns).set({
1369
+ status: "running",
1370
+ claimedAt: /* @__PURE__ */ new Date(),
1371
+ startedAt: /* @__PURE__ */ new Date(),
1372
+ attempts: job.attemptsMade + 1,
1373
+ updatedAt: /* @__PURE__ */ new Date()
1374
+ }).where(eq5(jobRuns.id, run.id));
1375
+ const HandlerClass = registryEntry.handlerClass;
1376
+ const handler = this.moduleRef.get(
1377
+ HandlerClass,
1378
+ { strict: false }
1379
+ );
1380
+ const ctx = {
1381
+ input: run.input,
1382
+ run,
1383
+ step: this.makeStepFn(run),
1384
+ spawnChild: this.makeSpawnFn(run),
1385
+ logger: new Logger3(`JobRun:${run.id}`)
1386
+ };
1387
+ const output = await handler.run(ctx);
1388
+ await this.db.update(jobRuns).set({
1389
+ status: "completed",
1390
+ output: output ?? {},
1391
+ finishedAt: /* @__PURE__ */ new Date(),
1392
+ updatedAt: /* @__PURE__ */ new Date()
1393
+ }).where(eq5(jobRuns.id, run.id));
1394
+ return output ?? {};
1395
+ }
1396
+ async markFailed(runId, err, finalAttempts) {
1397
+ const [row] = await this.db.select().from(jobRuns).where(eq5(jobRuns.id, runId)).limit(1);
1398
+ if (!row) return;
1399
+ const run = row;
1400
+ await this.db.update(jobRuns).set({
1401
+ status: "failed",
1402
+ attempts: finalAttempts,
1403
+ finishedAt: /* @__PURE__ */ new Date(),
1404
+ error: serialiseError(err, finalAttempts, false),
1405
+ updatedAt: /* @__PURE__ */ new Date()
1406
+ }).where(eq5(jobRuns.id, runId));
1407
+ if (run.parentClosePolicy === "terminate") {
1408
+ try {
1409
+ await this.orchestrator.cancel(run.id, {
1410
+ cascade: true,
1411
+ reason: "parent-failed",
1412
+ tenantId: run.tenantId
1413
+ });
1414
+ } catch (cascadeErr) {
1415
+ this.logger.warn(
1416
+ `cascade on failed run ${run.id}: ${cascadeErr.message}`
1417
+ );
1418
+ }
1419
+ }
805
1420
  }
806
- async findStep(runId, stepId) {
807
- const [row] = await this.db.select().from(jobSteps).where(and3(eq3(jobSteps.jobRunId, runId), eq3(jobSteps.stepId, stepId))).limit(1);
808
- return row ?? null;
1421
+ // ── ctx.step / ctx.spawnChild (mirror JobWorker) ──────────────────────────
1422
+ makeStepFn(run) {
1423
+ return async (stepId, fn, _opts) => {
1424
+ void _opts;
1425
+ const existing = await this.stepService.findStep(run.id, stepId);
1426
+ if (existing?.status === "completed") {
1427
+ return existing.output;
1428
+ }
1429
+ const nextAttempts = (existing?.attempts ?? 0) + 1;
1430
+ const seq = nextAttempts;
1431
+ await this.stepService.recordStep({
1432
+ jobRunId: run.id,
1433
+ stepId,
1434
+ kind: "task",
1435
+ seq,
1436
+ status: "running",
1437
+ startedAt: /* @__PURE__ */ new Date(),
1438
+ attempts: nextAttempts
1439
+ });
1440
+ try {
1441
+ const output = await fn();
1442
+ await this.stepService.recordStep({
1443
+ jobRunId: run.id,
1444
+ stepId,
1445
+ kind: "task",
1446
+ seq,
1447
+ status: "completed",
1448
+ output,
1449
+ finishedAt: /* @__PURE__ */ new Date(),
1450
+ attempts: nextAttempts
1451
+ });
1452
+ return output;
1453
+ } catch (err) {
1454
+ await this.stepService.recordStep({
1455
+ jobRunId: run.id,
1456
+ stepId,
1457
+ kind: "task",
1458
+ seq,
1459
+ status: "failed",
1460
+ error: serialiseError(err, nextAttempts, false),
1461
+ finishedAt: /* @__PURE__ */ new Date(),
1462
+ attempts: nextAttempts
1463
+ });
1464
+ throw err;
1465
+ }
1466
+ };
1467
+ }
1468
+ makeSpawnFn(run) {
1469
+ return async (type, input, opts) => {
1470
+ return this.orchestrator.start(type, input, {
1471
+ parentRunId: run.id,
1472
+ parentClosePolicy: opts?.closePolicy,
1473
+ runAt: opts?.runAt,
1474
+ priority: opts?.priority,
1475
+ tags: opts?.tags,
1476
+ triggerSource: "parent",
1477
+ triggerRef: run.id,
1478
+ tenantId: run.tenantId
1479
+ });
1480
+ };
809
1481
  }
810
1482
  };
811
- DrizzleJobStepService = __decorateClass([
812
- Injectable3(),
813
- __decorateParam(0, Inject3(DRIZZLE))
814
- ], DrizzleJobStepService);
815
1483
 
816
1484
  // runtime/subsystems/jobs/job-worker.ts
817
- import { Inject as Inject4, Injectable as Injectable4, Logger as Logger2 } from "@nestjs/common";
818
- import { and as and4, asc as asc2, desc as desc3, eq as eq4, inArray as inArray3, lt, lte, sql as sql4 } from "drizzle-orm";
1485
+ import { Inject as Inject5, Injectable as Injectable5, Logger as Logger4 } from "@nestjs/common";
1486
+ import { and as and4, asc as asc2, desc as desc3, eq as eq6, inArray as inArray3, lt as lt2, lte, sql as sql4 } from "drizzle-orm";
819
1487
  var JOB_WORKER_OPTIONS = /* @__PURE__ */ Symbol("JOB_WORKER_OPTIONS");
820
1488
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
821
1489
  var DEFAULT_STALE_SWEEPER_INTERVAL_MS = 6e4;
@@ -847,8 +1515,8 @@ function classifyError(err, policy, currentAttempts) {
847
1515
  function buildClaimQuery(db, pool) {
848
1516
  return db.select({ id: jobRuns.id }).from(jobRuns).where(
849
1517
  and4(
850
- eq4(jobRuns.status, "pending"),
851
- eq4(jobRuns.pool, pool),
1518
+ eq6(jobRuns.status, "pending"),
1519
+ eq6(jobRuns.pool, pool),
852
1520
  lte(jobRuns.runAt, /* @__PURE__ */ new Date())
853
1521
  )
854
1522
  ).orderBy(desc3(jobRuns.priority), asc2(jobRuns.runAt)).limit(1).for("update", { skipLocked: true });
@@ -857,12 +1525,12 @@ function buildStaleSweepQuery(db, staleThresholdMs) {
857
1525
  const threshold = new Date(Date.now() - staleThresholdMs);
858
1526
  return db.select({ id: jobRuns.id }).from(jobRuns).where(
859
1527
  and4(
860
- eq4(jobRuns.status, "running"),
861
- lt(jobRuns.claimedAt, threshold)
1528
+ eq6(jobRuns.status, "running"),
1529
+ lt2(jobRuns.claimedAt, threshold)
862
1530
  )
863
1531
  ).for("update", { skipLocked: true });
864
1532
  }
865
- function serialiseError(err, attempt, retryable) {
1533
+ function serialiseError2(err, attempt, retryable) {
866
1534
  const e = err;
867
1535
  return {
868
1536
  message: e?.message ?? String(err),
@@ -896,7 +1564,7 @@ var JobWorker = class {
896
1564
  stepService;
897
1565
  options;
898
1566
  moduleRef;
899
- logger = new Logger2(JobWorker.name);
1567
+ logger = new Logger4(JobWorker.name);
900
1568
  shuttingDown = false;
901
1569
  inFlight = /* @__PURE__ */ new Set();
902
1570
  pollTimer = null;
@@ -937,7 +1605,7 @@ var JobWorker = class {
937
1605
  await this.drainInFlight();
938
1606
  try {
939
1607
  await this.db.update(jobRuns).set({ status: "pending", claimedAt: null, startedAt: null }).where(
940
- and4(eq4(jobRuns.status, "running"), eq4(jobRuns.pool, this.options.pool))
1608
+ and4(eq6(jobRuns.status, "running"), eq6(jobRuns.pool, this.options.pool))
941
1609
  );
942
1610
  } catch (err) {
943
1611
  this.logger.error(`shutdown reset failed: ${err.message}`);
@@ -987,8 +1655,8 @@ var JobWorker = class {
987
1655
  return this.db.transaction(async (tx) => {
988
1656
  const candidates = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
989
1657
  and4(
990
- eq4(jobRuns.status, "pending"),
991
- eq4(jobRuns.pool, pool),
1658
+ eq6(jobRuns.status, "pending"),
1659
+ eq6(jobRuns.pool, pool),
992
1660
  lte(jobRuns.runAt, /* @__PURE__ */ new Date())
993
1661
  )
994
1662
  ).orderBy(desc3(jobRuns.priority), asc2(jobRuns.runAt)).limit(1).for("update", { skipLocked: true });
@@ -999,7 +1667,7 @@ var JobWorker = class {
999
1667
  claimedAt: /* @__PURE__ */ new Date(),
1000
1668
  startedAt: /* @__PURE__ */ new Date(),
1001
1669
  updatedAt: /* @__PURE__ */ new Date()
1002
- }).where(eq4(jobRuns.id, candidate.id)).returning();
1670
+ }).where(eq6(jobRuns.id, candidate.id)).returning();
1003
1671
  return claimed ?? null;
1004
1672
  });
1005
1673
  }
@@ -1017,7 +1685,7 @@ var JobWorker = class {
1017
1685
  await this.db.transaction(async (tx) => {
1018
1686
  const threshold = new Date(Date.now() - this.staleThresholdMs);
1019
1687
  const stale = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
1020
- and4(eq4(jobRuns.status, "running"), lt(jobRuns.claimedAt, threshold))
1688
+ and4(eq6(jobRuns.status, "running"), lt2(jobRuns.claimedAt, threshold))
1021
1689
  ).for("update", { skipLocked: true });
1022
1690
  if (stale.length === 0) return;
1023
1691
  const ids = stale.map((r) => r.id);
@@ -1050,8 +1718,8 @@ var JobWorker = class {
1050
1718
  if (claimed.concurrencyKey) {
1051
1719
  const inflight = await this.db.select({ id: jobRuns.id }).from(jobRuns).where(
1052
1720
  and4(
1053
- eq4(jobRuns.concurrencyKey, claimed.concurrencyKey),
1054
- eq4(jobRuns.status, "running")
1721
+ eq6(jobRuns.concurrencyKey, claimed.concurrencyKey),
1722
+ eq6(jobRuns.status, "running")
1055
1723
  )
1056
1724
  );
1057
1725
  const other = inflight.find((r) => r.id !== claimed.id);
@@ -1061,7 +1729,7 @@ var JobWorker = class {
1061
1729
  claimedAt: null,
1062
1730
  startedAt: null,
1063
1731
  updatedAt: /* @__PURE__ */ new Date()
1064
- }).where(eq4(jobRuns.id, claimed.id));
1732
+ }).where(eq6(jobRuns.id, claimed.id));
1065
1733
  return;
1066
1734
  }
1067
1735
  }
@@ -1076,7 +1744,7 @@ var JobWorker = class {
1076
1744
  run: claimed,
1077
1745
  step: this.makeStepFn(claimed),
1078
1746
  spawnChild: this.makeSpawnFn(claimed),
1079
- logger: new Logger2(`JobRun:${claimed.id}`)
1747
+ logger: new Logger4(`JobRun:${claimed.id}`)
1080
1748
  };
1081
1749
  const attemptsBefore = claimed.attempts ?? 0;
1082
1750
  try {
@@ -1087,7 +1755,7 @@ var JobWorker = class {
1087
1755
  finishedAt: /* @__PURE__ */ new Date(),
1088
1756
  updatedAt: /* @__PURE__ */ new Date(),
1089
1757
  attempts: attemptsBefore + 1
1090
- }).where(eq4(jobRuns.id, claimed.id));
1758
+ }).where(eq6(jobRuns.id, claimed.id));
1091
1759
  } catch (err) {
1092
1760
  const policy = meta.retry;
1093
1761
  const decision = classifyError(err, policy, attemptsBefore);
@@ -1100,9 +1768,9 @@ var JobWorker = class {
1100
1768
  runAt: new Date(Date.now() + delay),
1101
1769
  startedAt: null,
1102
1770
  claimedAt: null,
1103
- error: serialiseError(err, nextAttempts, true),
1771
+ error: serialiseError2(err, nextAttempts, true),
1104
1772
  updatedAt: /* @__PURE__ */ new Date()
1105
- }).where(eq4(jobRuns.id, claimed.id));
1773
+ }).where(eq6(jobRuns.id, claimed.id));
1106
1774
  } else {
1107
1775
  await this.markFailed(claimed, err, nextAttempts);
1108
1776
  }
@@ -1113,9 +1781,9 @@ var JobWorker = class {
1113
1781
  status: "failed",
1114
1782
  attempts: finalAttempts,
1115
1783
  finishedAt: /* @__PURE__ */ new Date(),
1116
- error: serialiseError(err, finalAttempts, false),
1784
+ error: serialiseError2(err, finalAttempts, false),
1117
1785
  updatedAt: /* @__PURE__ */ new Date()
1118
- }).where(eq4(jobRuns.id, claimed.id));
1786
+ }).where(eq6(jobRuns.id, claimed.id));
1119
1787
  if (claimed.parentClosePolicy === "terminate") {
1120
1788
  try {
1121
1789
  await this.orchestrator.cancel(claimed.id, {
@@ -1172,7 +1840,7 @@ var JobWorker = class {
1172
1840
  kind: "task",
1173
1841
  seq,
1174
1842
  status: "failed",
1175
- error: serialiseError(err, nextAttempts, false),
1843
+ error: serialiseError2(err, nextAttempts, false),
1176
1844
  finishedAt: /* @__PURE__ */ new Date(),
1177
1845
  attempts: nextAttempts
1178
1846
  });
@@ -1218,12 +1886,12 @@ var JobWorker = class {
1218
1886
  // ============================================================================
1219
1887
  };
1220
1888
  JobWorker = __decorateClass([
1221
- Injectable4(),
1222
- __decorateParam(0, Inject4(DRIZZLE)),
1223
- __decorateParam(1, Inject4(JOB_ORCHESTRATOR)),
1224
- __decorateParam(2, Inject4(JOB_RUN_SERVICE)),
1225
- __decorateParam(3, Inject4(JOB_STEP_SERVICE)),
1226
- __decorateParam(4, Inject4(JOB_WORKER_OPTIONS))
1889
+ Injectable5(),
1890
+ __decorateParam(0, Inject5(DRIZZLE)),
1891
+ __decorateParam(1, Inject5(JOB_ORCHESTRATOR)),
1892
+ __decorateParam(2, Inject5(JOB_RUN_SERVICE)),
1893
+ __decorateParam(3, Inject5(JOB_STEP_SERVICE)),
1894
+ __decorateParam(4, Inject5(JOB_WORKER_OPTIONS))
1227
1895
  ], JobWorker);
1228
1896
 
1229
1897
  // runtime/subsystems/jobs/memory-job-store.ts
@@ -1244,7 +1912,7 @@ var MemoryJobStore = class {
1244
1912
 
1245
1913
  // runtime/subsystems/jobs/job-orchestrator.memory-backend.ts
1246
1914
  import { randomUUID as randomUUID2 } from "crypto";
1247
- import { Inject as Inject5, Injectable as Injectable5, Logger as Logger3, Optional } from "@nestjs/common";
1915
+ import { Inject as Inject6, Injectable as Injectable6, Logger as Logger5, Optional as Optional2 } from "@nestjs/common";
1248
1916
  var QUEUED_RUN_AT = /* @__PURE__ */ new Date(864e13);
1249
1917
  var TERMINAL_STATUSES2 = [
1250
1918
  "completed",
@@ -1291,7 +1959,7 @@ var MemoryJobOrchestrator = class {
1291
1959
  stepService;
1292
1960
  multiTenant;
1293
1961
  moduleRef;
1294
- logger = new Logger3(MemoryJobOrchestrator.name);
1962
+ logger = new Logger5(MemoryJobOrchestrator.name);
1295
1963
  mutex = new PromiseMutex();
1296
1964
  handlerRegistry = /* @__PURE__ */ new Map();
1297
1965
  /**
@@ -1640,7 +2308,7 @@ var MemoryJobOrchestrator = class {
1640
2308
  run,
1641
2309
  step: this.makeStepFn(run),
1642
2310
  spawnChild: this.makeSpawnFn(run),
1643
- logger: new Logger3(`JobRun:${run.id}`)
2311
+ logger: new Logger5(`JobRun:${run.id}`)
1644
2312
  };
1645
2313
  const attemptsBefore = run.attempts ?? 0;
1646
2314
  try {
@@ -1697,7 +2365,7 @@ var MemoryJobOrchestrator = class {
1697
2365
  kind: "task",
1698
2366
  seq,
1699
2367
  status: "failed",
1700
- error: serialiseError2(err, nextAttempts, false),
2368
+ error: serialiseError3(err, nextAttempts, false),
1701
2369
  finishedAt: /* @__PURE__ */ new Date(),
1702
2370
  attempts: nextAttempts
1703
2371
  });
@@ -1752,7 +2420,7 @@ var MemoryJobOrchestrator = class {
1752
2420
  finishedAt: now,
1753
2421
  updatedAt: now,
1754
2422
  attempts,
1755
- error: serialiseError2(err, attempts, false)
2423
+ error: serialiseError3(err, attempts, false)
1756
2424
  });
1757
2425
  this.unblockQueuedDependents(run.id);
1758
2426
  });
@@ -1783,7 +2451,7 @@ var MemoryJobOrchestrator = class {
1783
2451
  startedAt: null,
1784
2452
  claimedAt: null,
1785
2453
  updatedAt: now,
1786
- error: serialiseError2(err, attempts, true)
2454
+ error: serialiseError3(err, attempts, true)
1787
2455
  });
1788
2456
  });
1789
2457
  }
@@ -1813,9 +2481,9 @@ var MemoryJobOrchestrator = class {
1813
2481
  }
1814
2482
  };
1815
2483
  MemoryJobOrchestrator = __decorateClass([
1816
- Injectable5(),
1817
- __decorateParam(2, Inject5(JOBS_MULTI_TENANT)),
1818
- __decorateParam(3, Optional())
2484
+ Injectable6(),
2485
+ __decorateParam(2, Inject6(JOBS_MULTI_TENANT)),
2486
+ __decorateParam(3, Optional2())
1819
2487
  ], MemoryJobOrchestrator);
1820
2488
  function classifyError2(err, policy, currentAttempts) {
1821
2489
  if (!policy) return "fail";
@@ -1838,7 +2506,7 @@ function computeBackoff2(policy, attempts) {
1838
2506
  }
1839
2507
  return raw;
1840
2508
  }
1841
- function serialiseError2(err, attempt, retryable) {
2509
+ function serialiseError3(err, attempt, retryable) {
1842
2510
  const e = err;
1843
2511
  return {
1844
2512
  message: e?.message ?? String(err),
@@ -1849,7 +2517,7 @@ function serialiseError2(err, attempt, retryable) {
1849
2517
  }
1850
2518
 
1851
2519
  // runtime/subsystems/jobs/job-run-service.memory-backend.ts
1852
- import { Inject as Inject6, Injectable as Injectable6 } from "@nestjs/common";
2520
+ import { Inject as Inject7, Injectable as Injectable7 } from "@nestjs/common";
1853
2521
  var NON_TERMINAL_STATUSES2 = [
1854
2522
  "pending",
1855
2523
  "running",
@@ -1966,6 +2634,38 @@ var MemoryJobRunService = class {
1966
2634
  createdAt: r.createdAt
1967
2635
  }));
1968
2636
  }
2637
+ async listJobRuns(query = {}) {
2638
+ const limit = clampLimit(query.limit);
2639
+ const tenantCheck = this.tenantPredicate("listJobRuns", query.tenantId);
2640
+ const keyset = query.cursor ? decodeKeysetCursor(query.cursor) : null;
2641
+ const matched = [];
2642
+ for (const r of this.store.runs.values()) {
2643
+ if (tenantCheck && !tenantCheck(r)) continue;
2644
+ if (query.poolId && r.pool !== query.poolId) continue;
2645
+ if (query.rootRunId && r.rootRunId !== query.rootRunId) continue;
2646
+ if (query.status && r.status !== query.status) continue;
2647
+ if (query.since && r.createdAt.getTime() < query.since.getTime()) continue;
2648
+ matched.push(r);
2649
+ }
2650
+ matched.sort((a, b) => {
2651
+ const dt = b.createdAt.getTime() - a.createdAt.getTime();
2652
+ if (dt !== 0) return dt;
2653
+ return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
2654
+ });
2655
+ const seeked = keyset ? matched.filter((r) => {
2656
+ const ct = r.createdAt.getTime();
2657
+ const kt = keyset.createdAt.getTime();
2658
+ if (ct < kt) return true;
2659
+ if (ct > kt) return false;
2660
+ return r.id < keyset.id;
2661
+ }) : matched;
2662
+ const hasMore = seeked.length > limit;
2663
+ const page = hasMore ? seeked.slice(0, limit) : seeked;
2664
+ const items = page.map(toJobRunSummary);
2665
+ const last = page[page.length - 1];
2666
+ const nextCursor = hasMore && last ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id }) : null;
2667
+ return { items, nextCursor };
2668
+ }
1969
2669
  /**
1970
2670
  * Direct lookup. Not on the protocol — concrete-class convenience for
1971
2671
  * tests. Matches `DrizzleJobRunService.findByRootRunId` in spirit; both
@@ -1984,9 +2684,9 @@ var MemoryJobRunService = class {
1984
2684
  }
1985
2685
  };
1986
2686
  MemoryJobRunService = __decorateClass([
1987
- Injectable6(),
1988
- __decorateParam(1, Inject6(JOB_ORCHESTRATOR)),
1989
- __decorateParam(2, Inject6(JOBS_MULTI_TENANT))
2687
+ Injectable7(),
2688
+ __decorateParam(1, Inject7(JOB_ORCHESTRATOR)),
2689
+ __decorateParam(2, Inject7(JOBS_MULTI_TENANT))
1990
2690
  ], MemoryJobRunService);
1991
2691
  function compareBy(a, b, order) {
1992
2692
  switch (order) {
@@ -2004,7 +2704,7 @@ function compareBy(a, b, order) {
2004
2704
 
2005
2705
  // runtime/subsystems/jobs/job-step-service.memory-backend.ts
2006
2706
  import { randomUUID as randomUUID3 } from "crypto";
2007
- import { Injectable as Injectable7 } from "@nestjs/common";
2707
+ import { Injectable as Injectable8 } from "@nestjs/common";
2008
2708
  var MemoryJobStepService = class {
2009
2709
  constructor(store) {
2010
2710
  this.store = store;
@@ -2097,14 +2797,13 @@ var MemoryJobStepService = class {
2097
2797
  }
2098
2798
  };
2099
2799
  MemoryJobStepService = __decorateClass([
2100
- Injectable7()
2800
+ Injectable8()
2101
2801
  ], MemoryJobStepService);
2102
2802
 
2103
2803
  // runtime/subsystems/jobs/jobs-domain.module.ts
2104
2804
  import { Module } from "@nestjs/common";
2105
2805
  var JobsDomainModule = class {
2106
2806
  static forRoot(opts) {
2107
- void opts.extensions;
2108
2807
  const multiTenant = opts.multiTenant ?? false;
2109
2808
  const providers = [
2110
2809
  // JOB-8 — boolean provider consumed by the four service-layer backends.
@@ -2123,21 +2822,32 @@ var JobsDomainModule = class {
2123
2822
  providers.push({ provide: JOB_ORCHESTRATOR, useExisting: MemoryJobOrchestrator });
2124
2823
  providers.push(MemoryJobRunService);
2125
2824
  providers.push({ provide: JOB_RUN_SERVICE, useExisting: MemoryJobRunService });
2825
+ } else if (opts.backend === "bullmq") {
2826
+ const resolved = resolveBullMqConfig(opts.extensions?.bullmq);
2827
+ providers.push({ provide: BULLMQ_CONNECTION, useValue: resolved.connection });
2828
+ providers.push({ provide: BULLMQ_RESOLVED_CONFIG, useValue: resolved });
2829
+ providers.push({ provide: JOB_ORCHESTRATOR, useClass: BullMQJobOrchestrator });
2830
+ providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });
2831
+ providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });
2126
2832
  } else {
2127
2833
  providers.push({ provide: JOB_ORCHESTRATOR, useClass: DrizzleJobOrchestrator });
2128
2834
  providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });
2129
2835
  providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });
2130
2836
  }
2837
+ const exports = [
2838
+ JOB_ORCHESTRATOR,
2839
+ JOB_RUN_SERVICE,
2840
+ JOB_STEP_SERVICE,
2841
+ JOBS_MULTI_TENANT
2842
+ ];
2843
+ if (opts.backend === "bullmq") {
2844
+ exports.push(BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG);
2845
+ }
2131
2846
  return {
2132
2847
  module: JobsDomainModule,
2133
2848
  global: true,
2134
2849
  providers,
2135
- exports: [
2136
- JOB_ORCHESTRATOR,
2137
- JOB_RUN_SERVICE,
2138
- JOB_STEP_SERVICE,
2139
- JOBS_MULTI_TENANT
2140
- ]
2850
+ exports
2141
2851
  };
2142
2852
  }
2143
2853
  };
@@ -2147,143 +2857,24 @@ JobsDomainModule = __decorateClass([
2147
2857
 
2148
2858
  // runtime/subsystems/jobs/job-worker.module.ts
2149
2859
  import {
2150
- Inject as Inject7,
2151
- Injectable as Injectable8,
2152
- Logger as Logger4,
2860
+ Inject as Inject8,
2861
+ Injectable as Injectable9,
2862
+ Logger as Logger6,
2153
2863
  Module as Module2,
2154
- Optional as Optional2
2864
+ Optional as Optional3
2155
2865
  } from "@nestjs/common";
2156
-
2157
- // runtime/subsystems/jobs/pool-config.loader.ts
2158
- import { existsSync, readFileSync } from "fs";
2159
- import { resolve } from "path";
2160
- import { parse as parseYaml } from "yaml";
2161
- var FRAMEWORK_POOLS = Object.freeze({
2162
- events_inbound: Object.freeze({
2163
- queue: "jobs-events-inbound",
2164
- concurrency: 20,
2165
- reserved: true,
2166
- description: "Inbound events drain (events subsystem outbox)."
2167
- }),
2168
- events_change: Object.freeze({
2169
- queue: "jobs-events-change",
2170
- concurrency: 30,
2171
- reserved: true,
2172
- description: "Change events drain (events subsystem outbox)."
2173
- }),
2174
- events_outbound: Object.freeze({
2175
- queue: "jobs-events-outbound",
2176
- concurrency: 10,
2177
- reserved: true,
2178
- description: "Outbound events drain (events subsystem outbox)."
2179
- }),
2180
- interactive: Object.freeze({
2181
- queue: "jobs-interactive",
2182
- concurrency: 20,
2183
- reserved: false,
2184
- description: "User-facing latency-sensitive jobs."
2185
- }),
2186
- batch: Object.freeze({
2187
- queue: "jobs-batch",
2188
- concurrency: 5,
2189
- reserved: false,
2190
- description: "Default pool for background jobs."
2191
- })
2192
- });
2193
- var RESERVED_POOL_NAMES = new Set(
2194
- Object.entries(FRAMEWORK_POOLS).filter(([, def]) => def.reserved).map(([name]) => name)
2195
- );
2196
- var cache = /* @__PURE__ */ new Map();
2197
- function loadPoolConfig(configPath) {
2198
- const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);
2199
- const cached = cache.get(resolved);
2200
- if (cached) return cached;
2201
- const merged = /* @__PURE__ */ new Map();
2202
- for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {
2203
- merged.set(name, { ...def });
2204
- }
2205
- if (!existsSync(resolved)) {
2206
- cache.set(resolved, merged);
2207
- return merged;
2208
- }
2209
- let raw;
2210
- try {
2211
- raw = parseYaml(readFileSync(resolved, "utf8"));
2212
- } catch (err) {
2213
- throw new Error(
2214
- `pool-config.loader: failed to parse YAML at ${resolved}: ${err.message}`
2215
- );
2216
- }
2217
- const userPools = extractUserPools(raw);
2218
- for (const [name, userDef] of Object.entries(userPools)) {
2219
- const existing = merged.get(name);
2220
- if (existing) {
2221
- const next = {
2222
- queue: existing.queue,
2223
- concurrency: typeof userDef.concurrency === "number" ? userDef.concurrency : existing.concurrency,
2224
- reserved: existing.reserved,
2225
- description: userDef.description ?? existing.description
2226
- };
2227
- merged.set(name, next);
2228
- continue;
2229
- }
2230
- if (typeof userDef.queue !== "string" || userDef.queue.length === 0) {
2231
- throw new Error(
2232
- `pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`
2233
- );
2234
- }
2235
- if (typeof userDef.concurrency !== "number" || userDef.concurrency <= 0) {
2236
- throw new Error(
2237
- `pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`
2238
- );
2239
- }
2240
- if (userDef.reserved === true) {
2241
- throw new Error(
2242
- `pool-config.loader: user-defined pool '${name}' cannot set 'reserved: true' \u2014 reserved is framework-only.`
2243
- );
2244
- }
2245
- merged.set(name, {
2246
- queue: userDef.queue,
2247
- concurrency: userDef.concurrency,
2248
- reserved: false,
2249
- description: userDef.description
2250
- });
2251
- }
2252
- cache.set(resolved, merged);
2253
- return merged;
2254
- }
2255
- function allNonReservedPoolNames(config) {
2256
- const out = [];
2257
- for (const [name, def] of config) {
2258
- if (!def.reserved) out.push(name);
2259
- }
2260
- return out;
2261
- }
2262
- function extractUserPools(raw) {
2263
- if (!raw || typeof raw !== "object") return {};
2264
- const jobs2 = raw.jobs;
2265
- if (!jobs2 || typeof jobs2 !== "object") return {};
2266
- const pools = jobs2.pools;
2267
- if (!pools || typeof pools !== "object") return {};
2268
- const out = {};
2269
- for (const [name, def] of Object.entries(pools)) {
2270
- if (!def || typeof def !== "object") continue;
2271
- out[name] = def;
2272
- }
2273
- return out;
2274
- }
2275
-
2276
- // runtime/subsystems/jobs/job-worker.module.ts
2277
2866
  var DEFAULT_SHUTDOWN_TIMEOUT_MS2 = 3e4;
2278
2867
  var JOB_WORKER_MODULE_OPTIONS = /* @__PURE__ */ Symbol("JOB_WORKER_MODULE_OPTIONS");
2279
2868
  var JobWorkerOrchestrator = class {
2280
- constructor(orchestrator, runService, stepService, options, db = null, moduleRef) {
2869
+ constructor(orchestrator, runService, stepService, options, db = null, moduleRef, bullConnection = null, bullConfig = null) {
2281
2870
  this.orchestrator = orchestrator;
2282
2871
  this.runService = runService;
2283
2872
  this.stepService = stepService;
2284
2873
  this.options = options;
2285
2874
  this.db = db;
2286
2875
  this.moduleRef = moduleRef;
2876
+ this.bullConnection = bullConnection;
2877
+ this.bullConfig = bullConfig;
2287
2878
  }
2288
2879
  orchestrator;
2289
2880
  runService;
@@ -2291,7 +2882,9 @@ var JobWorkerOrchestrator = class {
2291
2882
  options;
2292
2883
  db;
2293
2884
  moduleRef;
2294
- logger = new Logger4(JobWorkerOrchestrator.name);
2885
+ bullConnection;
2886
+ bullConfig;
2887
+ logger = new Logger6(JobWorkerOrchestrator.name);
2295
2888
  workers = [];
2296
2889
  // ============================================================================
2297
2890
  // Lifecycle
@@ -2308,7 +2901,7 @@ var JobWorkerOrchestrator = class {
2308
2901
  if (backend !== "memory" && orphaned.length > 0) {
2309
2902
  throw new BootValidationError(orphaned);
2310
2903
  }
2311
- const activePools = this.options.pools ?? allNonReservedPoolNames(poolConfig);
2904
+ const activePools = this.options.pools ? this.options.pools : this.options.allPools ? allPoolNames(poolConfig) : allNonReservedPoolNames(poolConfig);
2312
2905
  for (const poolName of activePools) {
2313
2906
  const def = poolConfig.get(poolName);
2314
2907
  if (!def) {
@@ -2321,11 +2914,11 @@ var JobWorkerOrchestrator = class {
2321
2914
  concurrency: def.concurrency,
2322
2915
  shutdownTimeoutMs: this.options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS2
2323
2916
  };
2324
- const worker = this.options.workerFactory ? this.options.workerFactory(workerOptions) : this.spawnWorker(workerOptions);
2325
- worker.onModuleInit();
2917
+ const worker = this.options.workerFactory ? this.options.workerFactory(workerOptions) : backend === "bullmq" ? this.spawnBullMQWorker(poolName, def.queue, def.concurrency, poolConfig) : this.spawnWorker(workerOptions);
2918
+ await worker.onModuleInit();
2326
2919
  this.workers.push(worker);
2327
2920
  this.logger.log(
2328
- `JobWorker started: pool='${poolName}' (queue='${def.queue}') concurrency=${def.concurrency}`
2921
+ `JobWorker started: pool='${poolName}' (queue='${def.queue}') concurrency=${def.concurrency} backend='${backend}'`
2329
2922
  );
2330
2923
  }
2331
2924
  }
@@ -2342,6 +2935,16 @@ var JobWorkerOrchestrator = class {
2342
2935
  }
2343
2936
  }
2344
2937
  this.workers.length = 0;
2938
+ const orch = this.orchestrator;
2939
+ if (typeof orch.closeConnections === "function") {
2940
+ try {
2941
+ await orch.closeConnections();
2942
+ } catch (err) {
2943
+ this.logger.error(
2944
+ `BullMQ orchestrator connection close failed: ${err.message}`
2945
+ );
2946
+ }
2947
+ }
2345
2948
  }
2346
2949
  // ============================================================================
2347
2950
  // Internals
@@ -2403,15 +3006,57 @@ var JobWorkerOrchestrator = class {
2403
3006
  this.moduleRef
2404
3007
  );
2405
3008
  }
3009
+ /**
3010
+ * BULLMQ-1 — spawn a per-pool `BullMQJobWorker`. Requires the Drizzle
3011
+ * client (the worker drives `job_run` as the source of truth) AND the
3012
+ * resolved BullMQ connection (bound by `JobsDomainModule` when
3013
+ * `backend: 'bullmq'`). The queue name is derived identically to the
3014
+ * orchestrator's `dispatch` via `resolvePoolQueueName(pool, …)` so producer
3015
+ * and consumer agree.
3016
+ */
3017
+ spawnBullMQWorker(pool, _queueAlias, concurrency, poolConfig) {
3018
+ if (!this.db) {
3019
+ throw new Error(
3020
+ `JobWorkerModule: BullMQ worker spawning requires the Drizzle client (no DRIZZLE provider available) \u2014 job_run remains the source of truth.`
3021
+ );
3022
+ }
3023
+ if (!this.bullConnection) {
3024
+ throw new Error(
3025
+ `JobWorkerModule: BullMQ worker spawning requires a resolved BULLMQ_CONNECTION. Ensure JobsDomainModule was booted with backend: 'bullmq'.`
3026
+ );
3027
+ }
3028
+ if (!this.moduleRef) {
3029
+ throw new Error(
3030
+ `JobWorkerModule: ModuleRef not available \u2014 cannot construct BullMQJobWorker with handler DI support.`
3031
+ );
3032
+ }
3033
+ const queueName = resolvePoolQueueName(pool, this.bullConfig, poolConfig);
3034
+ return new BullMQJobWorker(
3035
+ this.db,
3036
+ this.orchestrator,
3037
+ this.stepService,
3038
+ {
3039
+ pool,
3040
+ queueName,
3041
+ concurrency,
3042
+ connection: this.bullConnection
3043
+ },
3044
+ this.moduleRef
3045
+ );
3046
+ }
2406
3047
  };
2407
3048
  JobWorkerOrchestrator = __decorateClass([
2408
- Injectable8(),
2409
- __decorateParam(0, Inject7(JOB_ORCHESTRATOR)),
2410
- __decorateParam(1, Inject7(JOB_RUN_SERVICE)),
2411
- __decorateParam(2, Inject7(JOB_STEP_SERVICE)),
2412
- __decorateParam(3, Inject7(JOB_WORKER_MODULE_OPTIONS)),
2413
- __decorateParam(4, Optional2()),
2414
- __decorateParam(4, Inject7(DRIZZLE))
3049
+ Injectable9(),
3050
+ __decorateParam(0, Inject8(JOB_ORCHESTRATOR)),
3051
+ __decorateParam(1, Inject8(JOB_RUN_SERVICE)),
3052
+ __decorateParam(2, Inject8(JOB_STEP_SERVICE)),
3053
+ __decorateParam(3, Inject8(JOB_WORKER_MODULE_OPTIONS)),
3054
+ __decorateParam(4, Optional3()),
3055
+ __decorateParam(4, Inject8(DRIZZLE)),
3056
+ __decorateParam(6, Optional3()),
3057
+ __decorateParam(6, Inject8(BULLMQ_CONNECTION)),
3058
+ __decorateParam(7, Optional3()),
3059
+ __decorateParam(7, Inject8(BULLMQ_RESOLVED_CONFIG))
2415
3060
  ], JobWorkerOrchestrator);
2416
3061
  var JobWorkerModule = class {
2417
3062
  static forRoot(opts) {
@@ -2428,7 +3073,14 @@ var JobWorkerModule = class {
2428
3073
  { provide: JOB_WORKER_MODULE_OPTIONS, useValue: opts },
2429
3074
  JobWorkerOrchestrator
2430
3075
  ],
2431
- exports: []
3076
+ // BULLMQ-1 Phase 1 — export the options token so `BridgeModule`'s
3077
+ // reserved-pool guard (`onModuleInit`) can actually inject it.
3078
+ // Previously `exports: []` left the `@Optional()` inject resolving to
3079
+ // `undefined` and the guard silently no-opped (a dead check). With the
3080
+ // token exported the guard fires for real; consumers that omit the
3081
+ // reserved pools (and don't set `allPools`) now fail fast with
3082
+ // `BridgeReservedPoolsNotPolledError` — which is correct.
3083
+ exports: [JOB_WORKER_MODULE_OPTIONS]
2432
3084
  };
2433
3085
  }
2434
3086
  };
@@ -2436,7 +3088,11 @@ JobWorkerModule = __decorateClass([
2436
3088
  Module2({})
2437
3089
  ], JobWorkerModule);
2438
3090
  export {
3091
+ BULLMQ_CONNECTION,
3092
+ BULLMQ_RESOLVED_CONFIG,
2439
3093
  BootValidationError,
3094
+ BullMQJobOrchestrator,
3095
+ BullMQJobWorker,
2440
3096
  DrizzleJobOrchestrator,
2441
3097
  DrizzleJobRunService,
2442
3098
  DrizzleJobStepService,
@@ -2468,6 +3124,7 @@ export {
2468
3124
  RESERVED_POOL_NAMES,
2469
3125
  ReservedPoolViolationError,
2470
3126
  allNonReservedPoolNames,
3127
+ allPoolNames,
2471
3128
  buildClaimQuery,
2472
3129
  buildStaleSweepQuery,
2473
3130
  classifyError,
@@ -2482,6 +3139,9 @@ export {
2482
3139
  loadPoolConfig,
2483
3140
  parentClosePolicyEnum,
2484
3141
  replayFromEnum,
3142
+ resolveBullMqConfig,
3143
+ resolvePoolQueueName,
3144
+ sha1JobId,
2485
3145
  triggerSourceEnum,
2486
3146
  waitKindEnum
2487
3147
  };