@pattern-stack/codegen 0.10.0 → 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 (44) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/consumer-skills/events/typed-bus-and-outbox.md +1 -1
  3. package/consumer-skills/subsystems/SKILL.md +56 -0
  4. package/dist/runtime/subsystems/bridge/bridge.module.d.ts +0 -1
  5. package/dist/runtime/subsystems/bridge/bridge.module.js +294 -710
  6. package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
  7. package/dist/runtime/subsystems/bridge/index.d.ts +0 -1
  8. package/dist/runtime/subsystems/bridge/index.js +248 -664
  9. package/dist/runtime/subsystems/bridge/index.js.map +1 -1
  10. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +18 -10
  11. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
  12. package/dist/runtime/subsystems/events/events.module.js +43 -244
  13. package/dist/runtime/subsystems/events/events.module.js.map +1 -1
  14. package/dist/runtime/subsystems/events/index.d.ts +0 -1
  15. package/dist/runtime/subsystems/events/index.js +39 -241
  16. package/dist/runtime/subsystems/events/index.js.map +1 -1
  17. package/dist/runtime/subsystems/index.js +174 -791
  18. package/dist/runtime/subsystems/index.js.map +1 -1
  19. package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +22 -3
  20. package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
  21. package/dist/runtime/subsystems/jobs/index.d.ts +1 -4
  22. package/dist/runtime/subsystems/jobs/index.js +87 -506
  23. package/dist/runtime/subsystems/jobs/index.js.map +1 -1
  24. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
  25. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +3 -0
  26. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
  27. package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +11 -4
  28. package/dist/runtime/subsystems/jobs/job-worker.module.js +248 -664
  29. package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
  30. package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +0 -1
  31. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +89 -391
  32. package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
  33. package/dist/src/cli/index.js +152 -35
  34. package/dist/src/cli/index.js.map +1 -1
  35. package/package.json +1 -1
  36. package/runtime/subsystems/events/event-bus.drizzle-backend.ts +32 -10
  37. package/runtime/subsystems/events/events.module.ts +38 -6
  38. package/runtime/subsystems/events/index.ts +7 -1
  39. package/runtime/subsystems/jobs/bullmq.config.ts +23 -3
  40. package/runtime/subsystems/jobs/index.ts +13 -8
  41. package/runtime/subsystems/jobs/job-worker.bullmq-backend.ts +5 -2
  42. package/runtime/subsystems/jobs/job-worker.module.ts +27 -7
  43. package/runtime/subsystems/jobs/jobs-domain.module.ts +27 -2
  44. package/templates/subsystem/events/domain-events.schema.ejs.t +43 -2
@@ -362,13 +362,12 @@ var BRIDGE_OUTBOX_DRAIN_HOOK = "BRIDGE_OUTBOX_DRAIN_HOOK";
362
362
  // runtime/subsystems/events/event-bus.drizzle-backend.ts
363
363
  var POLL_INTERVAL_MS = 1e3;
364
364
  var POLL_BATCH_SIZE = 50;
365
- function toInsertValues(event) {
365
+ function toInsertValues(event, multiTenant) {
366
366
  const metadata = event.metadata ?? void 0;
367
367
  const pool = metadata?.["pool"] ?? null;
368
368
  const direction = metadata?.["direction"] ?? null;
369
- const tenantId = metadata?.["tenantId"] ?? null;
370
369
  const tier = metadata?.["tier"] ?? "domain";
371
- return {
370
+ const base = {
372
371
  id: event.id,
373
372
  type: event.type,
374
373
  aggregateId: event.aggregateId,
@@ -380,9 +379,11 @@ function toInsertValues(event) {
380
379
  metadata: event.metadata,
381
380
  pool,
382
381
  direction,
383
- tier,
384
- tenantId
382
+ tier
385
383
  };
384
+ if (!multiTenant) return base;
385
+ const tenantId = metadata?.["tenantId"] ?? null;
386
+ return { ...base, tenantId };
386
387
  }
387
388
  function toEventSummary(r) {
388
389
  const metadata = r.metadata ?? void 0;
@@ -397,7 +398,11 @@ function toEventSummary(r) {
397
398
  direction: r.direction,
398
399
  tier: r.tier,
399
400
  rootRunId: typeof rootRunId === "string" ? rootRunId : null,
400
- tenantId: r.tenantId,
401
+ // EVT-8: `tenant_id` is a scaffold-time conditional column. Read it
402
+ // structurally so this projection typechecks against both the
403
+ // multi-tenant schema (column present) and the single-tenant schema
404
+ // (column absent → undefined → null).
405
+ tenantId: r.tenantId ?? null,
401
406
  occurredAt: r.occurredAt instanceof Date ? r.occurredAt : new Date(r.occurredAt),
402
407
  processedAt: r.processedAt == null ? null : r.processedAt instanceof Date ? r.processedAt : new Date(r.processedAt)
403
408
  };
@@ -434,12 +439,14 @@ var DrizzleEventBus = class {
434
439
  // ============================================================================
435
440
  async publish(event, tx) {
436
441
  const client = tx ?? this.db;
437
- await client.insert(domainEvents).values(toInsertValues(event));
442
+ const multiTenant = this.opts.multiTenant ?? false;
443
+ await client.insert(domainEvents).values(toInsertValues(event, multiTenant));
438
444
  }
439
445
  async publishMany(events, tx) {
440
446
  if (events.length === 0) return;
441
447
  const client = tx ?? this.db;
442
- await client.insert(domainEvents).values(events.map(toInsertValues));
448
+ const multiTenant = this.opts.multiTenant ?? false;
449
+ await client.insert(domainEvents).values(events.map((e) => toInsertValues(e, multiTenant)));
443
450
  }
444
451
  async findById(eventId) {
445
452
  const rows = await this.db.select().from(domainEvents).where(eq(domainEvents.id, eventId)).limit(1);
@@ -481,9 +488,10 @@ var DrizzleEventBus = class {
481
488
  sql2`${domainEvents.metadata}->>'rootRunId' = ${query.rootRunId}`
482
489
  );
483
490
  }
484
- if (query.tenantId !== void 0) {
491
+ if (this.opts.multiTenant && query.tenantId !== void 0) {
492
+ const tenantIdColumn = domainEvents.tenantId;
485
493
  conditions.push(
486
- query.tenantId === null ? sql2`${domainEvents.tenantId} is null` : eq(domainEvents.tenantId, query.tenantId)
494
+ query.tenantId === null ? sql2`${tenantIdColumn} is null` : eq(tenantIdColumn, query.tenantId)
487
495
  );
488
496
  }
489
497
  if (query.cursor) {
@@ -814,233 +822,12 @@ MemoryEventBus = __decorateClass([
814
822
  __decorateParam(0, Inject3(EVENTS_MODULE_OPTIONS))
815
823
  ], MemoryEventBus);
816
824
 
817
- // runtime/subsystems/events/event-bus.redis-backend.ts
818
- import { Injectable as Injectable4, Inject as Inject4, Logger as Logger3 } from "@nestjs/common";
819
- var CHANNEL_PREFIX = "events:";
820
- async function createRedisClient(url) {
821
- let Redis;
822
- try {
823
- const mod = await import("ioredis");
824
- Redis = mod.default ?? mod;
825
- } catch {
826
- throw new Error(
827
- 'RedisEventBus requires the "ioredis" package. Install it with: npm install ioredis'
828
- );
829
- }
830
- return new Redis(url);
831
- }
832
- var RedisEventBus = class {
833
- constructor(redisUrl) {
834
- this.redisUrl = redisUrl;
835
- }
836
- redisUrl;
837
- logger = new Logger3(RedisEventBus.name);
838
- publisher = null;
839
- subscriber = null;
840
- connected = false;
841
- /**
842
- * In-process subscriber registry. Handlers registered here are called when
843
- * a message arrives on the subscriber client — keeping fan-out within the
844
- * same process without an extra round-trip through Redis.
845
- */
846
- handlers = /* @__PURE__ */ new Map();
847
- /**
848
- * Track which event types have active Redis subscriptions.
849
- * Used to avoid subscribing multiple times to the same type channel.
850
- */
851
- subscribedTypes = /* @__PURE__ */ new Set();
852
- // ============================================================================
853
- // Lifecycle
854
- // ============================================================================
855
- async onModuleInit() {
856
- this.publisher = await createRedisClient(this.redisUrl);
857
- this.subscriber = await createRedisClient(this.redisUrl);
858
- this.publisher.on(
859
- "error",
860
- (err) => this.logger.error(`Redis publisher error: ${err.message}`, err.stack)
861
- );
862
- this.subscriber.on(
863
- "error",
864
- (err) => this.logger.error(`Redis subscriber error: ${err.message}`, err.stack)
865
- );
866
- this.subscriber.on("message", (channel, message) => {
867
- void this.handleMessage(channel, message);
868
- });
869
- this.connected = true;
870
- this.logger.log(`RedisEventBus connected to ${this.redisUrl}`);
871
- }
872
- async onModuleDestroy() {
873
- this.connected = false;
874
- if (this.subscriber) {
875
- await this.subscriber.unsubscribe();
876
- this.subscriber.disconnect();
877
- this.subscriber = null;
878
- }
879
- if (this.publisher) {
880
- this.publisher.disconnect();
881
- this.publisher = null;
882
- }
883
- this.subscribedTypes.clear();
884
- this.logger.log("RedisEventBus disconnected");
885
- }
886
- // ============================================================================
887
- // IEventBus
888
- // ============================================================================
889
- /**
890
- * Publish a single event.
891
- *
892
- * `tx` is accepted but ignored — see module-level JSDoc for details.
893
- */
894
- async publish(event, tx) {
895
- void tx;
896
- this.assertConnected();
897
- const payload = this.serialize(event);
898
- const channel = `${CHANNEL_PREFIX}${event.type}`;
899
- await this.publisher.publish(channel, payload);
900
- }
901
- /**
902
- * Publish multiple events using a pipeline so all PUBLISH commands are sent
903
- * in a single round-trip.
904
- *
905
- * `tx` is accepted but ignored — see module-level JSDoc for details.
906
- */
907
- async publishMany(events, tx) {
908
- void tx;
909
- if (events.length === 0) return;
910
- this.assertConnected();
911
- const pipeline = this.publisher.pipeline();
912
- for (const event of events) {
913
- const payload = this.serialize(event);
914
- const channel = `${CHANNEL_PREFIX}${event.type}`;
915
- pipeline.publish(channel, payload);
916
- }
917
- await pipeline.exec();
918
- }
919
- /**
920
- * Register a handler for a specific event type.
921
- * Returns an unsubscribe function — call it to remove the handler.
922
- *
923
- * On first handler for a type, subscribes to the per-type Redis channel.
924
- * On removal of the last handler for a type, unsubscribes from the channel.
925
- */
926
- /**
927
- * Lookup by id is unsupported on the Redis Pub/Sub backend — Pub/Sub
928
- * does not retain history. Always returns `null`. Logs a warning the
929
- * first time it's called so a misconfiguration surfaces visibly. Using
930
- * the bridge with the Redis backend is unsupported (the bridge requires
931
- * a durable event store).
932
- */
933
- warnedFindById = false;
934
- async findById(_eventId) {
935
- if (!this.warnedFindById) {
936
- this.warnedFindById = true;
937
- this.logger.warn(
938
- "RedisEventBus.findById is unsupported (Pub/Sub has no history). The bridge subsystem requires a durable event store; switch to DrizzleEventBus if you need bridge fanout."
939
- );
940
- }
941
- return null;
942
- }
943
- subscribe(eventType, handler) {
944
- if (!this.handlers.has(eventType)) {
945
- this.handlers.set(eventType, /* @__PURE__ */ new Set());
946
- void this.subscribeToType(eventType);
947
- }
948
- const set = this.handlers.get(eventType);
949
- const h = handler;
950
- set.add(h);
951
- return () => {
952
- set.delete(h);
953
- if (set.size === 0) {
954
- this.handlers.delete(eventType);
955
- void this.unsubscribeFromType(eventType);
956
- }
957
- };
958
- }
959
- // ============================================================================
960
- // Internal helpers
961
- // ============================================================================
962
- assertConnected() {
963
- if (!this.connected || !this.publisher) {
964
- throw new Error(
965
- "RedisEventBus is not connected. Ensure the module has been initialised before publishing."
966
- );
967
- }
968
- }
969
- serialize(event) {
970
- return JSON.stringify({
971
- ...event,
972
- occurredAt: event.occurredAt.toISOString()
973
- });
974
- }
975
- deserialize(raw) {
976
- const parsed = JSON.parse(raw);
977
- return {
978
- ...parsed,
979
- occurredAt: new Date(parsed.occurredAt)
980
- };
981
- }
982
- async handleMessage(channel, message) {
983
- let event;
984
- try {
985
- event = this.deserialize(message);
986
- } catch (err) {
987
- this.logger.warn(`Failed to deserialize event on channel "${channel}": ${err}`);
988
- return;
989
- }
990
- await this.dispatch(event);
991
- }
992
- async dispatch(event) {
993
- const set = this.handlers.get(event.type);
994
- if (!set) return;
995
- for (const handler of set) {
996
- try {
997
- await handler(event);
998
- } catch (err) {
999
- this.logger.error(
1000
- `Handler error for event type "${event.type}" (id: ${event.id}): ${err}`
1001
- );
1002
- }
1003
- }
1004
- }
1005
- /**
1006
- * Subscribe to a per-type Redis channel.
1007
- * Called lazily when the first handler is registered for a type.
1008
- */
1009
- async subscribeToType(eventType) {
1010
- if (this.subscribedTypes.has(eventType)) {
1011
- return;
1012
- }
1013
- const channel = `${CHANNEL_PREFIX}${eventType}`;
1014
- try {
1015
- await this.subscriber.subscribe(channel);
1016
- this.subscribedTypes.add(eventType);
1017
- } catch (err) {
1018
- this.logger.error(`Failed to subscribe to channel "${channel}": ${err}`);
1019
- }
1020
- }
1021
- /**
1022
- * Unsubscribe from a per-type Redis channel.
1023
- * Called when the last handler for a type is removed.
1024
- */
1025
- async unsubscribeFromType(eventType) {
1026
- if (!this.subscribedTypes.has(eventType)) {
1027
- return;
1028
- }
1029
- const channel = `${CHANNEL_PREFIX}${eventType}`;
1030
- try {
1031
- await this.subscriber.unsubscribe(channel);
1032
- this.subscribedTypes.delete(eventType);
1033
- } catch (err) {
1034
- this.logger.error(`Failed to unsubscribe from channel "${channel}": ${err}`);
1035
- }
1036
- }
1037
- };
1038
- RedisEventBus = __decorateClass([
1039
- Injectable4(),
1040
- __decorateParam(0, Inject4(REDIS_URL))
1041
- ], RedisEventBus);
1042
-
1043
825
  // runtime/subsystems/events/events.module.ts
826
+ async function loadRedisEventBus() {
827
+ const specifier = "./event-bus.redis-backend";
828
+ const mod = await import(specifier);
829
+ return mod.RedisEventBus;
830
+ }
1044
831
  function buildTypedBusProviders(multiTenant) {
1045
832
  return [
1046
833
  TypedEventBus,
@@ -1048,7 +835,7 @@ function buildTypedBusProviders(multiTenant) {
1048
835
  { provide: EVENTS_MULTI_TENANT, useValue: multiTenant }
1049
836
  ];
1050
837
  }
1051
- function buildEventBusAsync(options, db, redisUrl) {
838
+ async function buildEventBusAsync(options, db, redisUrl) {
1052
839
  if (options.backend === "drizzle") {
1053
840
  if (!db) {
1054
841
  throw new Error(
@@ -1058,6 +845,7 @@ function buildEventBusAsync(options, db, redisUrl) {
1058
845
  return new DrizzleEventBus(db, options);
1059
846
  }
1060
847
  if (options.backend === "redis") {
848
+ const RedisEventBus = await loadRedisEventBus();
1061
849
  return new RedisEventBus(redisUrl);
1062
850
  }
1063
851
  return new MemoryEventBus(options);
@@ -1119,9 +907,20 @@ var EventsModule = class {
1119
907
  providers: [
1120
908
  { provide: EVENTS_MODULE_OPTIONS, useValue: options },
1121
909
  { provide: REDIS_URL, useValue: resolvedUrl },
1122
- { provide: EVENT_BUS, useClass: RedisEventBus },
1123
- // Register concrete class so NestJS can resolve lifecycle hooks
1124
- RedisEventBus,
910
+ {
911
+ // #6: useFactory + dynamic import so the consumer's tsc never
912
+ // needs to resolve `event-bus.redis-backend.ts` for drizzle/
913
+ // memory installs (the file is filtered out by
914
+ // `backendFileFilter`). Nest awaits async factories + manages
915
+ // lifecycle on the returned instance, so we drop the old bare
916
+ // `RedisEventBus` provider entry.
917
+ provide: EVENT_BUS,
918
+ useFactory: async (url) => {
919
+ const RedisEventBus = await loadRedisEventBus();
920
+ return new RedisEventBus(url);
921
+ },
922
+ inject: [REDIS_URL]
923
+ },
1125
924
  ...buildTypedBusProviders(multiTenant)
1126
925
  ],
1127
926
  exports: [EVENT_BUS, TYPED_EVENT_BUS, EVENTS_MULTI_TENANT]
@@ -1324,7 +1123,7 @@ var HandlerRegistry;
1324
1123
 
1325
1124
  // runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts
1326
1125
  import { randomUUID as randomUUID2 } from "crypto";
1327
- import { Inject as Inject5, Injectable as Injectable5, Logger as Logger4 } from "@nestjs/common";
1126
+ import { Inject as Inject4, Injectable as Injectable4, Logger as Logger3 } from "@nestjs/common";
1328
1127
  import { and as and2, desc as desc2, eq as eq2, gt, inArray as inArray2, isNotNull, ne, notInArray, sql as sql4 } from "drizzle-orm";
1329
1128
 
1330
1129
  // runtime/subsystems/jobs/jobs-errors.ts
@@ -1432,7 +1231,7 @@ var DrizzleJobOrchestrator = class {
1432
1231
  db;
1433
1232
  multiTenant;
1434
1233
  // TODO(logging-subsystem): swap to ILogger once ADR-028 lands
1435
- logger = new Logger4(DrizzleJobOrchestrator.name);
1234
+ logger = new Logger3(DrizzleJobOrchestrator.name);
1436
1235
  /**
1437
1236
  * JOB-8 — resolve `tenantId` for a mutating / targeted-read call.
1438
1237
  * Returns the tenant value that should be written to the row (or compared
@@ -1721,9 +1520,9 @@ var DrizzleJobOrchestrator = class {
1721
1520
  }
1722
1521
  };
1723
1522
  DrizzleJobOrchestrator = __decorateClass([
1724
- Injectable5(),
1725
- __decorateParam(0, Inject5(DRIZZLE)),
1726
- __decorateParam(1, Inject5(JOBS_MULTI_TENANT))
1523
+ Injectable4(),
1524
+ __decorateParam(0, Inject4(DRIZZLE)),
1525
+ __decorateParam(1, Inject4(JOBS_MULTI_TENANT))
1727
1526
  ], DrizzleJobOrchestrator);
1728
1527
  function notInStatus(statuses) {
1729
1528
  const negated = statuses.map((s) => ne(jobRuns.status, s));
@@ -1731,7 +1530,7 @@ function notInStatus(statuses) {
1731
1530
  }
1732
1531
 
1733
1532
  // runtime/subsystems/jobs/job-run-service.drizzle-backend.ts
1734
- import { Inject as Inject6, Injectable as Injectable6 } from "@nestjs/common";
1533
+ import { Inject as Inject5, Injectable as Injectable5 } from "@nestjs/common";
1735
1534
  import { and as and3, asc as asc2, desc as desc3, eq as eq3, gte as gte2, inArray as inArray3, isNull, lt as lt2, or as or2, sql as sql5 } from "drizzle-orm";
1736
1535
 
1737
1536
  // runtime/subsystems/jobs/job-run-keyset-cursor.ts
@@ -1951,14 +1750,14 @@ var DrizzleJobRunService = class {
1951
1750
  }
1952
1751
  };
1953
1752
  DrizzleJobRunService = __decorateClass([
1954
- Injectable6(),
1955
- __decorateParam(0, Inject6(DRIZZLE)),
1956
- __decorateParam(1, Inject6(JOB_ORCHESTRATOR)),
1957
- __decorateParam(2, Inject6(JOBS_MULTI_TENANT))
1753
+ Injectable5(),
1754
+ __decorateParam(0, Inject5(DRIZZLE)),
1755
+ __decorateParam(1, Inject5(JOB_ORCHESTRATOR)),
1756
+ __decorateParam(2, Inject5(JOBS_MULTI_TENANT))
1958
1757
  ], DrizzleJobRunService);
1959
1758
 
1960
1759
  // runtime/subsystems/jobs/job-step-service.drizzle-backend.ts
1961
- import { Inject as Inject7, Injectable as Injectable7 } from "@nestjs/common";
1760
+ import { Inject as Inject6, Injectable as Injectable6 } from "@nestjs/common";
1962
1761
  import { and as and4, eq as eq4 } from "drizzle-orm";
1963
1762
  var DrizzleJobStepService = class {
1964
1763
  constructor(db) {
@@ -1997,15 +1796,10 @@ var DrizzleJobStepService = class {
1997
1796
  }
1998
1797
  };
1999
1798
  DrizzleJobStepService = __decorateClass([
2000
- Injectable7(),
2001
- __decorateParam(0, Inject7(DRIZZLE))
1799
+ Injectable6(),
1800
+ __decorateParam(0, Inject6(DRIZZLE))
2002
1801
  ], DrizzleJobStepService);
2003
1802
 
2004
- // runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts
2005
- import { createHash } from "crypto";
2006
- import { Inject as Inject8, Injectable as Injectable8, Logger as Logger5, Optional as Optional3 } from "@nestjs/common";
2007
- import { eq as eq5 } from "drizzle-orm";
2008
-
2009
1803
  // runtime/subsystems/jobs/pool-config.loader.ts
2010
1804
  import { existsSync, readFileSync } from "fs";
2011
1805
  import { resolve } from "path";
@@ -2153,443 +1947,9 @@ function resolvePoolQueueName(pool, config, poolConfig = loadPoolConfig()) {
2153
1947
  return prefix ? `${prefix}:${alias}` : alias;
2154
1948
  }
2155
1949
 
2156
- // runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts
2157
- function sha1JobId(idempotencyKey) {
2158
- return createHash("sha1").update(idempotencyKey).digest("hex");
2159
- }
2160
- var BullMQJobOrchestrator = class extends DrizzleJobOrchestrator {
2161
- constructor(db, multiTenant, connection, bullConfig = null) {
2162
- super(db, multiTenant);
2163
- this.connection = connection;
2164
- this.bullConfig = bullConfig;
2165
- this.bullDb = db;
2166
- }
2167
- connection;
2168
- bullConfig;
2169
- // TODO(logging-subsystem): swap to ILogger once ADR-028 lands
2170
- bullLogger = new Logger5(BullMQJobOrchestrator.name);
2171
- /** Lazily-opened `Queue` handles, one per pool. */
2172
- queues = /* @__PURE__ */ new Map();
2173
- /** Single FlowProducer for parent/child hierarchies. Lazily opened. */
2174
- _flow = null;
2175
- /**
2176
- * Cached `bullmq` value constructors, populated by `loadBullMq()` on first
2177
- * use (the `start`/`cancel`/`replay` entrypoints `await` it before touching
2178
- * a queue). Kept off the import graph so a `drizzle`-only consumer never
2179
- * resolves the optional `'bullmq'` package.
2180
- */
2181
- QueueCtor = null;
2182
- FlowProducerCtor = null;
2183
- bullMqLoad = null;
2184
- /**
2185
- * Own reference to the Drizzle client. `DrizzleJobOrchestrator.db` is
2186
- * `private` (can't be redeclared even privately in a subclass), and the
2187
- * spec forbids touching that file — so the subclass keeps its own handle
2188
- * under a distinct name (same instance, passed through to `super`) for the
2189
- * cancel-cascade snapshot + definition/run loads below.
2190
- */
2191
- bullDb;
2192
- /**
2193
- * Lazily load the optional `bullmq` package and cache its value
2194
- * constructors. Idempotent (single in-flight promise). Throws a friendly,
2195
- * actionable error when the consumer selected `backend: 'bullmq'` but did
2196
- * not install the package — mirrors `createRedisClient` in the redis event
2197
- * backend. Must be `await`ed before any `queueFor`/`flow` access.
2198
- */
2199
- async loadBullMq() {
2200
- if (this.QueueCtor && this.FlowProducerCtor) return;
2201
- if (!this.bullMqLoad) {
2202
- this.bullMqLoad = (async () => {
2203
- try {
2204
- const mod = await import("bullmq");
2205
- this.QueueCtor = mod.Queue;
2206
- this.FlowProducerCtor = mod.FlowProducer;
2207
- } catch {
2208
- throw new Error(
2209
- 'BullMQ backend requires the "bullmq" package. Install it with: npm install bullmq'
2210
- );
2211
- }
2212
- })();
2213
- }
2214
- await this.bullMqLoad;
2215
- }
2216
- /**
2217
- * Open (or reuse) the `Queue` for a pool. Synchronous — callers `await
2218
- * loadBullMq()` first so `QueueCtor` is populated.
2219
- */
2220
- queueFor(pool) {
2221
- if (!this.QueueCtor) {
2222
- throw new Error("BullMQJobOrchestrator: queueFor called before loadBullMq()");
2223
- }
2224
- const name = resolvePoolQueueName(pool, this.bullConfig);
2225
- let q = this.queues.get(name);
2226
- if (!q) {
2227
- q = new this.QueueCtor(name, { connection: this.connection });
2228
- this.queues.set(name, q);
2229
- }
2230
- return q;
2231
- }
2232
- flow() {
2233
- if (!this.FlowProducerCtor) {
2234
- throw new Error("BullMQJobOrchestrator: flow called before loadBullMq()");
2235
- }
2236
- if (!this._flow) {
2237
- this._flow = new this.FlowProducerCtor({ connection: this.connection });
2238
- }
2239
- return this._flow;
2240
- }
2241
- // ==========================================================================
2242
- // start — Postgres insert (super) + BullMQ dispatch
2243
- // ==========================================================================
2244
- async start(type, input, opts = {}, tx) {
2245
- const run = await super.start(type, input, opts, tx);
2246
- await this.dispatch(run, type);
2247
- return run;
2248
- }
2249
- /**
2250
- * Map a `job_run` row onto a BullMQ job via `queue.add`. When the run has a
2251
- * `parentRunId` we attach it to the parent's existing BullMQ job through the
2252
- * `parent: { id, queue }` opt — BullMQ then tracks the parent/child link in
2253
- * its own graph. (The FlowProducer is reserved for whole-tree atomic
2254
- * submits, exposed as an opt-in extension via `flowProducer()`; runtime
2255
- * `ctx.spawnChild` is incremental, so `queue.add` with a parent ref is the
2256
- * correct primitive here.)
2257
- *
2258
- * The `jobId` is colon-safe + stable: `sha1(dedupeKey)` when a dedupe key is
2259
- * present (so the same logical key dedups), else the `job_run.id` UUID
2260
- * (already colon-free).
2261
- *
2262
- * The domain `parentClosePolicy` cascade is still enforced in Postgres by
2263
- * the shared `cancel` path — BullMQ's parent link is dispatch bookkeeping,
2264
- * not the authority.
2265
- */
2266
- async dispatch(run, type) {
2267
- await this.loadBullMq();
2268
- const def = await this.loadDefinition(type);
2269
- const jobId = run.dedupeKey ? sha1JobId(run.dedupeKey) : run.id;
2270
- const jobOpts = {
2271
- jobId,
2272
- ...this.retryOpts(def),
2273
- ...this.dedupeOpts(run, def)
2274
- };
2275
- if (run.parentRunId) {
2276
- const parentRow = await this.loadRun(run.parentRunId);
2277
- if (parentRow) {
2278
- const parentJobId = parentRow.dedupeKey ? sha1JobId(parentRow.dedupeKey) : parentRow.id;
2279
- jobOpts.parent = {
2280
- id: parentJobId,
2281
- queue: resolvePoolQueueName(parentRow.pool, this.bullConfig)
2282
- };
2283
- }
2284
- }
2285
- const payload = { runId: run.id, type, input: run.input };
2286
- await this.queueFor(run.pool).add(type, payload, jobOpts);
2287
- }
2288
- /**
2289
- * Opt-in extension (spec §Extensions): expose the FlowProducer for
2290
- * consumers that want to submit a whole parent/child DAG atomically up
2291
- * front, rather than incrementally via `ctx.spawnChild`. Backend-specific —
2292
- * code using it is not portable to the Drizzle backend. Async because it
2293
- * lazily loads the optional `bullmq` package on first use.
2294
- */
2295
- async flowProducer() {
2296
- await this.loadBullMq();
2297
- return this.flow();
2298
- }
2299
- retryOpts(def) {
2300
- const policy = def.retryPolicy;
2301
- if (!policy) return {};
2302
- return {
2303
- attempts: policy.attempts,
2304
- backoff: {
2305
- type: policy.backoff === "exponential" ? "exponential" : "fixed",
2306
- delay: policy.baseMs
2307
- }
2308
- };
2309
- }
2310
- dedupeOpts(run, def) {
2311
- if (!run.dedupeKey || !def.dedupeWindowMs) return {};
2312
- return {
2313
- deduplication: {
2314
- id: sha1JobId(run.dedupeKey),
2315
- ttl: def.dedupeWindowMs
2316
- }
2317
- };
2318
- }
2319
- // ==========================================================================
2320
- // cancel — Postgres cascade (super) + remove from queue
2321
- // ==========================================================================
2322
- async cancel(runId, opts = {}) {
2323
- const target = await this.loadRun(runId);
2324
- await super.cancel(runId, opts);
2325
- if (!target) return;
2326
- await this.loadBullMq();
2327
- await this.removeFromQueue(target);
2328
- if (opts.cascade === false) return;
2329
- const descendants = await this.bullDb.select().from(jobRuns).where(eq5(jobRuns.rootRunId, target.rootRunId));
2330
- for (const child of descendants) {
2331
- if (child.id === runId) continue;
2332
- await this.removeFromQueue(child);
2333
- }
2334
- }
2335
- async removeFromQueue(run) {
2336
- const jobId = run.dedupeKey ? sha1JobId(run.dedupeKey) : run.id;
2337
- try {
2338
- const job = await this.queueFor(run.pool).getJob(jobId);
2339
- if (job) await job.remove();
2340
- } catch (err) {
2341
- this.bullLogger.warn(
2342
- `cancel: could not remove BullMQ job ${jobId} (pool=${run.pool}): ${err.message}`
2343
- );
2344
- }
2345
- }
2346
- // ==========================================================================
2347
- // replay — Postgres reset (super) + re-enqueue
2348
- // ==========================================================================
2349
- async replay(runId) {
2350
- const run = await super.replay(runId);
2351
- await this.dispatch(run, run.jobType);
2352
- return run;
2353
- }
2354
- // ==========================================================================
2355
- // Internals
2356
- // ==========================================================================
2357
- async loadDefinition(type) {
2358
- const [def] = await this.bullDb.select().from(jobs).where(eq5(jobs.type, type)).limit(1);
2359
- if (!def) {
2360
- throw new Error(`BullMQJobOrchestrator: no job definition for '${type}'`);
2361
- }
2362
- return def;
2363
- }
2364
- async loadRun(id) {
2365
- const [row] = await this.bullDb.select().from(jobRuns).where(eq5(jobRuns.id, id)).limit(1);
2366
- return row ?? null;
2367
- }
2368
- /** Close all open queue + flow connections. Called on module destroy. */
2369
- async closeConnections() {
2370
- for (const q of this.queues.values()) {
2371
- await q.close().catch(() => void 0);
2372
- }
2373
- this.queues.clear();
2374
- if (this._flow) {
2375
- await this._flow.close().catch(() => void 0);
2376
- this._flow = null;
2377
- }
2378
- }
2379
- };
2380
- BullMQJobOrchestrator = __decorateClass([
2381
- Injectable8(),
2382
- __decorateParam(0, Inject8(DRIZZLE)),
2383
- __decorateParam(1, Inject8(JOBS_MULTI_TENANT)),
2384
- __decorateParam(2, Inject8(BULLMQ_CONNECTION)),
2385
- __decorateParam(3, Optional3()),
2386
- __decorateParam(3, Inject8(BULLMQ_RESOLVED_CONFIG))
2387
- ], BullMQJobOrchestrator);
2388
-
2389
- // runtime/subsystems/jobs/job-worker.bullmq-backend.ts
2390
- import { Logger as Logger6 } from "@nestjs/common";
2391
- import { eq as eq6 } from "drizzle-orm";
2392
- function serialiseError(err, attempt, retryable) {
2393
- const e = err;
2394
- return {
2395
- message: e?.message ?? String(err),
2396
- stack: e?.stack,
2397
- retryable,
2398
- attempt
2399
- };
2400
- }
2401
- var BullMQJobWorker = class _BullMQJobWorker {
2402
- constructor(db, orchestrator, stepService, options, moduleRef) {
2403
- this.db = db;
2404
- this.orchestrator = orchestrator;
2405
- this.stepService = stepService;
2406
- this.options = options;
2407
- this.moduleRef = moduleRef;
2408
- }
2409
- db;
2410
- orchestrator;
2411
- stepService;
2412
- options;
2413
- moduleRef;
2414
- logger = new Logger6(_BullMQJobWorker.name);
2415
- worker = null;
2416
- async onModuleInit() {
2417
- let WorkerCtor;
2418
- try {
2419
- const mod = await import("bullmq");
2420
- WorkerCtor = mod.Worker;
2421
- } catch {
2422
- throw new Error(
2423
- 'BullMQ backend requires the "bullmq" package. Install it with: npm install bullmq'
2424
- );
2425
- }
2426
- this.worker = new WorkerCtor(
2427
- this.options.queueName,
2428
- (job) => this.process(job),
2429
- {
2430
- connection: this.options.connection,
2431
- concurrency: this.options.concurrency
2432
- }
2433
- );
2434
- this.worker.on("failed", (job, err) => {
2435
- if (!job) return;
2436
- const attemptsMade = job.attemptsMade;
2437
- const maxAttempts = job.opts.attempts ?? 1;
2438
- if (attemptsMade >= maxAttempts) {
2439
- void this.markFailed(job.data.runId, err, attemptsMade);
2440
- }
2441
- });
2442
- this.logger.log(
2443
- `BullMQ worker started: pool='${this.options.pool}' queue='${this.options.queueName}' concurrency=${this.options.concurrency}`
2444
- );
2445
- }
2446
- async onModuleDestroy() {
2447
- if (this.worker) {
2448
- await this.worker.close();
2449
- this.worker = null;
2450
- }
2451
- }
2452
- /**
2453
- * Process one BullMQ job. Returns the handler output (stored by BullMQ as
2454
- * the job return value AND written to `job_run.output`). Throws on handler
2455
- * failure so BullMQ applies the retry policy.
2456
- */
2457
- async process(job) {
2458
- const { runId } = job.data;
2459
- const [row] = await this.db.select().from(jobRuns).where(eq6(jobRuns.id, runId)).limit(1);
2460
- if (!row) {
2461
- this.logger.warn(`process: job_run ${runId} not found; skipping`);
2462
- return {};
2463
- }
2464
- const run = row;
2465
- if (run.status === "canceled") {
2466
- return {};
2467
- }
2468
- const registryEntry = JOB_HANDLER_REGISTRY.get(run.jobType);
2469
- if (!registryEntry) {
2470
- throw new Error(
2471
- `No handler registered for jobType='${run.jobType}' (run ${run.id})`
2472
- );
2473
- }
2474
- await this.db.update(jobRuns).set({
2475
- status: "running",
2476
- claimedAt: /* @__PURE__ */ new Date(),
2477
- startedAt: /* @__PURE__ */ new Date(),
2478
- attempts: job.attemptsMade + 1,
2479
- updatedAt: /* @__PURE__ */ new Date()
2480
- }).where(eq6(jobRuns.id, run.id));
2481
- const HandlerClass = registryEntry.handlerClass;
2482
- const handler = this.moduleRef.get(
2483
- HandlerClass,
2484
- { strict: false }
2485
- );
2486
- const ctx = {
2487
- input: run.input,
2488
- run,
2489
- step: this.makeStepFn(run),
2490
- spawnChild: this.makeSpawnFn(run),
2491
- logger: new Logger6(`JobRun:${run.id}`)
2492
- };
2493
- const output = await handler.run(ctx);
2494
- await this.db.update(jobRuns).set({
2495
- status: "completed",
2496
- output: output ?? {},
2497
- finishedAt: /* @__PURE__ */ new Date(),
2498
- updatedAt: /* @__PURE__ */ new Date()
2499
- }).where(eq6(jobRuns.id, run.id));
2500
- return output ?? {};
2501
- }
2502
- async markFailed(runId, err, finalAttempts) {
2503
- const [row] = await this.db.select().from(jobRuns).where(eq6(jobRuns.id, runId)).limit(1);
2504
- if (!row) return;
2505
- const run = row;
2506
- await this.db.update(jobRuns).set({
2507
- status: "failed",
2508
- attempts: finalAttempts,
2509
- finishedAt: /* @__PURE__ */ new Date(),
2510
- error: serialiseError(err, finalAttempts, false),
2511
- updatedAt: /* @__PURE__ */ new Date()
2512
- }).where(eq6(jobRuns.id, runId));
2513
- if (run.parentClosePolicy === "terminate") {
2514
- try {
2515
- await this.orchestrator.cancel(run.id, {
2516
- cascade: true,
2517
- reason: "parent-failed",
2518
- tenantId: run.tenantId
2519
- });
2520
- } catch (cascadeErr) {
2521
- this.logger.warn(
2522
- `cascade on failed run ${run.id}: ${cascadeErr.message}`
2523
- );
2524
- }
2525
- }
2526
- }
2527
- // ── ctx.step / ctx.spawnChild (mirror JobWorker) ──────────────────────────
2528
- makeStepFn(run) {
2529
- return async (stepId, fn, _opts) => {
2530
- void _opts;
2531
- const existing = await this.stepService.findStep(run.id, stepId);
2532
- if (existing?.status === "completed") {
2533
- return existing.output;
2534
- }
2535
- const nextAttempts = (existing?.attempts ?? 0) + 1;
2536
- const seq = nextAttempts;
2537
- await this.stepService.recordStep({
2538
- jobRunId: run.id,
2539
- stepId,
2540
- kind: "task",
2541
- seq,
2542
- status: "running",
2543
- startedAt: /* @__PURE__ */ new Date(),
2544
- attempts: nextAttempts
2545
- });
2546
- try {
2547
- const output = await fn();
2548
- await this.stepService.recordStep({
2549
- jobRunId: run.id,
2550
- stepId,
2551
- kind: "task",
2552
- seq,
2553
- status: "completed",
2554
- output,
2555
- finishedAt: /* @__PURE__ */ new Date(),
2556
- attempts: nextAttempts
2557
- });
2558
- return output;
2559
- } catch (err) {
2560
- await this.stepService.recordStep({
2561
- jobRunId: run.id,
2562
- stepId,
2563
- kind: "task",
2564
- seq,
2565
- status: "failed",
2566
- error: serialiseError(err, nextAttempts, false),
2567
- finishedAt: /* @__PURE__ */ new Date(),
2568
- attempts: nextAttempts
2569
- });
2570
- throw err;
2571
- }
2572
- };
2573
- }
2574
- makeSpawnFn(run) {
2575
- return async (type, input, opts) => {
2576
- return this.orchestrator.start(type, input, {
2577
- parentRunId: run.id,
2578
- parentClosePolicy: opts?.closePolicy,
2579
- runAt: opts?.runAt,
2580
- priority: opts?.priority,
2581
- tags: opts?.tags,
2582
- triggerSource: "parent",
2583
- triggerRef: run.id,
2584
- tenantId: run.tenantId
2585
- });
2586
- };
2587
- }
2588
- };
2589
-
2590
1950
  // runtime/subsystems/jobs/job-worker.ts
2591
- import { Inject as Inject9, Injectable as Injectable9, Logger as Logger7 } from "@nestjs/common";
2592
- import { and as and5, asc as asc3, desc as desc4, eq as eq7, inArray as inArray4, lt as lt3, lte, sql as sql6 } from "drizzle-orm";
1951
+ import { Inject as Inject7, Injectable as Injectable7, Logger as Logger4 } from "@nestjs/common";
1952
+ import { and as and5, asc as asc3, desc as desc4, eq as eq5, inArray as inArray4, lt as lt3, lte, sql as sql6 } from "drizzle-orm";
2593
1953
  var JOB_WORKER_OPTIONS = /* @__PURE__ */ Symbol("JOB_WORKER_OPTIONS");
2594
1954
  var DEFAULT_POLL_INTERVAL_MS = 1e3;
2595
1955
  var DEFAULT_STALE_SWEEPER_INTERVAL_MS = 6e4;
@@ -2618,7 +1978,7 @@ function classifyError(err, policy, currentAttempts) {
2618
1978
  if (currentAttempts + 1 >= policy.attempts) return "fail";
2619
1979
  return "retry";
2620
1980
  }
2621
- function serialiseError2(err, attempt, retryable) {
1981
+ function serialiseError(err, attempt, retryable) {
2622
1982
  const e = err;
2623
1983
  return {
2624
1984
  message: e?.message ?? String(err),
@@ -2652,7 +2012,7 @@ var JobWorker = class {
2652
2012
  stepService;
2653
2013
  options;
2654
2014
  moduleRef;
2655
- logger = new Logger7(JobWorker.name);
2015
+ logger = new Logger4(JobWorker.name);
2656
2016
  shuttingDown = false;
2657
2017
  inFlight = /* @__PURE__ */ new Set();
2658
2018
  pollTimer = null;
@@ -2693,7 +2053,7 @@ var JobWorker = class {
2693
2053
  await this.drainInFlight();
2694
2054
  try {
2695
2055
  await this.db.update(jobRuns).set({ status: "pending", claimedAt: null, startedAt: null }).where(
2696
- and5(eq7(jobRuns.status, "running"), eq7(jobRuns.pool, this.options.pool))
2056
+ and5(eq5(jobRuns.status, "running"), eq5(jobRuns.pool, this.options.pool))
2697
2057
  );
2698
2058
  } catch (err) {
2699
2059
  this.logger.error(`shutdown reset failed: ${err.message}`);
@@ -2743,8 +2103,8 @@ var JobWorker = class {
2743
2103
  return this.db.transaction(async (tx) => {
2744
2104
  const candidates = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
2745
2105
  and5(
2746
- eq7(jobRuns.status, "pending"),
2747
- eq7(jobRuns.pool, pool),
2106
+ eq5(jobRuns.status, "pending"),
2107
+ eq5(jobRuns.pool, pool),
2748
2108
  lte(jobRuns.runAt, /* @__PURE__ */ new Date())
2749
2109
  )
2750
2110
  ).orderBy(desc4(jobRuns.priority), asc3(jobRuns.runAt)).limit(1).for("update", { skipLocked: true });
@@ -2755,7 +2115,7 @@ var JobWorker = class {
2755
2115
  claimedAt: /* @__PURE__ */ new Date(),
2756
2116
  startedAt: /* @__PURE__ */ new Date(),
2757
2117
  updatedAt: /* @__PURE__ */ new Date()
2758
- }).where(eq7(jobRuns.id, candidate.id)).returning();
2118
+ }).where(eq5(jobRuns.id, candidate.id)).returning();
2759
2119
  return claimed ?? null;
2760
2120
  });
2761
2121
  }
@@ -2773,7 +2133,7 @@ var JobWorker = class {
2773
2133
  await this.db.transaction(async (tx) => {
2774
2134
  const threshold = new Date(Date.now() - this.staleThresholdMs);
2775
2135
  const stale = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
2776
- and5(eq7(jobRuns.status, "running"), lt3(jobRuns.claimedAt, threshold))
2136
+ and5(eq5(jobRuns.status, "running"), lt3(jobRuns.claimedAt, threshold))
2777
2137
  ).for("update", { skipLocked: true });
2778
2138
  if (stale.length === 0) return;
2779
2139
  const ids = stale.map((r) => r.id);
@@ -2806,8 +2166,8 @@ var JobWorker = class {
2806
2166
  if (claimed.concurrencyKey) {
2807
2167
  const inflight = await this.db.select({ id: jobRuns.id }).from(jobRuns).where(
2808
2168
  and5(
2809
- eq7(jobRuns.concurrencyKey, claimed.concurrencyKey),
2810
- eq7(jobRuns.status, "running")
2169
+ eq5(jobRuns.concurrencyKey, claimed.concurrencyKey),
2170
+ eq5(jobRuns.status, "running")
2811
2171
  )
2812
2172
  );
2813
2173
  const other = inflight.find((r) => r.id !== claimed.id);
@@ -2817,7 +2177,7 @@ var JobWorker = class {
2817
2177
  claimedAt: null,
2818
2178
  startedAt: null,
2819
2179
  updatedAt: /* @__PURE__ */ new Date()
2820
- }).where(eq7(jobRuns.id, claimed.id));
2180
+ }).where(eq5(jobRuns.id, claimed.id));
2821
2181
  return;
2822
2182
  }
2823
2183
  }
@@ -2832,7 +2192,7 @@ var JobWorker = class {
2832
2192
  run: claimed,
2833
2193
  step: this.makeStepFn(claimed),
2834
2194
  spawnChild: this.makeSpawnFn(claimed),
2835
- logger: new Logger7(`JobRun:${claimed.id}`)
2195
+ logger: new Logger4(`JobRun:${claimed.id}`)
2836
2196
  };
2837
2197
  const attemptsBefore = claimed.attempts ?? 0;
2838
2198
  try {
@@ -2843,7 +2203,7 @@ var JobWorker = class {
2843
2203
  finishedAt: /* @__PURE__ */ new Date(),
2844
2204
  updatedAt: /* @__PURE__ */ new Date(),
2845
2205
  attempts: attemptsBefore + 1
2846
- }).where(eq7(jobRuns.id, claimed.id));
2206
+ }).where(eq5(jobRuns.id, claimed.id));
2847
2207
  } catch (err) {
2848
2208
  const policy = meta.retry;
2849
2209
  const decision = classifyError(err, policy, attemptsBefore);
@@ -2856,9 +2216,9 @@ var JobWorker = class {
2856
2216
  runAt: new Date(Date.now() + delay),
2857
2217
  startedAt: null,
2858
2218
  claimedAt: null,
2859
- error: serialiseError2(err, nextAttempts, true),
2219
+ error: serialiseError(err, nextAttempts, true),
2860
2220
  updatedAt: /* @__PURE__ */ new Date()
2861
- }).where(eq7(jobRuns.id, claimed.id));
2221
+ }).where(eq5(jobRuns.id, claimed.id));
2862
2222
  } else {
2863
2223
  await this.markFailed(claimed, err, nextAttempts);
2864
2224
  }
@@ -2869,9 +2229,9 @@ var JobWorker = class {
2869
2229
  status: "failed",
2870
2230
  attempts: finalAttempts,
2871
2231
  finishedAt: /* @__PURE__ */ new Date(),
2872
- error: serialiseError2(err, finalAttempts, false),
2232
+ error: serialiseError(err, finalAttempts, false),
2873
2233
  updatedAt: /* @__PURE__ */ new Date()
2874
- }).where(eq7(jobRuns.id, claimed.id));
2234
+ }).where(eq5(jobRuns.id, claimed.id));
2875
2235
  if (claimed.parentClosePolicy === "terminate") {
2876
2236
  try {
2877
2237
  await this.orchestrator.cancel(claimed.id, {
@@ -2928,7 +2288,7 @@ var JobWorker = class {
2928
2288
  kind: "task",
2929
2289
  seq,
2930
2290
  status: "failed",
2931
- error: serialiseError2(err, nextAttempts, false),
2291
+ error: serialiseError(err, nextAttempts, false),
2932
2292
  finishedAt: /* @__PURE__ */ new Date(),
2933
2293
  attempts: nextAttempts
2934
2294
  });
@@ -2974,12 +2334,12 @@ var JobWorker = class {
2974
2334
  // ============================================================================
2975
2335
  };
2976
2336
  JobWorker = __decorateClass([
2977
- Injectable9(),
2978
- __decorateParam(0, Inject9(DRIZZLE)),
2979
- __decorateParam(1, Inject9(JOB_ORCHESTRATOR)),
2980
- __decorateParam(2, Inject9(JOB_RUN_SERVICE)),
2981
- __decorateParam(3, Inject9(JOB_STEP_SERVICE)),
2982
- __decorateParam(4, Inject9(JOB_WORKER_OPTIONS))
2337
+ Injectable7(),
2338
+ __decorateParam(0, Inject7(DRIZZLE)),
2339
+ __decorateParam(1, Inject7(JOB_ORCHESTRATOR)),
2340
+ __decorateParam(2, Inject7(JOB_RUN_SERVICE)),
2341
+ __decorateParam(3, Inject7(JOB_STEP_SERVICE)),
2342
+ __decorateParam(4, Inject7(JOB_WORKER_OPTIONS))
2983
2343
  ], JobWorker);
2984
2344
 
2985
2345
  // runtime/subsystems/jobs/memory-job-store.ts
@@ -3000,7 +2360,7 @@ var MemoryJobStore = class {
3000
2360
 
3001
2361
  // runtime/subsystems/jobs/job-orchestrator.memory-backend.ts
3002
2362
  import { randomUUID as randomUUID3 } from "crypto";
3003
- import { Inject as Inject10, Injectable as Injectable10, Logger as Logger8, Optional as Optional4 } from "@nestjs/common";
2363
+ import { Inject as Inject8, Injectable as Injectable8, Logger as Logger5, Optional as Optional3 } from "@nestjs/common";
3004
2364
  var QUEUED_RUN_AT = /* @__PURE__ */ new Date(864e13);
3005
2365
  var TERMINAL_STATUSES2 = [
3006
2366
  "completed",
@@ -3047,7 +2407,7 @@ var MemoryJobOrchestrator = class {
3047
2407
  stepService;
3048
2408
  multiTenant;
3049
2409
  moduleRef;
3050
- logger = new Logger8(MemoryJobOrchestrator.name);
2410
+ logger = new Logger5(MemoryJobOrchestrator.name);
3051
2411
  mutex = new PromiseMutex();
3052
2412
  handlerRegistry = /* @__PURE__ */ new Map();
3053
2413
  /**
@@ -3396,7 +2756,7 @@ var MemoryJobOrchestrator = class {
3396
2756
  run,
3397
2757
  step: this.makeStepFn(run),
3398
2758
  spawnChild: this.makeSpawnFn(run),
3399
- logger: new Logger8(`JobRun:${run.id}`)
2759
+ logger: new Logger5(`JobRun:${run.id}`)
3400
2760
  };
3401
2761
  const attemptsBefore = run.attempts ?? 0;
3402
2762
  try {
@@ -3453,7 +2813,7 @@ var MemoryJobOrchestrator = class {
3453
2813
  kind: "task",
3454
2814
  seq,
3455
2815
  status: "failed",
3456
- error: serialiseError3(err, nextAttempts, false),
2816
+ error: serialiseError2(err, nextAttempts, false),
3457
2817
  finishedAt: /* @__PURE__ */ new Date(),
3458
2818
  attempts: nextAttempts
3459
2819
  });
@@ -3508,7 +2868,7 @@ var MemoryJobOrchestrator = class {
3508
2868
  finishedAt: now,
3509
2869
  updatedAt: now,
3510
2870
  attempts,
3511
- error: serialiseError3(err, attempts, false)
2871
+ error: serialiseError2(err, attempts, false)
3512
2872
  });
3513
2873
  this.unblockQueuedDependents(run.id);
3514
2874
  });
@@ -3539,7 +2899,7 @@ var MemoryJobOrchestrator = class {
3539
2899
  startedAt: null,
3540
2900
  claimedAt: null,
3541
2901
  updatedAt: now,
3542
- error: serialiseError3(err, attempts, true)
2902
+ error: serialiseError2(err, attempts, true)
3543
2903
  });
3544
2904
  });
3545
2905
  }
@@ -3569,9 +2929,9 @@ var MemoryJobOrchestrator = class {
3569
2929
  }
3570
2930
  };
3571
2931
  MemoryJobOrchestrator = __decorateClass([
3572
- Injectable10(),
3573
- __decorateParam(2, Inject10(JOBS_MULTI_TENANT)),
3574
- __decorateParam(3, Optional4())
2932
+ Injectable8(),
2933
+ __decorateParam(2, Inject8(JOBS_MULTI_TENANT)),
2934
+ __decorateParam(3, Optional3())
3575
2935
  ], MemoryJobOrchestrator);
3576
2936
  function classifyError2(err, policy, currentAttempts) {
3577
2937
  if (!policy) return "fail";
@@ -3594,7 +2954,7 @@ function computeBackoff2(policy, attempts) {
3594
2954
  }
3595
2955
  return raw;
3596
2956
  }
3597
- function serialiseError3(err, attempt, retryable) {
2957
+ function serialiseError2(err, attempt, retryable) {
3598
2958
  const e = err;
3599
2959
  return {
3600
2960
  message: e?.message ?? String(err),
@@ -3605,7 +2965,7 @@ function serialiseError3(err, attempt, retryable) {
3605
2965
  }
3606
2966
 
3607
2967
  // runtime/subsystems/jobs/job-run-service.memory-backend.ts
3608
- import { Inject as Inject11, Injectable as Injectable11 } from "@nestjs/common";
2968
+ import { Inject as Inject9, Injectable as Injectable9 } from "@nestjs/common";
3609
2969
  var NON_TERMINAL_STATUSES2 = [
3610
2970
  "pending",
3611
2971
  "running",
@@ -3772,9 +3132,9 @@ var MemoryJobRunService = class {
3772
3132
  }
3773
3133
  };
3774
3134
  MemoryJobRunService = __decorateClass([
3775
- Injectable11(),
3776
- __decorateParam(1, Inject11(JOB_ORCHESTRATOR)),
3777
- __decorateParam(2, Inject11(JOBS_MULTI_TENANT))
3135
+ Injectable9(),
3136
+ __decorateParam(1, Inject9(JOB_ORCHESTRATOR)),
3137
+ __decorateParam(2, Inject9(JOBS_MULTI_TENANT))
3778
3138
  ], MemoryJobRunService);
3779
3139
  function compareBy(a, b, order) {
3780
3140
  switch (order) {
@@ -3792,7 +3152,7 @@ function compareBy(a, b, order) {
3792
3152
 
3793
3153
  // runtime/subsystems/jobs/job-step-service.memory-backend.ts
3794
3154
  import { randomUUID as randomUUID4 } from "crypto";
3795
- import { Injectable as Injectable12 } from "@nestjs/common";
3155
+ import { Injectable as Injectable10 } from "@nestjs/common";
3796
3156
  var MemoryJobStepService = class {
3797
3157
  constructor(store) {
3798
3158
  this.store = store;
@@ -3885,7 +3245,7 @@ var MemoryJobStepService = class {
3885
3245
  }
3886
3246
  };
3887
3247
  MemoryJobStepService = __decorateClass([
3888
- Injectable12()
3248
+ Injectable10()
3889
3249
  ], MemoryJobStepService);
3890
3250
 
3891
3251
  // runtime/subsystems/jobs/jobs-domain.module.ts
@@ -3914,7 +3274,20 @@ var JobsDomainModule = class {
3914
3274
  const resolved = resolveBullMqConfig(opts.extensions?.bullmq);
3915
3275
  providers.push({ provide: BULLMQ_CONNECTION, useValue: resolved.connection });
3916
3276
  providers.push({ provide: BULLMQ_RESOLVED_CONFIG, useValue: resolved });
3917
- providers.push({ provide: JOB_ORCHESTRATOR, useClass: BullMQJobOrchestrator });
3277
+ providers.push({
3278
+ provide: JOB_ORCHESTRATOR,
3279
+ useFactory: async (...args) => {
3280
+ const specifier = "./job-orchestrator.bullmq-backend";
3281
+ const mod = await import(specifier);
3282
+ return new mod.BullMQJobOrchestrator(...args);
3283
+ },
3284
+ // The bullmq orchestrator constructor mirrors DrizzleJobOrchestrator's
3285
+ // injection list: DRIZZLE + JOBS_MULTI_TENANT + the resolved BullMQ
3286
+ // tokens. Importing token references would force a static dep on the
3287
+ // tokens file in this module's import graph; using the existing
3288
+ // symbols already in scope is sufficient.
3289
+ inject: [DRIZZLE, JOBS_MULTI_TENANT, BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG]
3290
+ });
3918
3291
  providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });
3919
3292
  providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });
3920
3293
  } else {
@@ -3945,11 +3318,11 @@ JobsDomainModule = __decorateClass([
3945
3318
 
3946
3319
  // runtime/subsystems/jobs/job-worker.module.ts
3947
3320
  import {
3948
- Inject as Inject12,
3949
- Injectable as Injectable13,
3950
- Logger as Logger9,
3321
+ Inject as Inject10,
3322
+ Injectable as Injectable11,
3323
+ Logger as Logger6,
3951
3324
  Module as Module3,
3952
- Optional as Optional5
3325
+ Optional as Optional4
3953
3326
  } from "@nestjs/common";
3954
3327
  var DEFAULT_SHUTDOWN_TIMEOUT_MS2 = 3e4;
3955
3328
  var JOB_WORKER_MODULE_OPTIONS = /* @__PURE__ */ Symbol("JOB_WORKER_MODULE_OPTIONS");
@@ -3972,7 +3345,7 @@ var JobWorkerOrchestrator = class {
3972
3345
  moduleRef;
3973
3346
  bullConnection;
3974
3347
  bullConfig;
3975
- logger = new Logger9(JobWorkerOrchestrator.name);
3348
+ logger = new Logger6(JobWorkerOrchestrator.name);
3976
3349
  workers = [];
3977
3350
  // ============================================================================
3978
3351
  // Lifecycle
@@ -4002,7 +3375,7 @@ var JobWorkerOrchestrator = class {
4002
3375
  concurrency: def.concurrency,
4003
3376
  shutdownTimeoutMs: this.options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS2
4004
3377
  };
4005
- const worker = this.options.workerFactory ? this.options.workerFactory(workerOptions) : backend === "bullmq" ? this.spawnBullMQWorker(poolName, def.queue, def.concurrency, poolConfig) : this.spawnWorker(workerOptions);
3378
+ const worker = this.options.workerFactory ? this.options.workerFactory(workerOptions) : backend === "bullmq" ? await this.spawnBullMQWorker(poolName, def.queue, def.concurrency, poolConfig) : this.spawnWorker(workerOptions);
4006
3379
  await worker.onModuleInit();
4007
3380
  this.workers.push(worker);
4008
3381
  this.logger.log(
@@ -4102,7 +3475,15 @@ var JobWorkerOrchestrator = class {
4102
3475
  * orchestrator's `dispatch` via `resolvePoolQueueName(pool, …)` so producer
4103
3476
  * and consumer agree.
4104
3477
  */
4105
- spawnBullMQWorker(pool, _queueAlias, concurrency, poolConfig) {
3478
+ /**
3479
+ * #6 — async + dynamic-import. The `job-worker.bullmq-backend.ts` file is
3480
+ * filtered out of the vendor set for drizzle/memory installs (no `bullmq`
3481
+ * peer dep needed). The non-literal import specifier makes TS treat the
3482
+ * module as `any` so the consumer's tsc never tries to resolve an absent
3483
+ * file. This method is only entered when `backend === 'bullmq'` — at which
3484
+ * point the file IS vendored.
3485
+ */
3486
+ async spawnBullMQWorker(pool, _queueAlias, concurrency, poolConfig) {
4106
3487
  if (!this.db) {
4107
3488
  throw new Error(
4108
3489
  `JobWorkerModule: BullMQ worker spawning requires the Drizzle client (no DRIZZLE provider available) \u2014 job_run remains the source of truth.`
@@ -4119,7 +3500,9 @@ var JobWorkerOrchestrator = class {
4119
3500
  );
4120
3501
  }
4121
3502
  const queueName = resolvePoolQueueName(pool, this.bullConfig, poolConfig);
4122
- return new BullMQJobWorker(
3503
+ const specifier = "./job-worker.bullmq-backend";
3504
+ const mod = await import(specifier);
3505
+ return new mod.BullMQJobWorker(
4123
3506
  this.db,
4124
3507
  this.orchestrator,
4125
3508
  this.stepService,
@@ -4134,17 +3517,17 @@ var JobWorkerOrchestrator = class {
4134
3517
  }
4135
3518
  };
4136
3519
  JobWorkerOrchestrator = __decorateClass([
4137
- Injectable13(),
4138
- __decorateParam(0, Inject12(JOB_ORCHESTRATOR)),
4139
- __decorateParam(1, Inject12(JOB_RUN_SERVICE)),
4140
- __decorateParam(2, Inject12(JOB_STEP_SERVICE)),
4141
- __decorateParam(3, Inject12(JOB_WORKER_MODULE_OPTIONS)),
4142
- __decorateParam(4, Optional5()),
4143
- __decorateParam(4, Inject12(DRIZZLE)),
4144
- __decorateParam(6, Optional5()),
4145
- __decorateParam(6, Inject12(BULLMQ_CONNECTION)),
4146
- __decorateParam(7, Optional5()),
4147
- __decorateParam(7, Inject12(BULLMQ_RESOLVED_CONFIG))
3520
+ Injectable11(),
3521
+ __decorateParam(0, Inject10(JOB_ORCHESTRATOR)),
3522
+ __decorateParam(1, Inject10(JOB_RUN_SERVICE)),
3523
+ __decorateParam(2, Inject10(JOB_STEP_SERVICE)),
3524
+ __decorateParam(3, Inject10(JOB_WORKER_MODULE_OPTIONS)),
3525
+ __decorateParam(4, Optional4()),
3526
+ __decorateParam(4, Inject10(DRIZZLE)),
3527
+ __decorateParam(6, Optional4()),
3528
+ __decorateParam(6, Inject10(BULLMQ_CONNECTION)),
3529
+ __decorateParam(7, Optional4()),
3530
+ __decorateParam(7, Inject10(BULLMQ_RESOLVED_CONFIG))
4148
3531
  ], JobWorkerOrchestrator);
4149
3532
  var JobWorkerModule = class {
4150
3533
  static forRoot(opts) {
@@ -4184,8 +3567,8 @@ var CACHE_DEFAULT_TTL = /* @__PURE__ */ Symbol("CACHE_DEFAULT_TTL");
4184
3567
  import { Module as Module4 } from "@nestjs/common";
4185
3568
 
4186
3569
  // runtime/subsystems/cache/cache.drizzle-backend.ts
4187
- import { Injectable as Injectable14, Inject as Inject13, Optional as Optional6 } from "@nestjs/common";
4188
- import { gt as gt2, or as or3, like, sql as sql7, eq as eq8 } from "drizzle-orm";
3570
+ import { Injectable as Injectable12, Inject as Inject11, Optional as Optional5 } from "@nestjs/common";
3571
+ import { gt as gt2, or as or3, like, sql as sql7, eq as eq6 } from "drizzle-orm";
4189
3572
 
4190
3573
  // runtime/subsystems/cache/cache.schema.ts
4191
3574
  import { pgTable as pgTable3, text as text3, jsonb as jsonb3, timestamp as timestamp3 } from "drizzle-orm/pg-core";
@@ -4246,7 +3629,7 @@ var DrizzleCacheService = class {
4246
3629
  });
4247
3630
  }
4248
3631
  async delete(key) {
4249
- await this.db.delete(cacheEntries).where(eq8(cacheEntries.key, key));
3632
+ await this.db.delete(cacheEntries).where(eq6(cacheEntries.key, key));
4250
3633
  }
4251
3634
  async invalidateByPrefix(prefix) {
4252
3635
  const escaped = prefix.replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -4288,14 +3671,14 @@ var DrizzleCacheService = class {
4288
3671
  }
4289
3672
  };
4290
3673
  DrizzleCacheService = __decorateClass([
4291
- Injectable14(),
4292
- __decorateParam(0, Inject13(DRIZZLE)),
4293
- __decorateParam(1, Optional6()),
4294
- __decorateParam(1, Inject13(CACHE_DEFAULT_TTL))
3674
+ Injectable12(),
3675
+ __decorateParam(0, Inject11(DRIZZLE)),
3676
+ __decorateParam(1, Optional5()),
3677
+ __decorateParam(1, Inject11(CACHE_DEFAULT_TTL))
4295
3678
  ], DrizzleCacheService);
4296
3679
 
4297
3680
  // runtime/subsystems/cache/cache.memory-backend.ts
4298
- import { Injectable as Injectable15, Inject as Inject14, Optional as Optional7 } from "@nestjs/common";
3681
+ import { Injectable as Injectable13, Inject as Inject12, Optional as Optional6 } from "@nestjs/common";
4299
3682
  var MemoryCacheService = class {
4300
3683
  constructor(defaultTtl = null) {
4301
3684
  this.defaultTtl = defaultTtl;
@@ -4369,9 +3752,9 @@ var MemoryCacheService = class {
4369
3752
  }
4370
3753
  };
4371
3754
  MemoryCacheService = __decorateClass([
4372
- Injectable15(),
4373
- __decorateParam(0, Optional7()),
4374
- __decorateParam(0, Inject14(CACHE_DEFAULT_TTL))
3755
+ Injectable13(),
3756
+ __decorateParam(0, Optional6()),
3757
+ __decorateParam(0, Inject12(CACHE_DEFAULT_TTL))
4375
3758
  ], MemoryCacheService);
4376
3759
 
4377
3760
  // runtime/subsystems/cache/cache.module.ts
@@ -4631,7 +4014,7 @@ var OBSERVABILITY_MODULE_OPTIONS = "OBSERVABILITY_MODULE_OPTIONS";
4631
4014
  import { Module as Module6 } from "@nestjs/common";
4632
4015
 
4633
4016
  // runtime/subsystems/observability/observability.service.ts
4634
- import { Inject as Inject15, Injectable as Injectable16, Optional as Optional8 } from "@nestjs/common";
4017
+ import { Inject as Inject13, Injectable as Injectable14, Optional as Optional7 } from "@nestjs/common";
4635
4018
 
4636
4019
  // runtime/subsystems/sync/sync.tokens.ts
4637
4020
  var SYNC_CURSOR_STORE = "SYNC_CURSOR_STORE";
@@ -4784,24 +4167,24 @@ __publicField(ObservabilityService, "EMPTY_EVENT_PAGE", {
4784
4167
  nextCursor: null
4785
4168
  });
4786
4169
  ObservabilityService = __decorateClass([
4787
- Injectable16(),
4788
- __decorateParam(0, Optional8()),
4789
- __decorateParam(0, Inject15(JOB_RUN_SERVICE)),
4790
- __decorateParam(1, Optional8()),
4791
- __decorateParam(1, Inject15(BRIDGE_DELIVERY_REPO)),
4792
- __decorateParam(2, Optional8()),
4793
- __decorateParam(2, Inject15(SYNC_RUN_RECORDER)),
4794
- __decorateParam(3, Optional8()),
4795
- __decorateParam(3, Inject15(SYNC_CURSOR_STORE)),
4796
- __decorateParam(4, Optional8()),
4797
- __decorateParam(4, Inject15(EVENT_READ_PORT))
4170
+ Injectable14(),
4171
+ __decorateParam(0, Optional7()),
4172
+ __decorateParam(0, Inject13(JOB_RUN_SERVICE)),
4173
+ __decorateParam(1, Optional7()),
4174
+ __decorateParam(1, Inject13(BRIDGE_DELIVERY_REPO)),
4175
+ __decorateParam(2, Optional7()),
4176
+ __decorateParam(2, Inject13(SYNC_RUN_RECORDER)),
4177
+ __decorateParam(3, Optional7()),
4178
+ __decorateParam(3, Inject13(SYNC_CURSOR_STORE)),
4179
+ __decorateParam(4, Optional7()),
4180
+ __decorateParam(4, Inject13(EVENT_READ_PORT))
4798
4181
  ], ObservabilityService);
4799
4182
 
4800
4183
  // runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts
4801
4184
  import {
4802
- Inject as Inject16,
4803
- Injectable as Injectable17,
4804
- Logger as Logger10
4185
+ Inject as Inject14,
4186
+ Injectable as Injectable15,
4187
+ Logger as Logger7
4805
4188
  } from "@nestjs/common";
4806
4189
  var BridgeMetricsReporter = class {
4807
4190
  constructor(observability, options) {
@@ -4809,7 +4192,7 @@ var BridgeMetricsReporter = class {
4809
4192
  this.config = options.reporters?.bridgeMetrics;
4810
4193
  }
4811
4194
  observability;
4812
- logger = new Logger10(BridgeMetricsReporter.name);
4195
+ logger = new Logger7(BridgeMetricsReporter.name);
4813
4196
  handle = null;
4814
4197
  config;
4815
4198
  onModuleInit() {
@@ -4860,9 +4243,9 @@ var BridgeMetricsReporter = class {
4860
4243
  }
4861
4244
  };
4862
4245
  BridgeMetricsReporter = __decorateClass([
4863
- Injectable17(),
4864
- __decorateParam(0, Inject16(OBSERVABILITY)),
4865
- __decorateParam(1, Inject16(OBSERVABILITY_MODULE_OPTIONS))
4246
+ Injectable15(),
4247
+ __decorateParam(0, Inject14(OBSERVABILITY)),
4248
+ __decorateParam(1, Inject14(OBSERVABILITY_MODULE_OPTIONS))
4866
4249
  ], BridgeMetricsReporter);
4867
4250
 
4868
4251
  // runtime/subsystems/observability/observability.module.ts
@@ -5156,7 +4539,7 @@ var MemoryOAuthStateStore = class {
5156
4539
 
5157
4540
  // runtime/subsystems/auth/backends/state-store.drizzle-backend.ts
5158
4541
  import { randomBytes as randomBytes3 } from "crypto";
5159
- import { eq as eq9 } from "drizzle-orm";
4542
+ import { eq as eq7 } from "drizzle-orm";
5160
4543
  var DrizzleOAuthStateStore = class {
5161
4544
  constructor(db, opts = {}) {
5162
4545
  this.db = db;
@@ -5180,7 +4563,7 @@ var DrizzleOAuthStateStore = class {
5180
4563
  return state;
5181
4564
  }
5182
4565
  async consume(state) {
5183
- const rows = await this.db.delete(authOAuthState).where(eq9(authOAuthState.state, state)).returning();
4566
+ const rows = await this.db.delete(authOAuthState).where(eq7(authOAuthState.state, state)).returning();
5184
4567
  const row = rows[0];
5185
4568
  if (!row) {
5186
4569
  throw new OAuthStateError(
@@ -5202,7 +4585,7 @@ var DrizzleOAuthStateStore = class {
5202
4585
  import {
5203
4586
  Controller,
5204
4587
  Get,
5205
- Inject as Inject17,
4588
+ Inject as Inject15,
5206
4589
  Param,
5207
4590
  Query,
5208
4591
  Req,
@@ -5304,11 +4687,11 @@ __decorateClass([
5304
4687
  ], AuthController.prototype, "callback", 1);
5305
4688
  AuthController = __decorateClass([
5306
4689
  Controller("auth"),
5307
- __decorateParam(0, Inject17(STRATEGY_REGISTRY)),
5308
- __decorateParam(1, Inject17(AUTH_USER_CONTEXT)),
5309
- __decorateParam(2, Inject17(OAUTH_STATE_STORE)),
5310
- __decorateParam(3, Inject17(AUTH_INTEGRATION_GRANT_SINK)),
5311
- __decorateParam(4, Inject17(AUTH_OPTIONS))
4690
+ __decorateParam(0, Inject15(STRATEGY_REGISTRY)),
4691
+ __decorateParam(1, Inject15(AUTH_USER_CONTEXT)),
4692
+ __decorateParam(2, Inject15(OAUTH_STATE_STORE)),
4693
+ __decorateParam(3, Inject15(AUTH_INTEGRATION_GRANT_SINK)),
4694
+ __decorateParam(4, Inject15(AUTH_OPTIONS))
5312
4695
  ], AuthController);
5313
4696
 
5314
4697
  // runtime/base-classes/tenant-context.ts