@pattern-stack/codegen 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/consumer-skills/events/typed-bus-and-outbox.md +1 -1
- package/consumer-skills/subsystems/SKILL.md +56 -0
- package/dist/runtime/subsystems/bridge/bridge.module.d.ts +0 -1
- package/dist/runtime/subsystems/bridge/bridge.module.js +294 -710
- package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
- package/dist/runtime/subsystems/bridge/index.d.ts +0 -1
- package/dist/runtime/subsystems/bridge/index.js +248 -664
- package/dist/runtime/subsystems/bridge/index.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +18 -10
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/events.module.js +43 -244
- package/dist/runtime/subsystems/events/events.module.js.map +1 -1
- package/dist/runtime/subsystems/events/index.d.ts +0 -1
- package/dist/runtime/subsystems/events/index.js +39 -241
- package/dist/runtime/subsystems/events/index.js.map +1 -1
- package/dist/runtime/subsystems/index.js +174 -791
- package/dist/runtime/subsystems/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +22 -3
- package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
- package/dist/runtime/subsystems/jobs/index.d.ts +1 -4
- package/dist/runtime/subsystems/jobs/index.js +87 -506
- package/dist/runtime/subsystems/jobs/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +3 -0
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +11 -4
- package/dist/runtime/subsystems/jobs/job-worker.module.js +248 -664
- package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +0 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +89 -391
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
- package/dist/src/cli/index.js +152 -35
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/runtime/subsystems/events/event-bus.drizzle-backend.ts +32 -10
- package/runtime/subsystems/events/events.module.ts +38 -6
- package/runtime/subsystems/events/index.ts +7 -1
- package/runtime/subsystems/jobs/bullmq.config.ts +23 -3
- package/runtime/subsystems/jobs/index.ts +13 -8
- package/runtime/subsystems/jobs/job-worker.bullmq-backend.ts +5 -2
- package/runtime/subsystems/jobs/job-worker.module.ts +27 -7
- package/runtime/subsystems/jobs/jobs-domain.module.ts +27 -2
- package/templates/subsystem/events/domain-events.schema.ejs.t +43 -2
|
@@ -13,6 +13,9 @@ var __decorateParam = (index2, decorator) => (target, key) => decorator(target,
|
|
|
13
13
|
// runtime/subsystems/jobs/jobs-domain.module.ts
|
|
14
14
|
import { Module } from "@nestjs/common";
|
|
15
15
|
|
|
16
|
+
// runtime/constants/tokens.ts
|
|
17
|
+
var DRIZZLE = "DRIZZLE";
|
|
18
|
+
|
|
16
19
|
// runtime/subsystems/jobs/jobs-domain.tokens.ts
|
|
17
20
|
var JOB_ORCHESTRATOR = /* @__PURE__ */ Symbol("JOB_ORCHESTRATOR");
|
|
18
21
|
var JOB_RUN_SERVICE = /* @__PURE__ */ Symbol("JOB_RUN_SERVICE");
|
|
@@ -24,9 +27,6 @@ import { randomUUID } from "crypto";
|
|
|
24
27
|
import { Inject, Injectable, Logger } from "@nestjs/common";
|
|
25
28
|
import { and, desc, eq, gt, inArray, isNotNull, ne, notInArray, sql as sql2 } from "drizzle-orm";
|
|
26
29
|
|
|
27
|
-
// runtime/constants/tokens.ts
|
|
28
|
-
var DRIZZLE = "DRIZZLE";
|
|
29
|
-
|
|
30
30
|
// runtime/subsystems/jobs/job-orchestration.schema.ts
|
|
31
31
|
import {
|
|
32
32
|
pgEnum,
|
|
@@ -834,384 +834,9 @@ DrizzleJobStepService = __decorateClass([
|
|
|
834
834
|
__decorateParam(0, Inject3(DRIZZLE))
|
|
835
835
|
], DrizzleJobStepService);
|
|
836
836
|
|
|
837
|
-
// runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts
|
|
838
|
-
import { createHash } from "crypto";
|
|
839
|
-
import { Inject as Inject4, Injectable as Injectable4, Logger as Logger2, Optional } from "@nestjs/common";
|
|
840
|
-
import { eq as eq4 } from "drizzle-orm";
|
|
841
|
-
|
|
842
|
-
// runtime/subsystems/jobs/pool-config.loader.ts
|
|
843
|
-
import { existsSync, readFileSync } from "fs";
|
|
844
|
-
import { resolve } from "path";
|
|
845
|
-
import { parse as parseYaml } from "yaml";
|
|
846
|
-
var FRAMEWORK_POOLS = Object.freeze({
|
|
847
|
-
events_inbound: Object.freeze({
|
|
848
|
-
queue: "jobs-events-inbound",
|
|
849
|
-
concurrency: 20,
|
|
850
|
-
reserved: true,
|
|
851
|
-
description: "Inbound events drain (events subsystem outbox)."
|
|
852
|
-
}),
|
|
853
|
-
events_change: Object.freeze({
|
|
854
|
-
queue: "jobs-events-change",
|
|
855
|
-
concurrency: 30,
|
|
856
|
-
reserved: true,
|
|
857
|
-
description: "Change events drain (events subsystem outbox)."
|
|
858
|
-
}),
|
|
859
|
-
events_outbound: Object.freeze({
|
|
860
|
-
queue: "jobs-events-outbound",
|
|
861
|
-
concurrency: 10,
|
|
862
|
-
reserved: true,
|
|
863
|
-
description: "Outbound events drain (events subsystem outbox)."
|
|
864
|
-
}),
|
|
865
|
-
interactive: Object.freeze({
|
|
866
|
-
queue: "jobs-interactive",
|
|
867
|
-
concurrency: 20,
|
|
868
|
-
reserved: false,
|
|
869
|
-
description: "User-facing latency-sensitive jobs."
|
|
870
|
-
}),
|
|
871
|
-
batch: Object.freeze({
|
|
872
|
-
queue: "jobs-batch",
|
|
873
|
-
concurrency: 5,
|
|
874
|
-
reserved: false,
|
|
875
|
-
description: "Default pool for background jobs."
|
|
876
|
-
})
|
|
877
|
-
});
|
|
878
|
-
var RESERVED_POOL_NAMES = new Set(
|
|
879
|
-
Object.entries(FRAMEWORK_POOLS).filter(([, def]) => def.reserved).map(([name]) => name)
|
|
880
|
-
);
|
|
881
|
-
var cache = /* @__PURE__ */ new Map();
|
|
882
|
-
function loadPoolConfig(configPath) {
|
|
883
|
-
const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);
|
|
884
|
-
const cached = cache.get(resolved);
|
|
885
|
-
if (cached) return cached;
|
|
886
|
-
const merged = /* @__PURE__ */ new Map();
|
|
887
|
-
for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {
|
|
888
|
-
merged.set(name, { ...def });
|
|
889
|
-
}
|
|
890
|
-
if (!existsSync(resolved)) {
|
|
891
|
-
cache.set(resolved, merged);
|
|
892
|
-
return merged;
|
|
893
|
-
}
|
|
894
|
-
let raw;
|
|
895
|
-
try {
|
|
896
|
-
raw = parseYaml(readFileSync(resolved, "utf8"));
|
|
897
|
-
} catch (err) {
|
|
898
|
-
throw new Error(
|
|
899
|
-
`pool-config.loader: failed to parse YAML at ${resolved}: ${err.message}`
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
|
-
const userPools = extractUserPools(raw);
|
|
903
|
-
for (const [name, userDef] of Object.entries(userPools)) {
|
|
904
|
-
const existing = merged.get(name);
|
|
905
|
-
if (existing) {
|
|
906
|
-
const next = {
|
|
907
|
-
queue: existing.queue,
|
|
908
|
-
concurrency: typeof userDef.concurrency === "number" ? userDef.concurrency : existing.concurrency,
|
|
909
|
-
reserved: existing.reserved,
|
|
910
|
-
description: userDef.description ?? existing.description
|
|
911
|
-
};
|
|
912
|
-
merged.set(name, next);
|
|
913
|
-
continue;
|
|
914
|
-
}
|
|
915
|
-
if (typeof userDef.queue !== "string" || userDef.queue.length === 0) {
|
|
916
|
-
throw new Error(
|
|
917
|
-
`pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`
|
|
918
|
-
);
|
|
919
|
-
}
|
|
920
|
-
if (typeof userDef.concurrency !== "number" || userDef.concurrency <= 0) {
|
|
921
|
-
throw new Error(
|
|
922
|
-
`pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`
|
|
923
|
-
);
|
|
924
|
-
}
|
|
925
|
-
if (userDef.reserved === true) {
|
|
926
|
-
throw new Error(
|
|
927
|
-
`pool-config.loader: user-defined pool '${name}' cannot set 'reserved: true' \u2014 reserved is framework-only.`
|
|
928
|
-
);
|
|
929
|
-
}
|
|
930
|
-
merged.set(name, {
|
|
931
|
-
queue: userDef.queue,
|
|
932
|
-
concurrency: userDef.concurrency,
|
|
933
|
-
reserved: false,
|
|
934
|
-
description: userDef.description
|
|
935
|
-
});
|
|
936
|
-
}
|
|
937
|
-
cache.set(resolved, merged);
|
|
938
|
-
return merged;
|
|
939
|
-
}
|
|
940
|
-
function extractUserPools(raw) {
|
|
941
|
-
if (!raw || typeof raw !== "object") return {};
|
|
942
|
-
const jobs2 = raw.jobs;
|
|
943
|
-
if (!jobs2 || typeof jobs2 !== "object") return {};
|
|
944
|
-
const pools = jobs2.pools;
|
|
945
|
-
if (!pools || typeof pools !== "object") return {};
|
|
946
|
-
const out = {};
|
|
947
|
-
for (const [name, def] of Object.entries(pools)) {
|
|
948
|
-
if (!def || typeof def !== "object") continue;
|
|
949
|
-
out[name] = def;
|
|
950
|
-
}
|
|
951
|
-
return out;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// runtime/subsystems/jobs/bullmq.config.ts
|
|
955
|
-
var BULLMQ_CONNECTION = /* @__PURE__ */ Symbol("BULLMQ_CONNECTION");
|
|
956
|
-
var BULLMQ_RESOLVED_CONFIG = /* @__PURE__ */ Symbol("BULLMQ_RESOLVED_CONFIG");
|
|
957
|
-
var DEFAULT_REDIS_URL = "redis://localhost:6379";
|
|
958
|
-
var DEFAULT_BULL_BOARD_MOUNT = "/admin/queues";
|
|
959
|
-
function resolveBullMqConfig(ext) {
|
|
960
|
-
const url = ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;
|
|
961
|
-
const resolved = {
|
|
962
|
-
connection: { url },
|
|
963
|
-
queuePrefix: ext?.queue_prefix
|
|
964
|
-
};
|
|
965
|
-
if (ext?.bull_board?.enabled) {
|
|
966
|
-
resolved.bullBoard = {
|
|
967
|
-
enabled: true,
|
|
968
|
-
mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
return resolved;
|
|
972
|
-
}
|
|
973
|
-
function resolvePoolQueueName(pool, config, poolConfig = loadPoolConfig()) {
|
|
974
|
-
const alias = poolConfig.get(pool)?.queue ?? pool;
|
|
975
|
-
const prefix = config?.queuePrefix;
|
|
976
|
-
return prefix ? `${prefix}:${alias}` : alias;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
// runtime/subsystems/jobs/job-orchestrator.bullmq-backend.ts
|
|
980
|
-
function sha1JobId(idempotencyKey) {
|
|
981
|
-
return createHash("sha1").update(idempotencyKey).digest("hex");
|
|
982
|
-
}
|
|
983
|
-
var BullMQJobOrchestrator = class extends DrizzleJobOrchestrator {
|
|
984
|
-
constructor(db, multiTenant, connection, bullConfig = null) {
|
|
985
|
-
super(db, multiTenant);
|
|
986
|
-
this.connection = connection;
|
|
987
|
-
this.bullConfig = bullConfig;
|
|
988
|
-
this.bullDb = db;
|
|
989
|
-
}
|
|
990
|
-
connection;
|
|
991
|
-
bullConfig;
|
|
992
|
-
// TODO(logging-subsystem): swap to ILogger once ADR-028 lands
|
|
993
|
-
bullLogger = new Logger2(BullMQJobOrchestrator.name);
|
|
994
|
-
/** Lazily-opened `Queue` handles, one per pool. */
|
|
995
|
-
queues = /* @__PURE__ */ new Map();
|
|
996
|
-
/** Single FlowProducer for parent/child hierarchies. Lazily opened. */
|
|
997
|
-
_flow = null;
|
|
998
|
-
/**
|
|
999
|
-
* Cached `bullmq` value constructors, populated by `loadBullMq()` on first
|
|
1000
|
-
* use (the `start`/`cancel`/`replay` entrypoints `await` it before touching
|
|
1001
|
-
* a queue). Kept off the import graph so a `drizzle`-only consumer never
|
|
1002
|
-
* resolves the optional `'bullmq'` package.
|
|
1003
|
-
*/
|
|
1004
|
-
QueueCtor = null;
|
|
1005
|
-
FlowProducerCtor = null;
|
|
1006
|
-
bullMqLoad = null;
|
|
1007
|
-
/**
|
|
1008
|
-
* Own reference to the Drizzle client. `DrizzleJobOrchestrator.db` is
|
|
1009
|
-
* `private` (can't be redeclared even privately in a subclass), and the
|
|
1010
|
-
* spec forbids touching that file — so the subclass keeps its own handle
|
|
1011
|
-
* under a distinct name (same instance, passed through to `super`) for the
|
|
1012
|
-
* cancel-cascade snapshot + definition/run loads below.
|
|
1013
|
-
*/
|
|
1014
|
-
bullDb;
|
|
1015
|
-
/**
|
|
1016
|
-
* Lazily load the optional `bullmq` package and cache its value
|
|
1017
|
-
* constructors. Idempotent (single in-flight promise). Throws a friendly,
|
|
1018
|
-
* actionable error when the consumer selected `backend: 'bullmq'` but did
|
|
1019
|
-
* not install the package — mirrors `createRedisClient` in the redis event
|
|
1020
|
-
* backend. Must be `await`ed before any `queueFor`/`flow` access.
|
|
1021
|
-
*/
|
|
1022
|
-
async loadBullMq() {
|
|
1023
|
-
if (this.QueueCtor && this.FlowProducerCtor) return;
|
|
1024
|
-
if (!this.bullMqLoad) {
|
|
1025
|
-
this.bullMqLoad = (async () => {
|
|
1026
|
-
try {
|
|
1027
|
-
const mod = await import("bullmq");
|
|
1028
|
-
this.QueueCtor = mod.Queue;
|
|
1029
|
-
this.FlowProducerCtor = mod.FlowProducer;
|
|
1030
|
-
} catch {
|
|
1031
|
-
throw new Error(
|
|
1032
|
-
'BullMQ backend requires the "bullmq" package. Install it with: npm install bullmq'
|
|
1033
|
-
);
|
|
1034
|
-
}
|
|
1035
|
-
})();
|
|
1036
|
-
}
|
|
1037
|
-
await this.bullMqLoad;
|
|
1038
|
-
}
|
|
1039
|
-
/**
|
|
1040
|
-
* Open (or reuse) the `Queue` for a pool. Synchronous — callers `await
|
|
1041
|
-
* loadBullMq()` first so `QueueCtor` is populated.
|
|
1042
|
-
*/
|
|
1043
|
-
queueFor(pool) {
|
|
1044
|
-
if (!this.QueueCtor) {
|
|
1045
|
-
throw new Error("BullMQJobOrchestrator: queueFor called before loadBullMq()");
|
|
1046
|
-
}
|
|
1047
|
-
const name = resolvePoolQueueName(pool, this.bullConfig);
|
|
1048
|
-
let q = this.queues.get(name);
|
|
1049
|
-
if (!q) {
|
|
1050
|
-
q = new this.QueueCtor(name, { connection: this.connection });
|
|
1051
|
-
this.queues.set(name, q);
|
|
1052
|
-
}
|
|
1053
|
-
return q;
|
|
1054
|
-
}
|
|
1055
|
-
flow() {
|
|
1056
|
-
if (!this.FlowProducerCtor) {
|
|
1057
|
-
throw new Error("BullMQJobOrchestrator: flow called before loadBullMq()");
|
|
1058
|
-
}
|
|
1059
|
-
if (!this._flow) {
|
|
1060
|
-
this._flow = new this.FlowProducerCtor({ connection: this.connection });
|
|
1061
|
-
}
|
|
1062
|
-
return this._flow;
|
|
1063
|
-
}
|
|
1064
|
-
// ==========================================================================
|
|
1065
|
-
// start — Postgres insert (super) + BullMQ dispatch
|
|
1066
|
-
// ==========================================================================
|
|
1067
|
-
async start(type, input, opts = {}, tx) {
|
|
1068
|
-
const run = await super.start(type, input, opts, tx);
|
|
1069
|
-
await this.dispatch(run, type);
|
|
1070
|
-
return run;
|
|
1071
|
-
}
|
|
1072
|
-
/**
|
|
1073
|
-
* Map a `job_run` row onto a BullMQ job via `queue.add`. When the run has a
|
|
1074
|
-
* `parentRunId` we attach it to the parent's existing BullMQ job through the
|
|
1075
|
-
* `parent: { id, queue }` opt — BullMQ then tracks the parent/child link in
|
|
1076
|
-
* its own graph. (The FlowProducer is reserved for whole-tree atomic
|
|
1077
|
-
* submits, exposed as an opt-in extension via `flowProducer()`; runtime
|
|
1078
|
-
* `ctx.spawnChild` is incremental, so `queue.add` with a parent ref is the
|
|
1079
|
-
* correct primitive here.)
|
|
1080
|
-
*
|
|
1081
|
-
* The `jobId` is colon-safe + stable: `sha1(dedupeKey)` when a dedupe key is
|
|
1082
|
-
* present (so the same logical key dedups), else the `job_run.id` UUID
|
|
1083
|
-
* (already colon-free).
|
|
1084
|
-
*
|
|
1085
|
-
* The domain `parentClosePolicy` cascade is still enforced in Postgres by
|
|
1086
|
-
* the shared `cancel` path — BullMQ's parent link is dispatch bookkeeping,
|
|
1087
|
-
* not the authority.
|
|
1088
|
-
*/
|
|
1089
|
-
async dispatch(run, type) {
|
|
1090
|
-
await this.loadBullMq();
|
|
1091
|
-
const def = await this.loadDefinition(type);
|
|
1092
|
-
const jobId = run.dedupeKey ? sha1JobId(run.dedupeKey) : run.id;
|
|
1093
|
-
const jobOpts = {
|
|
1094
|
-
jobId,
|
|
1095
|
-
...this.retryOpts(def),
|
|
1096
|
-
...this.dedupeOpts(run, def)
|
|
1097
|
-
};
|
|
1098
|
-
if (run.parentRunId) {
|
|
1099
|
-
const parentRow = await this.loadRun(run.parentRunId);
|
|
1100
|
-
if (parentRow) {
|
|
1101
|
-
const parentJobId = parentRow.dedupeKey ? sha1JobId(parentRow.dedupeKey) : parentRow.id;
|
|
1102
|
-
jobOpts.parent = {
|
|
1103
|
-
id: parentJobId,
|
|
1104
|
-
queue: resolvePoolQueueName(parentRow.pool, this.bullConfig)
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
const payload = { runId: run.id, type, input: run.input };
|
|
1109
|
-
await this.queueFor(run.pool).add(type, payload, jobOpts);
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* Opt-in extension (spec §Extensions): expose the FlowProducer for
|
|
1113
|
-
* consumers that want to submit a whole parent/child DAG atomically up
|
|
1114
|
-
* front, rather than incrementally via `ctx.spawnChild`. Backend-specific —
|
|
1115
|
-
* code using it is not portable to the Drizzle backend. Async because it
|
|
1116
|
-
* lazily loads the optional `bullmq` package on first use.
|
|
1117
|
-
*/
|
|
1118
|
-
async flowProducer() {
|
|
1119
|
-
await this.loadBullMq();
|
|
1120
|
-
return this.flow();
|
|
1121
|
-
}
|
|
1122
|
-
retryOpts(def) {
|
|
1123
|
-
const policy = def.retryPolicy;
|
|
1124
|
-
if (!policy) return {};
|
|
1125
|
-
return {
|
|
1126
|
-
attempts: policy.attempts,
|
|
1127
|
-
backoff: {
|
|
1128
|
-
type: policy.backoff === "exponential" ? "exponential" : "fixed",
|
|
1129
|
-
delay: policy.baseMs
|
|
1130
|
-
}
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1133
|
-
dedupeOpts(run, def) {
|
|
1134
|
-
if (!run.dedupeKey || !def.dedupeWindowMs) return {};
|
|
1135
|
-
return {
|
|
1136
|
-
deduplication: {
|
|
1137
|
-
id: sha1JobId(run.dedupeKey),
|
|
1138
|
-
ttl: def.dedupeWindowMs
|
|
1139
|
-
}
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
// ==========================================================================
|
|
1143
|
-
// cancel — Postgres cascade (super) + remove from queue
|
|
1144
|
-
// ==========================================================================
|
|
1145
|
-
async cancel(runId, opts = {}) {
|
|
1146
|
-
const target = await this.loadRun(runId);
|
|
1147
|
-
await super.cancel(runId, opts);
|
|
1148
|
-
if (!target) return;
|
|
1149
|
-
await this.loadBullMq();
|
|
1150
|
-
await this.removeFromQueue(target);
|
|
1151
|
-
if (opts.cascade === false) return;
|
|
1152
|
-
const descendants = await this.bullDb.select().from(jobRuns).where(eq4(jobRuns.rootRunId, target.rootRunId));
|
|
1153
|
-
for (const child of descendants) {
|
|
1154
|
-
if (child.id === runId) continue;
|
|
1155
|
-
await this.removeFromQueue(child);
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
async removeFromQueue(run) {
|
|
1159
|
-
const jobId = run.dedupeKey ? sha1JobId(run.dedupeKey) : run.id;
|
|
1160
|
-
try {
|
|
1161
|
-
const job = await this.queueFor(run.pool).getJob(jobId);
|
|
1162
|
-
if (job) await job.remove();
|
|
1163
|
-
} catch (err) {
|
|
1164
|
-
this.bullLogger.warn(
|
|
1165
|
-
`cancel: could not remove BullMQ job ${jobId} (pool=${run.pool}): ${err.message}`
|
|
1166
|
-
);
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
// ==========================================================================
|
|
1170
|
-
// replay — Postgres reset (super) + re-enqueue
|
|
1171
|
-
// ==========================================================================
|
|
1172
|
-
async replay(runId) {
|
|
1173
|
-
const run = await super.replay(runId);
|
|
1174
|
-
await this.dispatch(run, run.jobType);
|
|
1175
|
-
return run;
|
|
1176
|
-
}
|
|
1177
|
-
// ==========================================================================
|
|
1178
|
-
// Internals
|
|
1179
|
-
// ==========================================================================
|
|
1180
|
-
async loadDefinition(type) {
|
|
1181
|
-
const [def] = await this.bullDb.select().from(jobs).where(eq4(jobs.type, type)).limit(1);
|
|
1182
|
-
if (!def) {
|
|
1183
|
-
throw new Error(`BullMQJobOrchestrator: no job definition for '${type}'`);
|
|
1184
|
-
}
|
|
1185
|
-
return def;
|
|
1186
|
-
}
|
|
1187
|
-
async loadRun(id) {
|
|
1188
|
-
const [row] = await this.bullDb.select().from(jobRuns).where(eq4(jobRuns.id, id)).limit(1);
|
|
1189
|
-
return row ?? null;
|
|
1190
|
-
}
|
|
1191
|
-
/** Close all open queue + flow connections. Called on module destroy. */
|
|
1192
|
-
async closeConnections() {
|
|
1193
|
-
for (const q of this.queues.values()) {
|
|
1194
|
-
await q.close().catch(() => void 0);
|
|
1195
|
-
}
|
|
1196
|
-
this.queues.clear();
|
|
1197
|
-
if (this._flow) {
|
|
1198
|
-
await this._flow.close().catch(() => void 0);
|
|
1199
|
-
this._flow = null;
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
};
|
|
1203
|
-
BullMQJobOrchestrator = __decorateClass([
|
|
1204
|
-
Injectable4(),
|
|
1205
|
-
__decorateParam(0, Inject4(DRIZZLE)),
|
|
1206
|
-
__decorateParam(1, Inject4(JOBS_MULTI_TENANT)),
|
|
1207
|
-
__decorateParam(2, Inject4(BULLMQ_CONNECTION)),
|
|
1208
|
-
__decorateParam(3, Optional()),
|
|
1209
|
-
__decorateParam(3, Inject4(BULLMQ_RESOLVED_CONFIG))
|
|
1210
|
-
], BullMQJobOrchestrator);
|
|
1211
|
-
|
|
1212
837
|
// runtime/subsystems/jobs/job-orchestrator.memory-backend.ts
|
|
1213
838
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1214
|
-
import { Inject as
|
|
839
|
+
import { Inject as Inject4, Injectable as Injectable4, Logger as Logger2, Optional } from "@nestjs/common";
|
|
1215
840
|
|
|
1216
841
|
// runtime/subsystems/jobs/job-handler.base.ts
|
|
1217
842
|
var JOB_HANDLER_REGISTRY = /* @__PURE__ */ new Map();
|
|
@@ -1274,7 +899,7 @@ var MemoryJobOrchestrator = class {
|
|
|
1274
899
|
stepService;
|
|
1275
900
|
multiTenant;
|
|
1276
901
|
moduleRef;
|
|
1277
|
-
logger = new
|
|
902
|
+
logger = new Logger2(MemoryJobOrchestrator.name);
|
|
1278
903
|
mutex = new PromiseMutex();
|
|
1279
904
|
handlerRegistry = /* @__PURE__ */ new Map();
|
|
1280
905
|
/**
|
|
@@ -1623,7 +1248,7 @@ var MemoryJobOrchestrator = class {
|
|
|
1623
1248
|
run,
|
|
1624
1249
|
step: this.makeStepFn(run),
|
|
1625
1250
|
spawnChild: this.makeSpawnFn(run),
|
|
1626
|
-
logger: new
|
|
1251
|
+
logger: new Logger2(`JobRun:${run.id}`)
|
|
1627
1252
|
};
|
|
1628
1253
|
const attemptsBefore = run.attempts ?? 0;
|
|
1629
1254
|
try {
|
|
@@ -1796,9 +1421,9 @@ var MemoryJobOrchestrator = class {
|
|
|
1796
1421
|
}
|
|
1797
1422
|
};
|
|
1798
1423
|
MemoryJobOrchestrator = __decorateClass([
|
|
1799
|
-
|
|
1800
|
-
__decorateParam(2,
|
|
1801
|
-
__decorateParam(3,
|
|
1424
|
+
Injectable4(),
|
|
1425
|
+
__decorateParam(2, Inject4(JOBS_MULTI_TENANT)),
|
|
1426
|
+
__decorateParam(3, Optional())
|
|
1802
1427
|
], MemoryJobOrchestrator);
|
|
1803
1428
|
function classifyError(err, policy, currentAttempts) {
|
|
1804
1429
|
if (!policy) return "fail";
|
|
@@ -1832,7 +1457,7 @@ function serialiseError(err, attempt, retryable) {
|
|
|
1832
1457
|
}
|
|
1833
1458
|
|
|
1834
1459
|
// runtime/subsystems/jobs/job-run-service.memory-backend.ts
|
|
1835
|
-
import { Inject as
|
|
1460
|
+
import { Inject as Inject5, Injectable as Injectable5 } from "@nestjs/common";
|
|
1836
1461
|
var NON_TERMINAL_STATUSES2 = [
|
|
1837
1462
|
"pending",
|
|
1838
1463
|
"running",
|
|
@@ -1999,9 +1624,9 @@ var MemoryJobRunService = class {
|
|
|
1999
1624
|
}
|
|
2000
1625
|
};
|
|
2001
1626
|
MemoryJobRunService = __decorateClass([
|
|
2002
|
-
|
|
2003
|
-
__decorateParam(1,
|
|
2004
|
-
__decorateParam(2,
|
|
1627
|
+
Injectable5(),
|
|
1628
|
+
__decorateParam(1, Inject5(JOB_ORCHESTRATOR)),
|
|
1629
|
+
__decorateParam(2, Inject5(JOBS_MULTI_TENANT))
|
|
2005
1630
|
], MemoryJobRunService);
|
|
2006
1631
|
function compareBy(a, b, order) {
|
|
2007
1632
|
switch (order) {
|
|
@@ -2019,7 +1644,7 @@ function compareBy(a, b, order) {
|
|
|
2019
1644
|
|
|
2020
1645
|
// runtime/subsystems/jobs/job-step-service.memory-backend.ts
|
|
2021
1646
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2022
|
-
import { Injectable as
|
|
1647
|
+
import { Injectable as Injectable6 } from "@nestjs/common";
|
|
2023
1648
|
var MemoryJobStepService = class {
|
|
2024
1649
|
constructor(store) {
|
|
2025
1650
|
this.store = store;
|
|
@@ -2112,7 +1737,7 @@ var MemoryJobStepService = class {
|
|
|
2112
1737
|
}
|
|
2113
1738
|
};
|
|
2114
1739
|
MemoryJobStepService = __decorateClass([
|
|
2115
|
-
|
|
1740
|
+
Injectable6()
|
|
2116
1741
|
], MemoryJobStepService);
|
|
2117
1742
|
|
|
2118
1743
|
// runtime/subsystems/jobs/memory-job-store.ts
|
|
@@ -2131,6 +1756,66 @@ var MemoryJobStore = class {
|
|
|
2131
1756
|
}
|
|
2132
1757
|
};
|
|
2133
1758
|
|
|
1759
|
+
// runtime/subsystems/jobs/pool-config.loader.ts
|
|
1760
|
+
import { existsSync, readFileSync } from "fs";
|
|
1761
|
+
import { resolve } from "path";
|
|
1762
|
+
import { parse as parseYaml } from "yaml";
|
|
1763
|
+
var FRAMEWORK_POOLS = Object.freeze({
|
|
1764
|
+
events_inbound: Object.freeze({
|
|
1765
|
+
queue: "jobs-events-inbound",
|
|
1766
|
+
concurrency: 20,
|
|
1767
|
+
reserved: true,
|
|
1768
|
+
description: "Inbound events drain (events subsystem outbox)."
|
|
1769
|
+
}),
|
|
1770
|
+
events_change: Object.freeze({
|
|
1771
|
+
queue: "jobs-events-change",
|
|
1772
|
+
concurrency: 30,
|
|
1773
|
+
reserved: true,
|
|
1774
|
+
description: "Change events drain (events subsystem outbox)."
|
|
1775
|
+
}),
|
|
1776
|
+
events_outbound: Object.freeze({
|
|
1777
|
+
queue: "jobs-events-outbound",
|
|
1778
|
+
concurrency: 10,
|
|
1779
|
+
reserved: true,
|
|
1780
|
+
description: "Outbound events drain (events subsystem outbox)."
|
|
1781
|
+
}),
|
|
1782
|
+
interactive: Object.freeze({
|
|
1783
|
+
queue: "jobs-interactive",
|
|
1784
|
+
concurrency: 20,
|
|
1785
|
+
reserved: false,
|
|
1786
|
+
description: "User-facing latency-sensitive jobs."
|
|
1787
|
+
}),
|
|
1788
|
+
batch: Object.freeze({
|
|
1789
|
+
queue: "jobs-batch",
|
|
1790
|
+
concurrency: 5,
|
|
1791
|
+
reserved: false,
|
|
1792
|
+
description: "Default pool for background jobs."
|
|
1793
|
+
})
|
|
1794
|
+
});
|
|
1795
|
+
var RESERVED_POOL_NAMES = new Set(
|
|
1796
|
+
Object.entries(FRAMEWORK_POOLS).filter(([, def]) => def.reserved).map(([name]) => name)
|
|
1797
|
+
);
|
|
1798
|
+
|
|
1799
|
+
// runtime/subsystems/jobs/bullmq.config.ts
|
|
1800
|
+
var BULLMQ_CONNECTION = /* @__PURE__ */ Symbol("BULLMQ_CONNECTION");
|
|
1801
|
+
var BULLMQ_RESOLVED_CONFIG = /* @__PURE__ */ Symbol("BULLMQ_RESOLVED_CONFIG");
|
|
1802
|
+
var DEFAULT_REDIS_URL = "redis://localhost:6379";
|
|
1803
|
+
var DEFAULT_BULL_BOARD_MOUNT = "/admin/queues";
|
|
1804
|
+
function resolveBullMqConfig(ext) {
|
|
1805
|
+
const url = ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;
|
|
1806
|
+
const resolved = {
|
|
1807
|
+
connection: { url },
|
|
1808
|
+
queuePrefix: ext?.queue_prefix
|
|
1809
|
+
};
|
|
1810
|
+
if (ext?.bull_board?.enabled) {
|
|
1811
|
+
resolved.bullBoard = {
|
|
1812
|
+
enabled: true,
|
|
1813
|
+
mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
return resolved;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
2134
1819
|
// runtime/subsystems/jobs/jobs-domain.module.ts
|
|
2135
1820
|
var JobsDomainModule = class {
|
|
2136
1821
|
static forRoot(opts) {
|
|
@@ -2156,7 +1841,20 @@ var JobsDomainModule = class {
|
|
|
2156
1841
|
const resolved = resolveBullMqConfig(opts.extensions?.bullmq);
|
|
2157
1842
|
providers.push({ provide: BULLMQ_CONNECTION, useValue: resolved.connection });
|
|
2158
1843
|
providers.push({ provide: BULLMQ_RESOLVED_CONFIG, useValue: resolved });
|
|
2159
|
-
providers.push({
|
|
1844
|
+
providers.push({
|
|
1845
|
+
provide: JOB_ORCHESTRATOR,
|
|
1846
|
+
useFactory: async (...args) => {
|
|
1847
|
+
const specifier = "./job-orchestrator.bullmq-backend";
|
|
1848
|
+
const mod = await import(specifier);
|
|
1849
|
+
return new mod.BullMQJobOrchestrator(...args);
|
|
1850
|
+
},
|
|
1851
|
+
// The bullmq orchestrator constructor mirrors DrizzleJobOrchestrator's
|
|
1852
|
+
// injection list: DRIZZLE + JOBS_MULTI_TENANT + the resolved BullMQ
|
|
1853
|
+
// tokens. Importing token references would force a static dep on the
|
|
1854
|
+
// tokens file in this module's import graph; using the existing
|
|
1855
|
+
// symbols already in scope is sufficient.
|
|
1856
|
+
inject: [DRIZZLE, JOBS_MULTI_TENANT, BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG]
|
|
1857
|
+
});
|
|
2160
1858
|
providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });
|
|
2161
1859
|
providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });
|
|
2162
1860
|
} else {
|