@pattern-stack/codegen 0.8.0 → 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.
- package/CHANGELOG.md +70 -0
- package/dist/runtime/subsystems/auth/controllers/auth.controller.d.ts +1 -0
- package/dist/runtime/subsystems/auth/index.d.ts +2 -0
- package/dist/runtime/subsystems/auth/index.js +55 -0
- package/dist/runtime/subsystems/auth/index.js.map +1 -1
- package/dist/runtime/subsystems/auth/middleware/requester-context.d.ts +81 -0
- package/dist/runtime/subsystems/auth/middleware/requester-context.js +60 -0
- package/dist/runtime/subsystems/auth/middleware/requester-context.js.map +1 -0
- package/dist/runtime/subsystems/auth/protocols/user-context.d.ts +18 -0
- package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge.module.d.ts +3 -0
- package/dist/runtime/subsystems/bridge/bridge.module.js +930 -275
- package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
- package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
- package/dist/runtime/subsystems/bridge/index.d.ts +3 -0
- package/dist/runtime/subsystems/bridge/index.js +837 -182
- package/dist/runtime/subsystems/bridge/index.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.d.ts +3 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +92 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.memory-backend.d.ts +3 -1
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js +99 -0
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-keyset-cursor.d.ts +32 -0
- package/dist/runtime/subsystems/events/event-keyset-cursor.js +38 -0
- package/dist/runtime/subsystems/events/event-keyset-cursor.js.map +1 -0
- package/dist/runtime/subsystems/events/event-read.protocol.d.ts +94 -0
- package/dist/runtime/subsystems/events/event-read.protocol.js +9 -0
- package/dist/runtime/subsystems/events/event-read.protocol.js.map +1 -0
- package/dist/runtime/subsystems/events/events.module.js +177 -3
- package/dist/runtime/subsystems/events/events.module.js.map +1 -1
- package/dist/runtime/subsystems/events/events.tokens.d.ts +16 -1
- package/dist/runtime/subsystems/events/events.tokens.js +2 -0
- package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
- package/dist/runtime/subsystems/events/index.d.ts +2 -1
- package/dist/runtime/subsystems/events/index.js +178 -3
- package/dist/runtime/subsystems/events/index.js.map +1 -1
- package/dist/runtime/subsystems/index.d.ts +2 -0
- package/dist/runtime/subsystems/index.js +1198 -264
- package/dist/runtime/subsystems/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +98 -0
- package/dist/runtime/subsystems/jobs/bullmq.config.js +143 -0
- package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -0
- package/dist/runtime/subsystems/jobs/index.d.ts +6 -2
- package/dist/runtime/subsystems/jobs/index.js +861 -201
- package/dist/runtime/subsystems/jobs/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.d.ts +107 -0
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +922 -0
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.d.ts +52 -0
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js +57 -0
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.d.ts +2 -1
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +81 -1
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.d.ts +2 -1
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +81 -0
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.protocol.d.ts +74 -1
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.d.ts +48 -0
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +374 -0
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +42 -4
- package/dist/runtime/subsystems/jobs/job-worker.module.js +832 -178
- package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +10 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +519 -20
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/pool-config.loader.d.ts +9 -1
- package/dist/runtime/subsystems/jobs/pool-config.loader.js +4 -0
- package/dist/runtime/subsystems/jobs/pool-config.loader.js.map +1 -1
- package/dist/runtime/subsystems/observability/index.d.ts +4 -3
- package/dist/runtime/subsystems/observability/index.js +109 -2
- package/dist/runtime/subsystems/observability/index.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.module.js +109 -2
- package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.protocol.d.ts +63 -2
- package/dist/runtime/subsystems/observability/observability.service.d.ts +21 -3
- package/dist/runtime/subsystems/observability/observability.service.js +109 -2
- package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
- package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +1 -0
- package/dist/runtime/subsystems/observability/reporters/index.d.ts +1 -0
- package/dist/src/cli/index.js +43 -7
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/runtime/subsystems/auth/index.ts +8 -0
- package/runtime/subsystems/auth/middleware/requester-context.ts +141 -0
- package/runtime/subsystems/auth/protocols/user-context.ts +17 -0
- package/runtime/subsystems/bridge/bridge.module.ts +5 -0
- package/runtime/subsystems/events/event-bus.drizzle-backend.ts +109 -3
- package/runtime/subsystems/events/event-bus.memory-backend.ts +103 -1
- package/runtime/subsystems/events/event-keyset-cursor.ts +59 -0
- package/runtime/subsystems/events/event-read.protocol.ts +97 -0
- package/runtime/subsystems/events/events.module.ts +18 -2
- package/runtime/subsystems/events/events.tokens.ts +16 -0
- package/runtime/subsystems/events/index.ts +7 -0
- package/runtime/subsystems/jobs/bullmq.config.ts +125 -0
- package/runtime/subsystems/jobs/index.ts +22 -0
- package/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts +381 -0
- package/runtime/subsystems/jobs/job-run-keyset-cursor.ts +88 -0
- package/runtime/subsystems/jobs/job-run-service.drizzle-backend.ts +59 -1
- package/runtime/subsystems/jobs/job-run-service.memory-backend.ts +53 -0
- package/runtime/subsystems/jobs/job-run-service.protocol.ts +77 -0
- package/runtime/subsystems/jobs/job-worker.bullmq-backend.ts +311 -0
- package/runtime/subsystems/jobs/job-worker.module.ts +124 -10
- package/runtime/subsystems/jobs/jobs-domain.module.ts +40 -21
- package/runtime/subsystems/jobs/pool-config.loader.ts +11 -0
- package/runtime/subsystems/observability/index.ts +8 -0
- package/runtime/subsystems/observability/observability.protocol.ts +76 -0
- package/runtime/subsystems/observability/observability.service.ts +148 -1
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +14 -12
- package/templates/relationship/new/prompt.js +8 -5
- package/templates/subsystem/jobs/worker.ejs.t +30 -7
- package/templates/subsystem/sync/sync-audit.schema.ejs.t +12 -16
|
@@ -14,6 +14,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
14
14
|
|
|
15
15
|
// runtime/subsystems/events/events.tokens.ts
|
|
16
16
|
var EVENT_BUS = "EVENT_BUS";
|
|
17
|
+
var EVENT_READ_PORT = "EVENT_READ_PORT";
|
|
17
18
|
var TYPED_EVENT_BUS = "TYPED_EVENT_BUS";
|
|
18
19
|
var EVENTS_MULTI_TENANT = "EVENTS_MULTI_TENANT";
|
|
19
20
|
var REDIS_URL = /* @__PURE__ */ Symbol("REDIS_URL");
|
|
@@ -247,7 +248,38 @@ var DRIZZLE = "DRIZZLE";
|
|
|
247
248
|
|
|
248
249
|
// runtime/subsystems/events/event-bus.drizzle-backend.ts
|
|
249
250
|
import { Injectable as Injectable2, Inject as Inject2, Logger, Optional } from "@nestjs/common";
|
|
250
|
-
import { eq, and, inArray, asc } from "drizzle-orm";
|
|
251
|
+
import { eq, and, inArray, asc, desc, gte, lt, or, sql as sql2 } from "drizzle-orm";
|
|
252
|
+
|
|
253
|
+
// runtime/subsystems/events/event-keyset-cursor.ts
|
|
254
|
+
var DEFAULT_EVENT_LIST_LIMIT = 50;
|
|
255
|
+
var MAX_EVENT_LIST_LIMIT = 200;
|
|
256
|
+
function clampEventLimit(limit) {
|
|
257
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
258
|
+
return DEFAULT_EVENT_LIST_LIMIT;
|
|
259
|
+
}
|
|
260
|
+
const floored = Math.floor(limit);
|
|
261
|
+
if (floored < 1) return 1;
|
|
262
|
+
if (floored > MAX_EVENT_LIST_LIMIT) return MAX_EVENT_LIST_LIMIT;
|
|
263
|
+
return floored;
|
|
264
|
+
}
|
|
265
|
+
function encodeEventCursor(keyset) {
|
|
266
|
+
const tuple = [keyset.occurredAt.toISOString(), keyset.id];
|
|
267
|
+
return Buffer.from(JSON.stringify(tuple), "utf8").toString("base64url");
|
|
268
|
+
}
|
|
269
|
+
function decodeEventCursor(cursor) {
|
|
270
|
+
try {
|
|
271
|
+
const json = Buffer.from(cursor, "base64url").toString("utf8");
|
|
272
|
+
const parsed = JSON.parse(json);
|
|
273
|
+
if (!Array.isArray(parsed) || parsed.length !== 2) return null;
|
|
274
|
+
const [iso, id] = parsed;
|
|
275
|
+
if (typeof iso !== "string" || typeof id !== "string") return null;
|
|
276
|
+
const occurredAt = new Date(iso);
|
|
277
|
+
if (Number.isNaN(occurredAt.getTime())) return null;
|
|
278
|
+
return { occurredAt, id };
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
251
283
|
|
|
252
284
|
// runtime/subsystems/events/domain-events.schema.ts
|
|
253
285
|
import {
|
|
@@ -352,6 +384,24 @@ function toInsertValues(event) {
|
|
|
352
384
|
tenantId
|
|
353
385
|
};
|
|
354
386
|
}
|
|
387
|
+
function toEventSummary(r) {
|
|
388
|
+
const metadata = r.metadata ?? void 0;
|
|
389
|
+
const rootRunId = metadata?.["rootRunId"];
|
|
390
|
+
return {
|
|
391
|
+
id: r.id,
|
|
392
|
+
type: r.type,
|
|
393
|
+
aggregateId: r.aggregateId,
|
|
394
|
+
aggregateType: r.aggregateType,
|
|
395
|
+
status: r.status,
|
|
396
|
+
pool: r.pool,
|
|
397
|
+
direction: r.direction,
|
|
398
|
+
tier: r.tier,
|
|
399
|
+
rootRunId: typeof rootRunId === "string" ? rootRunId : null,
|
|
400
|
+
tenantId: r.tenantId,
|
|
401
|
+
occurredAt: r.occurredAt instanceof Date ? r.occurredAt : new Date(r.occurredAt),
|
|
402
|
+
processedAt: r.processedAt == null ? null : r.processedAt instanceof Date ? r.processedAt : new Date(r.processedAt)
|
|
403
|
+
};
|
|
404
|
+
}
|
|
355
405
|
var DrizzleEventBus = class {
|
|
356
406
|
constructor(db, opts, bridgeHook = null) {
|
|
357
407
|
this.db = db;
|
|
@@ -417,6 +467,48 @@ var DrizzleEventBus = class {
|
|
|
417
467
|
};
|
|
418
468
|
}
|
|
419
469
|
// ============================================================================
|
|
470
|
+
// IEventReadPort (OBS-LIST-1)
|
|
471
|
+
// ============================================================================
|
|
472
|
+
async listEvents(query = {}) {
|
|
473
|
+
const limit = clampEventLimit(query.limit);
|
|
474
|
+
const conditions = [];
|
|
475
|
+
if (query.poolId) conditions.push(eq(domainEvents.pool, query.poolId));
|
|
476
|
+
if (query.direction)
|
|
477
|
+
conditions.push(eq(domainEvents.direction, query.direction));
|
|
478
|
+
if (query.since) conditions.push(gte(domainEvents.occurredAt, query.since));
|
|
479
|
+
if (query.rootRunId) {
|
|
480
|
+
conditions.push(
|
|
481
|
+
sql2`${domainEvents.metadata}->>'rootRunId' = ${query.rootRunId}`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
if (query.tenantId !== void 0) {
|
|
485
|
+
conditions.push(
|
|
486
|
+
query.tenantId === null ? sql2`${domainEvents.tenantId} is null` : eq(domainEvents.tenantId, query.tenantId)
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
if (query.cursor) {
|
|
490
|
+
const keyset = decodeEventCursor(query.cursor);
|
|
491
|
+
if (keyset) {
|
|
492
|
+
conditions.push(
|
|
493
|
+
or(
|
|
494
|
+
lt(domainEvents.occurredAt, keyset.occurredAt),
|
|
495
|
+
and(
|
|
496
|
+
eq(domainEvents.occurredAt, keyset.occurredAt),
|
|
497
|
+
lt(domainEvents.id, keyset.id)
|
|
498
|
+
)
|
|
499
|
+
)
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const rows = await this.db.select().from(domainEvents).where(conditions.length > 0 ? and(...conditions) : void 0).orderBy(desc(domainEvents.occurredAt), desc(domainEvents.id)).limit(limit + 1);
|
|
504
|
+
const hasMore = rows.length > limit;
|
|
505
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
506
|
+
const items = page.map(toEventSummary);
|
|
507
|
+
const last = page[page.length - 1];
|
|
508
|
+
const nextCursor = hasMore && last ? encodeEventCursor({ occurredAt: last.occurredAt, id: last.id }) : null;
|
|
509
|
+
return { items, nextCursor };
|
|
510
|
+
}
|
|
511
|
+
// ============================================================================
|
|
420
512
|
// Polling
|
|
421
513
|
// ============================================================================
|
|
422
514
|
/**
|
|
@@ -541,6 +633,27 @@ DrizzleEventBus = __decorateClass([
|
|
|
541
633
|
|
|
542
634
|
// runtime/subsystems/events/event-bus.memory-backend.ts
|
|
543
635
|
import { Inject as Inject3, Injectable as Injectable3, Logger as Logger2, Optional as Optional2 } from "@nestjs/common";
|
|
636
|
+
function toEventSummary2(event) {
|
|
637
|
+
const metadata = event.metadata;
|
|
638
|
+
const str = (key) => {
|
|
639
|
+
const v = metadata?.[key];
|
|
640
|
+
return typeof v === "string" ? v : null;
|
|
641
|
+
};
|
|
642
|
+
return {
|
|
643
|
+
id: event.id,
|
|
644
|
+
type: event.type,
|
|
645
|
+
aggregateId: event.aggregateId,
|
|
646
|
+
aggregateType: event.aggregateType,
|
|
647
|
+
status: "processed",
|
|
648
|
+
pool: str("pool"),
|
|
649
|
+
direction: str("direction"),
|
|
650
|
+
tier: str("tier") ?? "domain",
|
|
651
|
+
rootRunId: str("rootRunId"),
|
|
652
|
+
tenantId: str("tenantId"),
|
|
653
|
+
occurredAt: event.occurredAt,
|
|
654
|
+
processedAt: event.occurredAt
|
|
655
|
+
};
|
|
656
|
+
}
|
|
544
657
|
var MemoryEventBus = class {
|
|
545
658
|
logger = new Logger2(MemoryEventBus.name);
|
|
546
659
|
/** All events published since construction (or last clear). */
|
|
@@ -576,6 +689,53 @@ var MemoryEventBus = class {
|
|
|
576
689
|
set.delete(h);
|
|
577
690
|
};
|
|
578
691
|
}
|
|
692
|
+
// ============================================================================
|
|
693
|
+
// IEventReadPort (OBS-LIST-1)
|
|
694
|
+
// ============================================================================
|
|
695
|
+
async listEvents(query = {}) {
|
|
696
|
+
const limit = clampEventLimit(query.limit);
|
|
697
|
+
const keyset = query.cursor ? decodeEventCursor(query.cursor) : null;
|
|
698
|
+
const str = (e, key) => {
|
|
699
|
+
const v = e.metadata?.[key];
|
|
700
|
+
return typeof v === "string" ? v : null;
|
|
701
|
+
};
|
|
702
|
+
const matched = this.publishedEvents.filter((e) => {
|
|
703
|
+
if (query.poolId && str(e, "pool") !== query.poolId) return false;
|
|
704
|
+
if (query.direction && str(e, "direction") !== query.direction)
|
|
705
|
+
return false;
|
|
706
|
+
if (query.rootRunId && str(e, "rootRunId") !== query.rootRunId)
|
|
707
|
+
return false;
|
|
708
|
+
if (query.since && e.occurredAt.getTime() < query.since.getTime())
|
|
709
|
+
return false;
|
|
710
|
+
if (query.tenantId !== void 0) {
|
|
711
|
+
const t = str(e, "tenantId");
|
|
712
|
+
if (query.tenantId === null) {
|
|
713
|
+
if (t !== null) return false;
|
|
714
|
+
} else if (t !== query.tenantId) {
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return true;
|
|
719
|
+
});
|
|
720
|
+
matched.sort((a, b) => {
|
|
721
|
+
const dt = b.occurredAt.getTime() - a.occurredAt.getTime();
|
|
722
|
+
if (dt !== 0) return dt;
|
|
723
|
+
return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
|
|
724
|
+
});
|
|
725
|
+
const seeked = keyset ? matched.filter((e) => {
|
|
726
|
+
const ct = e.occurredAt.getTime();
|
|
727
|
+
const kt = keyset.occurredAt.getTime();
|
|
728
|
+
if (ct < kt) return true;
|
|
729
|
+
if (ct > kt) return false;
|
|
730
|
+
return e.id < keyset.id;
|
|
731
|
+
}) : matched;
|
|
732
|
+
const hasMore = seeked.length > limit;
|
|
733
|
+
const page = hasMore ? seeked.slice(0, limit) : seeked;
|
|
734
|
+
const items = page.map(toEventSummary2);
|
|
735
|
+
const last = page[page.length - 1];
|
|
736
|
+
const nextCursor = hasMore && last ? encodeEventCursor({ occurredAt: last.occurredAt, id: last.id }) : null;
|
|
737
|
+
return { items, nextCursor };
|
|
738
|
+
}
|
|
579
739
|
/** Remove all published events and subscriptions. Useful in beforeEach. */
|
|
580
740
|
clear() {
|
|
581
741
|
this.publishedEvents.length = 0;
|
|
@@ -933,10 +1093,20 @@ var EventsModule = class {
|
|
|
933
1093
|
REDIS_URL
|
|
934
1094
|
]
|
|
935
1095
|
},
|
|
1096
|
+
{
|
|
1097
|
+
// Read port (OBS-LIST-1). Drizzle + memory backends implement
|
|
1098
|
+
// IEventReadPort on the EVENT_BUS instance; the redis backend
|
|
1099
|
+
// retains no history, so EVENT_READ_PORT resolves to `null` and
|
|
1100
|
+
// optional consumers (the observability combiner) degrade to
|
|
1101
|
+
// empty results.
|
|
1102
|
+
provide: EVENT_READ_PORT,
|
|
1103
|
+
useFactory: (options, bus) => options.backend === "redis" ? null : bus,
|
|
1104
|
+
inject: [EVENTS_MODULE_OPTIONS, EVENT_BUS]
|
|
1105
|
+
},
|
|
936
1106
|
TypedEventBus,
|
|
937
1107
|
{ provide: TYPED_EVENT_BUS, useExisting: TypedEventBus }
|
|
938
1108
|
],
|
|
939
|
-
exports: [EVENT_BUS, TYPED_EVENT_BUS, EVENTS_MULTI_TENANT]
|
|
1109
|
+
exports: [EVENT_BUS, EVENT_READ_PORT, TYPED_EVENT_BUS, EVENTS_MULTI_TENANT]
|
|
940
1110
|
};
|
|
941
1111
|
}
|
|
942
1112
|
static forRoot(options = { backend: "drizzle" }) {
|
|
@@ -964,9 +1134,13 @@ var EventsModule = class {
|
|
|
964
1134
|
providers: [
|
|
965
1135
|
{ provide: EVENTS_MODULE_OPTIONS, useValue: options },
|
|
966
1136
|
provider,
|
|
1137
|
+
// Read port (OBS-LIST-1): drizzle + memory backends implement
|
|
1138
|
+
// IEventReadPort on the same instance as EVENT_BUS. The redis
|
|
1139
|
+
// backend retains no history and does not provide this token.
|
|
1140
|
+
{ provide: EVENT_READ_PORT, useExisting: EVENT_BUS },
|
|
967
1141
|
...buildTypedBusProviders(multiTenant)
|
|
968
1142
|
],
|
|
969
|
-
exports: [EVENT_BUS, TYPED_EVENT_BUS, EVENTS_MULTI_TENANT]
|
|
1143
|
+
exports: [EVENT_BUS, EVENT_READ_PORT, TYPED_EVENT_BUS, EVENTS_MULTI_TENANT]
|
|
970
1144
|
};
|
|
971
1145
|
}
|
|
972
1146
|
};
|
|
@@ -986,7 +1160,7 @@ import {
|
|
|
986
1160
|
index as index2,
|
|
987
1161
|
uniqueIndex
|
|
988
1162
|
} from "drizzle-orm/pg-core";
|
|
989
|
-
import { sql as
|
|
1163
|
+
import { sql as sql3 } from "drizzle-orm";
|
|
990
1164
|
var jobRunStatusEnum = pgEnum("job_run_status", [
|
|
991
1165
|
"pending",
|
|
992
1166
|
"running",
|
|
@@ -1091,10 +1265,10 @@ var jobRuns = pgTable2(
|
|
|
1091
1265
|
/** listForScope query. */
|
|
1092
1266
|
idxJobRunScope: index2("idx_job_run_scope").on(t.scopeEntityType, t.scopeEntityId),
|
|
1093
1267
|
/** Idempotency collapse — partial index. */
|
|
1094
|
-
idxJobRunDedupe: index2("idx_job_run_dedupe").on(t.jobType, t.dedupeKey).where(
|
|
1268
|
+
idxJobRunDedupe: index2("idx_job_run_dedupe").on(t.jobType, t.dedupeKey).where(sql3`${t.dedupeKey} IS NOT NULL`),
|
|
1095
1269
|
/** Collision check — partial index. */
|
|
1096
1270
|
idxJobRunConcurrency: index2("idx_job_run_concurrency").on(t.concurrencyKey).where(
|
|
1097
|
-
|
|
1271
|
+
sql3`${t.concurrencyKey} IS NOT NULL AND ${t.status} IN ('pending','running')`
|
|
1098
1272
|
)
|
|
1099
1273
|
})
|
|
1100
1274
|
);
|
|
@@ -1151,7 +1325,7 @@ var HandlerRegistry;
|
|
|
1151
1325
|
// runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts
|
|
1152
1326
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1153
1327
|
import { Inject as Inject5, Injectable as Injectable5, Logger as Logger4 } from "@nestjs/common";
|
|
1154
|
-
import { and as and2, desc, eq as eq2, gt, inArray as inArray2, isNotNull, ne, notInArray, sql as
|
|
1328
|
+
import { and as and2, desc as desc2, eq as eq2, gt, inArray as inArray2, isNotNull, ne, notInArray, sql as sql4 } from "drizzle-orm";
|
|
1155
1329
|
|
|
1156
1330
|
// runtime/subsystems/jobs/jobs-errors.ts
|
|
1157
1331
|
var JobTypeNotFoundError = class extends Error {
|
|
@@ -1292,7 +1466,7 @@ var DrizzleJobOrchestrator = class {
|
|
|
1292
1466
|
// status NOT IN ('canceled', 'failed')
|
|
1293
1467
|
notInStatus(DEDUPE_EXCLUDED_STATUSES)
|
|
1294
1468
|
)
|
|
1295
|
-
).orderBy(
|
|
1469
|
+
).orderBy(desc2(jobRuns.createdAt)).limit(1);
|
|
1296
1470
|
if (existing.length > 0) {
|
|
1297
1471
|
return existing[0];
|
|
1298
1472
|
}
|
|
@@ -1510,24 +1684,24 @@ var DrizzleJobOrchestrator = class {
|
|
|
1510
1684
|
}).onConflictDoUpdate({
|
|
1511
1685
|
target: jobs.type,
|
|
1512
1686
|
set: {
|
|
1513
|
-
pool:
|
|
1514
|
-
scopeEntityType:
|
|
1515
|
-
retryPolicy:
|
|
1516
|
-
timeoutMs:
|
|
1517
|
-
concurrencyKeyTemplate:
|
|
1518
|
-
collisionMode:
|
|
1519
|
-
dedupeKeyTemplate:
|
|
1520
|
-
dedupeWindowMs:
|
|
1521
|
-
priorityDefault:
|
|
1522
|
-
replayFrom:
|
|
1523
|
-
version:
|
|
1524
|
-
updatedAt:
|
|
1687
|
+
pool: sql4`EXCLUDED.pool`,
|
|
1688
|
+
scopeEntityType: sql4`EXCLUDED.scope_entity_type`,
|
|
1689
|
+
retryPolicy: sql4`EXCLUDED.retry_policy`,
|
|
1690
|
+
timeoutMs: sql4`EXCLUDED.timeout_ms`,
|
|
1691
|
+
concurrencyKeyTemplate: sql4`EXCLUDED.concurrency_key_template`,
|
|
1692
|
+
collisionMode: sql4`EXCLUDED.collision_mode`,
|
|
1693
|
+
dedupeKeyTemplate: sql4`EXCLUDED.dedupe_key_template`,
|
|
1694
|
+
dedupeWindowMs: sql4`EXCLUDED.dedupe_window_ms`,
|
|
1695
|
+
priorityDefault: sql4`EXCLUDED.priority_default`,
|
|
1696
|
+
replayFrom: sql4`EXCLUDED.replay_from`,
|
|
1697
|
+
version: sql4`${jobs.version} + 1`,
|
|
1698
|
+
updatedAt: sql4`now()`
|
|
1525
1699
|
},
|
|
1526
1700
|
// The hash gate: every field listed in the Q3 resolution appears
|
|
1527
1701
|
// here. `IS DISTINCT FROM` is the null-safe inequality operator;
|
|
1528
1702
|
// jsonb cast to text gives stable comparison without invoking a
|
|
1529
1703
|
// dedicated hash column (avoids a JOB-1 schema migration).
|
|
1530
|
-
setWhere:
|
|
1704
|
+
setWhere: sql4`
|
|
1531
1705
|
${jobs.pool} IS DISTINCT FROM EXCLUDED.pool OR
|
|
1532
1706
|
${jobs.retryPolicy}::text IS DISTINCT FROM EXCLUDED.retry_policy::text OR
|
|
1533
1707
|
${jobs.timeoutMs} IS DISTINCT FROM EXCLUDED.timeout_ms OR
|
|
@@ -1558,7 +1732,58 @@ function notInStatus(statuses) {
|
|
|
1558
1732
|
|
|
1559
1733
|
// runtime/subsystems/jobs/job-run-service.drizzle-backend.ts
|
|
1560
1734
|
import { Inject as Inject6, Injectable as Injectable6 } from "@nestjs/common";
|
|
1561
|
-
import { and as and3, asc as asc2, desc as
|
|
1735
|
+
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
|
+
|
|
1737
|
+
// runtime/subsystems/jobs/job-run-keyset-cursor.ts
|
|
1738
|
+
var DEFAULT_LIST_LIMIT = 50;
|
|
1739
|
+
var MAX_LIST_LIMIT = 200;
|
|
1740
|
+
function clampLimit(limit) {
|
|
1741
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
1742
|
+
return DEFAULT_LIST_LIMIT;
|
|
1743
|
+
}
|
|
1744
|
+
const floored = Math.floor(limit);
|
|
1745
|
+
if (floored < 1) return 1;
|
|
1746
|
+
if (floored > MAX_LIST_LIMIT) return MAX_LIST_LIMIT;
|
|
1747
|
+
return floored;
|
|
1748
|
+
}
|
|
1749
|
+
function encodeKeysetCursor(keyset) {
|
|
1750
|
+
const tuple = [keyset.createdAt.toISOString(), keyset.id];
|
|
1751
|
+
return Buffer.from(JSON.stringify(tuple), "utf8").toString("base64url");
|
|
1752
|
+
}
|
|
1753
|
+
function decodeKeysetCursor(cursor) {
|
|
1754
|
+
try {
|
|
1755
|
+
const json = Buffer.from(cursor, "base64url").toString("utf8");
|
|
1756
|
+
const parsed = JSON.parse(json);
|
|
1757
|
+
if (!Array.isArray(parsed) || parsed.length !== 2) return null;
|
|
1758
|
+
const [iso, id] = parsed;
|
|
1759
|
+
if (typeof iso !== "string" || typeof id !== "string") return null;
|
|
1760
|
+
const createdAt = new Date(iso);
|
|
1761
|
+
if (Number.isNaN(createdAt.getTime())) return null;
|
|
1762
|
+
return { createdAt, id };
|
|
1763
|
+
} catch {
|
|
1764
|
+
return null;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
function toJobRunSummary(r) {
|
|
1768
|
+
return {
|
|
1769
|
+
runId: r.id,
|
|
1770
|
+
rootRunId: r.rootRunId,
|
|
1771
|
+
jobType: r.jobType,
|
|
1772
|
+
pool: r.pool,
|
|
1773
|
+
status: r.status,
|
|
1774
|
+
scopeEntityType: r.scopeEntityType,
|
|
1775
|
+
scopeEntityId: r.scopeEntityId,
|
|
1776
|
+
tenantId: r.tenantId,
|
|
1777
|
+
attempts: r.attempts,
|
|
1778
|
+
errorMessage: r.error?.message ?? null,
|
|
1779
|
+
runAt: r.runAt,
|
|
1780
|
+
startedAt: r.startedAt,
|
|
1781
|
+
finishedAt: r.finishedAt,
|
|
1782
|
+
createdAt: r.createdAt
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
// runtime/subsystems/jobs/job-run-service.drizzle-backend.ts
|
|
1562
1787
|
var NON_TERMINAL_STATUSES = [
|
|
1563
1788
|
"pending",
|
|
1564
1789
|
"running",
|
|
@@ -1606,12 +1831,12 @@ var DrizzleJobRunService = class {
|
|
|
1606
1831
|
case "created_at asc":
|
|
1607
1832
|
return asc2(jobRuns.createdAt);
|
|
1608
1833
|
case "run_at desc":
|
|
1609
|
-
return
|
|
1834
|
+
return desc3(jobRuns.runAt);
|
|
1610
1835
|
case "run_at asc":
|
|
1611
1836
|
return asc2(jobRuns.runAt);
|
|
1612
1837
|
case "created_at desc":
|
|
1613
1838
|
default:
|
|
1614
|
-
return
|
|
1839
|
+
return desc3(jobRuns.createdAt);
|
|
1615
1840
|
}
|
|
1616
1841
|
})();
|
|
1617
1842
|
let q = this.db.select().from(jobRuns).where(and3(...conditions)).orderBy(orderCol).$dynamic();
|
|
@@ -1655,7 +1880,7 @@ var DrizzleJobRunService = class {
|
|
|
1655
1880
|
const rows = await this.db.select({
|
|
1656
1881
|
pool: jobRuns.pool,
|
|
1657
1882
|
status: jobRuns.status,
|
|
1658
|
-
count:
|
|
1883
|
+
count: sql5`count(*)::int`.as("count")
|
|
1659
1884
|
}).from(jobRuns).where(tenantCond ?? void 0).groupBy(jobRuns.pool, jobRuns.status);
|
|
1660
1885
|
return rows.map((r) => ({
|
|
1661
1886
|
pool: r.pool,
|
|
@@ -1667,7 +1892,7 @@ var DrizzleJobRunService = class {
|
|
|
1667
1892
|
const conditions = [eq3(jobRuns.status, "failed")];
|
|
1668
1893
|
const tenantCond = this.tenantCondition("listRecentFailed", tenantId);
|
|
1669
1894
|
if (tenantCond) conditions.push(tenantCond);
|
|
1670
|
-
const rows = await this.db.select().from(jobRuns).where(and3(...conditions)).orderBy(
|
|
1895
|
+
const rows = await this.db.select().from(jobRuns).where(and3(...conditions)).orderBy(desc3(jobRuns.finishedAt), desc3(jobRuns.updatedAt)).limit(limit);
|
|
1671
1896
|
return rows.map((r) => ({
|
|
1672
1897
|
runId: r.id,
|
|
1673
1898
|
jobType: r.jobType,
|
|
@@ -1681,6 +1906,37 @@ var DrizzleJobRunService = class {
|
|
|
1681
1906
|
createdAt: r.createdAt
|
|
1682
1907
|
}));
|
|
1683
1908
|
}
|
|
1909
|
+
async listJobRuns(query = {}) {
|
|
1910
|
+
const limit = clampLimit(query.limit);
|
|
1911
|
+
const conditions = [];
|
|
1912
|
+
const tenantCond = this.tenantCondition("listJobRuns", query.tenantId);
|
|
1913
|
+
if (tenantCond) conditions.push(tenantCond);
|
|
1914
|
+
if (query.poolId) conditions.push(eq3(jobRuns.pool, query.poolId));
|
|
1915
|
+
if (query.rootRunId) conditions.push(eq3(jobRuns.rootRunId, query.rootRunId));
|
|
1916
|
+
if (query.status) conditions.push(eq3(jobRuns.status, query.status));
|
|
1917
|
+
if (query.since) conditions.push(gte2(jobRuns.createdAt, query.since));
|
|
1918
|
+
if (query.cursor) {
|
|
1919
|
+
const keyset = decodeKeysetCursor(query.cursor);
|
|
1920
|
+
if (keyset) {
|
|
1921
|
+
conditions.push(
|
|
1922
|
+
or2(
|
|
1923
|
+
lt2(jobRuns.createdAt, keyset.createdAt),
|
|
1924
|
+
and3(
|
|
1925
|
+
eq3(jobRuns.createdAt, keyset.createdAt),
|
|
1926
|
+
lt2(jobRuns.id, keyset.id)
|
|
1927
|
+
)
|
|
1928
|
+
)
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
const rows = await this.db.select().from(jobRuns).where(conditions.length > 0 ? and3(...conditions) : void 0).orderBy(desc3(jobRuns.createdAt), desc3(jobRuns.id)).limit(limit + 1);
|
|
1933
|
+
const hasMore = rows.length > limit;
|
|
1934
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
1935
|
+
const items = page.map(toJobRunSummary);
|
|
1936
|
+
const last = page[page.length - 1];
|
|
1937
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id }) : null;
|
|
1938
|
+
return { items, nextCursor };
|
|
1939
|
+
}
|
|
1684
1940
|
/**
|
|
1685
1941
|
* Internal helper used by cascade paths (not on the public protocol).
|
|
1686
1942
|
* Exposed as a public method on the concrete class so infrastructure
|
|
@@ -1729,22 +1985,608 @@ var DrizzleJobStepService = class {
|
|
|
1729
1985
|
finishedAt: values.finishedAt,
|
|
1730
1986
|
attempts: values.attempts
|
|
1731
1987
|
}
|
|
1732
|
-
}).returning();
|
|
1733
|
-
return row;
|
|
1988
|
+
}).returning();
|
|
1989
|
+
return row;
|
|
1990
|
+
}
|
|
1991
|
+
async findStep(runId, stepId) {
|
|
1992
|
+
const [row] = await this.db.select().from(jobSteps).where(and4(eq4(jobSteps.jobRunId, runId), eq4(jobSteps.stepId, stepId))).limit(1);
|
|
1993
|
+
return row ?? null;
|
|
1994
|
+
}
|
|
1995
|
+
};
|
|
1996
|
+
DrizzleJobStepService = __decorateClass([
|
|
1997
|
+
Injectable7(),
|
|
1998
|
+
__decorateParam(0, Inject7(DRIZZLE))
|
|
1999
|
+
], DrizzleJobStepService);
|
|
2000
|
+
|
|
2001
|
+
// runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts
|
|
2002
|
+
import { createHash } from "crypto";
|
|
2003
|
+
import { Inject as Inject8, Injectable as Injectable8, Logger as Logger5, Optional as Optional3 } from "@nestjs/common";
|
|
2004
|
+
import { eq as eq5 } from "drizzle-orm";
|
|
2005
|
+
|
|
2006
|
+
// runtime/subsystems/jobs/pool-config.loader.ts
|
|
2007
|
+
import { existsSync, readFileSync } from "fs";
|
|
2008
|
+
import { resolve } from "path";
|
|
2009
|
+
import { parse as parseYaml } from "yaml";
|
|
2010
|
+
var FRAMEWORK_POOLS = Object.freeze({
|
|
2011
|
+
events_inbound: Object.freeze({
|
|
2012
|
+
queue: "jobs-events-inbound",
|
|
2013
|
+
concurrency: 20,
|
|
2014
|
+
reserved: true,
|
|
2015
|
+
description: "Inbound events drain (events subsystem outbox)."
|
|
2016
|
+
}),
|
|
2017
|
+
events_change: Object.freeze({
|
|
2018
|
+
queue: "jobs-events-change",
|
|
2019
|
+
concurrency: 30,
|
|
2020
|
+
reserved: true,
|
|
2021
|
+
description: "Change events drain (events subsystem outbox)."
|
|
2022
|
+
}),
|
|
2023
|
+
events_outbound: Object.freeze({
|
|
2024
|
+
queue: "jobs-events-outbound",
|
|
2025
|
+
concurrency: 10,
|
|
2026
|
+
reserved: true,
|
|
2027
|
+
description: "Outbound events drain (events subsystem outbox)."
|
|
2028
|
+
}),
|
|
2029
|
+
interactive: Object.freeze({
|
|
2030
|
+
queue: "jobs-interactive",
|
|
2031
|
+
concurrency: 20,
|
|
2032
|
+
reserved: false,
|
|
2033
|
+
description: "User-facing latency-sensitive jobs."
|
|
2034
|
+
}),
|
|
2035
|
+
batch: Object.freeze({
|
|
2036
|
+
queue: "jobs-batch",
|
|
2037
|
+
concurrency: 5,
|
|
2038
|
+
reserved: false,
|
|
2039
|
+
description: "Default pool for background jobs."
|
|
2040
|
+
})
|
|
2041
|
+
});
|
|
2042
|
+
var RESERVED_POOL_NAMES = new Set(
|
|
2043
|
+
Object.entries(FRAMEWORK_POOLS).filter(([, def]) => def.reserved).map(([name]) => name)
|
|
2044
|
+
);
|
|
2045
|
+
var cache = /* @__PURE__ */ new Map();
|
|
2046
|
+
function loadPoolConfig(configPath) {
|
|
2047
|
+
const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);
|
|
2048
|
+
const cached = cache.get(resolved);
|
|
2049
|
+
if (cached) return cached;
|
|
2050
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2051
|
+
for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {
|
|
2052
|
+
merged.set(name, { ...def });
|
|
2053
|
+
}
|
|
2054
|
+
if (!existsSync(resolved)) {
|
|
2055
|
+
cache.set(resolved, merged);
|
|
2056
|
+
return merged;
|
|
2057
|
+
}
|
|
2058
|
+
let raw;
|
|
2059
|
+
try {
|
|
2060
|
+
raw = parseYaml(readFileSync(resolved, "utf8"));
|
|
2061
|
+
} catch (err) {
|
|
2062
|
+
throw new Error(
|
|
2063
|
+
`pool-config.loader: failed to parse YAML at ${resolved}: ${err.message}`
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
const userPools = extractUserPools(raw);
|
|
2067
|
+
for (const [name, userDef] of Object.entries(userPools)) {
|
|
2068
|
+
const existing = merged.get(name);
|
|
2069
|
+
if (existing) {
|
|
2070
|
+
const next = {
|
|
2071
|
+
queue: existing.queue,
|
|
2072
|
+
concurrency: typeof userDef.concurrency === "number" ? userDef.concurrency : existing.concurrency,
|
|
2073
|
+
reserved: existing.reserved,
|
|
2074
|
+
description: userDef.description ?? existing.description
|
|
2075
|
+
};
|
|
2076
|
+
merged.set(name, next);
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
2079
|
+
if (typeof userDef.queue !== "string" || userDef.queue.length === 0) {
|
|
2080
|
+
throw new Error(
|
|
2081
|
+
`pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
if (typeof userDef.concurrency !== "number" || userDef.concurrency <= 0) {
|
|
2085
|
+
throw new Error(
|
|
2086
|
+
`pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
if (userDef.reserved === true) {
|
|
2090
|
+
throw new Error(
|
|
2091
|
+
`pool-config.loader: user-defined pool '${name}' cannot set 'reserved: true' \u2014 reserved is framework-only.`
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
merged.set(name, {
|
|
2095
|
+
queue: userDef.queue,
|
|
2096
|
+
concurrency: userDef.concurrency,
|
|
2097
|
+
reserved: false,
|
|
2098
|
+
description: userDef.description
|
|
2099
|
+
});
|
|
2100
|
+
}
|
|
2101
|
+
cache.set(resolved, merged);
|
|
2102
|
+
return merged;
|
|
2103
|
+
}
|
|
2104
|
+
function allNonReservedPoolNames(config) {
|
|
2105
|
+
const out = [];
|
|
2106
|
+
for (const [name, def] of config) {
|
|
2107
|
+
if (!def.reserved) out.push(name);
|
|
2108
|
+
}
|
|
2109
|
+
return out;
|
|
2110
|
+
}
|
|
2111
|
+
function allPoolNames(config) {
|
|
2112
|
+
return [...config.keys()];
|
|
2113
|
+
}
|
|
2114
|
+
function extractUserPools(raw) {
|
|
2115
|
+
if (!raw || typeof raw !== "object") return {};
|
|
2116
|
+
const jobs2 = raw.jobs;
|
|
2117
|
+
if (!jobs2 || typeof jobs2 !== "object") return {};
|
|
2118
|
+
const pools = jobs2.pools;
|
|
2119
|
+
if (!pools || typeof pools !== "object") return {};
|
|
2120
|
+
const out = {};
|
|
2121
|
+
for (const [name, def] of Object.entries(pools)) {
|
|
2122
|
+
if (!def || typeof def !== "object") continue;
|
|
2123
|
+
out[name] = def;
|
|
2124
|
+
}
|
|
2125
|
+
return out;
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
// runtime/subsystems/jobs/bullmq.config.ts
|
|
2129
|
+
var BULLMQ_CONNECTION = /* @__PURE__ */ Symbol("BULLMQ_CONNECTION");
|
|
2130
|
+
var BULLMQ_RESOLVED_CONFIG = /* @__PURE__ */ Symbol("BULLMQ_RESOLVED_CONFIG");
|
|
2131
|
+
var DEFAULT_REDIS_URL = "redis://localhost:6379";
|
|
2132
|
+
var DEFAULT_BULL_BOARD_MOUNT = "/admin/queues";
|
|
2133
|
+
function resolveBullMqConfig(ext) {
|
|
2134
|
+
const url = ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;
|
|
2135
|
+
const resolved = {
|
|
2136
|
+
connection: { url },
|
|
2137
|
+
queuePrefix: ext?.queue_prefix
|
|
2138
|
+
};
|
|
2139
|
+
if (ext?.bull_board?.enabled) {
|
|
2140
|
+
resolved.bullBoard = {
|
|
2141
|
+
enabled: true,
|
|
2142
|
+
mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
return resolved;
|
|
2146
|
+
}
|
|
2147
|
+
function resolvePoolQueueName(pool, config, poolConfig = loadPoolConfig()) {
|
|
2148
|
+
const alias = poolConfig.get(pool)?.queue ?? pool;
|
|
2149
|
+
const prefix = config?.queuePrefix;
|
|
2150
|
+
return prefix ? `${prefix}:${alias}` : alias;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
// runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts
|
|
2154
|
+
function sha1JobId(idempotencyKey) {
|
|
2155
|
+
return createHash("sha1").update(idempotencyKey).digest("hex");
|
|
2156
|
+
}
|
|
2157
|
+
var BullMQJobOrchestrator = class extends DrizzleJobOrchestrator {
|
|
2158
|
+
constructor(db, multiTenant, connection, bullConfig = null) {
|
|
2159
|
+
super(db, multiTenant);
|
|
2160
|
+
this.connection = connection;
|
|
2161
|
+
this.bullConfig = bullConfig;
|
|
2162
|
+
this.bullDb = db;
|
|
2163
|
+
}
|
|
2164
|
+
connection;
|
|
2165
|
+
bullConfig;
|
|
2166
|
+
// TODO(logging-subsystem): swap to ILogger once ADR-028 lands
|
|
2167
|
+
bullLogger = new Logger5(BullMQJobOrchestrator.name);
|
|
2168
|
+
/** Lazily-opened `Queue` handles, one per pool. */
|
|
2169
|
+
queues = /* @__PURE__ */ new Map();
|
|
2170
|
+
/** Single FlowProducer for parent/child hierarchies. Lazily opened. */
|
|
2171
|
+
_flow = null;
|
|
2172
|
+
/**
|
|
2173
|
+
* Cached `bullmq` value constructors, populated by `loadBullMq()` on first
|
|
2174
|
+
* use (the `start`/`cancel`/`replay` entrypoints `await` it before touching
|
|
2175
|
+
* a queue). Kept off the import graph so a `drizzle`-only consumer never
|
|
2176
|
+
* resolves the optional `'bullmq'` package.
|
|
2177
|
+
*/
|
|
2178
|
+
QueueCtor = null;
|
|
2179
|
+
FlowProducerCtor = null;
|
|
2180
|
+
bullMqLoad = null;
|
|
2181
|
+
/**
|
|
2182
|
+
* Own reference to the Drizzle client. `DrizzleJobOrchestrator.db` is
|
|
2183
|
+
* `private` (can't be redeclared even privately in a subclass), and the
|
|
2184
|
+
* spec forbids touching that file — so the subclass keeps its own handle
|
|
2185
|
+
* under a distinct name (same instance, passed through to `super`) for the
|
|
2186
|
+
* cancel-cascade snapshot + definition/run loads below.
|
|
2187
|
+
*/
|
|
2188
|
+
bullDb;
|
|
2189
|
+
/**
|
|
2190
|
+
* Lazily load the optional `bullmq` package and cache its value
|
|
2191
|
+
* constructors. Idempotent (single in-flight promise). Throws a friendly,
|
|
2192
|
+
* actionable error when the consumer selected `backend: 'bullmq'` but did
|
|
2193
|
+
* not install the package — mirrors `createRedisClient` in the redis event
|
|
2194
|
+
* backend. Must be `await`ed before any `queueFor`/`flow` access.
|
|
2195
|
+
*/
|
|
2196
|
+
async loadBullMq() {
|
|
2197
|
+
if (this.QueueCtor && this.FlowProducerCtor) return;
|
|
2198
|
+
if (!this.bullMqLoad) {
|
|
2199
|
+
this.bullMqLoad = (async () => {
|
|
2200
|
+
try {
|
|
2201
|
+
const mod = await import("bullmq");
|
|
2202
|
+
this.QueueCtor = mod.Queue;
|
|
2203
|
+
this.FlowProducerCtor = mod.FlowProducer;
|
|
2204
|
+
} catch {
|
|
2205
|
+
throw new Error(
|
|
2206
|
+
'BullMQ backend requires the "bullmq" package. Install it with: npm install bullmq'
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
})();
|
|
2210
|
+
}
|
|
2211
|
+
await this.bullMqLoad;
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Open (or reuse) the `Queue` for a pool. Synchronous — callers `await
|
|
2215
|
+
* loadBullMq()` first so `QueueCtor` is populated.
|
|
2216
|
+
*/
|
|
2217
|
+
queueFor(pool) {
|
|
2218
|
+
if (!this.QueueCtor) {
|
|
2219
|
+
throw new Error("BullMQJobOrchestrator: queueFor called before loadBullMq()");
|
|
2220
|
+
}
|
|
2221
|
+
const name = resolvePoolQueueName(pool, this.bullConfig);
|
|
2222
|
+
let q = this.queues.get(name);
|
|
2223
|
+
if (!q) {
|
|
2224
|
+
q = new this.QueueCtor(name, { connection: this.connection });
|
|
2225
|
+
this.queues.set(name, q);
|
|
2226
|
+
}
|
|
2227
|
+
return q;
|
|
2228
|
+
}
|
|
2229
|
+
flow() {
|
|
2230
|
+
if (!this.FlowProducerCtor) {
|
|
2231
|
+
throw new Error("BullMQJobOrchestrator: flow called before loadBullMq()");
|
|
2232
|
+
}
|
|
2233
|
+
if (!this._flow) {
|
|
2234
|
+
this._flow = new this.FlowProducerCtor({ connection: this.connection });
|
|
2235
|
+
}
|
|
2236
|
+
return this._flow;
|
|
2237
|
+
}
|
|
2238
|
+
// ==========================================================================
|
|
2239
|
+
// start — Postgres insert (super) + BullMQ dispatch
|
|
2240
|
+
// ==========================================================================
|
|
2241
|
+
async start(type, input, opts = {}, tx) {
|
|
2242
|
+
const run = await super.start(type, input, opts, tx);
|
|
2243
|
+
await this.dispatch(run, type);
|
|
2244
|
+
return run;
|
|
2245
|
+
}
|
|
2246
|
+
/**
|
|
2247
|
+
* Map a `job_run` row onto a BullMQ job via `queue.add`. When the run has a
|
|
2248
|
+
* `parentRunId` we attach it to the parent's existing BullMQ job through the
|
|
2249
|
+
* `parent: { id, queue }` opt — BullMQ then tracks the parent/child link in
|
|
2250
|
+
* its own graph. (The FlowProducer is reserved for whole-tree atomic
|
|
2251
|
+
* submits, exposed as an opt-in extension via `flowProducer()`; runtime
|
|
2252
|
+
* `ctx.spawnChild` is incremental, so `queue.add` with a parent ref is the
|
|
2253
|
+
* correct primitive here.)
|
|
2254
|
+
*
|
|
2255
|
+
* The `jobId` is colon-safe + stable: `sha1(dedupeKey)` when a dedupe key is
|
|
2256
|
+
* present (so the same logical key dedups), else the `job_run.id` UUID
|
|
2257
|
+
* (already colon-free).
|
|
2258
|
+
*
|
|
2259
|
+
* The domain `parentClosePolicy` cascade is still enforced in Postgres by
|
|
2260
|
+
* the shared `cancel` path — BullMQ's parent link is dispatch bookkeeping,
|
|
2261
|
+
* not the authority.
|
|
2262
|
+
*/
|
|
2263
|
+
async dispatch(run, type) {
|
|
2264
|
+
await this.loadBullMq();
|
|
2265
|
+
const def = await this.loadDefinition(type);
|
|
2266
|
+
const jobId = run.dedupeKey ? sha1JobId(run.dedupeKey) : run.id;
|
|
2267
|
+
const jobOpts = {
|
|
2268
|
+
jobId,
|
|
2269
|
+
...this.retryOpts(def),
|
|
2270
|
+
...this.dedupeOpts(run, def)
|
|
2271
|
+
};
|
|
2272
|
+
if (run.parentRunId) {
|
|
2273
|
+
const parentRow = await this.loadRun(run.parentRunId);
|
|
2274
|
+
if (parentRow) {
|
|
2275
|
+
const parentJobId = parentRow.dedupeKey ? sha1JobId(parentRow.dedupeKey) : parentRow.id;
|
|
2276
|
+
jobOpts.parent = {
|
|
2277
|
+
id: parentJobId,
|
|
2278
|
+
queue: resolvePoolQueueName(parentRow.pool, this.bullConfig)
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
const payload = { runId: run.id, type, input: run.input };
|
|
2283
|
+
await this.queueFor(run.pool).add(type, payload, jobOpts);
|
|
2284
|
+
}
|
|
2285
|
+
/**
|
|
2286
|
+
* Opt-in extension (spec §Extensions): expose the FlowProducer for
|
|
2287
|
+
* consumers that want to submit a whole parent/child DAG atomically up
|
|
2288
|
+
* front, rather than incrementally via `ctx.spawnChild`. Backend-specific —
|
|
2289
|
+
* code using it is not portable to the Drizzle backend. Async because it
|
|
2290
|
+
* lazily loads the optional `bullmq` package on first use.
|
|
2291
|
+
*/
|
|
2292
|
+
async flowProducer() {
|
|
2293
|
+
await this.loadBullMq();
|
|
2294
|
+
return this.flow();
|
|
2295
|
+
}
|
|
2296
|
+
retryOpts(def) {
|
|
2297
|
+
const policy = def.retryPolicy;
|
|
2298
|
+
if (!policy) return {};
|
|
2299
|
+
return {
|
|
2300
|
+
attempts: policy.attempts,
|
|
2301
|
+
backoff: {
|
|
2302
|
+
type: policy.backoff === "exponential" ? "exponential" : "fixed",
|
|
2303
|
+
delay: policy.baseMs
|
|
2304
|
+
}
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
dedupeOpts(run, def) {
|
|
2308
|
+
if (!run.dedupeKey || !def.dedupeWindowMs) return {};
|
|
2309
|
+
return {
|
|
2310
|
+
deduplication: {
|
|
2311
|
+
id: sha1JobId(run.dedupeKey),
|
|
2312
|
+
ttl: def.dedupeWindowMs
|
|
2313
|
+
}
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
// ==========================================================================
|
|
2317
|
+
// cancel — Postgres cascade (super) + remove from queue
|
|
2318
|
+
// ==========================================================================
|
|
2319
|
+
async cancel(runId, opts = {}) {
|
|
2320
|
+
const target = await this.loadRun(runId);
|
|
2321
|
+
await super.cancel(runId, opts);
|
|
2322
|
+
if (!target) return;
|
|
2323
|
+
await this.loadBullMq();
|
|
2324
|
+
await this.removeFromQueue(target);
|
|
2325
|
+
if (opts.cascade === false) return;
|
|
2326
|
+
const descendants = await this.bullDb.select().from(jobRuns).where(eq5(jobRuns.rootRunId, target.rootRunId));
|
|
2327
|
+
for (const child of descendants) {
|
|
2328
|
+
if (child.id === runId) continue;
|
|
2329
|
+
await this.removeFromQueue(child);
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
async removeFromQueue(run) {
|
|
2333
|
+
const jobId = run.dedupeKey ? sha1JobId(run.dedupeKey) : run.id;
|
|
2334
|
+
try {
|
|
2335
|
+
const job = await this.queueFor(run.pool).getJob(jobId);
|
|
2336
|
+
if (job) await job.remove();
|
|
2337
|
+
} catch (err) {
|
|
2338
|
+
this.bullLogger.warn(
|
|
2339
|
+
`cancel: could not remove BullMQ job ${jobId} (pool=${run.pool}): ${err.message}`
|
|
2340
|
+
);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
// ==========================================================================
|
|
2344
|
+
// replay — Postgres reset (super) + re-enqueue
|
|
2345
|
+
// ==========================================================================
|
|
2346
|
+
async replay(runId) {
|
|
2347
|
+
const run = await super.replay(runId);
|
|
2348
|
+
await this.dispatch(run, run.jobType);
|
|
2349
|
+
return run;
|
|
2350
|
+
}
|
|
2351
|
+
// ==========================================================================
|
|
2352
|
+
// Internals
|
|
2353
|
+
// ==========================================================================
|
|
2354
|
+
async loadDefinition(type) {
|
|
2355
|
+
const [def] = await this.bullDb.select().from(jobs).where(eq5(jobs.type, type)).limit(1);
|
|
2356
|
+
if (!def) {
|
|
2357
|
+
throw new Error(`BullMQJobOrchestrator: no job definition for '${type}'`);
|
|
2358
|
+
}
|
|
2359
|
+
return def;
|
|
2360
|
+
}
|
|
2361
|
+
async loadRun(id) {
|
|
2362
|
+
const [row] = await this.bullDb.select().from(jobRuns).where(eq5(jobRuns.id, id)).limit(1);
|
|
2363
|
+
return row ?? null;
|
|
2364
|
+
}
|
|
2365
|
+
/** Close all open queue + flow connections. Called on module destroy. */
|
|
2366
|
+
async closeConnections() {
|
|
2367
|
+
for (const q of this.queues.values()) {
|
|
2368
|
+
await q.close().catch(() => void 0);
|
|
2369
|
+
}
|
|
2370
|
+
this.queues.clear();
|
|
2371
|
+
if (this._flow) {
|
|
2372
|
+
await this._flow.close().catch(() => void 0);
|
|
2373
|
+
this._flow = null;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
};
|
|
2377
|
+
BullMQJobOrchestrator = __decorateClass([
|
|
2378
|
+
Injectable8(),
|
|
2379
|
+
__decorateParam(0, Inject8(DRIZZLE)),
|
|
2380
|
+
__decorateParam(1, Inject8(JOBS_MULTI_TENANT)),
|
|
2381
|
+
__decorateParam(2, Inject8(BULLMQ_CONNECTION)),
|
|
2382
|
+
__decorateParam(3, Optional3()),
|
|
2383
|
+
__decorateParam(3, Inject8(BULLMQ_RESOLVED_CONFIG))
|
|
2384
|
+
], BullMQJobOrchestrator);
|
|
2385
|
+
|
|
2386
|
+
// runtime/subsystems/jobs/job-worker.bullmq-backend.ts
|
|
2387
|
+
import { Logger as Logger6 } from "@nestjs/common";
|
|
2388
|
+
import { eq as eq6 } from "drizzle-orm";
|
|
2389
|
+
function serialiseError(err, attempt, retryable) {
|
|
2390
|
+
const e = err;
|
|
2391
|
+
return {
|
|
2392
|
+
message: e?.message ?? String(err),
|
|
2393
|
+
stack: e?.stack,
|
|
2394
|
+
retryable,
|
|
2395
|
+
attempt
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
var BullMQJobWorker = class _BullMQJobWorker {
|
|
2399
|
+
constructor(db, orchestrator, stepService, options, moduleRef) {
|
|
2400
|
+
this.db = db;
|
|
2401
|
+
this.orchestrator = orchestrator;
|
|
2402
|
+
this.stepService = stepService;
|
|
2403
|
+
this.options = options;
|
|
2404
|
+
this.moduleRef = moduleRef;
|
|
2405
|
+
}
|
|
2406
|
+
db;
|
|
2407
|
+
orchestrator;
|
|
2408
|
+
stepService;
|
|
2409
|
+
options;
|
|
2410
|
+
moduleRef;
|
|
2411
|
+
logger = new Logger6(_BullMQJobWorker.name);
|
|
2412
|
+
worker = null;
|
|
2413
|
+
async onModuleInit() {
|
|
2414
|
+
let WorkerCtor;
|
|
2415
|
+
try {
|
|
2416
|
+
const mod = await import("bullmq");
|
|
2417
|
+
WorkerCtor = mod.Worker;
|
|
2418
|
+
} catch {
|
|
2419
|
+
throw new Error(
|
|
2420
|
+
'BullMQ backend requires the "bullmq" package. Install it with: npm install bullmq'
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
this.worker = new WorkerCtor(
|
|
2424
|
+
this.options.queueName,
|
|
2425
|
+
(job) => this.process(job),
|
|
2426
|
+
{
|
|
2427
|
+
connection: this.options.connection,
|
|
2428
|
+
concurrency: this.options.concurrency
|
|
2429
|
+
}
|
|
2430
|
+
);
|
|
2431
|
+
this.worker.on("failed", (job, err) => {
|
|
2432
|
+
if (!job) return;
|
|
2433
|
+
const attemptsMade = job.attemptsMade;
|
|
2434
|
+
const maxAttempts = job.opts.attempts ?? 1;
|
|
2435
|
+
if (attemptsMade >= maxAttempts) {
|
|
2436
|
+
void this.markFailed(job.data.runId, err, attemptsMade);
|
|
2437
|
+
}
|
|
2438
|
+
});
|
|
2439
|
+
this.logger.log(
|
|
2440
|
+
`BullMQ worker started: pool='${this.options.pool}' queue='${this.options.queueName}' concurrency=${this.options.concurrency}`
|
|
2441
|
+
);
|
|
2442
|
+
}
|
|
2443
|
+
async onModuleDestroy() {
|
|
2444
|
+
if (this.worker) {
|
|
2445
|
+
await this.worker.close();
|
|
2446
|
+
this.worker = null;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
/**
|
|
2450
|
+
* Process one BullMQ job. Returns the handler output (stored by BullMQ as
|
|
2451
|
+
* the job return value AND written to `job_run.output`). Throws on handler
|
|
2452
|
+
* failure so BullMQ applies the retry policy.
|
|
2453
|
+
*/
|
|
2454
|
+
async process(job) {
|
|
2455
|
+
const { runId } = job.data;
|
|
2456
|
+
const [row] = await this.db.select().from(jobRuns).where(eq6(jobRuns.id, runId)).limit(1);
|
|
2457
|
+
if (!row) {
|
|
2458
|
+
this.logger.warn(`process: job_run ${runId} not found; skipping`);
|
|
2459
|
+
return {};
|
|
2460
|
+
}
|
|
2461
|
+
const run = row;
|
|
2462
|
+
if (run.status === "canceled") {
|
|
2463
|
+
return {};
|
|
2464
|
+
}
|
|
2465
|
+
const registryEntry = JOB_HANDLER_REGISTRY.get(run.jobType);
|
|
2466
|
+
if (!registryEntry) {
|
|
2467
|
+
throw new Error(
|
|
2468
|
+
`No handler registered for jobType='${run.jobType}' (run ${run.id})`
|
|
2469
|
+
);
|
|
2470
|
+
}
|
|
2471
|
+
await this.db.update(jobRuns).set({
|
|
2472
|
+
status: "running",
|
|
2473
|
+
claimedAt: /* @__PURE__ */ new Date(),
|
|
2474
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
2475
|
+
attempts: job.attemptsMade + 1,
|
|
2476
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2477
|
+
}).where(eq6(jobRuns.id, run.id));
|
|
2478
|
+
const HandlerClass = registryEntry.handlerClass;
|
|
2479
|
+
const handler = this.moduleRef.get(
|
|
2480
|
+
HandlerClass,
|
|
2481
|
+
{ strict: false }
|
|
2482
|
+
);
|
|
2483
|
+
const ctx = {
|
|
2484
|
+
input: run.input,
|
|
2485
|
+
run,
|
|
2486
|
+
step: this.makeStepFn(run),
|
|
2487
|
+
spawnChild: this.makeSpawnFn(run),
|
|
2488
|
+
logger: new Logger6(`JobRun:${run.id}`)
|
|
2489
|
+
};
|
|
2490
|
+
const output = await handler.run(ctx);
|
|
2491
|
+
await this.db.update(jobRuns).set({
|
|
2492
|
+
status: "completed",
|
|
2493
|
+
output: output ?? {},
|
|
2494
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
2495
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2496
|
+
}).where(eq6(jobRuns.id, run.id));
|
|
2497
|
+
return output ?? {};
|
|
2498
|
+
}
|
|
2499
|
+
async markFailed(runId, err, finalAttempts) {
|
|
2500
|
+
const [row] = await this.db.select().from(jobRuns).where(eq6(jobRuns.id, runId)).limit(1);
|
|
2501
|
+
if (!row) return;
|
|
2502
|
+
const run = row;
|
|
2503
|
+
await this.db.update(jobRuns).set({
|
|
2504
|
+
status: "failed",
|
|
2505
|
+
attempts: finalAttempts,
|
|
2506
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
2507
|
+
error: serialiseError(err, finalAttempts, false),
|
|
2508
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2509
|
+
}).where(eq6(jobRuns.id, runId));
|
|
2510
|
+
if (run.parentClosePolicy === "terminate") {
|
|
2511
|
+
try {
|
|
2512
|
+
await this.orchestrator.cancel(run.id, {
|
|
2513
|
+
cascade: true,
|
|
2514
|
+
reason: "parent-failed",
|
|
2515
|
+
tenantId: run.tenantId
|
|
2516
|
+
});
|
|
2517
|
+
} catch (cascadeErr) {
|
|
2518
|
+
this.logger.warn(
|
|
2519
|
+
`cascade on failed run ${run.id}: ${cascadeErr.message}`
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
// ── ctx.step / ctx.spawnChild (mirror JobWorker) ──────────────────────────
|
|
2525
|
+
makeStepFn(run) {
|
|
2526
|
+
return async (stepId, fn, _opts) => {
|
|
2527
|
+
void _opts;
|
|
2528
|
+
const existing = await this.stepService.findStep(run.id, stepId);
|
|
2529
|
+
if (existing?.status === "completed") {
|
|
2530
|
+
return existing.output;
|
|
2531
|
+
}
|
|
2532
|
+
const nextAttempts = (existing?.attempts ?? 0) + 1;
|
|
2533
|
+
const seq = nextAttempts;
|
|
2534
|
+
await this.stepService.recordStep({
|
|
2535
|
+
jobRunId: run.id,
|
|
2536
|
+
stepId,
|
|
2537
|
+
kind: "task",
|
|
2538
|
+
seq,
|
|
2539
|
+
status: "running",
|
|
2540
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
2541
|
+
attempts: nextAttempts
|
|
2542
|
+
});
|
|
2543
|
+
try {
|
|
2544
|
+
const output = await fn();
|
|
2545
|
+
await this.stepService.recordStep({
|
|
2546
|
+
jobRunId: run.id,
|
|
2547
|
+
stepId,
|
|
2548
|
+
kind: "task",
|
|
2549
|
+
seq,
|
|
2550
|
+
status: "completed",
|
|
2551
|
+
output,
|
|
2552
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
2553
|
+
attempts: nextAttempts
|
|
2554
|
+
});
|
|
2555
|
+
return output;
|
|
2556
|
+
} catch (err) {
|
|
2557
|
+
await this.stepService.recordStep({
|
|
2558
|
+
jobRunId: run.id,
|
|
2559
|
+
stepId,
|
|
2560
|
+
kind: "task",
|
|
2561
|
+
seq,
|
|
2562
|
+
status: "failed",
|
|
2563
|
+
error: serialiseError(err, nextAttempts, false),
|
|
2564
|
+
finishedAt: /* @__PURE__ */ new Date(),
|
|
2565
|
+
attempts: nextAttempts
|
|
2566
|
+
});
|
|
2567
|
+
throw err;
|
|
2568
|
+
}
|
|
2569
|
+
};
|
|
1734
2570
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
2571
|
+
makeSpawnFn(run) {
|
|
2572
|
+
return async (type, input, opts) => {
|
|
2573
|
+
return this.orchestrator.start(type, input, {
|
|
2574
|
+
parentRunId: run.id,
|
|
2575
|
+
parentClosePolicy: opts?.closePolicy,
|
|
2576
|
+
runAt: opts?.runAt,
|
|
2577
|
+
priority: opts?.priority,
|
|
2578
|
+
tags: opts?.tags,
|
|
2579
|
+
triggerSource: "parent",
|
|
2580
|
+
triggerRef: run.id,
|
|
2581
|
+
tenantId: run.tenantId
|
|
2582
|
+
});
|
|
2583
|
+
};
|
|
1738
2584
|
}
|
|
1739
2585
|
};
|
|
1740
|
-
DrizzleJobStepService = __decorateClass([
|
|
1741
|
-
Injectable7(),
|
|
1742
|
-
__decorateParam(0, Inject7(DRIZZLE))
|
|
1743
|
-
], DrizzleJobStepService);
|
|
1744
2586
|
|
|
1745
2587
|
// runtime/subsystems/jobs/job-worker.ts
|
|
1746
|
-
import { Inject as
|
|
1747
|
-
import { and as and5, asc as asc3, desc as
|
|
2588
|
+
import { Inject as Inject9, Injectable as Injectable9, Logger as Logger7 } from "@nestjs/common";
|
|
2589
|
+
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";
|
|
1748
2590
|
var JOB_WORKER_OPTIONS = /* @__PURE__ */ Symbol("JOB_WORKER_OPTIONS");
|
|
1749
2591
|
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
1750
2592
|
var DEFAULT_STALE_SWEEPER_INTERVAL_MS = 6e4;
|
|
@@ -1773,7 +2615,7 @@ function classifyError(err, policy, currentAttempts) {
|
|
|
1773
2615
|
if (currentAttempts + 1 >= policy.attempts) return "fail";
|
|
1774
2616
|
return "retry";
|
|
1775
2617
|
}
|
|
1776
|
-
function
|
|
2618
|
+
function serialiseError2(err, attempt, retryable) {
|
|
1777
2619
|
const e = err;
|
|
1778
2620
|
return {
|
|
1779
2621
|
message: e?.message ?? String(err),
|
|
@@ -1807,7 +2649,7 @@ var JobWorker = class {
|
|
|
1807
2649
|
stepService;
|
|
1808
2650
|
options;
|
|
1809
2651
|
moduleRef;
|
|
1810
|
-
logger = new
|
|
2652
|
+
logger = new Logger7(JobWorker.name);
|
|
1811
2653
|
shuttingDown = false;
|
|
1812
2654
|
inFlight = /* @__PURE__ */ new Set();
|
|
1813
2655
|
pollTimer = null;
|
|
@@ -1848,7 +2690,7 @@ var JobWorker = class {
|
|
|
1848
2690
|
await this.drainInFlight();
|
|
1849
2691
|
try {
|
|
1850
2692
|
await this.db.update(jobRuns).set({ status: "pending", claimedAt: null, startedAt: null }).where(
|
|
1851
|
-
and5(
|
|
2693
|
+
and5(eq7(jobRuns.status, "running"), eq7(jobRuns.pool, this.options.pool))
|
|
1852
2694
|
);
|
|
1853
2695
|
} catch (err) {
|
|
1854
2696
|
this.logger.error(`shutdown reset failed: ${err.message}`);
|
|
@@ -1898,11 +2740,11 @@ var JobWorker = class {
|
|
|
1898
2740
|
return this.db.transaction(async (tx) => {
|
|
1899
2741
|
const candidates = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
|
|
1900
2742
|
and5(
|
|
1901
|
-
|
|
1902
|
-
|
|
2743
|
+
eq7(jobRuns.status, "pending"),
|
|
2744
|
+
eq7(jobRuns.pool, pool),
|
|
1903
2745
|
lte(jobRuns.runAt, /* @__PURE__ */ new Date())
|
|
1904
2746
|
)
|
|
1905
|
-
).orderBy(
|
|
2747
|
+
).orderBy(desc4(jobRuns.priority), asc3(jobRuns.runAt)).limit(1).for("update", { skipLocked: true });
|
|
1906
2748
|
const candidate = candidates[0];
|
|
1907
2749
|
if (!candidate) return null;
|
|
1908
2750
|
const [claimed] = await tx.update(jobRuns).set({
|
|
@@ -1910,7 +2752,7 @@ var JobWorker = class {
|
|
|
1910
2752
|
claimedAt: /* @__PURE__ */ new Date(),
|
|
1911
2753
|
startedAt: /* @__PURE__ */ new Date(),
|
|
1912
2754
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1913
|
-
}).where(
|
|
2755
|
+
}).where(eq7(jobRuns.id, candidate.id)).returning();
|
|
1914
2756
|
return claimed ?? null;
|
|
1915
2757
|
});
|
|
1916
2758
|
}
|
|
@@ -1928,7 +2770,7 @@ var JobWorker = class {
|
|
|
1928
2770
|
await this.db.transaction(async (tx) => {
|
|
1929
2771
|
const threshold = new Date(Date.now() - this.staleThresholdMs);
|
|
1930
2772
|
const stale = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
|
|
1931
|
-
and5(
|
|
2773
|
+
and5(eq7(jobRuns.status, "running"), lt3(jobRuns.claimedAt, threshold))
|
|
1932
2774
|
).for("update", { skipLocked: true });
|
|
1933
2775
|
if (stale.length === 0) return;
|
|
1934
2776
|
const ids = stale.map((r) => r.id);
|
|
@@ -1961,8 +2803,8 @@ var JobWorker = class {
|
|
|
1961
2803
|
if (claimed.concurrencyKey) {
|
|
1962
2804
|
const inflight = await this.db.select({ id: jobRuns.id }).from(jobRuns).where(
|
|
1963
2805
|
and5(
|
|
1964
|
-
|
|
1965
|
-
|
|
2806
|
+
eq7(jobRuns.concurrencyKey, claimed.concurrencyKey),
|
|
2807
|
+
eq7(jobRuns.status, "running")
|
|
1966
2808
|
)
|
|
1967
2809
|
);
|
|
1968
2810
|
const other = inflight.find((r) => r.id !== claimed.id);
|
|
@@ -1972,7 +2814,7 @@ var JobWorker = class {
|
|
|
1972
2814
|
claimedAt: null,
|
|
1973
2815
|
startedAt: null,
|
|
1974
2816
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1975
|
-
}).where(
|
|
2817
|
+
}).where(eq7(jobRuns.id, claimed.id));
|
|
1976
2818
|
return;
|
|
1977
2819
|
}
|
|
1978
2820
|
}
|
|
@@ -1987,7 +2829,7 @@ var JobWorker = class {
|
|
|
1987
2829
|
run: claimed,
|
|
1988
2830
|
step: this.makeStepFn(claimed),
|
|
1989
2831
|
spawnChild: this.makeSpawnFn(claimed),
|
|
1990
|
-
logger: new
|
|
2832
|
+
logger: new Logger7(`JobRun:${claimed.id}`)
|
|
1991
2833
|
};
|
|
1992
2834
|
const attemptsBefore = claimed.attempts ?? 0;
|
|
1993
2835
|
try {
|
|
@@ -1998,7 +2840,7 @@ var JobWorker = class {
|
|
|
1998
2840
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
1999
2841
|
updatedAt: /* @__PURE__ */ new Date(),
|
|
2000
2842
|
attempts: attemptsBefore + 1
|
|
2001
|
-
}).where(
|
|
2843
|
+
}).where(eq7(jobRuns.id, claimed.id));
|
|
2002
2844
|
} catch (err) {
|
|
2003
2845
|
const policy = meta.retry;
|
|
2004
2846
|
const decision = classifyError(err, policy, attemptsBefore);
|
|
@@ -2011,9 +2853,9 @@ var JobWorker = class {
|
|
|
2011
2853
|
runAt: new Date(Date.now() + delay),
|
|
2012
2854
|
startedAt: null,
|
|
2013
2855
|
claimedAt: null,
|
|
2014
|
-
error:
|
|
2856
|
+
error: serialiseError2(err, nextAttempts, true),
|
|
2015
2857
|
updatedAt: /* @__PURE__ */ new Date()
|
|
2016
|
-
}).where(
|
|
2858
|
+
}).where(eq7(jobRuns.id, claimed.id));
|
|
2017
2859
|
} else {
|
|
2018
2860
|
await this.markFailed(claimed, err, nextAttempts);
|
|
2019
2861
|
}
|
|
@@ -2024,9 +2866,9 @@ var JobWorker = class {
|
|
|
2024
2866
|
status: "failed",
|
|
2025
2867
|
attempts: finalAttempts,
|
|
2026
2868
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
2027
|
-
error:
|
|
2869
|
+
error: serialiseError2(err, finalAttempts, false),
|
|
2028
2870
|
updatedAt: /* @__PURE__ */ new Date()
|
|
2029
|
-
}).where(
|
|
2871
|
+
}).where(eq7(jobRuns.id, claimed.id));
|
|
2030
2872
|
if (claimed.parentClosePolicy === "terminate") {
|
|
2031
2873
|
try {
|
|
2032
2874
|
await this.orchestrator.cancel(claimed.id, {
|
|
@@ -2083,7 +2925,7 @@ var JobWorker = class {
|
|
|
2083
2925
|
kind: "task",
|
|
2084
2926
|
seq,
|
|
2085
2927
|
status: "failed",
|
|
2086
|
-
error:
|
|
2928
|
+
error: serialiseError2(err, nextAttempts, false),
|
|
2087
2929
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
2088
2930
|
attempts: nextAttempts
|
|
2089
2931
|
});
|
|
@@ -2113,7 +2955,7 @@ var JobWorker = class {
|
|
|
2113
2955
|
*/
|
|
2114
2956
|
async nextStepSeq(runId) {
|
|
2115
2957
|
const [row] = await this.db.execute(
|
|
2116
|
-
|
|
2958
|
+
sql6`SELECT COALESCE(MAX(seq), 0) + 1 AS next FROM job_step WHERE job_run_id = ${runId}`
|
|
2117
2959
|
);
|
|
2118
2960
|
const maybeRows = row?.rows;
|
|
2119
2961
|
if (Array.isArray(maybeRows) && maybeRows.length > 0) {
|
|
@@ -2129,12 +2971,12 @@ var JobWorker = class {
|
|
|
2129
2971
|
// ============================================================================
|
|
2130
2972
|
};
|
|
2131
2973
|
JobWorker = __decorateClass([
|
|
2132
|
-
|
|
2133
|
-
__decorateParam(0,
|
|
2134
|
-
__decorateParam(1,
|
|
2135
|
-
__decorateParam(2,
|
|
2136
|
-
__decorateParam(3,
|
|
2137
|
-
__decorateParam(4,
|
|
2974
|
+
Injectable9(),
|
|
2975
|
+
__decorateParam(0, Inject9(DRIZZLE)),
|
|
2976
|
+
__decorateParam(1, Inject9(JOB_ORCHESTRATOR)),
|
|
2977
|
+
__decorateParam(2, Inject9(JOB_RUN_SERVICE)),
|
|
2978
|
+
__decorateParam(3, Inject9(JOB_STEP_SERVICE)),
|
|
2979
|
+
__decorateParam(4, Inject9(JOB_WORKER_OPTIONS))
|
|
2138
2980
|
], JobWorker);
|
|
2139
2981
|
|
|
2140
2982
|
// runtime/subsystems/jobs/memory-job-store.ts
|
|
@@ -2155,7 +2997,7 @@ var MemoryJobStore = class {
|
|
|
2155
2997
|
|
|
2156
2998
|
// runtime/subsystems/jobs/job-orchestrator.memory-backend.ts
|
|
2157
2999
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2158
|
-
import { Inject as
|
|
3000
|
+
import { Inject as Inject10, Injectable as Injectable10, Logger as Logger8, Optional as Optional4 } from "@nestjs/common";
|
|
2159
3001
|
var QUEUED_RUN_AT = /* @__PURE__ */ new Date(864e13);
|
|
2160
3002
|
var TERMINAL_STATUSES2 = [
|
|
2161
3003
|
"completed",
|
|
@@ -2202,7 +3044,7 @@ var MemoryJobOrchestrator = class {
|
|
|
2202
3044
|
stepService;
|
|
2203
3045
|
multiTenant;
|
|
2204
3046
|
moduleRef;
|
|
2205
|
-
logger = new
|
|
3047
|
+
logger = new Logger8(MemoryJobOrchestrator.name);
|
|
2206
3048
|
mutex = new PromiseMutex();
|
|
2207
3049
|
handlerRegistry = /* @__PURE__ */ new Map();
|
|
2208
3050
|
/**
|
|
@@ -2551,7 +3393,7 @@ var MemoryJobOrchestrator = class {
|
|
|
2551
3393
|
run,
|
|
2552
3394
|
step: this.makeStepFn(run),
|
|
2553
3395
|
spawnChild: this.makeSpawnFn(run),
|
|
2554
|
-
logger: new
|
|
3396
|
+
logger: new Logger8(`JobRun:${run.id}`)
|
|
2555
3397
|
};
|
|
2556
3398
|
const attemptsBefore = run.attempts ?? 0;
|
|
2557
3399
|
try {
|
|
@@ -2608,7 +3450,7 @@ var MemoryJobOrchestrator = class {
|
|
|
2608
3450
|
kind: "task",
|
|
2609
3451
|
seq,
|
|
2610
3452
|
status: "failed",
|
|
2611
|
-
error:
|
|
3453
|
+
error: serialiseError3(err, nextAttempts, false),
|
|
2612
3454
|
finishedAt: /* @__PURE__ */ new Date(),
|
|
2613
3455
|
attempts: nextAttempts
|
|
2614
3456
|
});
|
|
@@ -2663,7 +3505,7 @@ var MemoryJobOrchestrator = class {
|
|
|
2663
3505
|
finishedAt: now,
|
|
2664
3506
|
updatedAt: now,
|
|
2665
3507
|
attempts,
|
|
2666
|
-
error:
|
|
3508
|
+
error: serialiseError3(err, attempts, false)
|
|
2667
3509
|
});
|
|
2668
3510
|
this.unblockQueuedDependents(run.id);
|
|
2669
3511
|
});
|
|
@@ -2694,7 +3536,7 @@ var MemoryJobOrchestrator = class {
|
|
|
2694
3536
|
startedAt: null,
|
|
2695
3537
|
claimedAt: null,
|
|
2696
3538
|
updatedAt: now,
|
|
2697
|
-
error:
|
|
3539
|
+
error: serialiseError3(err, attempts, true)
|
|
2698
3540
|
});
|
|
2699
3541
|
});
|
|
2700
3542
|
}
|
|
@@ -2724,9 +3566,9 @@ var MemoryJobOrchestrator = class {
|
|
|
2724
3566
|
}
|
|
2725
3567
|
};
|
|
2726
3568
|
MemoryJobOrchestrator = __decorateClass([
|
|
2727
|
-
|
|
2728
|
-
__decorateParam(2,
|
|
2729
|
-
__decorateParam(3,
|
|
3569
|
+
Injectable10(),
|
|
3570
|
+
__decorateParam(2, Inject10(JOBS_MULTI_TENANT)),
|
|
3571
|
+
__decorateParam(3, Optional4())
|
|
2730
3572
|
], MemoryJobOrchestrator);
|
|
2731
3573
|
function classifyError2(err, policy, currentAttempts) {
|
|
2732
3574
|
if (!policy) return "fail";
|
|
@@ -2749,7 +3591,7 @@ function computeBackoff2(policy, attempts) {
|
|
|
2749
3591
|
}
|
|
2750
3592
|
return raw;
|
|
2751
3593
|
}
|
|
2752
|
-
function
|
|
3594
|
+
function serialiseError3(err, attempt, retryable) {
|
|
2753
3595
|
const e = err;
|
|
2754
3596
|
return {
|
|
2755
3597
|
message: e?.message ?? String(err),
|
|
@@ -2760,7 +3602,7 @@ function serialiseError2(err, attempt, retryable) {
|
|
|
2760
3602
|
}
|
|
2761
3603
|
|
|
2762
3604
|
// runtime/subsystems/jobs/job-run-service.memory-backend.ts
|
|
2763
|
-
import { Inject as
|
|
3605
|
+
import { Inject as Inject11, Injectable as Injectable11 } from "@nestjs/common";
|
|
2764
3606
|
var NON_TERMINAL_STATUSES2 = [
|
|
2765
3607
|
"pending",
|
|
2766
3608
|
"running",
|
|
@@ -2877,6 +3719,38 @@ var MemoryJobRunService = class {
|
|
|
2877
3719
|
createdAt: r.createdAt
|
|
2878
3720
|
}));
|
|
2879
3721
|
}
|
|
3722
|
+
async listJobRuns(query = {}) {
|
|
3723
|
+
const limit = clampLimit(query.limit);
|
|
3724
|
+
const tenantCheck = this.tenantPredicate("listJobRuns", query.tenantId);
|
|
3725
|
+
const keyset = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
3726
|
+
const matched = [];
|
|
3727
|
+
for (const r of this.store.runs.values()) {
|
|
3728
|
+
if (tenantCheck && !tenantCheck(r)) continue;
|
|
3729
|
+
if (query.poolId && r.pool !== query.poolId) continue;
|
|
3730
|
+
if (query.rootRunId && r.rootRunId !== query.rootRunId) continue;
|
|
3731
|
+
if (query.status && r.status !== query.status) continue;
|
|
3732
|
+
if (query.since && r.createdAt.getTime() < query.since.getTime()) continue;
|
|
3733
|
+
matched.push(r);
|
|
3734
|
+
}
|
|
3735
|
+
matched.sort((a, b) => {
|
|
3736
|
+
const dt = b.createdAt.getTime() - a.createdAt.getTime();
|
|
3737
|
+
if (dt !== 0) return dt;
|
|
3738
|
+
return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
|
|
3739
|
+
});
|
|
3740
|
+
const seeked = keyset ? matched.filter((r) => {
|
|
3741
|
+
const ct = r.createdAt.getTime();
|
|
3742
|
+
const kt = keyset.createdAt.getTime();
|
|
3743
|
+
if (ct < kt) return true;
|
|
3744
|
+
if (ct > kt) return false;
|
|
3745
|
+
return r.id < keyset.id;
|
|
3746
|
+
}) : matched;
|
|
3747
|
+
const hasMore = seeked.length > limit;
|
|
3748
|
+
const page = hasMore ? seeked.slice(0, limit) : seeked;
|
|
3749
|
+
const items = page.map(toJobRunSummary);
|
|
3750
|
+
const last = page[page.length - 1];
|
|
3751
|
+
const nextCursor = hasMore && last ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id }) : null;
|
|
3752
|
+
return { items, nextCursor };
|
|
3753
|
+
}
|
|
2880
3754
|
/**
|
|
2881
3755
|
* Direct lookup. Not on the protocol — concrete-class convenience for
|
|
2882
3756
|
* tests. Matches `DrizzleJobRunService.findByRootRunId` in spirit; both
|
|
@@ -2895,9 +3769,9 @@ var MemoryJobRunService = class {
|
|
|
2895
3769
|
}
|
|
2896
3770
|
};
|
|
2897
3771
|
MemoryJobRunService = __decorateClass([
|
|
2898
|
-
|
|
2899
|
-
__decorateParam(1,
|
|
2900
|
-
__decorateParam(2,
|
|
3772
|
+
Injectable11(),
|
|
3773
|
+
__decorateParam(1, Inject11(JOB_ORCHESTRATOR)),
|
|
3774
|
+
__decorateParam(2, Inject11(JOBS_MULTI_TENANT))
|
|
2901
3775
|
], MemoryJobRunService);
|
|
2902
3776
|
function compareBy(a, b, order) {
|
|
2903
3777
|
switch (order) {
|
|
@@ -2915,7 +3789,7 @@ function compareBy(a, b, order) {
|
|
|
2915
3789
|
|
|
2916
3790
|
// runtime/subsystems/jobs/job-step-service.memory-backend.ts
|
|
2917
3791
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
2918
|
-
import { Injectable as
|
|
3792
|
+
import { Injectable as Injectable12 } from "@nestjs/common";
|
|
2919
3793
|
var MemoryJobStepService = class {
|
|
2920
3794
|
constructor(store) {
|
|
2921
3795
|
this.store = store;
|
|
@@ -3008,14 +3882,13 @@ var MemoryJobStepService = class {
|
|
|
3008
3882
|
}
|
|
3009
3883
|
};
|
|
3010
3884
|
MemoryJobStepService = __decorateClass([
|
|
3011
|
-
|
|
3885
|
+
Injectable12()
|
|
3012
3886
|
], MemoryJobStepService);
|
|
3013
3887
|
|
|
3014
3888
|
// runtime/subsystems/jobs/jobs-domain.module.ts
|
|
3015
3889
|
import { Module as Module2 } from "@nestjs/common";
|
|
3016
3890
|
var JobsDomainModule = class {
|
|
3017
3891
|
static forRoot(opts) {
|
|
3018
|
-
void opts.extensions;
|
|
3019
3892
|
const multiTenant = opts.multiTenant ?? false;
|
|
3020
3893
|
const providers = [
|
|
3021
3894
|
// JOB-8 — boolean provider consumed by the four service-layer backends.
|
|
@@ -3034,21 +3907,32 @@ var JobsDomainModule = class {
|
|
|
3034
3907
|
providers.push({ provide: JOB_ORCHESTRATOR, useExisting: MemoryJobOrchestrator });
|
|
3035
3908
|
providers.push(MemoryJobRunService);
|
|
3036
3909
|
providers.push({ provide: JOB_RUN_SERVICE, useExisting: MemoryJobRunService });
|
|
3910
|
+
} else if (opts.backend === "bullmq") {
|
|
3911
|
+
const resolved = resolveBullMqConfig(opts.extensions?.bullmq);
|
|
3912
|
+
providers.push({ provide: BULLMQ_CONNECTION, useValue: resolved.connection });
|
|
3913
|
+
providers.push({ provide: BULLMQ_RESOLVED_CONFIG, useValue: resolved });
|
|
3914
|
+
providers.push({ provide: JOB_ORCHESTRATOR, useClass: BullMQJobOrchestrator });
|
|
3915
|
+
providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });
|
|
3916
|
+
providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });
|
|
3037
3917
|
} else {
|
|
3038
3918
|
providers.push({ provide: JOB_ORCHESTRATOR, useClass: DrizzleJobOrchestrator });
|
|
3039
3919
|
providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });
|
|
3040
3920
|
providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });
|
|
3041
3921
|
}
|
|
3922
|
+
const exports = [
|
|
3923
|
+
JOB_ORCHESTRATOR,
|
|
3924
|
+
JOB_RUN_SERVICE,
|
|
3925
|
+
JOB_STEP_SERVICE,
|
|
3926
|
+
JOBS_MULTI_TENANT
|
|
3927
|
+
];
|
|
3928
|
+
if (opts.backend === "bullmq") {
|
|
3929
|
+
exports.push(BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG);
|
|
3930
|
+
}
|
|
3042
3931
|
return {
|
|
3043
3932
|
module: JobsDomainModule,
|
|
3044
3933
|
global: true,
|
|
3045
3934
|
providers,
|
|
3046
|
-
exports
|
|
3047
|
-
JOB_ORCHESTRATOR,
|
|
3048
|
-
JOB_RUN_SERVICE,
|
|
3049
|
-
JOB_STEP_SERVICE,
|
|
3050
|
-
JOBS_MULTI_TENANT
|
|
3051
|
-
]
|
|
3935
|
+
exports
|
|
3052
3936
|
};
|
|
3053
3937
|
}
|
|
3054
3938
|
};
|
|
@@ -3058,143 +3942,24 @@ JobsDomainModule = __decorateClass([
|
|
|
3058
3942
|
|
|
3059
3943
|
// runtime/subsystems/jobs/job-worker.module.ts
|
|
3060
3944
|
import {
|
|
3061
|
-
Inject as
|
|
3062
|
-
Injectable as
|
|
3063
|
-
Logger as
|
|
3945
|
+
Inject as Inject12,
|
|
3946
|
+
Injectable as Injectable13,
|
|
3947
|
+
Logger as Logger9,
|
|
3064
3948
|
Module as Module3,
|
|
3065
|
-
Optional as
|
|
3949
|
+
Optional as Optional5
|
|
3066
3950
|
} from "@nestjs/common";
|
|
3067
|
-
|
|
3068
|
-
// runtime/subsystems/jobs/pool-config.loader.ts
|
|
3069
|
-
import { existsSync, readFileSync } from "fs";
|
|
3070
|
-
import { resolve } from "path";
|
|
3071
|
-
import { parse as parseYaml } from "yaml";
|
|
3072
|
-
var FRAMEWORK_POOLS = Object.freeze({
|
|
3073
|
-
events_inbound: Object.freeze({
|
|
3074
|
-
queue: "jobs-events-inbound",
|
|
3075
|
-
concurrency: 20,
|
|
3076
|
-
reserved: true,
|
|
3077
|
-
description: "Inbound events drain (events subsystem outbox)."
|
|
3078
|
-
}),
|
|
3079
|
-
events_change: Object.freeze({
|
|
3080
|
-
queue: "jobs-events-change",
|
|
3081
|
-
concurrency: 30,
|
|
3082
|
-
reserved: true,
|
|
3083
|
-
description: "Change events drain (events subsystem outbox)."
|
|
3084
|
-
}),
|
|
3085
|
-
events_outbound: Object.freeze({
|
|
3086
|
-
queue: "jobs-events-outbound",
|
|
3087
|
-
concurrency: 10,
|
|
3088
|
-
reserved: true,
|
|
3089
|
-
description: "Outbound events drain (events subsystem outbox)."
|
|
3090
|
-
}),
|
|
3091
|
-
interactive: Object.freeze({
|
|
3092
|
-
queue: "jobs-interactive",
|
|
3093
|
-
concurrency: 20,
|
|
3094
|
-
reserved: false,
|
|
3095
|
-
description: "User-facing latency-sensitive jobs."
|
|
3096
|
-
}),
|
|
3097
|
-
batch: Object.freeze({
|
|
3098
|
-
queue: "jobs-batch",
|
|
3099
|
-
concurrency: 5,
|
|
3100
|
-
reserved: false,
|
|
3101
|
-
description: "Default pool for background jobs."
|
|
3102
|
-
})
|
|
3103
|
-
});
|
|
3104
|
-
var RESERVED_POOL_NAMES = new Set(
|
|
3105
|
-
Object.entries(FRAMEWORK_POOLS).filter(([, def]) => def.reserved).map(([name]) => name)
|
|
3106
|
-
);
|
|
3107
|
-
var cache = /* @__PURE__ */ new Map();
|
|
3108
|
-
function loadPoolConfig(configPath) {
|
|
3109
|
-
const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);
|
|
3110
|
-
const cached = cache.get(resolved);
|
|
3111
|
-
if (cached) return cached;
|
|
3112
|
-
const merged = /* @__PURE__ */ new Map();
|
|
3113
|
-
for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {
|
|
3114
|
-
merged.set(name, { ...def });
|
|
3115
|
-
}
|
|
3116
|
-
if (!existsSync(resolved)) {
|
|
3117
|
-
cache.set(resolved, merged);
|
|
3118
|
-
return merged;
|
|
3119
|
-
}
|
|
3120
|
-
let raw;
|
|
3121
|
-
try {
|
|
3122
|
-
raw = parseYaml(readFileSync(resolved, "utf8"));
|
|
3123
|
-
} catch (err) {
|
|
3124
|
-
throw new Error(
|
|
3125
|
-
`pool-config.loader: failed to parse YAML at ${resolved}: ${err.message}`
|
|
3126
|
-
);
|
|
3127
|
-
}
|
|
3128
|
-
const userPools = extractUserPools(raw);
|
|
3129
|
-
for (const [name, userDef] of Object.entries(userPools)) {
|
|
3130
|
-
const existing = merged.get(name);
|
|
3131
|
-
if (existing) {
|
|
3132
|
-
const next = {
|
|
3133
|
-
queue: existing.queue,
|
|
3134
|
-
concurrency: typeof userDef.concurrency === "number" ? userDef.concurrency : existing.concurrency,
|
|
3135
|
-
reserved: existing.reserved,
|
|
3136
|
-
description: userDef.description ?? existing.description
|
|
3137
|
-
};
|
|
3138
|
-
merged.set(name, next);
|
|
3139
|
-
continue;
|
|
3140
|
-
}
|
|
3141
|
-
if (typeof userDef.queue !== "string" || userDef.queue.length === 0) {
|
|
3142
|
-
throw new Error(
|
|
3143
|
-
`pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`
|
|
3144
|
-
);
|
|
3145
|
-
}
|
|
3146
|
-
if (typeof userDef.concurrency !== "number" || userDef.concurrency <= 0) {
|
|
3147
|
-
throw new Error(
|
|
3148
|
-
`pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`
|
|
3149
|
-
);
|
|
3150
|
-
}
|
|
3151
|
-
if (userDef.reserved === true) {
|
|
3152
|
-
throw new Error(
|
|
3153
|
-
`pool-config.loader: user-defined pool '${name}' cannot set 'reserved: true' \u2014 reserved is framework-only.`
|
|
3154
|
-
);
|
|
3155
|
-
}
|
|
3156
|
-
merged.set(name, {
|
|
3157
|
-
queue: userDef.queue,
|
|
3158
|
-
concurrency: userDef.concurrency,
|
|
3159
|
-
reserved: false,
|
|
3160
|
-
description: userDef.description
|
|
3161
|
-
});
|
|
3162
|
-
}
|
|
3163
|
-
cache.set(resolved, merged);
|
|
3164
|
-
return merged;
|
|
3165
|
-
}
|
|
3166
|
-
function allNonReservedPoolNames(config) {
|
|
3167
|
-
const out = [];
|
|
3168
|
-
for (const [name, def] of config) {
|
|
3169
|
-
if (!def.reserved) out.push(name);
|
|
3170
|
-
}
|
|
3171
|
-
return out;
|
|
3172
|
-
}
|
|
3173
|
-
function extractUserPools(raw) {
|
|
3174
|
-
if (!raw || typeof raw !== "object") return {};
|
|
3175
|
-
const jobs2 = raw.jobs;
|
|
3176
|
-
if (!jobs2 || typeof jobs2 !== "object") return {};
|
|
3177
|
-
const pools = jobs2.pools;
|
|
3178
|
-
if (!pools || typeof pools !== "object") return {};
|
|
3179
|
-
const out = {};
|
|
3180
|
-
for (const [name, def] of Object.entries(pools)) {
|
|
3181
|
-
if (!def || typeof def !== "object") continue;
|
|
3182
|
-
out[name] = def;
|
|
3183
|
-
}
|
|
3184
|
-
return out;
|
|
3185
|
-
}
|
|
3186
|
-
|
|
3187
|
-
// runtime/subsystems/jobs/job-worker.module.ts
|
|
3188
3951
|
var DEFAULT_SHUTDOWN_TIMEOUT_MS2 = 3e4;
|
|
3189
3952
|
var JOB_WORKER_MODULE_OPTIONS = /* @__PURE__ */ Symbol("JOB_WORKER_MODULE_OPTIONS");
|
|
3190
3953
|
var JobWorkerOrchestrator = class {
|
|
3191
|
-
constructor(orchestrator, runService, stepService, options, db = null, moduleRef) {
|
|
3954
|
+
constructor(orchestrator, runService, stepService, options, db = null, moduleRef, bullConnection = null, bullConfig = null) {
|
|
3192
3955
|
this.orchestrator = orchestrator;
|
|
3193
3956
|
this.runService = runService;
|
|
3194
3957
|
this.stepService = stepService;
|
|
3195
3958
|
this.options = options;
|
|
3196
3959
|
this.db = db;
|
|
3197
3960
|
this.moduleRef = moduleRef;
|
|
3961
|
+
this.bullConnection = bullConnection;
|
|
3962
|
+
this.bullConfig = bullConfig;
|
|
3198
3963
|
}
|
|
3199
3964
|
orchestrator;
|
|
3200
3965
|
runService;
|
|
@@ -3202,7 +3967,9 @@ var JobWorkerOrchestrator = class {
|
|
|
3202
3967
|
options;
|
|
3203
3968
|
db;
|
|
3204
3969
|
moduleRef;
|
|
3205
|
-
|
|
3970
|
+
bullConnection;
|
|
3971
|
+
bullConfig;
|
|
3972
|
+
logger = new Logger9(JobWorkerOrchestrator.name);
|
|
3206
3973
|
workers = [];
|
|
3207
3974
|
// ============================================================================
|
|
3208
3975
|
// Lifecycle
|
|
@@ -3219,7 +3986,7 @@ var JobWorkerOrchestrator = class {
|
|
|
3219
3986
|
if (backend !== "memory" && orphaned.length > 0) {
|
|
3220
3987
|
throw new BootValidationError(orphaned);
|
|
3221
3988
|
}
|
|
3222
|
-
const activePools = this.options.pools
|
|
3989
|
+
const activePools = this.options.pools ? this.options.pools : this.options.allPools ? allPoolNames(poolConfig) : allNonReservedPoolNames(poolConfig);
|
|
3223
3990
|
for (const poolName of activePools) {
|
|
3224
3991
|
const def = poolConfig.get(poolName);
|
|
3225
3992
|
if (!def) {
|
|
@@ -3232,11 +3999,11 @@ var JobWorkerOrchestrator = class {
|
|
|
3232
3999
|
concurrency: def.concurrency,
|
|
3233
4000
|
shutdownTimeoutMs: this.options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS2
|
|
3234
4001
|
};
|
|
3235
|
-
const worker = this.options.workerFactory ? this.options.workerFactory(workerOptions) : this.spawnWorker(workerOptions);
|
|
3236
|
-
worker.onModuleInit();
|
|
4002
|
+
const worker = this.options.workerFactory ? this.options.workerFactory(workerOptions) : backend === "bullmq" ? this.spawnBullMQWorker(poolName, def.queue, def.concurrency, poolConfig) : this.spawnWorker(workerOptions);
|
|
4003
|
+
await worker.onModuleInit();
|
|
3237
4004
|
this.workers.push(worker);
|
|
3238
4005
|
this.logger.log(
|
|
3239
|
-
`JobWorker started: pool='${poolName}' (queue='${def.queue}') concurrency=${def.concurrency}`
|
|
4006
|
+
`JobWorker started: pool='${poolName}' (queue='${def.queue}') concurrency=${def.concurrency} backend='${backend}'`
|
|
3240
4007
|
);
|
|
3241
4008
|
}
|
|
3242
4009
|
}
|
|
@@ -3253,6 +4020,16 @@ var JobWorkerOrchestrator = class {
|
|
|
3253
4020
|
}
|
|
3254
4021
|
}
|
|
3255
4022
|
this.workers.length = 0;
|
|
4023
|
+
const orch = this.orchestrator;
|
|
4024
|
+
if (typeof orch.closeConnections === "function") {
|
|
4025
|
+
try {
|
|
4026
|
+
await orch.closeConnections();
|
|
4027
|
+
} catch (err) {
|
|
4028
|
+
this.logger.error(
|
|
4029
|
+
`BullMQ orchestrator connection close failed: ${err.message}`
|
|
4030
|
+
);
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
3256
4033
|
}
|
|
3257
4034
|
// ============================================================================
|
|
3258
4035
|
// Internals
|
|
@@ -3314,15 +4091,57 @@ var JobWorkerOrchestrator = class {
|
|
|
3314
4091
|
this.moduleRef
|
|
3315
4092
|
);
|
|
3316
4093
|
}
|
|
4094
|
+
/**
|
|
4095
|
+
* BULLMQ-1 — spawn a per-pool `BullMQJobWorker`. Requires the Drizzle
|
|
4096
|
+
* client (the worker drives `job_run` as the source of truth) AND the
|
|
4097
|
+
* resolved BullMQ connection (bound by `JobsDomainModule` when
|
|
4098
|
+
* `backend: 'bullmq'`). The queue name is derived identically to the
|
|
4099
|
+
* orchestrator's `dispatch` via `resolvePoolQueueName(pool, …)` so producer
|
|
4100
|
+
* and consumer agree.
|
|
4101
|
+
*/
|
|
4102
|
+
spawnBullMQWorker(pool, _queueAlias, concurrency, poolConfig) {
|
|
4103
|
+
if (!this.db) {
|
|
4104
|
+
throw new Error(
|
|
4105
|
+
`JobWorkerModule: BullMQ worker spawning requires the Drizzle client (no DRIZZLE provider available) \u2014 job_run remains the source of truth.`
|
|
4106
|
+
);
|
|
4107
|
+
}
|
|
4108
|
+
if (!this.bullConnection) {
|
|
4109
|
+
throw new Error(
|
|
4110
|
+
`JobWorkerModule: BullMQ worker spawning requires a resolved BULLMQ_CONNECTION. Ensure JobsDomainModule was booted with backend: 'bullmq'.`
|
|
4111
|
+
);
|
|
4112
|
+
}
|
|
4113
|
+
if (!this.moduleRef) {
|
|
4114
|
+
throw new Error(
|
|
4115
|
+
`JobWorkerModule: ModuleRef not available \u2014 cannot construct BullMQJobWorker with handler DI support.`
|
|
4116
|
+
);
|
|
4117
|
+
}
|
|
4118
|
+
const queueName = resolvePoolQueueName(pool, this.bullConfig, poolConfig);
|
|
4119
|
+
return new BullMQJobWorker(
|
|
4120
|
+
this.db,
|
|
4121
|
+
this.orchestrator,
|
|
4122
|
+
this.stepService,
|
|
4123
|
+
{
|
|
4124
|
+
pool,
|
|
4125
|
+
queueName,
|
|
4126
|
+
concurrency,
|
|
4127
|
+
connection: this.bullConnection
|
|
4128
|
+
},
|
|
4129
|
+
this.moduleRef
|
|
4130
|
+
);
|
|
4131
|
+
}
|
|
3317
4132
|
};
|
|
3318
4133
|
JobWorkerOrchestrator = __decorateClass([
|
|
3319
|
-
|
|
3320
|
-
__decorateParam(0,
|
|
3321
|
-
__decorateParam(1,
|
|
3322
|
-
__decorateParam(2,
|
|
3323
|
-
__decorateParam(3,
|
|
3324
|
-
__decorateParam(4,
|
|
3325
|
-
__decorateParam(4,
|
|
4134
|
+
Injectable13(),
|
|
4135
|
+
__decorateParam(0, Inject12(JOB_ORCHESTRATOR)),
|
|
4136
|
+
__decorateParam(1, Inject12(JOB_RUN_SERVICE)),
|
|
4137
|
+
__decorateParam(2, Inject12(JOB_STEP_SERVICE)),
|
|
4138
|
+
__decorateParam(3, Inject12(JOB_WORKER_MODULE_OPTIONS)),
|
|
4139
|
+
__decorateParam(4, Optional5()),
|
|
4140
|
+
__decorateParam(4, Inject12(DRIZZLE)),
|
|
4141
|
+
__decorateParam(6, Optional5()),
|
|
4142
|
+
__decorateParam(6, Inject12(BULLMQ_CONNECTION)),
|
|
4143
|
+
__decorateParam(7, Optional5()),
|
|
4144
|
+
__decorateParam(7, Inject12(BULLMQ_RESOLVED_CONFIG))
|
|
3326
4145
|
], JobWorkerOrchestrator);
|
|
3327
4146
|
var JobWorkerModule = class {
|
|
3328
4147
|
static forRoot(opts) {
|
|
@@ -3339,7 +4158,14 @@ var JobWorkerModule = class {
|
|
|
3339
4158
|
{ provide: JOB_WORKER_MODULE_OPTIONS, useValue: opts },
|
|
3340
4159
|
JobWorkerOrchestrator
|
|
3341
4160
|
],
|
|
3342
|
-
|
|
4161
|
+
// BULLMQ-1 Phase 1 — export the options token so `BridgeModule`'s
|
|
4162
|
+
// reserved-pool guard (`onModuleInit`) can actually inject it.
|
|
4163
|
+
// Previously `exports: []` left the `@Optional()` inject resolving to
|
|
4164
|
+
// `undefined` and the guard silently no-opped (a dead check). With the
|
|
4165
|
+
// token exported the guard fires for real; consumers that omit the
|
|
4166
|
+
// reserved pools (and don't set `allPools`) now fail fast with
|
|
4167
|
+
// `BridgeReservedPoolsNotPolledError` — which is correct.
|
|
4168
|
+
exports: [JOB_WORKER_MODULE_OPTIONS]
|
|
3343
4169
|
};
|
|
3344
4170
|
}
|
|
3345
4171
|
};
|
|
@@ -3355,8 +4181,8 @@ var CACHE_DEFAULT_TTL = /* @__PURE__ */ Symbol("CACHE_DEFAULT_TTL");
|
|
|
3355
4181
|
import { Module as Module4 } from "@nestjs/common";
|
|
3356
4182
|
|
|
3357
4183
|
// runtime/subsystems/cache/cache.drizzle-backend.ts
|
|
3358
|
-
import { Injectable as
|
|
3359
|
-
import { gt as gt2, or, like, sql as
|
|
4184
|
+
import { Injectable as Injectable14, Inject as Inject13, Optional as Optional6 } from "@nestjs/common";
|
|
4185
|
+
import { gt as gt2, or as or3, like, sql as sql7, eq as eq8 } from "drizzle-orm";
|
|
3360
4186
|
|
|
3361
4187
|
// runtime/subsystems/cache/cache.schema.ts
|
|
3362
4188
|
import { pgTable as pgTable3, text as text3, jsonb as jsonb3, timestamp as timestamp3 } from "drizzle-orm/pg-core";
|
|
@@ -3399,7 +4225,7 @@ var DrizzleCacheService = class {
|
|
|
3399
4225
|
async get(key) {
|
|
3400
4226
|
try {
|
|
3401
4227
|
const rows = await this.db.select().from(cacheEntries).where(
|
|
3402
|
-
|
|
4228
|
+
sql7`${cacheEntries.key} = ${key} AND (${cacheEntries.expiresAt} IS NULL OR ${cacheEntries.expiresAt} > now())`
|
|
3403
4229
|
).limit(1);
|
|
3404
4230
|
if (rows.length === 0) return null;
|
|
3405
4231
|
return rows[0].value;
|
|
@@ -3417,7 +4243,7 @@ var DrizzleCacheService = class {
|
|
|
3417
4243
|
});
|
|
3418
4244
|
}
|
|
3419
4245
|
async delete(key) {
|
|
3420
|
-
await this.db.delete(cacheEntries).where(
|
|
4246
|
+
await this.db.delete(cacheEntries).where(eq8(cacheEntries.key, key));
|
|
3421
4247
|
}
|
|
3422
4248
|
async invalidateByPrefix(prefix) {
|
|
3423
4249
|
const escaped = prefix.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -3450,8 +4276,8 @@ var DrizzleCacheService = class {
|
|
|
3450
4276
|
async deleteExpired() {
|
|
3451
4277
|
try {
|
|
3452
4278
|
await this.db.delete(cacheEntries).where(
|
|
3453
|
-
|
|
3454
|
-
gt2(
|
|
4279
|
+
or3(
|
|
4280
|
+
gt2(sql7`now()`, cacheEntries.expiresAt)
|
|
3455
4281
|
)
|
|
3456
4282
|
);
|
|
3457
4283
|
} catch {
|
|
@@ -3459,14 +4285,14 @@ var DrizzleCacheService = class {
|
|
|
3459
4285
|
}
|
|
3460
4286
|
};
|
|
3461
4287
|
DrizzleCacheService = __decorateClass([
|
|
3462
|
-
|
|
3463
|
-
__decorateParam(0,
|
|
3464
|
-
__decorateParam(1,
|
|
3465
|
-
__decorateParam(1,
|
|
4288
|
+
Injectable14(),
|
|
4289
|
+
__decorateParam(0, Inject13(DRIZZLE)),
|
|
4290
|
+
__decorateParam(1, Optional6()),
|
|
4291
|
+
__decorateParam(1, Inject13(CACHE_DEFAULT_TTL))
|
|
3466
4292
|
], DrizzleCacheService);
|
|
3467
4293
|
|
|
3468
4294
|
// runtime/subsystems/cache/cache.memory-backend.ts
|
|
3469
|
-
import { Injectable as
|
|
4295
|
+
import { Injectable as Injectable15, Inject as Inject14, Optional as Optional7 } from "@nestjs/common";
|
|
3470
4296
|
var MemoryCacheService = class {
|
|
3471
4297
|
constructor(defaultTtl = null) {
|
|
3472
4298
|
this.defaultTtl = defaultTtl;
|
|
@@ -3540,9 +4366,9 @@ var MemoryCacheService = class {
|
|
|
3540
4366
|
}
|
|
3541
4367
|
};
|
|
3542
4368
|
MemoryCacheService = __decorateClass([
|
|
3543
|
-
|
|
3544
|
-
__decorateParam(0,
|
|
3545
|
-
__decorateParam(0,
|
|
4369
|
+
Injectable15(),
|
|
4370
|
+
__decorateParam(0, Optional7()),
|
|
4371
|
+
__decorateParam(0, Inject14(CACHE_DEFAULT_TTL))
|
|
3546
4372
|
], MemoryCacheService);
|
|
3547
4373
|
|
|
3548
4374
|
// runtime/subsystems/cache/cache.module.ts
|
|
@@ -3802,24 +4628,27 @@ var OBSERVABILITY_MODULE_OPTIONS = "OBSERVABILITY_MODULE_OPTIONS";
|
|
|
3802
4628
|
import { Module as Module6 } from "@nestjs/common";
|
|
3803
4629
|
|
|
3804
4630
|
// runtime/subsystems/observability/observability.service.ts
|
|
3805
|
-
import { Inject as
|
|
4631
|
+
import { Inject as Inject15, Injectable as Injectable16, Optional as Optional8 } from "@nestjs/common";
|
|
3806
4632
|
|
|
3807
4633
|
// runtime/subsystems/sync/sync.tokens.ts
|
|
3808
4634
|
var SYNC_CURSOR_STORE = "SYNC_CURSOR_STORE";
|
|
3809
4635
|
var SYNC_RUN_RECORDER = "SYNC_RUN_RECORDER";
|
|
3810
4636
|
|
|
3811
4637
|
// runtime/subsystems/observability/observability.service.ts
|
|
4638
|
+
var MAX_TIMELINE_PAGES = 50;
|
|
3812
4639
|
var ObservabilityService = class {
|
|
3813
|
-
constructor(jobRuns2, bridge, syncRuns, cursors) {
|
|
4640
|
+
constructor(jobRuns2, bridge, syncRuns, cursors, events) {
|
|
3814
4641
|
this.jobRuns = jobRuns2;
|
|
3815
4642
|
this.bridge = bridge;
|
|
3816
4643
|
this.syncRuns = syncRuns;
|
|
3817
4644
|
this.cursors = cursors;
|
|
4645
|
+
this.events = events;
|
|
3818
4646
|
}
|
|
3819
4647
|
jobRuns;
|
|
3820
4648
|
bridge;
|
|
3821
4649
|
syncRuns;
|
|
3822
4650
|
cursors;
|
|
4651
|
+
events;
|
|
3823
4652
|
async getPoolDepths(tenantId) {
|
|
3824
4653
|
if (!this.jobRuns) return [];
|
|
3825
4654
|
return this.jobRuns.countByPoolAndStatus(tenantId);
|
|
@@ -3840,6 +4669,96 @@ var ObservabilityService = class {
|
|
|
3840
4669
|
if (!this.cursors) return [];
|
|
3841
4670
|
return this.cursors.listAll(tenantId);
|
|
3842
4671
|
}
|
|
4672
|
+
async listJobRuns(query) {
|
|
4673
|
+
if (!this.jobRuns) {
|
|
4674
|
+
return { ...ObservabilityService.EMPTY_JOB_RUN_PAGE };
|
|
4675
|
+
}
|
|
4676
|
+
return this.jobRuns.listJobRuns(query);
|
|
4677
|
+
}
|
|
4678
|
+
async listEvents(query) {
|
|
4679
|
+
if (!this.events) {
|
|
4680
|
+
return { ...ObservabilityService.EMPTY_EVENT_PAGE };
|
|
4681
|
+
}
|
|
4682
|
+
return this.events.listEvents(query);
|
|
4683
|
+
}
|
|
4684
|
+
async getCorrelationTimeline(rootRunId, tenantId) {
|
|
4685
|
+
const runs = await this.collectRuns(rootRunId, tenantId);
|
|
4686
|
+
const events = await this.collectEvents(rootRunId, tenantId);
|
|
4687
|
+
const entries = [
|
|
4688
|
+
...runs.map(
|
|
4689
|
+
(run) => ({
|
|
4690
|
+
kind: "job_run",
|
|
4691
|
+
at: run.createdAt,
|
|
4692
|
+
run
|
|
4693
|
+
})
|
|
4694
|
+
),
|
|
4695
|
+
...events.map(
|
|
4696
|
+
(event) => ({
|
|
4697
|
+
kind: "event",
|
|
4698
|
+
at: event.occurredAt,
|
|
4699
|
+
event
|
|
4700
|
+
})
|
|
4701
|
+
)
|
|
4702
|
+
];
|
|
4703
|
+
entries.sort((a, b) => {
|
|
4704
|
+
const dt = a.at.getTime() - b.at.getTime();
|
|
4705
|
+
if (dt !== 0) return dt;
|
|
4706
|
+
if (a.kind === b.kind) return 0;
|
|
4707
|
+
return a.kind === "job_run" ? -1 : 1;
|
|
4708
|
+
});
|
|
4709
|
+
const startedAt = entries.length > 0 ? entries[0].at : null;
|
|
4710
|
+
const lastActivityAt = entries.length > 0 ? entries[entries.length - 1].at : null;
|
|
4711
|
+
return {
|
|
4712
|
+
rootRunId,
|
|
4713
|
+
entries,
|
|
4714
|
+
summary: {
|
|
4715
|
+
runCount: runs.length,
|
|
4716
|
+
eventCount: events.length,
|
|
4717
|
+
startedAt,
|
|
4718
|
+
lastActivityAt
|
|
4719
|
+
}
|
|
4720
|
+
};
|
|
4721
|
+
}
|
|
4722
|
+
/**
|
|
4723
|
+
* Drain every `job_run` sharing `rootRunId` by walking the keyset cursor.
|
|
4724
|
+
* Empty when the jobs subsystem is absent.
|
|
4725
|
+
*/
|
|
4726
|
+
async collectRuns(rootRunId, tenantId) {
|
|
4727
|
+
if (!this.jobRuns) return [];
|
|
4728
|
+
const out = [];
|
|
4729
|
+
let cursor;
|
|
4730
|
+
for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {
|
|
4731
|
+
const result = await this.jobRuns.listJobRuns({
|
|
4732
|
+
rootRunId,
|
|
4733
|
+
tenantId,
|
|
4734
|
+
cursor
|
|
4735
|
+
});
|
|
4736
|
+
out.push(...result.items);
|
|
4737
|
+
if (!result.nextCursor) break;
|
|
4738
|
+
cursor = result.nextCursor;
|
|
4739
|
+
}
|
|
4740
|
+
return out;
|
|
4741
|
+
}
|
|
4742
|
+
/**
|
|
4743
|
+
* Drain every `domain_event` whose `metadata.rootRunId` matches by walking
|
|
4744
|
+
* the keyset cursor. Empty when the events read port is absent.
|
|
4745
|
+
*/
|
|
4746
|
+
async collectEvents(rootRunId, tenantId) {
|
|
4747
|
+
if (!this.events) return [];
|
|
4748
|
+
const out = [];
|
|
4749
|
+
let cursor;
|
|
4750
|
+
for (let page = 0; page < MAX_TIMELINE_PAGES; page += 1) {
|
|
4751
|
+
const result = await this.events.listEvents({
|
|
4752
|
+
rootRunId,
|
|
4753
|
+
tenantId,
|
|
4754
|
+
cursor
|
|
4755
|
+
});
|
|
4756
|
+
out.push(...result.items);
|
|
4757
|
+
if (!result.nextCursor) break;
|
|
4758
|
+
cursor = result.nextCursor;
|
|
4759
|
+
}
|
|
4760
|
+
return out;
|
|
4761
|
+
}
|
|
3843
4762
|
};
|
|
3844
4763
|
/**
|
|
3845
4764
|
* All-zero histogram used when the bridge subsystem is absent. Matches
|
|
@@ -3852,23 +4771,34 @@ __publicField(ObservabilityService, "EMPTY_HISTOGRAM", {
|
|
|
3852
4771
|
skipped: 0,
|
|
3853
4772
|
failed: 0
|
|
3854
4773
|
});
|
|
4774
|
+
/** Empty page used when a sibling read port is absent. */
|
|
4775
|
+
__publicField(ObservabilityService, "EMPTY_JOB_RUN_PAGE", {
|
|
4776
|
+
items: [],
|
|
4777
|
+
nextCursor: null
|
|
4778
|
+
});
|
|
4779
|
+
__publicField(ObservabilityService, "EMPTY_EVENT_PAGE", {
|
|
4780
|
+
items: [],
|
|
4781
|
+
nextCursor: null
|
|
4782
|
+
});
|
|
3855
4783
|
ObservabilityService = __decorateClass([
|
|
3856
|
-
|
|
3857
|
-
__decorateParam(0,
|
|
3858
|
-
__decorateParam(0,
|
|
3859
|
-
__decorateParam(1,
|
|
3860
|
-
__decorateParam(1,
|
|
3861
|
-
__decorateParam(2,
|
|
3862
|
-
__decorateParam(2,
|
|
3863
|
-
__decorateParam(3,
|
|
3864
|
-
__decorateParam(3,
|
|
4784
|
+
Injectable16(),
|
|
4785
|
+
__decorateParam(0, Optional8()),
|
|
4786
|
+
__decorateParam(0, Inject15(JOB_RUN_SERVICE)),
|
|
4787
|
+
__decorateParam(1, Optional8()),
|
|
4788
|
+
__decorateParam(1, Inject15(BRIDGE_DELIVERY_REPO)),
|
|
4789
|
+
__decorateParam(2, Optional8()),
|
|
4790
|
+
__decorateParam(2, Inject15(SYNC_RUN_RECORDER)),
|
|
4791
|
+
__decorateParam(3, Optional8()),
|
|
4792
|
+
__decorateParam(3, Inject15(SYNC_CURSOR_STORE)),
|
|
4793
|
+
__decorateParam(4, Optional8()),
|
|
4794
|
+
__decorateParam(4, Inject15(EVENT_READ_PORT))
|
|
3865
4795
|
], ObservabilityService);
|
|
3866
4796
|
|
|
3867
4797
|
// runtime/subsystems/observability/reporters/bridge-metrics.reporter.ts
|
|
3868
4798
|
import {
|
|
3869
|
-
Inject as
|
|
3870
|
-
Injectable as
|
|
3871
|
-
Logger as
|
|
4799
|
+
Inject as Inject16,
|
|
4800
|
+
Injectable as Injectable17,
|
|
4801
|
+
Logger as Logger10
|
|
3872
4802
|
} from "@nestjs/common";
|
|
3873
4803
|
var BridgeMetricsReporter = class {
|
|
3874
4804
|
constructor(observability, options) {
|
|
@@ -3876,7 +4806,7 @@ var BridgeMetricsReporter = class {
|
|
|
3876
4806
|
this.config = options.reporters?.bridgeMetrics;
|
|
3877
4807
|
}
|
|
3878
4808
|
observability;
|
|
3879
|
-
logger = new
|
|
4809
|
+
logger = new Logger10(BridgeMetricsReporter.name);
|
|
3880
4810
|
handle = null;
|
|
3881
4811
|
config;
|
|
3882
4812
|
onModuleInit() {
|
|
@@ -3927,9 +4857,9 @@ var BridgeMetricsReporter = class {
|
|
|
3927
4857
|
}
|
|
3928
4858
|
};
|
|
3929
4859
|
BridgeMetricsReporter = __decorateClass([
|
|
3930
|
-
|
|
3931
|
-
__decorateParam(0,
|
|
3932
|
-
__decorateParam(1,
|
|
4860
|
+
Injectable17(),
|
|
4861
|
+
__decorateParam(0, Inject16(OBSERVABILITY)),
|
|
4862
|
+
__decorateParam(1, Inject16(OBSERVABILITY_MODULE_OPTIONS))
|
|
3933
4863
|
], BridgeMetricsReporter);
|
|
3934
4864
|
|
|
3935
4865
|
// runtime/subsystems/observability/observability.module.ts
|
|
@@ -4223,7 +5153,7 @@ var MemoryOAuthStateStore = class {
|
|
|
4223
5153
|
|
|
4224
5154
|
// runtime/subsystems/auth/backends/state-store.drizzle-backend.ts
|
|
4225
5155
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
4226
|
-
import { eq as
|
|
5156
|
+
import { eq as eq9 } from "drizzle-orm";
|
|
4227
5157
|
var DrizzleOAuthStateStore = class {
|
|
4228
5158
|
constructor(db, opts = {}) {
|
|
4229
5159
|
this.db = db;
|
|
@@ -4247,7 +5177,7 @@ var DrizzleOAuthStateStore = class {
|
|
|
4247
5177
|
return state;
|
|
4248
5178
|
}
|
|
4249
5179
|
async consume(state) {
|
|
4250
|
-
const rows = await this.db.delete(authOAuthState).where(
|
|
5180
|
+
const rows = await this.db.delete(authOAuthState).where(eq9(authOAuthState.state, state)).returning();
|
|
4251
5181
|
const row = rows[0];
|
|
4252
5182
|
if (!row) {
|
|
4253
5183
|
throw new OAuthStateError(
|
|
@@ -4269,7 +5199,7 @@ var DrizzleOAuthStateStore = class {
|
|
|
4269
5199
|
import {
|
|
4270
5200
|
Controller,
|
|
4271
5201
|
Get,
|
|
4272
|
-
Inject as
|
|
5202
|
+
Inject as Inject17,
|
|
4273
5203
|
Param,
|
|
4274
5204
|
Query,
|
|
4275
5205
|
Req,
|
|
@@ -4371,13 +5301,17 @@ __decorateClass([
|
|
|
4371
5301
|
], AuthController.prototype, "callback", 1);
|
|
4372
5302
|
AuthController = __decorateClass([
|
|
4373
5303
|
Controller("auth"),
|
|
4374
|
-
__decorateParam(0,
|
|
4375
|
-
__decorateParam(1,
|
|
4376
|
-
__decorateParam(2,
|
|
4377
|
-
__decorateParam(3,
|
|
4378
|
-
__decorateParam(4,
|
|
5304
|
+
__decorateParam(0, Inject17(STRATEGY_REGISTRY)),
|
|
5305
|
+
__decorateParam(1, Inject17(AUTH_USER_CONTEXT)),
|
|
5306
|
+
__decorateParam(2, Inject17(OAUTH_STATE_STORE)),
|
|
5307
|
+
__decorateParam(3, Inject17(AUTH_INTEGRATION_GRANT_SINK)),
|
|
5308
|
+
__decorateParam(4, Inject17(AUTH_OPTIONS))
|
|
4379
5309
|
], AuthController);
|
|
4380
5310
|
|
|
5311
|
+
// runtime/base-classes/tenant-context.ts
|
|
5312
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
5313
|
+
var als = new AsyncLocalStorage();
|
|
5314
|
+
|
|
4381
5315
|
// runtime/subsystems/auth/auth.module.ts
|
|
4382
5316
|
import { Module as Module7 } from "@nestjs/common";
|
|
4383
5317
|
function resolveEncryptionKeyProvider(choice) {
|