@dudousxd/nestjs-durable-transport-db 0.3.1 → 0.3.2

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/dist/index.cjs CHANGED
@@ -83,7 +83,10 @@ function isDeadlock(err) {
83
83
  return codes.some((c) => c === "ER_LOCK_DEADLOCK" || c === 1213 || c === "40P01");
84
84
  }
85
85
  __name(isDeadlock, "isDeadlock");
86
+ var TASKS_INSERT_CHUNK = 200;
86
87
  var RUNS_CLAIM_MAX = 50;
88
+ var RESULTS_IN_FLIGHT = 512;
89
+ var RESULTS_CLAIM_MAX = 256;
87
90
  function numberOption(value, fallback, min) {
88
91
  return value !== void 0 && Number.isFinite(value) && value >= min ? value : fallback;
89
92
  }
@@ -113,6 +116,10 @@ var DbTransport = class {
113
116
  resultLoop;
114
117
  /** Tasks running on this instance (at most `concurrency`). */
115
118
  runningTasks = /* @__PURE__ */ new Set();
119
+ /** Claim rounds of results being handled on this instance — see {@link drainResults}. */
120
+ runningResults = /* @__PURE__ */ new Set();
121
+ /** Results being handled across {@link runningResults} (at most {@link RESULTS_IN_FLIGHT}). */
122
+ resultsInFlight = 0;
116
123
  /** Claim tokens this instance holds, by table — every one is renewed on `renewTimer`. */
117
124
  held = /* @__PURE__ */ new Map();
118
125
  renewTimer;
@@ -214,9 +221,15 @@ var DbTransport = class {
214
221
  }
215
222
  // ── engine → worker ────────────────────────────────────────────────────────────────────────
216
223
  async dispatch(task) {
224
+ await this.dispatchMany([
225
+ task
226
+ ]);
227
+ }
228
+ /** {@link dispatch} for many tasks: one multi-row insert per {@link TASKS_INSERT_CHUNK} tasks. */
229
+ async dispatchMany(tasks) {
230
+ if (tasks.length === 0) return;
217
231
  await this.ensureSchema();
218
232
  const t = this.q(this.tasksTable);
219
- const ignore = this.isPg ? `INSERT INTO ${t} (...) ON CONFLICT (${this.q("step_id")}) DO NOTHING` : `INSERT IGNORE INTO ${t} (...)`;
220
233
  const cols = [
221
234
  "step_id",
222
235
  "run_id",
@@ -228,18 +241,24 @@ var DbTransport = class {
228
241
  "status",
229
242
  "created_at"
230
243
  ];
231
- const sql = ignore.replace("(...)", `(${cols.map((c) => this.q(c)).join(", ")}) VALUES (${cols.map((_, i) => this.ph(i + 1)).join(", ")})`);
232
- await this.exec.query(sql, [
233
- task.stepId,
234
- task.runId,
235
- task.seq,
236
- task.name,
237
- task.group,
238
- JSON.stringify(task.input ?? null),
239
- task.attempt,
240
- "pending",
241
- this.now()
242
- ]);
244
+ for (let at = 0; at < tasks.length; at += TASKS_INSERT_CHUNK) {
245
+ const chunk = tasks.slice(at, at + TASKS_INSERT_CHUNK);
246
+ const values = chunk.map((_, r) => `(${cols.map((__, c) => this.ph(r * cols.length + c + 1)).join(", ")})`).join(", ");
247
+ const into = `${t} (${cols.map((c) => this.q(c)).join(", ")}) VALUES ${values}`;
248
+ const sql = this.isPg ? `INSERT INTO ${into} ON CONFLICT (${this.q("step_id")}) DO NOTHING` : `INSERT IGNORE INTO ${into}`;
249
+ const now = this.now();
250
+ await this.exec.query(sql, chunk.flatMap((task) => [
251
+ task.stepId,
252
+ task.runId,
253
+ task.seq,
254
+ task.name,
255
+ task.group,
256
+ JSON.stringify(task.input ?? null),
257
+ task.attempt,
258
+ "pending",
259
+ now
260
+ ]));
261
+ }
243
262
  }
244
263
  // ── worker → engine ────────────────────────────────────────────────────────────────────────
245
264
  handle(name, fn) {
@@ -534,43 +553,61 @@ var DbTransport = class {
534
553
  });
535
554
  }
536
555
  /**
537
- * Claim a batch of results, deliver each to the engine, and delete it — only after its handler ran
538
- * (at-least-once), and only while this round still holds the claim (a newer result for the same
539
- * step that replaced the row resets the claim, so it is delivered next instead of deleted). The
540
- * batch's claim is renewed until it is done. A throwing handler is reported and its row left for
541
- * re-delivery after the lease; the rest of the batch continues.
556
+ * Claim a batch of results and hand them all to the engine at once, then delete the ones whose
557
+ * handler resolved — only after it did (at-least-once), and only while this round still holds the
558
+ * claim (a newer result for the same step that replaced the row resets the claim, so it is
559
+ * delivered next instead of deleted). The next round is claimed while this one runs, up to
560
+ * {@link RESULTS_IN_FLIGHT} results; the round's claim is renewed until it is done. A throwing
561
+ * handler is reported and its row left for re-delivery after the lease.
542
562
  */
543
563
  async drainResults(handler) {
544
- const { rows, token } = await this.claim(this.resultsTable, this.batchSize, "1 = 1", []);
564
+ const free = RESULTS_IN_FLIGHT - this.resultsInFlight;
565
+ if (free <= 0) {
566
+ await Promise.race(this.runningResults);
567
+ return 1;
568
+ }
569
+ const { rows, token } = await this.claim(this.resultsTable, Math.min(free, Math.max(this.batchSize, RESULTS_CLAIM_MAX)), "1 = 1", []);
545
570
  if (rows.length === 0) return 0;
546
571
  this.hold(this.resultsTable, token);
547
- try {
548
- for (const row of rows) {
549
- if (!this.running) break;
550
- try {
551
- const result = {
552
- runId: row.run_id,
553
- seq: Number(row.seq),
554
- stepId: row.step_id,
555
- status: row.status,
556
- output: row.output == null ? void 0 : JSON.parse(row.output),
557
- error: row.error == null ? void 0 : JSON.parse(row.error),
558
- startedAt: row.started_at == null ? void 0 : Number(row.started_at)
559
- };
560
- await handler(result);
561
- await this.exec.query(`DELETE FROM ${this.q(this.resultsTable)} WHERE ${this.q("step_id")} = ${this.ph(1)} AND ${this.q("claimed_by")} = ${this.ph(2)}`, [
562
- row.step_id,
563
- token
564
- ]);
565
- } catch (err) {
566
- this.onError(err);
567
- }
568
- }
569
- } finally {
572
+ this.resultsInFlight += rows.length;
573
+ const round = this.handleResults(rows, token, handler).finally(() => {
574
+ this.resultsInFlight -= rows.length;
575
+ this.runningResults.delete(round);
570
576
  this.release(this.resultsTable, token);
571
- }
577
+ });
578
+ this.runningResults.add(round);
572
579
  return rows.length;
573
580
  }
581
+ /** Hand one claimed round of results to the engine, then delete the delivered ones in one statement. */
582
+ async handleResults(rows, token, handler) {
583
+ const delivered = [];
584
+ await Promise.all(rows.map(async (row) => {
585
+ try {
586
+ await handler({
587
+ runId: row.run_id,
588
+ seq: Number(row.seq),
589
+ stepId: row.step_id,
590
+ status: row.status,
591
+ output: row.output == null ? void 0 : JSON.parse(row.output),
592
+ error: row.error == null ? void 0 : JSON.parse(row.error),
593
+ startedAt: row.started_at == null ? void 0 : Number(row.started_at)
594
+ });
595
+ delivered.push(row.step_id);
596
+ } catch (err) {
597
+ this.onError(err);
598
+ }
599
+ }));
600
+ if (delivered.length === 0) return;
601
+ try {
602
+ const ids = delivered.map((_, i) => this.ph(i + 2)).join(", ");
603
+ await this.exec.query(`DELETE FROM ${this.q(this.resultsTable)} WHERE ${this.q("claimed_by")} = ${this.ph(1)} AND ${this.q("step_id")} IN (${ids})`, [
604
+ token,
605
+ ...delivered
606
+ ]);
607
+ } catch (err) {
608
+ this.onError(err);
609
+ }
610
+ }
574
611
  /**
575
612
  * Atomically claim up to `limit` un-leased rows matching `where` and stamp them with a claim token
576
613
  * unique to this round (`<instanceId>:<uuid>`). `SELECT … FOR UPDATE SKIP LOCKED` inside the txn
@@ -708,7 +745,8 @@ var DbTransport = class {
708
745
  ].map((c) => this.stopRunConsumer(c))
709
746
  ]);
710
747
  await Promise.allSettled([
711
- ...this.runningTasks
748
+ ...this.runningTasks,
749
+ ...this.runningResults
712
750
  ]);
713
751
  if (this.renewTimer) clearInterval(this.renewTimer);
714
752
  this.renewTimer = void 0;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/db-transport.ts","../src/executors.ts"],"sourcesContent":["export * from './db-transport';\n","import { randomUUID } from 'node:crypto';\nimport {\n type Heartbeat,\n type RemoteTask,\n type RunConsumerOptions,\n type RunDispatch,\n type RunQueueOptions,\n type StepHandler,\n type StepResult,\n type Transport,\n runStepHandler,\n} from '@dudousxd/nestjs-durable-core';\nimport type { SqlExecutor, SqlTx } from './executors';\n\nexport { mikroOrmExecutor, type SqlExecutor, type SqlTx, typeOrmExecutor } from './executors';\n\n/** Raw row shapes returned by `SELECT *` (declared fields, so they're not `| undefined` under noUncheckedIndexedAccess). */\ninterface TaskRow {\n step_id: string;\n run_id: string;\n seq: string | number;\n name: string;\n input: string | null;\n attempt: string | number;\n}\ninterface RunRow {\n run_id: string;\n wake: number | string;\n}\n\n/** A claimed run row: its run id and the token of this claim. */\ninterface ClaimedRun {\n run: RunDispatch;\n token: string;\n}\n\n/** Most rows one {@link DbTransport.dispatchRuns} insert writes. */\nconst RUNS_INSERT_CHUNK = 500;\n/** Tries for a statement the database rolled back as a deadlock (see `retryOnDeadlock`). */\nconst DEADLOCK_RETRIES = 5;\n\n/** Whether `err` is the database rolling a statement back to break a deadlock. */\nfunction isDeadlock(err: unknown): boolean {\n const codes = [\n (err as { code?: unknown }).code,\n (err as { errno?: unknown }).errno,\n (err as { driverError?: { code?: unknown; errno?: unknown } }).driverError?.code,\n (err as { driverError?: { code?: unknown; errno?: unknown } }).driverError?.errno,\n ];\n return codes.some((c) => c === 'ER_LOCK_DEADLOCK' || c === 1213 || c === '40P01');\n}\n\n/** Most run rows one consumer poll claims. */\nconst RUNS_CLAIM_MAX = 50;\n\n/** A run-queue consumer started by {@link DbTransport.onDispatchedRun}. */\ninterface RunConsumer {\n stopped: boolean;\n /** Claim tokens of the rows whose runs are in flight. */\n inflight: Set<string>;\n slotFreed: (() => void) | undefined;\n loop: Promise<void>;\n}\n\ninterface ResultRow {\n step_id: string;\n run_id: string;\n seq: string | number;\n status: string;\n output: string | null;\n error: string | null;\n started_at: string | number | null;\n}\n\nexport interface DbTransportOptions {\n /**\n * How to run SQL — built from the app's **own** ORM/connection (no broker, no extra connection).\n * Use `mikroOrmExecutor(em)` to ride the app's MikroORM, `typeOrmExecutor(dataSource)` for TypeORM,\n * `drizzlePgExecutor(db)` (from `@dudousxd/nestjs-durable-store-drizzle/pg`) for Drizzle on\n * Postgres, or implement `SqlExecutor` for anything else.\n */\n executor: SqlExecutor;\n /** The worker group this instance serves. Required to register `handle()` consumers. */\n group?: string;\n /**\n * Table-name prefix. Tables: `${prefix}_transport_tasks` / `${prefix}_transport_results`, and\n * `${prefix}_transport_runs` for the run queue. Default `durable`.\n */\n prefix?: string;\n /** Poll interval in ms when a queue is empty. Default 500. */\n pollMs?: number;\n /**\n * How long a claimed row is owned before it's reclaimable (crash recovery), in ms. Default 30_000.\n * Every claim this instance holds is renewed every `leaseMs / 3` while it is waiting or running, so\n * a step may run longer than `leaseMs`; only a crashed (or event-loop-blocked) instance loses it.\n */\n leaseMs?: number;\n /**\n * Max rows claimed per poll. Default 10. The task loop claims only as many rows as it has free\n * {@link concurrency} slots (capped by this); the results loop claims up to this many.\n */\n batchSize?: number;\n /**\n * How many tasks this instance runs at once (worker side). Default 1. A row is claimed only when a\n * slot is free to run it, so rows never wait claimed behind a slow task while a sibling sits idle.\n */\n concurrency?: number;\n /**\n * Where a transport failure is reported: a failing poll, a throwing result handler (the result is\n * re-delivered after the lease), a lost claim. Default `console.error`.\n */\n onError?: (err: unknown) => void;\n /** Create the tables on first use if missing. Default true. */\n autoCreate?: boolean;\n /** Identifies this instance in `claimed_by` (for debugging). Defaults to a random id. */\n instanceId?: string;\n}\n\n/**\n * A numeric option: `fallback` unless `value` is a finite number of at least `min`. `Math.max(1, NaN)`\n * is `NaN`, so a clamp alone would let a `NaN` through (to `LIMIT NaN`, or a `NaN` timer).\n */\nfunction numberOption(value: number | undefined, fallback: number, min: number): number {\n return value !== undefined && Number.isFinite(value) && value >= min ? value : fallback;\n}\n\n/**\n * A SQL-backed `Transport` — DBOS-style. Instead of a broker (Redis/SQS), remote steps are **rows**\n * in the same database the durable store already uses: `dispatch` inserts a task row, a worker\n * claims it with `SELECT … FOR UPDATE SKIP LOCKED` (so instances never double-claim), runs it, and\n * writes a result row the engine polls. Zero new infrastructure — the DB you already have IS the\n * queue.\n *\n * Trade-off vs a real broker: throughput is bounded by polling + row contention. Great for\n * workflow/pipeline scale (modest rate, long steps); not for high-fanout firehoses.\n *\n * Requires `FOR UPDATE SKIP LOCKED` — **MySQL 8+** or **Postgres 9.5+** (not SQLite). Run one\n * instance engine-side (`onResult`, dispatches) and one per worker process (`handle()` for its group).\n */\nexport class DbTransport implements Transport {\n private readonly exec: SqlExecutor;\n private readonly group?: string | undefined;\n private readonly tasksTable: string;\n private readonly resultsTable: string;\n private readonly runsTable: string;\n private readonly runConsumers = new Set<RunConsumer>();\n private readonly pollMs: number;\n private readonly leaseMs: number;\n private readonly batchSize: number;\n private readonly concurrency: number;\n private readonly onError: (err: unknown) => void;\n private readonly autoCreate: boolean;\n private readonly instanceId: string;\n private readonly isPg: boolean;\n\n private readonly handlers = new Map<string, StepHandler>();\n private running = true;\n private schemaReady?: Promise<void> | undefined;\n private taskLoop?: Promise<void>;\n private resultLoop?: Promise<void>;\n /** Tasks running on this instance (at most `concurrency`). */\n private readonly runningTasks = new Set<Promise<void>>();\n /** Claim tokens this instance holds, by table — every one is renewed on `renewTimer`. */\n private readonly held = new Map<string, Set<string>>();\n private renewTimer?: ReturnType<typeof setInterval> | undefined;\n private renewal?: Promise<void> | undefined;\n\n constructor(options: DbTransportOptions) {\n this.exec = options.executor;\n this.group = options.group;\n const prefix = options.prefix ?? 'durable';\n this.tasksTable = `${prefix}_transport_tasks`;\n this.resultsTable = `${prefix}_transport_results`;\n this.runsTable = `${prefix}_transport_runs`;\n this.pollMs = numberOption(options.pollMs, 500, 0);\n this.leaseMs = numberOption(options.leaseMs, 30_000, 1);\n this.batchSize = Math.floor(numberOption(options.batchSize, 10, 1));\n this.concurrency = Math.floor(numberOption(options.concurrency, 1, 1));\n this.onError = options.onError ?? ((err) => console.error('[DbTransport]', err));\n this.autoCreate = options.autoCreate ?? true;\n // Random, not time-based: two instances booted in the same millisecond must not share an id.\n this.instanceId = options.instanceId ?? `db-${randomUUID()}`;\n this.isPg = this.exec.dialect === 'postgres';\n }\n\n // ── dialect helpers ──────────────────────────────────────────────────────────────────────────\n /** Positional placeholder for param `i` (1-based): `$i` on Postgres, `?` elsewhere. */\n private ph(i: number): string {\n return this.isPg ? `$${i}` : '?';\n }\n private q(id: string): string {\n return this.exec.escapeId(id);\n }\n private now(): number {\n return Math.floor(Date.now());\n }\n\n // ── schema ───────────────────────────────────────────────────────────────────────────────────\n private ensureSchema(): Promise<void> {\n if (!this.schemaReady) {\n // A failed attempt is not cached: the next call tries again instead of failing forever.\n this.schemaReady = this.createTables().catch((err) => {\n this.schemaReady = undefined;\n throw err;\n });\n }\n return this.schemaReady;\n }\n\n /**\n * Run one `CREATE … IF NOT EXISTS`. Two instances booting on a fresh database race it, and Postgres\n * can reject the loser with a unique violation on its catalog even with `IF NOT EXISTS`; the\n * statement is simply re-run, by which point the table exists and it is a no-op.\n */\n private async createIfMissing(sql: string): Promise<void> {\n try {\n await this.exec.query(sql);\n } catch {\n await this.exec.query(sql);\n }\n }\n\n private async createTables(): Promise<void> {\n if (!this.autoCreate) return;\n const str = 'varchar(191)';\n const txt = this.isPg ? 'text' : 'longtext';\n const tasks = this.q(this.tasksTable);\n const results = this.q(this.resultsTable);\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${tasks} (\n ${this.q('step_id')} ${str} PRIMARY KEY,\n ${this.q('run_id')} ${str} NOT NULL,\n ${this.q('seq')} integer NOT NULL,\n ${this.q('name')} ${str} NOT NULL,\n ${this.q('grp')} ${str} NOT NULL,\n ${this.q('input')} ${txt},\n ${this.q('attempt')} integer NOT NULL,\n ${this.q('status')} varchar(32) NOT NULL,\n ${this.q('claimed_by')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${results} (\n ${this.q('step_id')} ${str} PRIMARY KEY,\n ${this.q('run_id')} ${str} NOT NULL,\n ${this.q('seq')} integer NOT NULL,\n ${this.q('status')} varchar(32) NOT NULL,\n ${this.q('output')} ${txt},\n ${this.q('error')} ${txt},\n ${this.q('started_at')} bigint,\n ${this.q('claimed_by')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${this.q(this.runsTable)} (\n ${this.q('run_id')} ${str} PRIMARY KEY,\n ${this.q('ns')} ${str} NOT NULL,\n ${this.q('priority')} integer NOT NULL,\n ${this.q('wake')} integer NOT NULL,\n ${this.q('claimed_by')} ${str},\n ${this.q('claim_token')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.exec\n .query(\n `CREATE INDEX ${this.q(`${this.runsTable}_ns_idx`)} ON ${this.q(this.runsTable)} (${this.q('ns')}, ${this.q('priority')}, ${this.q('created_at')})`,\n )\n .catch(() => {});\n // Best-effort index for the group poll (MySQL has no CREATE INDEX IF NOT EXISTS < 8.0.13).\n await this.exec\n .query(\n `CREATE INDEX ${this.q(`${this.tasksTable}_grp_idx`)} ON ${tasks} (${this.q('grp')}, ${this.q('status')}, ${this.q('created_at')})`,\n )\n .catch(() => {});\n }\n\n // ── engine → worker ────────────────────────────────────────────────────────────────────────\n async dispatch(task: RemoteTask): Promise<void> {\n await this.ensureSchema();\n const t = this.q(this.tasksTable);\n // Idempotent: a redelivered dispatch for the same step_id is ignored (the row already exists).\n const ignore = this.isPg\n ? `INSERT INTO ${t} (...) ON CONFLICT (${this.q('step_id')}) DO NOTHING`\n : `INSERT IGNORE INTO ${t} (...)`;\n const cols = [\n 'step_id',\n 'run_id',\n 'seq',\n 'name',\n 'grp',\n 'input',\n 'attempt',\n 'status',\n 'created_at',\n ];\n const sql = ignore.replace(\n '(...)',\n `(${cols.map((c) => this.q(c)).join(', ')}) VALUES (${cols.map((_, i) => this.ph(i + 1)).join(', ')})`,\n );\n await this.exec.query(sql, [\n task.stepId,\n task.runId,\n task.seq,\n task.name,\n task.group,\n JSON.stringify(task.input ?? null),\n task.attempt,\n 'pending',\n this.now(),\n ]);\n }\n\n // ── worker → engine ────────────────────────────────────────────────────────────────────────\n handle(name: string, fn: StepHandler): void {\n if (!this.group) throw new Error('DbTransport needs a `group` to register handlers');\n this.handlers.set(name, fn);\n if (!this.taskLoop) this.taskLoop = this.loop(() => this.drainTasks(this.group as string));\n }\n\n onResult(handler: (result: StepResult) => Promise<void>): void {\n if (!this.resultLoop) this.resultLoop = this.loop(() => this.drainResults(handler));\n }\n\n onHeartbeat(_handler: (beat: Heartbeat) => Promise<void>): void {\n // Not modelled; the lease/reclaim on `claimed_at` is the liveness mechanism.\n }\n\n // ── run queue (engine ↔ engine) ─────────────────────────────────────────────────────────────\n /**\n * Queue `pending` runs for whichever engine instance polls the run queue first: one row per run,\n * keyed by run id, so a run already waiting is not queued twice. A run whose row an instance holds\n * right now is released for another take, so a dispatch landing while the run's previous entry is\n * being handled is not lost when that entry is deleted. Up to {@link RUNS_INSERT_CHUNK} rows per\n * insert.\n */\n async dispatchRuns(runs: readonly RunDispatch[], opts?: RunQueueOptions): Promise<void> {\n if (runs.length === 0) return;\n await this.ensureSchema();\n // One row per run (an upsert may not touch the same row twice in one statement); a wake among\n // a run's dispatches makes its row a wake.\n const byRun = new Map<string, RunDispatch>();\n for (const run of runs) {\n if (!byRun.has(run.runId) || run.reason === 'wake') byRun.set(run.runId, run);\n }\n // In run-id order: two dispatches touching overlapping rows lock them in the same order.\n const unique = [...byRun.values()].sort((a, b) =>\n a.runId < b.runId ? -1 : a.runId > b.runId ? 1 : 0,\n );\n const t = this.q(this.runsTable);\n const cols = ['run_id', 'ns', 'priority', 'wake', 'created_at'];\n const now = this.now();\n for (let at = 0; at < unique.length; at += RUNS_INSERT_CHUNK) {\n const chunk = unique.slice(at, at + RUNS_INSERT_CHUNK);\n const values = chunk\n .map((_, r) => `(${cols.map((__, c) => this.ph(r * cols.length + c + 1)).join(', ')})`)\n .join(', ');\n const insert = `INTO ${t} (${cols.map((c) => this.q(c)).join(', ')}) VALUES ${values}`;\n const release = ['claimed_by', 'claim_token', 'claimed_at']\n .map((c) => `${this.q(c)} = NULL`)\n .join(', ');\n const wake = this.q('wake');\n const sql = this.isPg\n ? `INSERT ${insert} ON CONFLICT (${this.q('run_id')}) DO UPDATE SET ${release}, ${wake} = GREATEST(${t}.${wake}, EXCLUDED.${wake})`\n : `INSERT ${insert} ON DUPLICATE KEY UPDATE ${release}, ${wake} = GREATEST(${wake}, VALUES(${wake}))`;\n await this.retryOnDeadlock(() =>\n this.exec.query(\n sql,\n chunk.flatMap((run, r) => [\n run.runId,\n opts?.namespace ?? '',\n run.priority ?? 0,\n run.reason === 'wake' ? 1 : 0,\n now + r,\n ]),\n ),\n );\n }\n }\n\n /**\n * Poll the run queue, at most `opts.concurrency` runs in flight on this instance. Rows are claimed\n * with `FOR UPDATE SKIP LOCKED` (instances never take the same run), highest priority then oldest\n * first; a row is deleted once its handler settles, and its lease is renewed meanwhile, so a row is\n * re-taken only after the instance holding it went down. The returned stop takes no more rows and\n * resolves once the runs in flight settled.\n */\n onDispatchedRun(\n handler: (run: RunDispatch) => Promise<void>,\n opts: RunConsumerOptions,\n ): () => Promise<void> {\n const consumer: RunConsumer = {\n stopped: false,\n inflight: new Set(),\n slotFreed: undefined,\n loop: Promise.resolve(),\n };\n const concurrency = Math.max(1, Math.floor(opts.concurrency));\n const ns = opts.namespace ?? '';\n consumer.loop = this.consumeRuns(consumer, handler, concurrency, ns).catch((err) => {\n this.onError(err);\n });\n this.runConsumers.add(consumer);\n return () => this.stopRunConsumer(consumer);\n }\n\n private async stopRunConsumer(consumer: RunConsumer): Promise<void> {\n consumer.stopped = true;\n consumer.slotFreed?.();\n await consumer.loop;\n this.runConsumers.delete(consumer);\n }\n\n private async consumeRuns(\n consumer: RunConsumer,\n handler: (run: RunDispatch) => Promise<void>,\n concurrency: number,\n ns: string,\n ): Promise<void> {\n const renew = setInterval(\n () => void this.renewRunLeases(consumer).catch(() => undefined),\n Math.max(1_000, Math.floor(this.leaseMs / 3)),\n );\n renew.unref?.();\n const settled = new Set<Promise<unknown>>();\n try {\n while (this.running && !consumer.stopped) {\n const free = concurrency - consumer.inflight.size;\n if (free <= 0) {\n await new Promise<void>((resolve) => {\n consumer.slotFreed = resolve;\n });\n consumer.slotFreed = undefined;\n continue;\n }\n let rows: ClaimedRun[] = [];\n try {\n await this.ensureSchema();\n rows = await this.retryOnDeadlock(() =>\n this.claimRuns(ns, Math.min(free, RUNS_CLAIM_MAX)),\n );\n } catch (err) {\n if (this.running && !consumer.stopped) this.onError(err);\n }\n for (const { run, token } of rows) {\n consumer.inflight.add(token);\n const done = handler(run)\n .catch(() => undefined)\n .then(() =>\n this.retryOnDeadlock(() =>\n this.exec.query(\n `DELETE FROM ${this.q(this.runsTable)} WHERE ${this.q('claim_token')} = ${this.ph(1)}`,\n [token],\n ),\n ).catch(() => undefined),\n )\n .finally(() => {\n consumer.inflight.delete(token);\n settled.delete(done);\n consumer.slotFreed?.();\n });\n settled.add(done);\n }\n if (rows.length === 0) await this.sleep(this.pollMs);\n }\n await Promise.allSettled([...settled]);\n } finally {\n clearInterval(renew);\n }\n }\n\n /**\n * Run `statement`, again on a deadlock: queueing a run (an upsert), taking one (`SKIP LOCKED`)\n * and deleting a handled one touch the same rows from different instances, and the database then\n * rolls one of them back rather than wait (InnoDB reports `ER_LOCK_DEADLOCK`, Postgres `40P01`).\n */\n private async retryOnDeadlock<T>(statement: () => Promise<T>): Promise<T> {\n for (let attempt = 1; ; attempt += 1) {\n try {\n return await statement();\n } catch (err) {\n if (attempt >= DEADLOCK_RETRIES || !isDeadlock(err)) throw err;\n await this.sleep(10 * attempt + Math.floor(Math.random() * 10));\n }\n }\n }\n\n private async claimRuns(ns: string, limit: number): Promise<ClaimedRun[]> {\n const t = this.q(this.runsTable);\n const staleBefore = this.now() - this.leaseMs;\n return this.exec.transaction(async (tx) => {\n const rows = await tx.query<RunRow>(\n `SELECT ${this.q('run_id')}, ${this.q('wake')} FROM ${t}\n WHERE ${this.q('ns')} = ${this.ph(1)}\n AND (${this.q('claimed_at')} IS NULL OR ${this.q('claimed_at')} < ${this.ph(2)})\n ORDER BY ${this.q('priority')} DESC, ${this.q('created_at')} ASC\n LIMIT ${Math.floor(limit)} FOR UPDATE SKIP LOCKED`,\n [ns, staleBefore],\n );\n const claimed = rows.map((r) => ({\n run: {\n runId: r.run_id,\n reason: Number(r.wake) === 1 ? ('wake' as const) : ('start' as const),\n },\n token: globalThis.crypto.randomUUID(),\n }));\n const now = this.now();\n for (const {\n run: { runId },\n token,\n } of claimed) {\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_by')} = ${this.ph(1)}, ${this.q('claim_token')} = ${this.ph(2)}, ${this.q('claimed_at')} = ${this.ph(3)}\n WHERE ${this.q('run_id')} = ${this.ph(4)}`,\n [this.instanceId, token, now, runId],\n );\n }\n return claimed;\n });\n }\n\n /** Keep the rows of the runs in flight leased, so a long turn is not re-taken by another instance. */\n private async renewRunLeases(consumer: RunConsumer): Promise<void> {\n const tokens = [...consumer.inflight];\n if (tokens.length === 0) return;\n await this.exec.query(\n `UPDATE ${this.q(this.runsTable)} SET ${this.q('claimed_at')} = ${this.ph(1)}\n WHERE ${this.q('claim_token')} IN (${tokens.map((_, i) => this.ph(i + 2)).join(', ')})`,\n [this.now(), ...tokens],\n );\n }\n\n // ── polling ──────────────────────────────────────────────────────────────────────────────────\n /** Run `tick` repeatedly; sleep `pollMs` when it reports an empty queue. Stops on `close()`. */\n private async loop(tick: () => Promise<number>): Promise<void> {\n while (this.running) {\n let processed = 0;\n try {\n await this.ensureSchema();\n processed = await tick();\n } catch (err) {\n if (this.running) this.onError(err);\n }\n if (processed === 0 && this.running) await this.sleep(this.pollMs);\n }\n }\n\n /**\n * One task-loop round: claim as many rows as there are free slots (never more — a claimed row that\n * isn't running is one an idle sibling can't run), and start each. With every slot busy, wait for\n * one to free instead of polling.\n */\n private async drainTasks(group: string): Promise<number> {\n const free = this.concurrency - this.runningTasks.size;\n if (free <= 0) {\n await Promise.race(this.runningTasks);\n return 1;\n }\n const { rows, token } = await this.claim<TaskRow>(\n this.tasksTable,\n Math.min(free, this.batchSize),\n `${this.q('grp')} = ${this.ph(1)}`,\n [group],\n );\n if (rows.length === 0) return 0;\n this.hold(this.tasksTable, token);\n let left = rows.length;\n for (const row of rows) {\n const run = this.runTask(row, group, token).finally(() => {\n this.runningTasks.delete(run);\n left -= 1;\n if (left === 0) this.release(this.tasksTable, token);\n });\n this.runningTasks.add(run);\n }\n return rows.length;\n }\n\n /**\n * Run one claimed task and settle it — only while this instance still holds the claim: the claim is\n * re-checked before running, and the task row is deleted and its result written in ONE transaction,\n * conditional on the claim token, so an instance whose claim was taken over neither deletes the new\n * holder's row nor publishes a second result.\n */\n private async runTask(row: TaskRow, group: string, token: string): Promise<void> {\n try {\n if (!(await this.stillHolds(row.step_id, token))) {\n this.onError(\n new Error(\n `[DbTransport] lost the claim on task ${row.step_id} before running it — another instance took it over; not running it here`,\n ),\n );\n return;\n }\n const task: RemoteTask = {\n runId: row.run_id,\n seq: Number(row.seq),\n stepId: row.step_id,\n name: row.name,\n group,\n input: row.input == null ? undefined : JSON.parse(row.input),\n attempt: Number(row.attempt),\n };\n const result = await runStepHandler(task, this.handlers.get(task.name));\n if (!(await this.settleTask(task.stepId, token, result))) {\n this.onError(\n new Error(\n `[DbTransport] lost the claim on task ${task.stepId} while running it (this instance went silent past leaseMs) — another instance took it over and may run it too; this run's result was dropped. Raise leaseMs if steps block the event loop`,\n ),\n );\n }\n } catch (err) {\n // A database error settling the row (or a malformed row): it stays claimed, stops being\n // renewed, and is reclaimed after the lease — at-least-once, like a crash.\n this.onError(err);\n }\n }\n\n /** Whether the claim on `stepId` is still `token`'s (re-stamping it if so). */\n private async stillHolds(stepId: string, token: string): Promise<boolean> {\n const t = this.q(this.tasksTable);\n const rows = await this.exec.transaction(async (tx) => {\n const held = await tx.query<{ step_id: string }>(\n `SELECT ${this.q('step_id')} FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)} FOR UPDATE`,\n [stepId, token],\n );\n if (held.length > 0) {\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_at')} = ${this.ph(1)} WHERE ${this.q('step_id')} = ${this.ph(2)} AND ${this.q('claimed_by')} = ${this.ph(3)}`,\n [this.now(), stepId, token],\n );\n }\n return held;\n });\n return rows.length > 0;\n }\n\n /** Delete the task row and publish its result in ONE transaction, only while `token` holds it. */\n private async settleTask(stepId: string, token: string, result: StepResult): Promise<boolean> {\n const t = this.q(this.tasksTable);\n return this.exec.transaction(async (tx) => {\n const held = await tx.query<{ step_id: string }>(\n `SELECT ${this.q('step_id')} FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)} FOR UPDATE`,\n [stepId, token],\n );\n if (held.length === 0) return false;\n await tx.query(\n `DELETE FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)}`,\n [stepId, token],\n );\n await this.insertResult(result, tx);\n return true;\n });\n }\n\n /**\n * Claim a batch of results, deliver each to the engine, and delete it — only after its handler ran\n * (at-least-once), and only while this round still holds the claim (a newer result for the same\n * step that replaced the row resets the claim, so it is delivered next instead of deleted). The\n * batch's claim is renewed until it is done. A throwing handler is reported and its row left for\n * re-delivery after the lease; the rest of the batch continues.\n */\n private async drainResults(handler: (result: StepResult) => Promise<void>): Promise<number> {\n const { rows, token } = await this.claim<ResultRow>(\n this.resultsTable,\n this.batchSize,\n '1 = 1',\n [],\n );\n if (rows.length === 0) return 0;\n this.hold(this.resultsTable, token);\n try {\n for (const row of rows) {\n if (!this.running) break;\n try {\n const result: StepResult = {\n runId: row.run_id,\n seq: Number(row.seq),\n stepId: row.step_id,\n status: row.status as StepResult['status'],\n output: row.output == null ? undefined : JSON.parse(row.output),\n error: row.error == null ? undefined : JSON.parse(row.error),\n startedAt: row.started_at == null ? undefined : Number(row.started_at),\n };\n await handler(result);\n await this.exec.query(\n `DELETE FROM ${this.q(this.resultsTable)} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)}`,\n [row.step_id, token],\n );\n } catch (err) {\n this.onError(err);\n }\n }\n } finally {\n this.release(this.resultsTable, token);\n }\n return rows.length;\n }\n\n /**\n * Atomically claim up to `limit` un-leased rows matching `where` and stamp them with a claim token\n * unique to this round (`<instanceId>:<uuid>`). `SELECT … FOR UPDATE SKIP LOCKED` inside the txn\n * means concurrent instances skip each other's locked rows instead of blocking, so no row is\n * claimed twice; the token is what every later delete/settle is conditional on.\n */\n private async claim<T extends { step_id: string }>(\n table: string,\n limit: number,\n where: string,\n whereParams: unknown[],\n ): Promise<{ rows: T[]; token: string }> {\n const t = this.q(table);\n const staleBefore = this.now() - this.leaseMs;\n const token = `${this.instanceId}:${randomUUID()}`;\n const rows = await this.exec.transaction(async (tx) => {\n const leaseClause = `(${this.q('claimed_at')} IS NULL OR ${this.q('claimed_at')} < ${this.ph(whereParams.length + 1)})`;\n const found = await tx.query<T>(\n `SELECT * FROM ${t} WHERE ${where} AND ${leaseClause}\n ORDER BY ${this.q('created_at')} ASC LIMIT ${Math.max(1, Math.floor(limit))} FOR UPDATE SKIP LOCKED`,\n [...whereParams, staleBefore],\n );\n if (found.length > 0) {\n const ids = found.map((r) => r.step_id);\n const placeholders = ids.map((_, i) => this.ph(i + 3)).join(', ');\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_by')} = ${this.ph(1)}, ${this.q('claimed_at')} = ${this.ph(2)}\n WHERE ${this.q('step_id')} IN (${placeholders})`,\n [token, this.now(), ...ids],\n );\n }\n return found;\n });\n return { rows, token };\n }\n\n // ── claim renewal: every claim held, not only the row being run ─────────────────────────────\n private hold(table: string, token: string): void {\n let tokens = this.held.get(table);\n if (!tokens) {\n tokens = new Set();\n this.held.set(table, tokens);\n }\n tokens.add(token);\n if (this.renewTimer) return;\n const intervalMs = Math.max(10, Math.floor(this.leaseMs / 3));\n this.renewTimer = setInterval(() => {\n if (this.renewal) return; // a slow database never stacks renewal rounds\n const round = this.renewHeld()\n .catch((err) => this.onError(err))\n .finally(() => {\n if (this.renewal === round) this.renewal = undefined;\n });\n this.renewal = round;\n }, intervalMs);\n this.renewTimer.unref?.();\n }\n\n private release(table: string, token: string): void {\n const tokens = this.held.get(table);\n tokens?.delete(token);\n if (tokens?.size === 0) this.held.delete(table);\n if (this.held.size === 0 && this.renewTimer) {\n clearInterval(this.renewTimer);\n this.renewTimer = undefined;\n }\n }\n\n /** Re-stamp `claimed_at` on every row still claimed under a token this instance holds. */\n private async renewHeld(): Promise<void> {\n for (const [table, tokens] of this.held) {\n if (tokens.size === 0) continue;\n const list = [...tokens];\n const placeholders = list.map((_, i) => this.ph(i + 2)).join(', ');\n await this.exec.query(\n `UPDATE ${this.q(table)} SET ${this.q('claimed_at')} = ${this.ph(1)} WHERE ${this.q('claimed_by')} IN (${placeholders})`,\n [this.now(), ...list],\n );\n }\n }\n\n /**\n * Write a result row. A result already waiting for the same step (an earlier attempt's, not yet\n * deleted by the engine) is REPLACED and its claim reset, never kept: `step_id` is stable across\n * attempts, so keeping the old row would silently drop a retry's result.\n */\n private async insertResult(result: StepResult, tx: SqlTx = this.exec): Promise<void> {\n const t = this.q(this.resultsTable);\n const cols = [\n 'step_id',\n 'run_id',\n 'seq',\n 'status',\n 'output',\n 'error',\n 'started_at',\n 'created_at',\n ];\n const replace = ['run_id', 'seq', 'status', 'output', 'error', 'started_at', 'created_at'];\n const values = `(${cols.map((c) => this.q(c)).join(', ')}) VALUES (${cols.map((_, i) => this.ph(i + 1)).join(', ')})`;\n const sql = this.isPg\n ? `INSERT INTO ${t} ${values} ON CONFLICT (${this.q('step_id')}) DO UPDATE SET ${[\n ...replace.map((c) => `${this.q(c)} = EXCLUDED.${this.q(c)}`),\n `${this.q('claimed_by')} = NULL`,\n `${this.q('claimed_at')} = NULL`,\n ].join(', ')}`\n : `INSERT INTO ${t} ${values} ON DUPLICATE KEY UPDATE ${[\n ...replace.map((c) => `${this.q(c)} = VALUES(${this.q(c)})`),\n `${this.q('claimed_by')} = NULL`,\n `${this.q('claimed_at')} = NULL`,\n ].join(', ')}`;\n await tx.query(sql, [\n result.stepId,\n result.runId,\n result.seq,\n result.status,\n result.output === undefined ? null : JSON.stringify(result.output),\n result.error === undefined ? null : JSON.stringify(result.error),\n result.startedAt ?? null,\n this.now(),\n ]);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n }\n\n /** Stop the pollers and wait for the current ticks to settle. Does not close the shared DataSource. */\n async close(): Promise<void> {\n this.running = false;\n await Promise.allSettled([\n this.taskLoop,\n this.resultLoop,\n ...[...this.runConsumers].map((c) => this.stopRunConsumer(c)),\n ]);\n // Let running tasks settle (their claims keep being renewed until they do).\n await Promise.allSettled([...this.runningTasks]);\n if (this.renewTimer) clearInterval(this.renewTimer);\n this.renewTimer = undefined;\n await this.renewal;\n }\n}\n","/**\n * The tiny SQL surface `DbTransport` needs, so it rides **whatever ORM/connection the app already\n * has** instead of forcing a specific one. Implement it for your ORM (adapters for TypeORM and\n * MikroORM ship below) and hand it to `new DbTransport({ executor })`. Mirrors how the durable\n * *store* is ORM-pluggable (store-typeorm / store-mikro-orm / …).\n */\nexport interface SqlExecutor {\n /** Drives placeholder style (`$n` vs `?`) and a couple of DDL type choices. */\n readonly dialect: 'mysql' | 'postgres';\n /** Quote/escape an identifier (table/column name) for this dialect. */\n escapeId(id: string): string;\n /** Run a statement outside a transaction. */\n query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;\n /** Run `fn` inside a transaction — the claim uses `FOR UPDATE SKIP LOCKED` within it. */\n transaction<T>(fn: (tx: SqlTx) => Promise<T>): Promise<T>;\n}\n\nexport interface SqlTx {\n query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;\n}\n\n// ── TypeORM ──────────────────────────────────────────────────────────────────────────────────────\n\n/** Structural slice of a TypeORM `DataSource` — avoids a hard dependency on typeorm. */\ninterface TypeOrmDataSourceLike {\n options: { type: string };\n driver: { escape(id: string): string };\n query(sql: string, params?: unknown[]): Promise<unknown>;\n createQueryRunner(): {\n connect(): Promise<void>;\n startTransaction(): Promise<void>;\n commitTransaction(): Promise<void>;\n rollbackTransaction(): Promise<void>;\n release(): Promise<void>;\n query(sql: string, params?: unknown[]): Promise<unknown>;\n };\n}\n\n/** Build an executor from a TypeORM `DataSource` (e.g. the one the durable store already uses). */\nexport function typeOrmExecutor(ds: TypeOrmDataSourceLike): SqlExecutor {\n return {\n dialect: /postgres/.test(String(ds.options.type)) ? 'postgres' : 'mysql',\n escapeId: (id) => ds.driver.escape(id),\n query: <T>(sql: string, params?: unknown[]) => ds.query(sql, params) as Promise<T[]>,\n transaction: async <T>(fn: (tx: SqlTx) => Promise<T>) => {\n const qr = ds.createQueryRunner();\n await qr.connect();\n await qr.startTransaction();\n try {\n const result = await fn({\n query: <U>(sql: string, params?: unknown[]) => qr.query(sql, params) as Promise<U[]>,\n });\n await qr.commitTransaction();\n return result;\n } catch (err) {\n await qr.rollbackTransaction();\n throw err;\n } finally {\n await qr.release();\n }\n },\n };\n}\n\n// ── MikroORM ─────────────────────────────────────────────────────────────────────────────────────\n\n/** Structural slice of a MikroORM `EntityManager` — avoids a hard dependency on @mikro-orm/core. */\ninterface MikroOrmEmLike {\n getPlatform(): { constructor: { name: string }; quoteIdentifier(id: string): string };\n getConnection(): {\n execute(sql: string, params?: unknown[], method?: 'all', ctx?: unknown): Promise<unknown>;\n };\n getTransactionContext?(): unknown;\n transactional<T>(cb: (em: MikroOrmEmLike) => Promise<T>): Promise<T>;\n}\n\n/**\n * Build an executor from a MikroORM `EntityManager` — i.e. **the app's own configured ORM**. Runs\n * raw SQL through `em.getConnection().execute(...)` and wraps the claim in `em.transactional(...)`,\n * threading the transaction context so `FOR UPDATE SKIP LOCKED` locks within the tx.\n */\nexport function mikroOrmExecutor(em: MikroOrmEmLike): SqlExecutor {\n const platform = em.getPlatform();\n return {\n dialect: /postgre/i.test(platform.constructor.name) ? 'postgres' : 'mysql',\n escapeId: (id) => platform.quoteIdentifier(id),\n query: <T>(sql: string, params?: unknown[]) =>\n em.getConnection().execute(sql, params ?? [], 'all') as Promise<T[]>,\n transaction: <T>(fn: (tx: SqlTx) => Promise<T>) =>\n em.transactional((txEm) =>\n fn({\n query: <U>(sql: string, params?: unknown[]) =>\n txEm\n .getConnection()\n .execute(sql, params ?? [], 'all', txEm.getTransactionContext?.()) as Promise<U[]>,\n }),\n ),\n };\n}\n"],"mappings":"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","names":["typeOrmExecutor","ds","dialect","test","String","options","type","escapeId","id","driver","escape","query","sql","params","transaction","fn","qr","createQueryRunner","connect","startTransaction","result","commitTransaction","err","rollbackTransaction","release","mikroOrmExecutor","em","platform","getPlatform","name","quoteIdentifier","getConnection","execute","transactional","txEm","getTransactionContext","RUNS_INSERT_CHUNK","DEADLOCK_RETRIES","isDeadlock","err","codes","code","errno","driverError","some","c","RUNS_CLAIM_MAX","numberOption","value","fallback","min","undefined","Number","isFinite","DbTransport","exec","group","tasksTable","resultsTable","runsTable","runConsumers","Set","pollMs","leaseMs","batchSize","concurrency","onError","autoCreate","instanceId","isPg","handlers","Map","running","schemaReady","taskLoop","resultLoop","runningTasks","held","renewTimer","renewal","options","executor","prefix","Math","floor","console","error","randomUUID","dialect","ph","i","q","id","escapeId","now","Date","ensureSchema","createTables","catch","createIfMissing","sql","query","str","txt","tasks","results","dispatch","task","t","ignore","cols","replace","map","join","_","stepId","runId","seq","name","JSON","stringify","input","attempt","handle","fn","Error","set","loop","drainTasks","onResult","handler","drainResults","onHeartbeat","_handler","dispatchRuns","runs","opts","length","byRun","run","has","reason","unique","values","sort","a","b","at","chunk","slice","r","__","insert","release","wake","retryOnDeadlock","flatMap","namespace","priority","onDispatchedRun","consumer","stopped","inflight","slotFreed","Promise","resolve","max","ns","consumeRuns","add","stopRunConsumer","delete","renew","setInterval","renewRunLeases","unref","settled","free","size","rows","claimRuns","token","done","then","finally","sleep","allSettled","clearInterval","statement","random","limit","staleBefore","transaction","tx","claimed","run_id","globalThis","crypto","tokens","tick","processed","race","claim","hold","left","row","runTask","stillHolds","step_id","parse","result","runStepHandler","get","settleTask","insertResult","status","output","startedAt","started_at","table","where","whereParams","leaseClause","found","ids","placeholders","intervalMs","round","renewHeld","list","ms","setTimeout","close"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/db-transport.ts","../src/executors.ts"],"sourcesContent":["export * from './db-transport';\n","import { randomUUID } from 'node:crypto';\nimport {\n type Heartbeat,\n type RemoteTask,\n type RunConsumerOptions,\n type RunDispatch,\n type RunQueueOptions,\n type StepHandler,\n type StepResult,\n type Transport,\n runStepHandler,\n} from '@dudousxd/nestjs-durable-core';\nimport type { SqlExecutor, SqlTx } from './executors';\n\nexport { mikroOrmExecutor, type SqlExecutor, type SqlTx, typeOrmExecutor } from './executors';\n\n/** Raw row shapes returned by `SELECT *` (declared fields, so they're not `| undefined` under noUncheckedIndexedAccess). */\ninterface TaskRow {\n step_id: string;\n run_id: string;\n seq: string | number;\n name: string;\n input: string | null;\n attempt: string | number;\n}\ninterface RunRow {\n run_id: string;\n wake: number | string;\n}\n\n/** A claimed run row: its run id and the token of this claim. */\ninterface ClaimedRun {\n run: RunDispatch;\n token: string;\n}\n\n/** Most rows one {@link DbTransport.dispatchRuns} insert writes. */\nconst RUNS_INSERT_CHUNK = 500;\n/** Tries for a statement the database rolled back as a deadlock (see `retryOnDeadlock`). */\nconst DEADLOCK_RETRIES = 5;\n\n/** Whether `err` is the database rolling a statement back to break a deadlock. */\nfunction isDeadlock(err: unknown): boolean {\n const codes = [\n (err as { code?: unknown }).code,\n (err as { errno?: unknown }).errno,\n (err as { driverError?: { code?: unknown; errno?: unknown } }).driverError?.code,\n (err as { driverError?: { code?: unknown; errno?: unknown } }).driverError?.errno,\n ];\n return codes.some((c) => c === 'ER_LOCK_DEADLOCK' || c === 1213 || c === '40P01');\n}\n\n/** Most task rows one {@link DbTransport.dispatchMany} insert writes. */\nconst TASKS_INSERT_CHUNK = 200;\n\n/** Most run rows one consumer poll claims. */\nconst RUNS_CLAIM_MAX = 50;\n\n/**\n * Most results this instance hands the engine at once, and the most one poll claims. Results are\n * cheap to hand over: the engine writes one run's waiting results together, so the more of a wide\n * fan's results are in flight here, the fewer writes and barrier reads settle them.\n */\nconst RESULTS_IN_FLIGHT = 512;\nconst RESULTS_CLAIM_MAX = 256;\n\n/** A run-queue consumer started by {@link DbTransport.onDispatchedRun}. */\ninterface RunConsumer {\n stopped: boolean;\n /** Claim tokens of the rows whose runs are in flight. */\n inflight: Set<string>;\n slotFreed: (() => void) | undefined;\n loop: Promise<void>;\n}\n\ninterface ResultRow {\n step_id: string;\n run_id: string;\n seq: string | number;\n status: string;\n output: string | null;\n error: string | null;\n started_at: string | number | null;\n}\n\nexport interface DbTransportOptions {\n /**\n * How to run SQL — built from the app's **own** ORM/connection (no broker, no extra connection).\n * Use `mikroOrmExecutor(em)` to ride the app's MikroORM, `typeOrmExecutor(dataSource)` for TypeORM,\n * `drizzlePgExecutor(db)` (from `@dudousxd/nestjs-durable-store-drizzle/pg`) for Drizzle on\n * Postgres, or implement `SqlExecutor` for anything else.\n */\n executor: SqlExecutor;\n /** The worker group this instance serves. Required to register `handle()` consumers. */\n group?: string;\n /**\n * Table-name prefix. Tables: `${prefix}_transport_tasks` / `${prefix}_transport_results`, and\n * `${prefix}_transport_runs` for the run queue. Default `durable`.\n */\n prefix?: string;\n /** Poll interval in ms when a queue is empty. Default 500. */\n pollMs?: number;\n /**\n * How long a claimed row is owned before it's reclaimable (crash recovery), in ms. Default 30_000.\n * Every claim this instance holds is renewed every `leaseMs / 3` while it is waiting or running, so\n * a step may run longer than `leaseMs`; only a crashed (or event-loop-blocked) instance loses it.\n */\n leaseMs?: number;\n /**\n * Max rows claimed per poll. Default 10. The task loop claims only as many rows as it has free\n * {@link concurrency} slots (capped by this). The results loop claims results in larger rounds of\n * its own (a few hundred, or this if larger), since the engine settles them together.\n */\n batchSize?: number;\n /**\n * How many tasks this instance runs at once (worker side). Default 1. A row is claimed only when a\n * slot is free to run it, so rows never wait claimed behind a slow task while a sibling sits idle.\n */\n concurrency?: number;\n /**\n * Where a transport failure is reported: a failing poll, a throwing result handler (the result is\n * re-delivered after the lease), a lost claim. Default `console.error`.\n */\n onError?: (err: unknown) => void;\n /** Create the tables on first use if missing. Default true. */\n autoCreate?: boolean;\n /** Identifies this instance in `claimed_by` (for debugging). Defaults to a random id. */\n instanceId?: string;\n}\n\n/**\n * A numeric option: `fallback` unless `value` is a finite number of at least `min`. `Math.max(1, NaN)`\n * is `NaN`, so a clamp alone would let a `NaN` through (to `LIMIT NaN`, or a `NaN` timer).\n */\nfunction numberOption(value: number | undefined, fallback: number, min: number): number {\n return value !== undefined && Number.isFinite(value) && value >= min ? value : fallback;\n}\n\n/**\n * A SQL-backed `Transport` — DBOS-style. Instead of a broker (Redis/SQS), remote steps are **rows**\n * in the same database the durable store already uses: `dispatch` inserts a task row, a worker\n * claims it with `SELECT … FOR UPDATE SKIP LOCKED` (so instances never double-claim), runs it, and\n * writes a result row the engine polls. Zero new infrastructure — the DB you already have IS the\n * queue.\n *\n * Trade-off vs a real broker: throughput is bounded by polling + row contention. Great for\n * workflow/pipeline scale (modest rate, long steps); not for high-fanout firehoses.\n *\n * Requires `FOR UPDATE SKIP LOCKED` — **MySQL 8+** or **Postgres 9.5+** (not SQLite). Run one\n * instance engine-side (`onResult`, dispatches) and one per worker process (`handle()` for its group).\n */\nexport class DbTransport implements Transport {\n private readonly exec: SqlExecutor;\n private readonly group?: string | undefined;\n private readonly tasksTable: string;\n private readonly resultsTable: string;\n private readonly runsTable: string;\n private readonly runConsumers = new Set<RunConsumer>();\n private readonly pollMs: number;\n private readonly leaseMs: number;\n private readonly batchSize: number;\n private readonly concurrency: number;\n private readonly onError: (err: unknown) => void;\n private readonly autoCreate: boolean;\n private readonly instanceId: string;\n private readonly isPg: boolean;\n\n private readonly handlers = new Map<string, StepHandler>();\n private running = true;\n private schemaReady?: Promise<void> | undefined;\n private taskLoop?: Promise<void>;\n private resultLoop?: Promise<void>;\n /** Tasks running on this instance (at most `concurrency`). */\n private readonly runningTasks = new Set<Promise<void>>();\n /** Claim rounds of results being handled on this instance — see {@link drainResults}. */\n private readonly runningResults = new Set<Promise<void>>();\n /** Results being handled across {@link runningResults} (at most {@link RESULTS_IN_FLIGHT}). */\n private resultsInFlight = 0;\n /** Claim tokens this instance holds, by table — every one is renewed on `renewTimer`. */\n private readonly held = new Map<string, Set<string>>();\n private renewTimer?: ReturnType<typeof setInterval> | undefined;\n private renewal?: Promise<void> | undefined;\n\n constructor(options: DbTransportOptions) {\n this.exec = options.executor;\n this.group = options.group;\n const prefix = options.prefix ?? 'durable';\n this.tasksTable = `${prefix}_transport_tasks`;\n this.resultsTable = `${prefix}_transport_results`;\n this.runsTable = `${prefix}_transport_runs`;\n this.pollMs = numberOption(options.pollMs, 500, 0);\n this.leaseMs = numberOption(options.leaseMs, 30_000, 1);\n this.batchSize = Math.floor(numberOption(options.batchSize, 10, 1));\n this.concurrency = Math.floor(numberOption(options.concurrency, 1, 1));\n this.onError = options.onError ?? ((err) => console.error('[DbTransport]', err));\n this.autoCreate = options.autoCreate ?? true;\n // Random, not time-based: two instances booted in the same millisecond must not share an id.\n this.instanceId = options.instanceId ?? `db-${randomUUID()}`;\n this.isPg = this.exec.dialect === 'postgres';\n }\n\n // ── dialect helpers ──────────────────────────────────────────────────────────────────────────\n /** Positional placeholder for param `i` (1-based): `$i` on Postgres, `?` elsewhere. */\n private ph(i: number): string {\n return this.isPg ? `$${i}` : '?';\n }\n private q(id: string): string {\n return this.exec.escapeId(id);\n }\n private now(): number {\n return Math.floor(Date.now());\n }\n\n // ── schema ───────────────────────────────────────────────────────────────────────────────────\n private ensureSchema(): Promise<void> {\n if (!this.schemaReady) {\n // A failed attempt is not cached: the next call tries again instead of failing forever.\n this.schemaReady = this.createTables().catch((err) => {\n this.schemaReady = undefined;\n throw err;\n });\n }\n return this.schemaReady;\n }\n\n /**\n * Run one `CREATE … IF NOT EXISTS`. Two instances booting on a fresh database race it, and Postgres\n * can reject the loser with a unique violation on its catalog even with `IF NOT EXISTS`; the\n * statement is simply re-run, by which point the table exists and it is a no-op.\n */\n private async createIfMissing(sql: string): Promise<void> {\n try {\n await this.exec.query(sql);\n } catch {\n await this.exec.query(sql);\n }\n }\n\n private async createTables(): Promise<void> {\n if (!this.autoCreate) return;\n const str = 'varchar(191)';\n const txt = this.isPg ? 'text' : 'longtext';\n const tasks = this.q(this.tasksTable);\n const results = this.q(this.resultsTable);\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${tasks} (\n ${this.q('step_id')} ${str} PRIMARY KEY,\n ${this.q('run_id')} ${str} NOT NULL,\n ${this.q('seq')} integer NOT NULL,\n ${this.q('name')} ${str} NOT NULL,\n ${this.q('grp')} ${str} NOT NULL,\n ${this.q('input')} ${txt},\n ${this.q('attempt')} integer NOT NULL,\n ${this.q('status')} varchar(32) NOT NULL,\n ${this.q('claimed_by')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${results} (\n ${this.q('step_id')} ${str} PRIMARY KEY,\n ${this.q('run_id')} ${str} NOT NULL,\n ${this.q('seq')} integer NOT NULL,\n ${this.q('status')} varchar(32) NOT NULL,\n ${this.q('output')} ${txt},\n ${this.q('error')} ${txt},\n ${this.q('started_at')} bigint,\n ${this.q('claimed_by')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${this.q(this.runsTable)} (\n ${this.q('run_id')} ${str} PRIMARY KEY,\n ${this.q('ns')} ${str} NOT NULL,\n ${this.q('priority')} integer NOT NULL,\n ${this.q('wake')} integer NOT NULL,\n ${this.q('claimed_by')} ${str},\n ${this.q('claim_token')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.exec\n .query(\n `CREATE INDEX ${this.q(`${this.runsTable}_ns_idx`)} ON ${this.q(this.runsTable)} (${this.q('ns')}, ${this.q('priority')}, ${this.q('created_at')})`,\n )\n .catch(() => {});\n // Best-effort index for the group poll (MySQL has no CREATE INDEX IF NOT EXISTS < 8.0.13).\n await this.exec\n .query(\n `CREATE INDEX ${this.q(`${this.tasksTable}_grp_idx`)} ON ${tasks} (${this.q('grp')}, ${this.q('status')}, ${this.q('created_at')})`,\n )\n .catch(() => {});\n }\n\n // ── engine → worker ────────────────────────────────────────────────────────────────────────\n async dispatch(task: RemoteTask): Promise<void> {\n await this.dispatchMany([task]);\n }\n\n /** {@link dispatch} for many tasks: one multi-row insert per {@link TASKS_INSERT_CHUNK} tasks. */\n async dispatchMany(tasks: RemoteTask[]): Promise<void> {\n if (tasks.length === 0) return;\n await this.ensureSchema();\n const t = this.q(this.tasksTable);\n const cols = [\n 'step_id',\n 'run_id',\n 'seq',\n 'name',\n 'grp',\n 'input',\n 'attempt',\n 'status',\n 'created_at',\n ];\n for (let at = 0; at < tasks.length; at += TASKS_INSERT_CHUNK) {\n const chunk = tasks.slice(at, at + TASKS_INSERT_CHUNK);\n const values = chunk\n .map((_, r) => `(${cols.map((__, c) => this.ph(r * cols.length + c + 1)).join(', ')})`)\n .join(', ');\n const into = `${t} (${cols.map((c) => this.q(c)).join(', ')}) VALUES ${values}`;\n // Idempotent: a redelivered dispatch for the same step_id is ignored (the row already exists).\n const sql = this.isPg\n ? `INSERT INTO ${into} ON CONFLICT (${this.q('step_id')}) DO NOTHING`\n : `INSERT IGNORE INTO ${into}`;\n const now = this.now();\n await this.exec.query(\n sql,\n chunk.flatMap((task) => [\n task.stepId,\n task.runId,\n task.seq,\n task.name,\n task.group,\n JSON.stringify(task.input ?? null),\n task.attempt,\n 'pending',\n now,\n ]),\n );\n }\n }\n\n // ── worker → engine ────────────────────────────────────────────────────────────────────────\n handle(name: string, fn: StepHandler): void {\n if (!this.group) throw new Error('DbTransport needs a `group` to register handlers');\n this.handlers.set(name, fn);\n if (!this.taskLoop) this.taskLoop = this.loop(() => this.drainTasks(this.group as string));\n }\n\n onResult(handler: (result: StepResult) => Promise<void>): void {\n if (!this.resultLoop) this.resultLoop = this.loop(() => this.drainResults(handler));\n }\n\n onHeartbeat(_handler: (beat: Heartbeat) => Promise<void>): void {\n // Not modelled; the lease/reclaim on `claimed_at` is the liveness mechanism.\n }\n\n // ── run queue (engine ↔ engine) ─────────────────────────────────────────────────────────────\n /**\n * Queue `pending` runs for whichever engine instance polls the run queue first: one row per run,\n * keyed by run id, so a run already waiting is not queued twice. A run whose row an instance holds\n * right now is released for another take, so a dispatch landing while the run's previous entry is\n * being handled is not lost when that entry is deleted. Up to {@link RUNS_INSERT_CHUNK} rows per\n * insert.\n */\n async dispatchRuns(runs: readonly RunDispatch[], opts?: RunQueueOptions): Promise<void> {\n if (runs.length === 0) return;\n await this.ensureSchema();\n // One row per run (an upsert may not touch the same row twice in one statement); a wake among\n // a run's dispatches makes its row a wake.\n const byRun = new Map<string, RunDispatch>();\n for (const run of runs) {\n if (!byRun.has(run.runId) || run.reason === 'wake') byRun.set(run.runId, run);\n }\n // In run-id order: two dispatches touching overlapping rows lock them in the same order.\n const unique = [...byRun.values()].sort((a, b) =>\n a.runId < b.runId ? -1 : a.runId > b.runId ? 1 : 0,\n );\n const t = this.q(this.runsTable);\n const cols = ['run_id', 'ns', 'priority', 'wake', 'created_at'];\n const now = this.now();\n for (let at = 0; at < unique.length; at += RUNS_INSERT_CHUNK) {\n const chunk = unique.slice(at, at + RUNS_INSERT_CHUNK);\n const values = chunk\n .map((_, r) => `(${cols.map((__, c) => this.ph(r * cols.length + c + 1)).join(', ')})`)\n .join(', ');\n const insert = `INTO ${t} (${cols.map((c) => this.q(c)).join(', ')}) VALUES ${values}`;\n const release = ['claimed_by', 'claim_token', 'claimed_at']\n .map((c) => `${this.q(c)} = NULL`)\n .join(', ');\n const wake = this.q('wake');\n const sql = this.isPg\n ? `INSERT ${insert} ON CONFLICT (${this.q('run_id')}) DO UPDATE SET ${release}, ${wake} = GREATEST(${t}.${wake}, EXCLUDED.${wake})`\n : `INSERT ${insert} ON DUPLICATE KEY UPDATE ${release}, ${wake} = GREATEST(${wake}, VALUES(${wake}))`;\n await this.retryOnDeadlock(() =>\n this.exec.query(\n sql,\n chunk.flatMap((run, r) => [\n run.runId,\n opts?.namespace ?? '',\n run.priority ?? 0,\n run.reason === 'wake' ? 1 : 0,\n now + r,\n ]),\n ),\n );\n }\n }\n\n /**\n * Poll the run queue, at most `opts.concurrency` runs in flight on this instance. Rows are claimed\n * with `FOR UPDATE SKIP LOCKED` (instances never take the same run), highest priority then oldest\n * first; a row is deleted once its handler settles, and its lease is renewed meanwhile, so a row is\n * re-taken only after the instance holding it went down. The returned stop takes no more rows and\n * resolves once the runs in flight settled.\n */\n onDispatchedRun(\n handler: (run: RunDispatch) => Promise<void>,\n opts: RunConsumerOptions,\n ): () => Promise<void> {\n const consumer: RunConsumer = {\n stopped: false,\n inflight: new Set(),\n slotFreed: undefined,\n loop: Promise.resolve(),\n };\n const concurrency = Math.max(1, Math.floor(opts.concurrency));\n const ns = opts.namespace ?? '';\n consumer.loop = this.consumeRuns(consumer, handler, concurrency, ns).catch((err) => {\n this.onError(err);\n });\n this.runConsumers.add(consumer);\n return () => this.stopRunConsumer(consumer);\n }\n\n private async stopRunConsumer(consumer: RunConsumer): Promise<void> {\n consumer.stopped = true;\n consumer.slotFreed?.();\n await consumer.loop;\n this.runConsumers.delete(consumer);\n }\n\n private async consumeRuns(\n consumer: RunConsumer,\n handler: (run: RunDispatch) => Promise<void>,\n concurrency: number,\n ns: string,\n ): Promise<void> {\n const renew = setInterval(\n () => void this.renewRunLeases(consumer).catch(() => undefined),\n Math.max(1_000, Math.floor(this.leaseMs / 3)),\n );\n renew.unref?.();\n const settled = new Set<Promise<unknown>>();\n try {\n while (this.running && !consumer.stopped) {\n const free = concurrency - consumer.inflight.size;\n if (free <= 0) {\n await new Promise<void>((resolve) => {\n consumer.slotFreed = resolve;\n });\n consumer.slotFreed = undefined;\n continue;\n }\n let rows: ClaimedRun[] = [];\n try {\n await this.ensureSchema();\n rows = await this.retryOnDeadlock(() =>\n this.claimRuns(ns, Math.min(free, RUNS_CLAIM_MAX)),\n );\n } catch (err) {\n if (this.running && !consumer.stopped) this.onError(err);\n }\n for (const { run, token } of rows) {\n consumer.inflight.add(token);\n const done = handler(run)\n .catch(() => undefined)\n .then(() =>\n this.retryOnDeadlock(() =>\n this.exec.query(\n `DELETE FROM ${this.q(this.runsTable)} WHERE ${this.q('claim_token')} = ${this.ph(1)}`,\n [token],\n ),\n ).catch(() => undefined),\n )\n .finally(() => {\n consumer.inflight.delete(token);\n settled.delete(done);\n consumer.slotFreed?.();\n });\n settled.add(done);\n }\n if (rows.length === 0) await this.sleep(this.pollMs);\n }\n await Promise.allSettled([...settled]);\n } finally {\n clearInterval(renew);\n }\n }\n\n /**\n * Run `statement`, again on a deadlock: queueing a run (an upsert), taking one (`SKIP LOCKED`)\n * and deleting a handled one touch the same rows from different instances, and the database then\n * rolls one of them back rather than wait (InnoDB reports `ER_LOCK_DEADLOCK`, Postgres `40P01`).\n */\n private async retryOnDeadlock<T>(statement: () => Promise<T>): Promise<T> {\n for (let attempt = 1; ; attempt += 1) {\n try {\n return await statement();\n } catch (err) {\n if (attempt >= DEADLOCK_RETRIES || !isDeadlock(err)) throw err;\n await this.sleep(10 * attempt + Math.floor(Math.random() * 10));\n }\n }\n }\n\n private async claimRuns(ns: string, limit: number): Promise<ClaimedRun[]> {\n const t = this.q(this.runsTable);\n const staleBefore = this.now() - this.leaseMs;\n return this.exec.transaction(async (tx) => {\n const rows = await tx.query<RunRow>(\n `SELECT ${this.q('run_id')}, ${this.q('wake')} FROM ${t}\n WHERE ${this.q('ns')} = ${this.ph(1)}\n AND (${this.q('claimed_at')} IS NULL OR ${this.q('claimed_at')} < ${this.ph(2)})\n ORDER BY ${this.q('priority')} DESC, ${this.q('created_at')} ASC\n LIMIT ${Math.floor(limit)} FOR UPDATE SKIP LOCKED`,\n [ns, staleBefore],\n );\n const claimed = rows.map((r) => ({\n run: {\n runId: r.run_id,\n reason: Number(r.wake) === 1 ? ('wake' as const) : ('start' as const),\n },\n token: globalThis.crypto.randomUUID(),\n }));\n const now = this.now();\n for (const {\n run: { runId },\n token,\n } of claimed) {\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_by')} = ${this.ph(1)}, ${this.q('claim_token')} = ${this.ph(2)}, ${this.q('claimed_at')} = ${this.ph(3)}\n WHERE ${this.q('run_id')} = ${this.ph(4)}`,\n [this.instanceId, token, now, runId],\n );\n }\n return claimed;\n });\n }\n\n /** Keep the rows of the runs in flight leased, so a long turn is not re-taken by another instance. */\n private async renewRunLeases(consumer: RunConsumer): Promise<void> {\n const tokens = [...consumer.inflight];\n if (tokens.length === 0) return;\n await this.exec.query(\n `UPDATE ${this.q(this.runsTable)} SET ${this.q('claimed_at')} = ${this.ph(1)}\n WHERE ${this.q('claim_token')} IN (${tokens.map((_, i) => this.ph(i + 2)).join(', ')})`,\n [this.now(), ...tokens],\n );\n }\n\n // ── polling ──────────────────────────────────────────────────────────────────────────────────\n /** Run `tick` repeatedly; sleep `pollMs` when it reports an empty queue. Stops on `close()`. */\n private async loop(tick: () => Promise<number>): Promise<void> {\n while (this.running) {\n let processed = 0;\n try {\n await this.ensureSchema();\n processed = await tick();\n } catch (err) {\n if (this.running) this.onError(err);\n }\n if (processed === 0 && this.running) await this.sleep(this.pollMs);\n }\n }\n\n /**\n * One task-loop round: claim as many rows as there are free slots (never more — a claimed row that\n * isn't running is one an idle sibling can't run), and start each. With every slot busy, wait for\n * one to free instead of polling.\n */\n private async drainTasks(group: string): Promise<number> {\n const free = this.concurrency - this.runningTasks.size;\n if (free <= 0) {\n await Promise.race(this.runningTasks);\n return 1;\n }\n const { rows, token } = await this.claim<TaskRow>(\n this.tasksTable,\n Math.min(free, this.batchSize),\n `${this.q('grp')} = ${this.ph(1)}`,\n [group],\n );\n if (rows.length === 0) return 0;\n this.hold(this.tasksTable, token);\n let left = rows.length;\n for (const row of rows) {\n const run = this.runTask(row, group, token).finally(() => {\n this.runningTasks.delete(run);\n left -= 1;\n if (left === 0) this.release(this.tasksTable, token);\n });\n this.runningTasks.add(run);\n }\n return rows.length;\n }\n\n /**\n * Run one claimed task and settle it — only while this instance still holds the claim: the claim is\n * re-checked before running, and the task row is deleted and its result written in ONE transaction,\n * conditional on the claim token, so an instance whose claim was taken over neither deletes the new\n * holder's row nor publishes a second result.\n */\n private async runTask(row: TaskRow, group: string, token: string): Promise<void> {\n try {\n if (!(await this.stillHolds(row.step_id, token))) {\n this.onError(\n new Error(\n `[DbTransport] lost the claim on task ${row.step_id} before running it — another instance took it over; not running it here`,\n ),\n );\n return;\n }\n const task: RemoteTask = {\n runId: row.run_id,\n seq: Number(row.seq),\n stepId: row.step_id,\n name: row.name,\n group,\n input: row.input == null ? undefined : JSON.parse(row.input),\n attempt: Number(row.attempt),\n };\n const result = await runStepHandler(task, this.handlers.get(task.name));\n if (!(await this.settleTask(task.stepId, token, result))) {\n this.onError(\n new Error(\n `[DbTransport] lost the claim on task ${task.stepId} while running it (this instance went silent past leaseMs) — another instance took it over and may run it too; this run's result was dropped. Raise leaseMs if steps block the event loop`,\n ),\n );\n }\n } catch (err) {\n // A database error settling the row (or a malformed row): it stays claimed, stops being\n // renewed, and is reclaimed after the lease — at-least-once, like a crash.\n this.onError(err);\n }\n }\n\n /** Whether the claim on `stepId` is still `token`'s (re-stamping it if so). */\n private async stillHolds(stepId: string, token: string): Promise<boolean> {\n const t = this.q(this.tasksTable);\n const rows = await this.exec.transaction(async (tx) => {\n const held = await tx.query<{ step_id: string }>(\n `SELECT ${this.q('step_id')} FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)} FOR UPDATE`,\n [stepId, token],\n );\n if (held.length > 0) {\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_at')} = ${this.ph(1)} WHERE ${this.q('step_id')} = ${this.ph(2)} AND ${this.q('claimed_by')} = ${this.ph(3)}`,\n [this.now(), stepId, token],\n );\n }\n return held;\n });\n return rows.length > 0;\n }\n\n /** Delete the task row and publish its result in ONE transaction, only while `token` holds it. */\n private async settleTask(stepId: string, token: string, result: StepResult): Promise<boolean> {\n const t = this.q(this.tasksTable);\n return this.exec.transaction(async (tx) => {\n const held = await tx.query<{ step_id: string }>(\n `SELECT ${this.q('step_id')} FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)} FOR UPDATE`,\n [stepId, token],\n );\n if (held.length === 0) return false;\n await tx.query(\n `DELETE FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)}`,\n [stepId, token],\n );\n await this.insertResult(result, tx);\n return true;\n });\n }\n\n /**\n * Claim a batch of results and hand them all to the engine at once, then delete the ones whose\n * handler resolved — only after it did (at-least-once), and only while this round still holds the\n * claim (a newer result for the same step that replaced the row resets the claim, so it is\n * delivered next instead of deleted). The next round is claimed while this one runs, up to\n * {@link RESULTS_IN_FLIGHT} results; the round's claim is renewed until it is done. A throwing\n * handler is reported and its row left for re-delivery after the lease.\n */\n private async drainResults(handler: (result: StepResult) => Promise<void>): Promise<number> {\n const free = RESULTS_IN_FLIGHT - this.resultsInFlight;\n if (free <= 0) {\n await Promise.race(this.runningResults);\n return 1;\n }\n const { rows, token } = await this.claim<ResultRow>(\n this.resultsTable,\n Math.min(free, Math.max(this.batchSize, RESULTS_CLAIM_MAX)),\n '1 = 1',\n [],\n );\n if (rows.length === 0) return 0;\n this.hold(this.resultsTable, token);\n this.resultsInFlight += rows.length;\n const round = this.handleResults(rows, token, handler).finally(() => {\n this.resultsInFlight -= rows.length;\n this.runningResults.delete(round);\n this.release(this.resultsTable, token);\n });\n this.runningResults.add(round);\n return rows.length;\n }\n\n /** Hand one claimed round of results to the engine, then delete the delivered ones in one statement. */\n private async handleResults(\n rows: ResultRow[],\n token: string,\n handler: (result: StepResult) => Promise<void>,\n ): Promise<void> {\n const delivered: string[] = [];\n await Promise.all(\n rows.map(async (row) => {\n try {\n await handler({\n runId: row.run_id,\n seq: Number(row.seq),\n stepId: row.step_id,\n status: row.status as StepResult['status'],\n output: row.output == null ? undefined : JSON.parse(row.output),\n error: row.error == null ? undefined : JSON.parse(row.error),\n startedAt: row.started_at == null ? undefined : Number(row.started_at),\n });\n delivered.push(row.step_id);\n } catch (err) {\n this.onError(err);\n }\n }),\n );\n if (delivered.length === 0) return;\n try {\n const ids = delivered.map((_, i) => this.ph(i + 2)).join(', ');\n await this.exec.query(\n `DELETE FROM ${this.q(this.resultsTable)} WHERE ${this.q('claimed_by')} = ${this.ph(1)} AND ${this.q('step_id')} IN (${ids})`,\n [token, ...delivered],\n );\n } catch (err) {\n // The rows stay and are re-delivered after the lease; the engine drops a settled step's copy.\n this.onError(err);\n }\n }\n\n /**\n * Atomically claim up to `limit` un-leased rows matching `where` and stamp them with a claim token\n * unique to this round (`<instanceId>:<uuid>`). `SELECT … FOR UPDATE SKIP LOCKED` inside the txn\n * means concurrent instances skip each other's locked rows instead of blocking, so no row is\n * claimed twice; the token is what every later delete/settle is conditional on.\n */\n private async claim<T extends { step_id: string }>(\n table: string,\n limit: number,\n where: string,\n whereParams: unknown[],\n ): Promise<{ rows: T[]; token: string }> {\n const t = this.q(table);\n const staleBefore = this.now() - this.leaseMs;\n const token = `${this.instanceId}:${randomUUID()}`;\n const rows = await this.exec.transaction(async (tx) => {\n const leaseClause = `(${this.q('claimed_at')} IS NULL OR ${this.q('claimed_at')} < ${this.ph(whereParams.length + 1)})`;\n const found = await tx.query<T>(\n `SELECT * FROM ${t} WHERE ${where} AND ${leaseClause}\n ORDER BY ${this.q('created_at')} ASC LIMIT ${Math.max(1, Math.floor(limit))} FOR UPDATE SKIP LOCKED`,\n [...whereParams, staleBefore],\n );\n if (found.length > 0) {\n const ids = found.map((r) => r.step_id);\n const placeholders = ids.map((_, i) => this.ph(i + 3)).join(', ');\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_by')} = ${this.ph(1)}, ${this.q('claimed_at')} = ${this.ph(2)}\n WHERE ${this.q('step_id')} IN (${placeholders})`,\n [token, this.now(), ...ids],\n );\n }\n return found;\n });\n return { rows, token };\n }\n\n // ── claim renewal: every claim held, not only the row being run ─────────────────────────────\n private hold(table: string, token: string): void {\n let tokens = this.held.get(table);\n if (!tokens) {\n tokens = new Set();\n this.held.set(table, tokens);\n }\n tokens.add(token);\n if (this.renewTimer) return;\n const intervalMs = Math.max(10, Math.floor(this.leaseMs / 3));\n this.renewTimer = setInterval(() => {\n if (this.renewal) return; // a slow database never stacks renewal rounds\n const round = this.renewHeld()\n .catch((err) => this.onError(err))\n .finally(() => {\n if (this.renewal === round) this.renewal = undefined;\n });\n this.renewal = round;\n }, intervalMs);\n this.renewTimer.unref?.();\n }\n\n private release(table: string, token: string): void {\n const tokens = this.held.get(table);\n tokens?.delete(token);\n if (tokens?.size === 0) this.held.delete(table);\n if (this.held.size === 0 && this.renewTimer) {\n clearInterval(this.renewTimer);\n this.renewTimer = undefined;\n }\n }\n\n /** Re-stamp `claimed_at` on every row still claimed under a token this instance holds. */\n private async renewHeld(): Promise<void> {\n for (const [table, tokens] of this.held) {\n if (tokens.size === 0) continue;\n const list = [...tokens];\n const placeholders = list.map((_, i) => this.ph(i + 2)).join(', ');\n await this.exec.query(\n `UPDATE ${this.q(table)} SET ${this.q('claimed_at')} = ${this.ph(1)} WHERE ${this.q('claimed_by')} IN (${placeholders})`,\n [this.now(), ...list],\n );\n }\n }\n\n /**\n * Write a result row. A result already waiting for the same step (an earlier attempt's, not yet\n * deleted by the engine) is REPLACED and its claim reset, never kept: `step_id` is stable across\n * attempts, so keeping the old row would silently drop a retry's result.\n */\n private async insertResult(result: StepResult, tx: SqlTx = this.exec): Promise<void> {\n const t = this.q(this.resultsTable);\n const cols = [\n 'step_id',\n 'run_id',\n 'seq',\n 'status',\n 'output',\n 'error',\n 'started_at',\n 'created_at',\n ];\n const replace = ['run_id', 'seq', 'status', 'output', 'error', 'started_at', 'created_at'];\n const values = `(${cols.map((c) => this.q(c)).join(', ')}) VALUES (${cols.map((_, i) => this.ph(i + 1)).join(', ')})`;\n const sql = this.isPg\n ? `INSERT INTO ${t} ${values} ON CONFLICT (${this.q('step_id')}) DO UPDATE SET ${[\n ...replace.map((c) => `${this.q(c)} = EXCLUDED.${this.q(c)}`),\n `${this.q('claimed_by')} = NULL`,\n `${this.q('claimed_at')} = NULL`,\n ].join(', ')}`\n : `INSERT INTO ${t} ${values} ON DUPLICATE KEY UPDATE ${[\n ...replace.map((c) => `${this.q(c)} = VALUES(${this.q(c)})`),\n `${this.q('claimed_by')} = NULL`,\n `${this.q('claimed_at')} = NULL`,\n ].join(', ')}`;\n await tx.query(sql, [\n result.stepId,\n result.runId,\n result.seq,\n result.status,\n result.output === undefined ? null : JSON.stringify(result.output),\n result.error === undefined ? null : JSON.stringify(result.error),\n result.startedAt ?? null,\n this.now(),\n ]);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n }\n\n /** Stop the pollers and wait for the current ticks to settle. Does not close the shared DataSource. */\n async close(): Promise<void> {\n this.running = false;\n await Promise.allSettled([\n this.taskLoop,\n this.resultLoop,\n ...[...this.runConsumers].map((c) => this.stopRunConsumer(c)),\n ]);\n // Let running tasks and results settle (their claims keep being renewed until they do).\n await Promise.allSettled([...this.runningTasks, ...this.runningResults]);\n if (this.renewTimer) clearInterval(this.renewTimer);\n this.renewTimer = undefined;\n await this.renewal;\n }\n}\n","/**\n * The tiny SQL surface `DbTransport` needs, so it rides **whatever ORM/connection the app already\n * has** instead of forcing a specific one. Implement it for your ORM (adapters for TypeORM and\n * MikroORM ship below) and hand it to `new DbTransport({ executor })`. Mirrors how the durable\n * *store* is ORM-pluggable (store-typeorm / store-mikro-orm / …).\n */\nexport interface SqlExecutor {\n /** Drives placeholder style (`$n` vs `?`) and a couple of DDL type choices. */\n readonly dialect: 'mysql' | 'postgres';\n /** Quote/escape an identifier (table/column name) for this dialect. */\n escapeId(id: string): string;\n /** Run a statement outside a transaction. */\n query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;\n /** Run `fn` inside a transaction — the claim uses `FOR UPDATE SKIP LOCKED` within it. */\n transaction<T>(fn: (tx: SqlTx) => Promise<T>): Promise<T>;\n}\n\nexport interface SqlTx {\n query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;\n}\n\n// ── TypeORM ──────────────────────────────────────────────────────────────────────────────────────\n\n/** Structural slice of a TypeORM `DataSource` — avoids a hard dependency on typeorm. */\ninterface TypeOrmDataSourceLike {\n options: { type: string };\n driver: { escape(id: string): string };\n query(sql: string, params?: unknown[]): Promise<unknown>;\n createQueryRunner(): {\n connect(): Promise<void>;\n startTransaction(): Promise<void>;\n commitTransaction(): Promise<void>;\n rollbackTransaction(): Promise<void>;\n release(): Promise<void>;\n query(sql: string, params?: unknown[]): Promise<unknown>;\n };\n}\n\n/** Build an executor from a TypeORM `DataSource` (e.g. the one the durable store already uses). */\nexport function typeOrmExecutor(ds: TypeOrmDataSourceLike): SqlExecutor {\n return {\n dialect: /postgres/.test(String(ds.options.type)) ? 'postgres' : 'mysql',\n escapeId: (id) => ds.driver.escape(id),\n query: <T>(sql: string, params?: unknown[]) => ds.query(sql, params) as Promise<T[]>,\n transaction: async <T>(fn: (tx: SqlTx) => Promise<T>) => {\n const qr = ds.createQueryRunner();\n await qr.connect();\n await qr.startTransaction();\n try {\n const result = await fn({\n query: <U>(sql: string, params?: unknown[]) => qr.query(sql, params) as Promise<U[]>,\n });\n await qr.commitTransaction();\n return result;\n } catch (err) {\n await qr.rollbackTransaction();\n throw err;\n } finally {\n await qr.release();\n }\n },\n };\n}\n\n// ── MikroORM ─────────────────────────────────────────────────────────────────────────────────────\n\n/** Structural slice of a MikroORM `EntityManager` — avoids a hard dependency on @mikro-orm/core. */\ninterface MikroOrmEmLike {\n getPlatform(): { constructor: { name: string }; quoteIdentifier(id: string): string };\n getConnection(): {\n execute(sql: string, params?: unknown[], method?: 'all', ctx?: unknown): Promise<unknown>;\n };\n getTransactionContext?(): unknown;\n transactional<T>(cb: (em: MikroOrmEmLike) => Promise<T>): Promise<T>;\n}\n\n/**\n * Build an executor from a MikroORM `EntityManager` — i.e. **the app's own configured ORM**. Runs\n * raw SQL through `em.getConnection().execute(...)` and wraps the claim in `em.transactional(...)`,\n * threading the transaction context so `FOR UPDATE SKIP LOCKED` locks within the tx.\n */\nexport function mikroOrmExecutor(em: MikroOrmEmLike): SqlExecutor {\n const platform = em.getPlatform();\n return {\n dialect: /postgre/i.test(platform.constructor.name) ? 'postgres' : 'mysql',\n escapeId: (id) => platform.quoteIdentifier(id),\n query: <T>(sql: string, params?: unknown[]) =>\n em.getConnection().execute(sql, params ?? [], 'all') as Promise<T[]>,\n transaction: <T>(fn: (tx: SqlTx) => Promise<T>) =>\n em.transactional((txEm) =>\n fn({\n query: <U>(sql: string, params?: unknown[]) =>\n txEm\n .getConnection()\n .execute(sql, params ?? [], 'all', txEm.getTransactionContext?.()) as Promise<U[]>,\n }),\n ),\n };\n}\n"],"mappings":"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","names":["typeOrmExecutor","ds","dialect","test","String","options","type","escapeId","id","driver","escape","query","sql","params","transaction","fn","qr","createQueryRunner","connect","startTransaction","result","commitTransaction","err","rollbackTransaction","release","mikroOrmExecutor","em","platform","getPlatform","name","quoteIdentifier","getConnection","execute","transactional","txEm","getTransactionContext","RUNS_INSERT_CHUNK","DEADLOCK_RETRIES","isDeadlock","err","codes","code","errno","driverError","some","c","TASKS_INSERT_CHUNK","RUNS_CLAIM_MAX","RESULTS_IN_FLIGHT","RESULTS_CLAIM_MAX","numberOption","value","fallback","min","undefined","Number","isFinite","DbTransport","exec","group","tasksTable","resultsTable","runsTable","runConsumers","Set","pollMs","leaseMs","batchSize","concurrency","onError","autoCreate","instanceId","isPg","handlers","Map","running","schemaReady","taskLoop","resultLoop","runningTasks","runningResults","resultsInFlight","held","renewTimer","renewal","options","executor","prefix","Math","floor","console","error","randomUUID","dialect","ph","i","q","id","escapeId","now","Date","ensureSchema","createTables","catch","createIfMissing","sql","query","str","txt","tasks","results","dispatch","task","dispatchMany","length","t","cols","at","chunk","slice","values","map","_","r","__","join","into","flatMap","stepId","runId","seq","name","JSON","stringify","input","attempt","handle","fn","Error","set","loop","drainTasks","onResult","handler","drainResults","onHeartbeat","_handler","dispatchRuns","runs","opts","byRun","run","has","reason","unique","sort","a","b","insert","release","wake","retryOnDeadlock","namespace","priority","onDispatchedRun","consumer","stopped","inflight","slotFreed","Promise","resolve","max","ns","consumeRuns","add","stopRunConsumer","delete","renew","setInterval","renewRunLeases","unref","settled","free","size","rows","claimRuns","token","done","then","finally","sleep","allSettled","clearInterval","statement","random","limit","staleBefore","transaction","tx","claimed","run_id","globalThis","crypto","tokens","tick","processed","race","claim","hold","left","row","runTask","stillHolds","step_id","parse","result","runStepHandler","get","settleTask","insertResult","round","handleResults","delivered","all","status","output","startedAt","started_at","push","ids","table","where","whereParams","leaseClause","found","placeholders","intervalMs","renewHeld","list","replace","ms","setTimeout","close"]}
package/dist/index.d.cts CHANGED
@@ -85,7 +85,8 @@ interface DbTransportOptions {
85
85
  leaseMs?: number;
86
86
  /**
87
87
  * Max rows claimed per poll. Default 10. The task loop claims only as many rows as it has free
88
- * {@link concurrency} slots (capped by this); the results loop claims up to this many.
88
+ * {@link concurrency} slots (capped by this). The results loop claims results in larger rounds of
89
+ * its own (a few hundred, or this if larger), since the engine settles them together.
89
90
  */
90
91
  batchSize?: number;
91
92
  /**
@@ -138,6 +139,10 @@ declare class DbTransport implements Transport {
138
139
  private resultLoop?;
139
140
  /** Tasks running on this instance (at most `concurrency`). */
140
141
  private readonly runningTasks;
142
+ /** Claim rounds of results being handled on this instance — see {@link drainResults}. */
143
+ private readonly runningResults;
144
+ /** Results being handled across {@link runningResults} (at most {@link RESULTS_IN_FLIGHT}). */
145
+ private resultsInFlight;
141
146
  /** Claim tokens this instance holds, by table — every one is renewed on `renewTimer`. */
142
147
  private readonly held;
143
148
  private renewTimer?;
@@ -156,6 +161,8 @@ declare class DbTransport implements Transport {
156
161
  private createIfMissing;
157
162
  private createTables;
158
163
  dispatch(task: RemoteTask): Promise<void>;
164
+ /** {@link dispatch} for many tasks: one multi-row insert per {@link TASKS_INSERT_CHUNK} tasks. */
165
+ dispatchMany(tasks: RemoteTask[]): Promise<void>;
159
166
  handle(name: string, fn: StepHandler): void;
160
167
  onResult(handler: (result: StepResult) => Promise<void>): void;
161
168
  onHeartbeat(_handler: (beat: Heartbeat) => Promise<void>): void;
@@ -206,13 +213,16 @@ declare class DbTransport implements Transport {
206
213
  /** Delete the task row and publish its result in ONE transaction, only while `token` holds it. */
207
214
  private settleTask;
208
215
  /**
209
- * Claim a batch of results, deliver each to the engine, and delete it — only after its handler ran
210
- * (at-least-once), and only while this round still holds the claim (a newer result for the same
211
- * step that replaced the row resets the claim, so it is delivered next instead of deleted). The
212
- * batch's claim is renewed until it is done. A throwing handler is reported and its row left for
213
- * re-delivery after the lease; the rest of the batch continues.
216
+ * Claim a batch of results and hand them all to the engine at once, then delete the ones whose
217
+ * handler resolved — only after it did (at-least-once), and only while this round still holds the
218
+ * claim (a newer result for the same step that replaced the row resets the claim, so it is
219
+ * delivered next instead of deleted). The next round is claimed while this one runs, up to
220
+ * {@link RESULTS_IN_FLIGHT} results; the round's claim is renewed until it is done. A throwing
221
+ * handler is reported and its row left for re-delivery after the lease.
214
222
  */
215
223
  private drainResults;
224
+ /** Hand one claimed round of results to the engine, then delete the delivered ones in one statement. */
225
+ private handleResults;
216
226
  /**
217
227
  * Atomically claim up to `limit` un-leased rows matching `where` and stamp them with a claim token
218
228
  * unique to this round (`<instanceId>:<uuid>`). `SELECT … FOR UPDATE SKIP LOCKED` inside the txn
package/dist/index.d.ts CHANGED
@@ -85,7 +85,8 @@ interface DbTransportOptions {
85
85
  leaseMs?: number;
86
86
  /**
87
87
  * Max rows claimed per poll. Default 10. The task loop claims only as many rows as it has free
88
- * {@link concurrency} slots (capped by this); the results loop claims up to this many.
88
+ * {@link concurrency} slots (capped by this). The results loop claims results in larger rounds of
89
+ * its own (a few hundred, or this if larger), since the engine settles them together.
89
90
  */
90
91
  batchSize?: number;
91
92
  /**
@@ -138,6 +139,10 @@ declare class DbTransport implements Transport {
138
139
  private resultLoop?;
139
140
  /** Tasks running on this instance (at most `concurrency`). */
140
141
  private readonly runningTasks;
142
+ /** Claim rounds of results being handled on this instance — see {@link drainResults}. */
143
+ private readonly runningResults;
144
+ /** Results being handled across {@link runningResults} (at most {@link RESULTS_IN_FLIGHT}). */
145
+ private resultsInFlight;
141
146
  /** Claim tokens this instance holds, by table — every one is renewed on `renewTimer`. */
142
147
  private readonly held;
143
148
  private renewTimer?;
@@ -156,6 +161,8 @@ declare class DbTransport implements Transport {
156
161
  private createIfMissing;
157
162
  private createTables;
158
163
  dispatch(task: RemoteTask): Promise<void>;
164
+ /** {@link dispatch} for many tasks: one multi-row insert per {@link TASKS_INSERT_CHUNK} tasks. */
165
+ dispatchMany(tasks: RemoteTask[]): Promise<void>;
159
166
  handle(name: string, fn: StepHandler): void;
160
167
  onResult(handler: (result: StepResult) => Promise<void>): void;
161
168
  onHeartbeat(_handler: (beat: Heartbeat) => Promise<void>): void;
@@ -206,13 +213,16 @@ declare class DbTransport implements Transport {
206
213
  /** Delete the task row and publish its result in ONE transaction, only while `token` holds it. */
207
214
  private settleTask;
208
215
  /**
209
- * Claim a batch of results, deliver each to the engine, and delete it — only after its handler ran
210
- * (at-least-once), and only while this round still holds the claim (a newer result for the same
211
- * step that replaced the row resets the claim, so it is delivered next instead of deleted). The
212
- * batch's claim is renewed until it is done. A throwing handler is reported and its row left for
213
- * re-delivery after the lease; the rest of the batch continues.
216
+ * Claim a batch of results and hand them all to the engine at once, then delete the ones whose
217
+ * handler resolved — only after it did (at-least-once), and only while this round still holds the
218
+ * claim (a newer result for the same step that replaced the row resets the claim, so it is
219
+ * delivered next instead of deleted). The next round is claimed while this one runs, up to
220
+ * {@link RESULTS_IN_FLIGHT} results; the round's claim is renewed until it is done. A throwing
221
+ * handler is reported and its row left for re-delivery after the lease.
214
222
  */
215
223
  private drainResults;
224
+ /** Hand one claimed round of results to the engine, then delete the delivered ones in one statement. */
225
+ private handleResults;
216
226
  /**
217
227
  * Atomically claim up to `limit` un-leased rows matching `where` and stamp them with a claim token
218
228
  * unique to this round (`<instanceId>:<uuid>`). `SELECT … FOR UPDATE SKIP LOCKED` inside the txn
package/dist/index.js CHANGED
@@ -57,7 +57,10 @@ function isDeadlock(err) {
57
57
  return codes.some((c) => c === "ER_LOCK_DEADLOCK" || c === 1213 || c === "40P01");
58
58
  }
59
59
  __name(isDeadlock, "isDeadlock");
60
+ var TASKS_INSERT_CHUNK = 200;
60
61
  var RUNS_CLAIM_MAX = 50;
62
+ var RESULTS_IN_FLIGHT = 512;
63
+ var RESULTS_CLAIM_MAX = 256;
61
64
  function numberOption(value, fallback, min) {
62
65
  return value !== void 0 && Number.isFinite(value) && value >= min ? value : fallback;
63
66
  }
@@ -87,6 +90,10 @@ var DbTransport = class {
87
90
  resultLoop;
88
91
  /** Tasks running on this instance (at most `concurrency`). */
89
92
  runningTasks = /* @__PURE__ */ new Set();
93
+ /** Claim rounds of results being handled on this instance — see {@link drainResults}. */
94
+ runningResults = /* @__PURE__ */ new Set();
95
+ /** Results being handled across {@link runningResults} (at most {@link RESULTS_IN_FLIGHT}). */
96
+ resultsInFlight = 0;
90
97
  /** Claim tokens this instance holds, by table — every one is renewed on `renewTimer`. */
91
98
  held = /* @__PURE__ */ new Map();
92
99
  renewTimer;
@@ -188,9 +195,15 @@ var DbTransport = class {
188
195
  }
189
196
  // ── engine → worker ────────────────────────────────────────────────────────────────────────
190
197
  async dispatch(task) {
198
+ await this.dispatchMany([
199
+ task
200
+ ]);
201
+ }
202
+ /** {@link dispatch} for many tasks: one multi-row insert per {@link TASKS_INSERT_CHUNK} tasks. */
203
+ async dispatchMany(tasks) {
204
+ if (tasks.length === 0) return;
191
205
  await this.ensureSchema();
192
206
  const t = this.q(this.tasksTable);
193
- const ignore = this.isPg ? `INSERT INTO ${t} (...) ON CONFLICT (${this.q("step_id")}) DO NOTHING` : `INSERT IGNORE INTO ${t} (...)`;
194
207
  const cols = [
195
208
  "step_id",
196
209
  "run_id",
@@ -202,18 +215,24 @@ var DbTransport = class {
202
215
  "status",
203
216
  "created_at"
204
217
  ];
205
- const sql = ignore.replace("(...)", `(${cols.map((c) => this.q(c)).join(", ")}) VALUES (${cols.map((_, i) => this.ph(i + 1)).join(", ")})`);
206
- await this.exec.query(sql, [
207
- task.stepId,
208
- task.runId,
209
- task.seq,
210
- task.name,
211
- task.group,
212
- JSON.stringify(task.input ?? null),
213
- task.attempt,
214
- "pending",
215
- this.now()
216
- ]);
218
+ for (let at = 0; at < tasks.length; at += TASKS_INSERT_CHUNK) {
219
+ const chunk = tasks.slice(at, at + TASKS_INSERT_CHUNK);
220
+ const values = chunk.map((_, r) => `(${cols.map((__, c) => this.ph(r * cols.length + c + 1)).join(", ")})`).join(", ");
221
+ const into = `${t} (${cols.map((c) => this.q(c)).join(", ")}) VALUES ${values}`;
222
+ const sql = this.isPg ? `INSERT INTO ${into} ON CONFLICT (${this.q("step_id")}) DO NOTHING` : `INSERT IGNORE INTO ${into}`;
223
+ const now = this.now();
224
+ await this.exec.query(sql, chunk.flatMap((task) => [
225
+ task.stepId,
226
+ task.runId,
227
+ task.seq,
228
+ task.name,
229
+ task.group,
230
+ JSON.stringify(task.input ?? null),
231
+ task.attempt,
232
+ "pending",
233
+ now
234
+ ]));
235
+ }
217
236
  }
218
237
  // ── worker → engine ────────────────────────────────────────────────────────────────────────
219
238
  handle(name, fn) {
@@ -508,43 +527,61 @@ var DbTransport = class {
508
527
  });
509
528
  }
510
529
  /**
511
- * Claim a batch of results, deliver each to the engine, and delete it — only after its handler ran
512
- * (at-least-once), and only while this round still holds the claim (a newer result for the same
513
- * step that replaced the row resets the claim, so it is delivered next instead of deleted). The
514
- * batch's claim is renewed until it is done. A throwing handler is reported and its row left for
515
- * re-delivery after the lease; the rest of the batch continues.
530
+ * Claim a batch of results and hand them all to the engine at once, then delete the ones whose
531
+ * handler resolved — only after it did (at-least-once), and only while this round still holds the
532
+ * claim (a newer result for the same step that replaced the row resets the claim, so it is
533
+ * delivered next instead of deleted). The next round is claimed while this one runs, up to
534
+ * {@link RESULTS_IN_FLIGHT} results; the round's claim is renewed until it is done. A throwing
535
+ * handler is reported and its row left for re-delivery after the lease.
516
536
  */
517
537
  async drainResults(handler) {
518
- const { rows, token } = await this.claim(this.resultsTable, this.batchSize, "1 = 1", []);
538
+ const free = RESULTS_IN_FLIGHT - this.resultsInFlight;
539
+ if (free <= 0) {
540
+ await Promise.race(this.runningResults);
541
+ return 1;
542
+ }
543
+ const { rows, token } = await this.claim(this.resultsTable, Math.min(free, Math.max(this.batchSize, RESULTS_CLAIM_MAX)), "1 = 1", []);
519
544
  if (rows.length === 0) return 0;
520
545
  this.hold(this.resultsTable, token);
521
- try {
522
- for (const row of rows) {
523
- if (!this.running) break;
524
- try {
525
- const result = {
526
- runId: row.run_id,
527
- seq: Number(row.seq),
528
- stepId: row.step_id,
529
- status: row.status,
530
- output: row.output == null ? void 0 : JSON.parse(row.output),
531
- error: row.error == null ? void 0 : JSON.parse(row.error),
532
- startedAt: row.started_at == null ? void 0 : Number(row.started_at)
533
- };
534
- await handler(result);
535
- await this.exec.query(`DELETE FROM ${this.q(this.resultsTable)} WHERE ${this.q("step_id")} = ${this.ph(1)} AND ${this.q("claimed_by")} = ${this.ph(2)}`, [
536
- row.step_id,
537
- token
538
- ]);
539
- } catch (err) {
540
- this.onError(err);
541
- }
542
- }
543
- } finally {
546
+ this.resultsInFlight += rows.length;
547
+ const round = this.handleResults(rows, token, handler).finally(() => {
548
+ this.resultsInFlight -= rows.length;
549
+ this.runningResults.delete(round);
544
550
  this.release(this.resultsTable, token);
545
- }
551
+ });
552
+ this.runningResults.add(round);
546
553
  return rows.length;
547
554
  }
555
+ /** Hand one claimed round of results to the engine, then delete the delivered ones in one statement. */
556
+ async handleResults(rows, token, handler) {
557
+ const delivered = [];
558
+ await Promise.all(rows.map(async (row) => {
559
+ try {
560
+ await handler({
561
+ runId: row.run_id,
562
+ seq: Number(row.seq),
563
+ stepId: row.step_id,
564
+ status: row.status,
565
+ output: row.output == null ? void 0 : JSON.parse(row.output),
566
+ error: row.error == null ? void 0 : JSON.parse(row.error),
567
+ startedAt: row.started_at == null ? void 0 : Number(row.started_at)
568
+ });
569
+ delivered.push(row.step_id);
570
+ } catch (err) {
571
+ this.onError(err);
572
+ }
573
+ }));
574
+ if (delivered.length === 0) return;
575
+ try {
576
+ const ids = delivered.map((_, i) => this.ph(i + 2)).join(", ");
577
+ await this.exec.query(`DELETE FROM ${this.q(this.resultsTable)} WHERE ${this.q("claimed_by")} = ${this.ph(1)} AND ${this.q("step_id")} IN (${ids})`, [
578
+ token,
579
+ ...delivered
580
+ ]);
581
+ } catch (err) {
582
+ this.onError(err);
583
+ }
584
+ }
548
585
  /**
549
586
  * Atomically claim up to `limit` un-leased rows matching `where` and stamp them with a claim token
550
587
  * unique to this round (`<instanceId>:<uuid>`). `SELECT … FOR UPDATE SKIP LOCKED` inside the txn
@@ -682,7 +719,8 @@ var DbTransport = class {
682
719
  ].map((c) => this.stopRunConsumer(c))
683
720
  ]);
684
721
  await Promise.allSettled([
685
- ...this.runningTasks
722
+ ...this.runningTasks,
723
+ ...this.runningResults
686
724
  ]);
687
725
  if (this.renewTimer) clearInterval(this.renewTimer);
688
726
  this.renewTimer = void 0;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/db-transport.ts","../src/executors.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport {\n type Heartbeat,\n type RemoteTask,\n type RunConsumerOptions,\n type RunDispatch,\n type RunQueueOptions,\n type StepHandler,\n type StepResult,\n type Transport,\n runStepHandler,\n} from '@dudousxd/nestjs-durable-core';\nimport type { SqlExecutor, SqlTx } from './executors';\n\nexport { mikroOrmExecutor, type SqlExecutor, type SqlTx, typeOrmExecutor } from './executors';\n\n/** Raw row shapes returned by `SELECT *` (declared fields, so they're not `| undefined` under noUncheckedIndexedAccess). */\ninterface TaskRow {\n step_id: string;\n run_id: string;\n seq: string | number;\n name: string;\n input: string | null;\n attempt: string | number;\n}\ninterface RunRow {\n run_id: string;\n wake: number | string;\n}\n\n/** A claimed run row: its run id and the token of this claim. */\ninterface ClaimedRun {\n run: RunDispatch;\n token: string;\n}\n\n/** Most rows one {@link DbTransport.dispatchRuns} insert writes. */\nconst RUNS_INSERT_CHUNK = 500;\n/** Tries for a statement the database rolled back as a deadlock (see `retryOnDeadlock`). */\nconst DEADLOCK_RETRIES = 5;\n\n/** Whether `err` is the database rolling a statement back to break a deadlock. */\nfunction isDeadlock(err: unknown): boolean {\n const codes = [\n (err as { code?: unknown }).code,\n (err as { errno?: unknown }).errno,\n (err as { driverError?: { code?: unknown; errno?: unknown } }).driverError?.code,\n (err as { driverError?: { code?: unknown; errno?: unknown } }).driverError?.errno,\n ];\n return codes.some((c) => c === 'ER_LOCK_DEADLOCK' || c === 1213 || c === '40P01');\n}\n\n/** Most run rows one consumer poll claims. */\nconst RUNS_CLAIM_MAX = 50;\n\n/** A run-queue consumer started by {@link DbTransport.onDispatchedRun}. */\ninterface RunConsumer {\n stopped: boolean;\n /** Claim tokens of the rows whose runs are in flight. */\n inflight: Set<string>;\n slotFreed: (() => void) | undefined;\n loop: Promise<void>;\n}\n\ninterface ResultRow {\n step_id: string;\n run_id: string;\n seq: string | number;\n status: string;\n output: string | null;\n error: string | null;\n started_at: string | number | null;\n}\n\nexport interface DbTransportOptions {\n /**\n * How to run SQL — built from the app's **own** ORM/connection (no broker, no extra connection).\n * Use `mikroOrmExecutor(em)` to ride the app's MikroORM, `typeOrmExecutor(dataSource)` for TypeORM,\n * `drizzlePgExecutor(db)` (from `@dudousxd/nestjs-durable-store-drizzle/pg`) for Drizzle on\n * Postgres, or implement `SqlExecutor` for anything else.\n */\n executor: SqlExecutor;\n /** The worker group this instance serves. Required to register `handle()` consumers. */\n group?: string;\n /**\n * Table-name prefix. Tables: `${prefix}_transport_tasks` / `${prefix}_transport_results`, and\n * `${prefix}_transport_runs` for the run queue. Default `durable`.\n */\n prefix?: string;\n /** Poll interval in ms when a queue is empty. Default 500. */\n pollMs?: number;\n /**\n * How long a claimed row is owned before it's reclaimable (crash recovery), in ms. Default 30_000.\n * Every claim this instance holds is renewed every `leaseMs / 3` while it is waiting or running, so\n * a step may run longer than `leaseMs`; only a crashed (or event-loop-blocked) instance loses it.\n */\n leaseMs?: number;\n /**\n * Max rows claimed per poll. Default 10. The task loop claims only as many rows as it has free\n * {@link concurrency} slots (capped by this); the results loop claims up to this many.\n */\n batchSize?: number;\n /**\n * How many tasks this instance runs at once (worker side). Default 1. A row is claimed only when a\n * slot is free to run it, so rows never wait claimed behind a slow task while a sibling sits idle.\n */\n concurrency?: number;\n /**\n * Where a transport failure is reported: a failing poll, a throwing result handler (the result is\n * re-delivered after the lease), a lost claim. Default `console.error`.\n */\n onError?: (err: unknown) => void;\n /** Create the tables on first use if missing. Default true. */\n autoCreate?: boolean;\n /** Identifies this instance in `claimed_by` (for debugging). Defaults to a random id. */\n instanceId?: string;\n}\n\n/**\n * A numeric option: `fallback` unless `value` is a finite number of at least `min`. `Math.max(1, NaN)`\n * is `NaN`, so a clamp alone would let a `NaN` through (to `LIMIT NaN`, or a `NaN` timer).\n */\nfunction numberOption(value: number | undefined, fallback: number, min: number): number {\n return value !== undefined && Number.isFinite(value) && value >= min ? value : fallback;\n}\n\n/**\n * A SQL-backed `Transport` — DBOS-style. Instead of a broker (Redis/SQS), remote steps are **rows**\n * in the same database the durable store already uses: `dispatch` inserts a task row, a worker\n * claims it with `SELECT … FOR UPDATE SKIP LOCKED` (so instances never double-claim), runs it, and\n * writes a result row the engine polls. Zero new infrastructure — the DB you already have IS the\n * queue.\n *\n * Trade-off vs a real broker: throughput is bounded by polling + row contention. Great for\n * workflow/pipeline scale (modest rate, long steps); not for high-fanout firehoses.\n *\n * Requires `FOR UPDATE SKIP LOCKED` — **MySQL 8+** or **Postgres 9.5+** (not SQLite). Run one\n * instance engine-side (`onResult`, dispatches) and one per worker process (`handle()` for its group).\n */\nexport class DbTransport implements Transport {\n private readonly exec: SqlExecutor;\n private readonly group?: string | undefined;\n private readonly tasksTable: string;\n private readonly resultsTable: string;\n private readonly runsTable: string;\n private readonly runConsumers = new Set<RunConsumer>();\n private readonly pollMs: number;\n private readonly leaseMs: number;\n private readonly batchSize: number;\n private readonly concurrency: number;\n private readonly onError: (err: unknown) => void;\n private readonly autoCreate: boolean;\n private readonly instanceId: string;\n private readonly isPg: boolean;\n\n private readonly handlers = new Map<string, StepHandler>();\n private running = true;\n private schemaReady?: Promise<void> | undefined;\n private taskLoop?: Promise<void>;\n private resultLoop?: Promise<void>;\n /** Tasks running on this instance (at most `concurrency`). */\n private readonly runningTasks = new Set<Promise<void>>();\n /** Claim tokens this instance holds, by table — every one is renewed on `renewTimer`. */\n private readonly held = new Map<string, Set<string>>();\n private renewTimer?: ReturnType<typeof setInterval> | undefined;\n private renewal?: Promise<void> | undefined;\n\n constructor(options: DbTransportOptions) {\n this.exec = options.executor;\n this.group = options.group;\n const prefix = options.prefix ?? 'durable';\n this.tasksTable = `${prefix}_transport_tasks`;\n this.resultsTable = `${prefix}_transport_results`;\n this.runsTable = `${prefix}_transport_runs`;\n this.pollMs = numberOption(options.pollMs, 500, 0);\n this.leaseMs = numberOption(options.leaseMs, 30_000, 1);\n this.batchSize = Math.floor(numberOption(options.batchSize, 10, 1));\n this.concurrency = Math.floor(numberOption(options.concurrency, 1, 1));\n this.onError = options.onError ?? ((err) => console.error('[DbTransport]', err));\n this.autoCreate = options.autoCreate ?? true;\n // Random, not time-based: two instances booted in the same millisecond must not share an id.\n this.instanceId = options.instanceId ?? `db-${randomUUID()}`;\n this.isPg = this.exec.dialect === 'postgres';\n }\n\n // ── dialect helpers ──────────────────────────────────────────────────────────────────────────\n /** Positional placeholder for param `i` (1-based): `$i` on Postgres, `?` elsewhere. */\n private ph(i: number): string {\n return this.isPg ? `$${i}` : '?';\n }\n private q(id: string): string {\n return this.exec.escapeId(id);\n }\n private now(): number {\n return Math.floor(Date.now());\n }\n\n // ── schema ───────────────────────────────────────────────────────────────────────────────────\n private ensureSchema(): Promise<void> {\n if (!this.schemaReady) {\n // A failed attempt is not cached: the next call tries again instead of failing forever.\n this.schemaReady = this.createTables().catch((err) => {\n this.schemaReady = undefined;\n throw err;\n });\n }\n return this.schemaReady;\n }\n\n /**\n * Run one `CREATE … IF NOT EXISTS`. Two instances booting on a fresh database race it, and Postgres\n * can reject the loser with a unique violation on its catalog even with `IF NOT EXISTS`; the\n * statement is simply re-run, by which point the table exists and it is a no-op.\n */\n private async createIfMissing(sql: string): Promise<void> {\n try {\n await this.exec.query(sql);\n } catch {\n await this.exec.query(sql);\n }\n }\n\n private async createTables(): Promise<void> {\n if (!this.autoCreate) return;\n const str = 'varchar(191)';\n const txt = this.isPg ? 'text' : 'longtext';\n const tasks = this.q(this.tasksTable);\n const results = this.q(this.resultsTable);\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${tasks} (\n ${this.q('step_id')} ${str} PRIMARY KEY,\n ${this.q('run_id')} ${str} NOT NULL,\n ${this.q('seq')} integer NOT NULL,\n ${this.q('name')} ${str} NOT NULL,\n ${this.q('grp')} ${str} NOT NULL,\n ${this.q('input')} ${txt},\n ${this.q('attempt')} integer NOT NULL,\n ${this.q('status')} varchar(32) NOT NULL,\n ${this.q('claimed_by')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${results} (\n ${this.q('step_id')} ${str} PRIMARY KEY,\n ${this.q('run_id')} ${str} NOT NULL,\n ${this.q('seq')} integer NOT NULL,\n ${this.q('status')} varchar(32) NOT NULL,\n ${this.q('output')} ${txt},\n ${this.q('error')} ${txt},\n ${this.q('started_at')} bigint,\n ${this.q('claimed_by')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${this.q(this.runsTable)} (\n ${this.q('run_id')} ${str} PRIMARY KEY,\n ${this.q('ns')} ${str} NOT NULL,\n ${this.q('priority')} integer NOT NULL,\n ${this.q('wake')} integer NOT NULL,\n ${this.q('claimed_by')} ${str},\n ${this.q('claim_token')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.exec\n .query(\n `CREATE INDEX ${this.q(`${this.runsTable}_ns_idx`)} ON ${this.q(this.runsTable)} (${this.q('ns')}, ${this.q('priority')}, ${this.q('created_at')})`,\n )\n .catch(() => {});\n // Best-effort index for the group poll (MySQL has no CREATE INDEX IF NOT EXISTS < 8.0.13).\n await this.exec\n .query(\n `CREATE INDEX ${this.q(`${this.tasksTable}_grp_idx`)} ON ${tasks} (${this.q('grp')}, ${this.q('status')}, ${this.q('created_at')})`,\n )\n .catch(() => {});\n }\n\n // ── engine → worker ────────────────────────────────────────────────────────────────────────\n async dispatch(task: RemoteTask): Promise<void> {\n await this.ensureSchema();\n const t = this.q(this.tasksTable);\n // Idempotent: a redelivered dispatch for the same step_id is ignored (the row already exists).\n const ignore = this.isPg\n ? `INSERT INTO ${t} (...) ON CONFLICT (${this.q('step_id')}) DO NOTHING`\n : `INSERT IGNORE INTO ${t} (...)`;\n const cols = [\n 'step_id',\n 'run_id',\n 'seq',\n 'name',\n 'grp',\n 'input',\n 'attempt',\n 'status',\n 'created_at',\n ];\n const sql = ignore.replace(\n '(...)',\n `(${cols.map((c) => this.q(c)).join(', ')}) VALUES (${cols.map((_, i) => this.ph(i + 1)).join(', ')})`,\n );\n await this.exec.query(sql, [\n task.stepId,\n task.runId,\n task.seq,\n task.name,\n task.group,\n JSON.stringify(task.input ?? null),\n task.attempt,\n 'pending',\n this.now(),\n ]);\n }\n\n // ── worker → engine ────────────────────────────────────────────────────────────────────────\n handle(name: string, fn: StepHandler): void {\n if (!this.group) throw new Error('DbTransport needs a `group` to register handlers');\n this.handlers.set(name, fn);\n if (!this.taskLoop) this.taskLoop = this.loop(() => this.drainTasks(this.group as string));\n }\n\n onResult(handler: (result: StepResult) => Promise<void>): void {\n if (!this.resultLoop) this.resultLoop = this.loop(() => this.drainResults(handler));\n }\n\n onHeartbeat(_handler: (beat: Heartbeat) => Promise<void>): void {\n // Not modelled; the lease/reclaim on `claimed_at` is the liveness mechanism.\n }\n\n // ── run queue (engine ↔ engine) ─────────────────────────────────────────────────────────────\n /**\n * Queue `pending` runs for whichever engine instance polls the run queue first: one row per run,\n * keyed by run id, so a run already waiting is not queued twice. A run whose row an instance holds\n * right now is released for another take, so a dispatch landing while the run's previous entry is\n * being handled is not lost when that entry is deleted. Up to {@link RUNS_INSERT_CHUNK} rows per\n * insert.\n */\n async dispatchRuns(runs: readonly RunDispatch[], opts?: RunQueueOptions): Promise<void> {\n if (runs.length === 0) return;\n await this.ensureSchema();\n // One row per run (an upsert may not touch the same row twice in one statement); a wake among\n // a run's dispatches makes its row a wake.\n const byRun = new Map<string, RunDispatch>();\n for (const run of runs) {\n if (!byRun.has(run.runId) || run.reason === 'wake') byRun.set(run.runId, run);\n }\n // In run-id order: two dispatches touching overlapping rows lock them in the same order.\n const unique = [...byRun.values()].sort((a, b) =>\n a.runId < b.runId ? -1 : a.runId > b.runId ? 1 : 0,\n );\n const t = this.q(this.runsTable);\n const cols = ['run_id', 'ns', 'priority', 'wake', 'created_at'];\n const now = this.now();\n for (let at = 0; at < unique.length; at += RUNS_INSERT_CHUNK) {\n const chunk = unique.slice(at, at + RUNS_INSERT_CHUNK);\n const values = chunk\n .map((_, r) => `(${cols.map((__, c) => this.ph(r * cols.length + c + 1)).join(', ')})`)\n .join(', ');\n const insert = `INTO ${t} (${cols.map((c) => this.q(c)).join(', ')}) VALUES ${values}`;\n const release = ['claimed_by', 'claim_token', 'claimed_at']\n .map((c) => `${this.q(c)} = NULL`)\n .join(', ');\n const wake = this.q('wake');\n const sql = this.isPg\n ? `INSERT ${insert} ON CONFLICT (${this.q('run_id')}) DO UPDATE SET ${release}, ${wake} = GREATEST(${t}.${wake}, EXCLUDED.${wake})`\n : `INSERT ${insert} ON DUPLICATE KEY UPDATE ${release}, ${wake} = GREATEST(${wake}, VALUES(${wake}))`;\n await this.retryOnDeadlock(() =>\n this.exec.query(\n sql,\n chunk.flatMap((run, r) => [\n run.runId,\n opts?.namespace ?? '',\n run.priority ?? 0,\n run.reason === 'wake' ? 1 : 0,\n now + r,\n ]),\n ),\n );\n }\n }\n\n /**\n * Poll the run queue, at most `opts.concurrency` runs in flight on this instance. Rows are claimed\n * with `FOR UPDATE SKIP LOCKED` (instances never take the same run), highest priority then oldest\n * first; a row is deleted once its handler settles, and its lease is renewed meanwhile, so a row is\n * re-taken only after the instance holding it went down. The returned stop takes no more rows and\n * resolves once the runs in flight settled.\n */\n onDispatchedRun(\n handler: (run: RunDispatch) => Promise<void>,\n opts: RunConsumerOptions,\n ): () => Promise<void> {\n const consumer: RunConsumer = {\n stopped: false,\n inflight: new Set(),\n slotFreed: undefined,\n loop: Promise.resolve(),\n };\n const concurrency = Math.max(1, Math.floor(opts.concurrency));\n const ns = opts.namespace ?? '';\n consumer.loop = this.consumeRuns(consumer, handler, concurrency, ns).catch((err) => {\n this.onError(err);\n });\n this.runConsumers.add(consumer);\n return () => this.stopRunConsumer(consumer);\n }\n\n private async stopRunConsumer(consumer: RunConsumer): Promise<void> {\n consumer.stopped = true;\n consumer.slotFreed?.();\n await consumer.loop;\n this.runConsumers.delete(consumer);\n }\n\n private async consumeRuns(\n consumer: RunConsumer,\n handler: (run: RunDispatch) => Promise<void>,\n concurrency: number,\n ns: string,\n ): Promise<void> {\n const renew = setInterval(\n () => void this.renewRunLeases(consumer).catch(() => undefined),\n Math.max(1_000, Math.floor(this.leaseMs / 3)),\n );\n renew.unref?.();\n const settled = new Set<Promise<unknown>>();\n try {\n while (this.running && !consumer.stopped) {\n const free = concurrency - consumer.inflight.size;\n if (free <= 0) {\n await new Promise<void>((resolve) => {\n consumer.slotFreed = resolve;\n });\n consumer.slotFreed = undefined;\n continue;\n }\n let rows: ClaimedRun[] = [];\n try {\n await this.ensureSchema();\n rows = await this.retryOnDeadlock(() =>\n this.claimRuns(ns, Math.min(free, RUNS_CLAIM_MAX)),\n );\n } catch (err) {\n if (this.running && !consumer.stopped) this.onError(err);\n }\n for (const { run, token } of rows) {\n consumer.inflight.add(token);\n const done = handler(run)\n .catch(() => undefined)\n .then(() =>\n this.retryOnDeadlock(() =>\n this.exec.query(\n `DELETE FROM ${this.q(this.runsTable)} WHERE ${this.q('claim_token')} = ${this.ph(1)}`,\n [token],\n ),\n ).catch(() => undefined),\n )\n .finally(() => {\n consumer.inflight.delete(token);\n settled.delete(done);\n consumer.slotFreed?.();\n });\n settled.add(done);\n }\n if (rows.length === 0) await this.sleep(this.pollMs);\n }\n await Promise.allSettled([...settled]);\n } finally {\n clearInterval(renew);\n }\n }\n\n /**\n * Run `statement`, again on a deadlock: queueing a run (an upsert), taking one (`SKIP LOCKED`)\n * and deleting a handled one touch the same rows from different instances, and the database then\n * rolls one of them back rather than wait (InnoDB reports `ER_LOCK_DEADLOCK`, Postgres `40P01`).\n */\n private async retryOnDeadlock<T>(statement: () => Promise<T>): Promise<T> {\n for (let attempt = 1; ; attempt += 1) {\n try {\n return await statement();\n } catch (err) {\n if (attempt >= DEADLOCK_RETRIES || !isDeadlock(err)) throw err;\n await this.sleep(10 * attempt + Math.floor(Math.random() * 10));\n }\n }\n }\n\n private async claimRuns(ns: string, limit: number): Promise<ClaimedRun[]> {\n const t = this.q(this.runsTable);\n const staleBefore = this.now() - this.leaseMs;\n return this.exec.transaction(async (tx) => {\n const rows = await tx.query<RunRow>(\n `SELECT ${this.q('run_id')}, ${this.q('wake')} FROM ${t}\n WHERE ${this.q('ns')} = ${this.ph(1)}\n AND (${this.q('claimed_at')} IS NULL OR ${this.q('claimed_at')} < ${this.ph(2)})\n ORDER BY ${this.q('priority')} DESC, ${this.q('created_at')} ASC\n LIMIT ${Math.floor(limit)} FOR UPDATE SKIP LOCKED`,\n [ns, staleBefore],\n );\n const claimed = rows.map((r) => ({\n run: {\n runId: r.run_id,\n reason: Number(r.wake) === 1 ? ('wake' as const) : ('start' as const),\n },\n token: globalThis.crypto.randomUUID(),\n }));\n const now = this.now();\n for (const {\n run: { runId },\n token,\n } of claimed) {\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_by')} = ${this.ph(1)}, ${this.q('claim_token')} = ${this.ph(2)}, ${this.q('claimed_at')} = ${this.ph(3)}\n WHERE ${this.q('run_id')} = ${this.ph(4)}`,\n [this.instanceId, token, now, runId],\n );\n }\n return claimed;\n });\n }\n\n /** Keep the rows of the runs in flight leased, so a long turn is not re-taken by another instance. */\n private async renewRunLeases(consumer: RunConsumer): Promise<void> {\n const tokens = [...consumer.inflight];\n if (tokens.length === 0) return;\n await this.exec.query(\n `UPDATE ${this.q(this.runsTable)} SET ${this.q('claimed_at')} = ${this.ph(1)}\n WHERE ${this.q('claim_token')} IN (${tokens.map((_, i) => this.ph(i + 2)).join(', ')})`,\n [this.now(), ...tokens],\n );\n }\n\n // ── polling ──────────────────────────────────────────────────────────────────────────────────\n /** Run `tick` repeatedly; sleep `pollMs` when it reports an empty queue. Stops on `close()`. */\n private async loop(tick: () => Promise<number>): Promise<void> {\n while (this.running) {\n let processed = 0;\n try {\n await this.ensureSchema();\n processed = await tick();\n } catch (err) {\n if (this.running) this.onError(err);\n }\n if (processed === 0 && this.running) await this.sleep(this.pollMs);\n }\n }\n\n /**\n * One task-loop round: claim as many rows as there are free slots (never more — a claimed row that\n * isn't running is one an idle sibling can't run), and start each. With every slot busy, wait for\n * one to free instead of polling.\n */\n private async drainTasks(group: string): Promise<number> {\n const free = this.concurrency - this.runningTasks.size;\n if (free <= 0) {\n await Promise.race(this.runningTasks);\n return 1;\n }\n const { rows, token } = await this.claim<TaskRow>(\n this.tasksTable,\n Math.min(free, this.batchSize),\n `${this.q('grp')} = ${this.ph(1)}`,\n [group],\n );\n if (rows.length === 0) return 0;\n this.hold(this.tasksTable, token);\n let left = rows.length;\n for (const row of rows) {\n const run = this.runTask(row, group, token).finally(() => {\n this.runningTasks.delete(run);\n left -= 1;\n if (left === 0) this.release(this.tasksTable, token);\n });\n this.runningTasks.add(run);\n }\n return rows.length;\n }\n\n /**\n * Run one claimed task and settle it — only while this instance still holds the claim: the claim is\n * re-checked before running, and the task row is deleted and its result written in ONE transaction,\n * conditional on the claim token, so an instance whose claim was taken over neither deletes the new\n * holder's row nor publishes a second result.\n */\n private async runTask(row: TaskRow, group: string, token: string): Promise<void> {\n try {\n if (!(await this.stillHolds(row.step_id, token))) {\n this.onError(\n new Error(\n `[DbTransport] lost the claim on task ${row.step_id} before running it — another instance took it over; not running it here`,\n ),\n );\n return;\n }\n const task: RemoteTask = {\n runId: row.run_id,\n seq: Number(row.seq),\n stepId: row.step_id,\n name: row.name,\n group,\n input: row.input == null ? undefined : JSON.parse(row.input),\n attempt: Number(row.attempt),\n };\n const result = await runStepHandler(task, this.handlers.get(task.name));\n if (!(await this.settleTask(task.stepId, token, result))) {\n this.onError(\n new Error(\n `[DbTransport] lost the claim on task ${task.stepId} while running it (this instance went silent past leaseMs) — another instance took it over and may run it too; this run's result was dropped. Raise leaseMs if steps block the event loop`,\n ),\n );\n }\n } catch (err) {\n // A database error settling the row (or a malformed row): it stays claimed, stops being\n // renewed, and is reclaimed after the lease — at-least-once, like a crash.\n this.onError(err);\n }\n }\n\n /** Whether the claim on `stepId` is still `token`'s (re-stamping it if so). */\n private async stillHolds(stepId: string, token: string): Promise<boolean> {\n const t = this.q(this.tasksTable);\n const rows = await this.exec.transaction(async (tx) => {\n const held = await tx.query<{ step_id: string }>(\n `SELECT ${this.q('step_id')} FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)} FOR UPDATE`,\n [stepId, token],\n );\n if (held.length > 0) {\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_at')} = ${this.ph(1)} WHERE ${this.q('step_id')} = ${this.ph(2)} AND ${this.q('claimed_by')} = ${this.ph(3)}`,\n [this.now(), stepId, token],\n );\n }\n return held;\n });\n return rows.length > 0;\n }\n\n /** Delete the task row and publish its result in ONE transaction, only while `token` holds it. */\n private async settleTask(stepId: string, token: string, result: StepResult): Promise<boolean> {\n const t = this.q(this.tasksTable);\n return this.exec.transaction(async (tx) => {\n const held = await tx.query<{ step_id: string }>(\n `SELECT ${this.q('step_id')} FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)} FOR UPDATE`,\n [stepId, token],\n );\n if (held.length === 0) return false;\n await tx.query(\n `DELETE FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)}`,\n [stepId, token],\n );\n await this.insertResult(result, tx);\n return true;\n });\n }\n\n /**\n * Claim a batch of results, deliver each to the engine, and delete it — only after its handler ran\n * (at-least-once), and only while this round still holds the claim (a newer result for the same\n * step that replaced the row resets the claim, so it is delivered next instead of deleted). The\n * batch's claim is renewed until it is done. A throwing handler is reported and its row left for\n * re-delivery after the lease; the rest of the batch continues.\n */\n private async drainResults(handler: (result: StepResult) => Promise<void>): Promise<number> {\n const { rows, token } = await this.claim<ResultRow>(\n this.resultsTable,\n this.batchSize,\n '1 = 1',\n [],\n );\n if (rows.length === 0) return 0;\n this.hold(this.resultsTable, token);\n try {\n for (const row of rows) {\n if (!this.running) break;\n try {\n const result: StepResult = {\n runId: row.run_id,\n seq: Number(row.seq),\n stepId: row.step_id,\n status: row.status as StepResult['status'],\n output: row.output == null ? undefined : JSON.parse(row.output),\n error: row.error == null ? undefined : JSON.parse(row.error),\n startedAt: row.started_at == null ? undefined : Number(row.started_at),\n };\n await handler(result);\n await this.exec.query(\n `DELETE FROM ${this.q(this.resultsTable)} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)}`,\n [row.step_id, token],\n );\n } catch (err) {\n this.onError(err);\n }\n }\n } finally {\n this.release(this.resultsTable, token);\n }\n return rows.length;\n }\n\n /**\n * Atomically claim up to `limit` un-leased rows matching `where` and stamp them with a claim token\n * unique to this round (`<instanceId>:<uuid>`). `SELECT … FOR UPDATE SKIP LOCKED` inside the txn\n * means concurrent instances skip each other's locked rows instead of blocking, so no row is\n * claimed twice; the token is what every later delete/settle is conditional on.\n */\n private async claim<T extends { step_id: string }>(\n table: string,\n limit: number,\n where: string,\n whereParams: unknown[],\n ): Promise<{ rows: T[]; token: string }> {\n const t = this.q(table);\n const staleBefore = this.now() - this.leaseMs;\n const token = `${this.instanceId}:${randomUUID()}`;\n const rows = await this.exec.transaction(async (tx) => {\n const leaseClause = `(${this.q('claimed_at')} IS NULL OR ${this.q('claimed_at')} < ${this.ph(whereParams.length + 1)})`;\n const found = await tx.query<T>(\n `SELECT * FROM ${t} WHERE ${where} AND ${leaseClause}\n ORDER BY ${this.q('created_at')} ASC LIMIT ${Math.max(1, Math.floor(limit))} FOR UPDATE SKIP LOCKED`,\n [...whereParams, staleBefore],\n );\n if (found.length > 0) {\n const ids = found.map((r) => r.step_id);\n const placeholders = ids.map((_, i) => this.ph(i + 3)).join(', ');\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_by')} = ${this.ph(1)}, ${this.q('claimed_at')} = ${this.ph(2)}\n WHERE ${this.q('step_id')} IN (${placeholders})`,\n [token, this.now(), ...ids],\n );\n }\n return found;\n });\n return { rows, token };\n }\n\n // ── claim renewal: every claim held, not only the row being run ─────────────────────────────\n private hold(table: string, token: string): void {\n let tokens = this.held.get(table);\n if (!tokens) {\n tokens = new Set();\n this.held.set(table, tokens);\n }\n tokens.add(token);\n if (this.renewTimer) return;\n const intervalMs = Math.max(10, Math.floor(this.leaseMs / 3));\n this.renewTimer = setInterval(() => {\n if (this.renewal) return; // a slow database never stacks renewal rounds\n const round = this.renewHeld()\n .catch((err) => this.onError(err))\n .finally(() => {\n if (this.renewal === round) this.renewal = undefined;\n });\n this.renewal = round;\n }, intervalMs);\n this.renewTimer.unref?.();\n }\n\n private release(table: string, token: string): void {\n const tokens = this.held.get(table);\n tokens?.delete(token);\n if (tokens?.size === 0) this.held.delete(table);\n if (this.held.size === 0 && this.renewTimer) {\n clearInterval(this.renewTimer);\n this.renewTimer = undefined;\n }\n }\n\n /** Re-stamp `claimed_at` on every row still claimed under a token this instance holds. */\n private async renewHeld(): Promise<void> {\n for (const [table, tokens] of this.held) {\n if (tokens.size === 0) continue;\n const list = [...tokens];\n const placeholders = list.map((_, i) => this.ph(i + 2)).join(', ');\n await this.exec.query(\n `UPDATE ${this.q(table)} SET ${this.q('claimed_at')} = ${this.ph(1)} WHERE ${this.q('claimed_by')} IN (${placeholders})`,\n [this.now(), ...list],\n );\n }\n }\n\n /**\n * Write a result row. A result already waiting for the same step (an earlier attempt's, not yet\n * deleted by the engine) is REPLACED and its claim reset, never kept: `step_id` is stable across\n * attempts, so keeping the old row would silently drop a retry's result.\n */\n private async insertResult(result: StepResult, tx: SqlTx = this.exec): Promise<void> {\n const t = this.q(this.resultsTable);\n const cols = [\n 'step_id',\n 'run_id',\n 'seq',\n 'status',\n 'output',\n 'error',\n 'started_at',\n 'created_at',\n ];\n const replace = ['run_id', 'seq', 'status', 'output', 'error', 'started_at', 'created_at'];\n const values = `(${cols.map((c) => this.q(c)).join(', ')}) VALUES (${cols.map((_, i) => this.ph(i + 1)).join(', ')})`;\n const sql = this.isPg\n ? `INSERT INTO ${t} ${values} ON CONFLICT (${this.q('step_id')}) DO UPDATE SET ${[\n ...replace.map((c) => `${this.q(c)} = EXCLUDED.${this.q(c)}`),\n `${this.q('claimed_by')} = NULL`,\n `${this.q('claimed_at')} = NULL`,\n ].join(', ')}`\n : `INSERT INTO ${t} ${values} ON DUPLICATE KEY UPDATE ${[\n ...replace.map((c) => `${this.q(c)} = VALUES(${this.q(c)})`),\n `${this.q('claimed_by')} = NULL`,\n `${this.q('claimed_at')} = NULL`,\n ].join(', ')}`;\n await tx.query(sql, [\n result.stepId,\n result.runId,\n result.seq,\n result.status,\n result.output === undefined ? null : JSON.stringify(result.output),\n result.error === undefined ? null : JSON.stringify(result.error),\n result.startedAt ?? null,\n this.now(),\n ]);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n }\n\n /** Stop the pollers and wait for the current ticks to settle. Does not close the shared DataSource. */\n async close(): Promise<void> {\n this.running = false;\n await Promise.allSettled([\n this.taskLoop,\n this.resultLoop,\n ...[...this.runConsumers].map((c) => this.stopRunConsumer(c)),\n ]);\n // Let running tasks settle (their claims keep being renewed until they do).\n await Promise.allSettled([...this.runningTasks]);\n if (this.renewTimer) clearInterval(this.renewTimer);\n this.renewTimer = undefined;\n await this.renewal;\n }\n}\n","/**\n * The tiny SQL surface `DbTransport` needs, so it rides **whatever ORM/connection the app already\n * has** instead of forcing a specific one. Implement it for your ORM (adapters for TypeORM and\n * MikroORM ship below) and hand it to `new DbTransport({ executor })`. Mirrors how the durable\n * *store* is ORM-pluggable (store-typeorm / store-mikro-orm / …).\n */\nexport interface SqlExecutor {\n /** Drives placeholder style (`$n` vs `?`) and a couple of DDL type choices. */\n readonly dialect: 'mysql' | 'postgres';\n /** Quote/escape an identifier (table/column name) for this dialect. */\n escapeId(id: string): string;\n /** Run a statement outside a transaction. */\n query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;\n /** Run `fn` inside a transaction — the claim uses `FOR UPDATE SKIP LOCKED` within it. */\n transaction<T>(fn: (tx: SqlTx) => Promise<T>): Promise<T>;\n}\n\nexport interface SqlTx {\n query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;\n}\n\n// ── TypeORM ──────────────────────────────────────────────────────────────────────────────────────\n\n/** Structural slice of a TypeORM `DataSource` — avoids a hard dependency on typeorm. */\ninterface TypeOrmDataSourceLike {\n options: { type: string };\n driver: { escape(id: string): string };\n query(sql: string, params?: unknown[]): Promise<unknown>;\n createQueryRunner(): {\n connect(): Promise<void>;\n startTransaction(): Promise<void>;\n commitTransaction(): Promise<void>;\n rollbackTransaction(): Promise<void>;\n release(): Promise<void>;\n query(sql: string, params?: unknown[]): Promise<unknown>;\n };\n}\n\n/** Build an executor from a TypeORM `DataSource` (e.g. the one the durable store already uses). */\nexport function typeOrmExecutor(ds: TypeOrmDataSourceLike): SqlExecutor {\n return {\n dialect: /postgres/.test(String(ds.options.type)) ? 'postgres' : 'mysql',\n escapeId: (id) => ds.driver.escape(id),\n query: <T>(sql: string, params?: unknown[]) => ds.query(sql, params) as Promise<T[]>,\n transaction: async <T>(fn: (tx: SqlTx) => Promise<T>) => {\n const qr = ds.createQueryRunner();\n await qr.connect();\n await qr.startTransaction();\n try {\n const result = await fn({\n query: <U>(sql: string, params?: unknown[]) => qr.query(sql, params) as Promise<U[]>,\n });\n await qr.commitTransaction();\n return result;\n } catch (err) {\n await qr.rollbackTransaction();\n throw err;\n } finally {\n await qr.release();\n }\n },\n };\n}\n\n// ── MikroORM ─────────────────────────────────────────────────────────────────────────────────────\n\n/** Structural slice of a MikroORM `EntityManager` — avoids a hard dependency on @mikro-orm/core. */\ninterface MikroOrmEmLike {\n getPlatform(): { constructor: { name: string }; quoteIdentifier(id: string): string };\n getConnection(): {\n execute(sql: string, params?: unknown[], method?: 'all', ctx?: unknown): Promise<unknown>;\n };\n getTransactionContext?(): unknown;\n transactional<T>(cb: (em: MikroOrmEmLike) => Promise<T>): Promise<T>;\n}\n\n/**\n * Build an executor from a MikroORM `EntityManager` — i.e. **the app's own configured ORM**. Runs\n * raw SQL through `em.getConnection().execute(...)` and wraps the claim in `em.transactional(...)`,\n * threading the transaction context so `FOR UPDATE SKIP LOCKED` locks within the tx.\n */\nexport function mikroOrmExecutor(em: MikroOrmEmLike): SqlExecutor {\n const platform = em.getPlatform();\n return {\n dialect: /postgre/i.test(platform.constructor.name) ? 'postgres' : 'mysql',\n escapeId: (id) => platform.quoteIdentifier(id),\n query: <T>(sql: string, params?: unknown[]) =>\n em.getConnection().execute(sql, params ?? [], 'all') as Promise<T[]>,\n transaction: <T>(fn: (tx: SqlTx) => Promise<T>) =>\n em.transactional((txEm) =>\n fn({\n query: <U>(sql: string, params?: unknown[]) =>\n txEm\n .getConnection()\n .execute(sql, params ?? [], 'all', txEm.getTransactionContext?.()) as Promise<U[]>,\n }),\n ),\n };\n}\n"],"mappings":"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","names":["randomUUID","runStepHandler","typeOrmExecutor","ds","dialect","test","String","options","type","escapeId","id","driver","escape","query","sql","params","transaction","fn","qr","createQueryRunner","connect","startTransaction","result","commitTransaction","err","rollbackTransaction","release","mikroOrmExecutor","em","platform","getPlatform","name","quoteIdentifier","getConnection","execute","transactional","txEm","getTransactionContext","RUNS_INSERT_CHUNK","DEADLOCK_RETRIES","isDeadlock","err","codes","code","errno","driverError","some","c","RUNS_CLAIM_MAX","numberOption","value","fallback","min","undefined","Number","isFinite","DbTransport","exec","group","tasksTable","resultsTable","runsTable","runConsumers","Set","pollMs","leaseMs","batchSize","concurrency","onError","autoCreate","instanceId","isPg","handlers","Map","running","schemaReady","taskLoop","resultLoop","runningTasks","held","renewTimer","renewal","options","executor","prefix","Math","floor","console","error","randomUUID","dialect","ph","i","q","id","escapeId","now","Date","ensureSchema","createTables","catch","createIfMissing","sql","query","str","txt","tasks","results","dispatch","task","t","ignore","cols","replace","map","join","_","stepId","runId","seq","name","JSON","stringify","input","attempt","handle","fn","Error","set","loop","drainTasks","onResult","handler","drainResults","onHeartbeat","_handler","dispatchRuns","runs","opts","length","byRun","run","has","reason","unique","values","sort","a","b","at","chunk","slice","r","__","insert","release","wake","retryOnDeadlock","flatMap","namespace","priority","onDispatchedRun","consumer","stopped","inflight","slotFreed","Promise","resolve","max","ns","consumeRuns","add","stopRunConsumer","delete","renew","setInterval","renewRunLeases","unref","settled","free","size","rows","claimRuns","token","done","then","finally","sleep","allSettled","clearInterval","statement","random","limit","staleBefore","transaction","tx","claimed","run_id","globalThis","crypto","tokens","tick","processed","race","claim","hold","left","row","runTask","stillHolds","step_id","parse","result","runStepHandler","get","settleTask","insertResult","status","output","startedAt","started_at","table","where","whereParams","leaseClause","found","ids","placeholders","intervalMs","round","renewHeld","list","ms","setTimeout","close"]}
1
+ {"version":3,"sources":["../src/db-transport.ts","../src/executors.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport {\n type Heartbeat,\n type RemoteTask,\n type RunConsumerOptions,\n type RunDispatch,\n type RunQueueOptions,\n type StepHandler,\n type StepResult,\n type Transport,\n runStepHandler,\n} from '@dudousxd/nestjs-durable-core';\nimport type { SqlExecutor, SqlTx } from './executors';\n\nexport { mikroOrmExecutor, type SqlExecutor, type SqlTx, typeOrmExecutor } from './executors';\n\n/** Raw row shapes returned by `SELECT *` (declared fields, so they're not `| undefined` under noUncheckedIndexedAccess). */\ninterface TaskRow {\n step_id: string;\n run_id: string;\n seq: string | number;\n name: string;\n input: string | null;\n attempt: string | number;\n}\ninterface RunRow {\n run_id: string;\n wake: number | string;\n}\n\n/** A claimed run row: its run id and the token of this claim. */\ninterface ClaimedRun {\n run: RunDispatch;\n token: string;\n}\n\n/** Most rows one {@link DbTransport.dispatchRuns} insert writes. */\nconst RUNS_INSERT_CHUNK = 500;\n/** Tries for a statement the database rolled back as a deadlock (see `retryOnDeadlock`). */\nconst DEADLOCK_RETRIES = 5;\n\n/** Whether `err` is the database rolling a statement back to break a deadlock. */\nfunction isDeadlock(err: unknown): boolean {\n const codes = [\n (err as { code?: unknown }).code,\n (err as { errno?: unknown }).errno,\n (err as { driverError?: { code?: unknown; errno?: unknown } }).driverError?.code,\n (err as { driverError?: { code?: unknown; errno?: unknown } }).driverError?.errno,\n ];\n return codes.some((c) => c === 'ER_LOCK_DEADLOCK' || c === 1213 || c === '40P01');\n}\n\n/** Most task rows one {@link DbTransport.dispatchMany} insert writes. */\nconst TASKS_INSERT_CHUNK = 200;\n\n/** Most run rows one consumer poll claims. */\nconst RUNS_CLAIM_MAX = 50;\n\n/**\n * Most results this instance hands the engine at once, and the most one poll claims. Results are\n * cheap to hand over: the engine writes one run's waiting results together, so the more of a wide\n * fan's results are in flight here, the fewer writes and barrier reads settle them.\n */\nconst RESULTS_IN_FLIGHT = 512;\nconst RESULTS_CLAIM_MAX = 256;\n\n/** A run-queue consumer started by {@link DbTransport.onDispatchedRun}. */\ninterface RunConsumer {\n stopped: boolean;\n /** Claim tokens of the rows whose runs are in flight. */\n inflight: Set<string>;\n slotFreed: (() => void) | undefined;\n loop: Promise<void>;\n}\n\ninterface ResultRow {\n step_id: string;\n run_id: string;\n seq: string | number;\n status: string;\n output: string | null;\n error: string | null;\n started_at: string | number | null;\n}\n\nexport interface DbTransportOptions {\n /**\n * How to run SQL — built from the app's **own** ORM/connection (no broker, no extra connection).\n * Use `mikroOrmExecutor(em)` to ride the app's MikroORM, `typeOrmExecutor(dataSource)` for TypeORM,\n * `drizzlePgExecutor(db)` (from `@dudousxd/nestjs-durable-store-drizzle/pg`) for Drizzle on\n * Postgres, or implement `SqlExecutor` for anything else.\n */\n executor: SqlExecutor;\n /** The worker group this instance serves. Required to register `handle()` consumers. */\n group?: string;\n /**\n * Table-name prefix. Tables: `${prefix}_transport_tasks` / `${prefix}_transport_results`, and\n * `${prefix}_transport_runs` for the run queue. Default `durable`.\n */\n prefix?: string;\n /** Poll interval in ms when a queue is empty. Default 500. */\n pollMs?: number;\n /**\n * How long a claimed row is owned before it's reclaimable (crash recovery), in ms. Default 30_000.\n * Every claim this instance holds is renewed every `leaseMs / 3` while it is waiting or running, so\n * a step may run longer than `leaseMs`; only a crashed (or event-loop-blocked) instance loses it.\n */\n leaseMs?: number;\n /**\n * Max rows claimed per poll. Default 10. The task loop claims only as many rows as it has free\n * {@link concurrency} slots (capped by this). The results loop claims results in larger rounds of\n * its own (a few hundred, or this if larger), since the engine settles them together.\n */\n batchSize?: number;\n /**\n * How many tasks this instance runs at once (worker side). Default 1. A row is claimed only when a\n * slot is free to run it, so rows never wait claimed behind a slow task while a sibling sits idle.\n */\n concurrency?: number;\n /**\n * Where a transport failure is reported: a failing poll, a throwing result handler (the result is\n * re-delivered after the lease), a lost claim. Default `console.error`.\n */\n onError?: (err: unknown) => void;\n /** Create the tables on first use if missing. Default true. */\n autoCreate?: boolean;\n /** Identifies this instance in `claimed_by` (for debugging). Defaults to a random id. */\n instanceId?: string;\n}\n\n/**\n * A numeric option: `fallback` unless `value` is a finite number of at least `min`. `Math.max(1, NaN)`\n * is `NaN`, so a clamp alone would let a `NaN` through (to `LIMIT NaN`, or a `NaN` timer).\n */\nfunction numberOption(value: number | undefined, fallback: number, min: number): number {\n return value !== undefined && Number.isFinite(value) && value >= min ? value : fallback;\n}\n\n/**\n * A SQL-backed `Transport` — DBOS-style. Instead of a broker (Redis/SQS), remote steps are **rows**\n * in the same database the durable store already uses: `dispatch` inserts a task row, a worker\n * claims it with `SELECT … FOR UPDATE SKIP LOCKED` (so instances never double-claim), runs it, and\n * writes a result row the engine polls. Zero new infrastructure — the DB you already have IS the\n * queue.\n *\n * Trade-off vs a real broker: throughput is bounded by polling + row contention. Great for\n * workflow/pipeline scale (modest rate, long steps); not for high-fanout firehoses.\n *\n * Requires `FOR UPDATE SKIP LOCKED` — **MySQL 8+** or **Postgres 9.5+** (not SQLite). Run one\n * instance engine-side (`onResult`, dispatches) and one per worker process (`handle()` for its group).\n */\nexport class DbTransport implements Transport {\n private readonly exec: SqlExecutor;\n private readonly group?: string | undefined;\n private readonly tasksTable: string;\n private readonly resultsTable: string;\n private readonly runsTable: string;\n private readonly runConsumers = new Set<RunConsumer>();\n private readonly pollMs: number;\n private readonly leaseMs: number;\n private readonly batchSize: number;\n private readonly concurrency: number;\n private readonly onError: (err: unknown) => void;\n private readonly autoCreate: boolean;\n private readonly instanceId: string;\n private readonly isPg: boolean;\n\n private readonly handlers = new Map<string, StepHandler>();\n private running = true;\n private schemaReady?: Promise<void> | undefined;\n private taskLoop?: Promise<void>;\n private resultLoop?: Promise<void>;\n /** Tasks running on this instance (at most `concurrency`). */\n private readonly runningTasks = new Set<Promise<void>>();\n /** Claim rounds of results being handled on this instance — see {@link drainResults}. */\n private readonly runningResults = new Set<Promise<void>>();\n /** Results being handled across {@link runningResults} (at most {@link RESULTS_IN_FLIGHT}). */\n private resultsInFlight = 0;\n /** Claim tokens this instance holds, by table — every one is renewed on `renewTimer`. */\n private readonly held = new Map<string, Set<string>>();\n private renewTimer?: ReturnType<typeof setInterval> | undefined;\n private renewal?: Promise<void> | undefined;\n\n constructor(options: DbTransportOptions) {\n this.exec = options.executor;\n this.group = options.group;\n const prefix = options.prefix ?? 'durable';\n this.tasksTable = `${prefix}_transport_tasks`;\n this.resultsTable = `${prefix}_transport_results`;\n this.runsTable = `${prefix}_transport_runs`;\n this.pollMs = numberOption(options.pollMs, 500, 0);\n this.leaseMs = numberOption(options.leaseMs, 30_000, 1);\n this.batchSize = Math.floor(numberOption(options.batchSize, 10, 1));\n this.concurrency = Math.floor(numberOption(options.concurrency, 1, 1));\n this.onError = options.onError ?? ((err) => console.error('[DbTransport]', err));\n this.autoCreate = options.autoCreate ?? true;\n // Random, not time-based: two instances booted in the same millisecond must not share an id.\n this.instanceId = options.instanceId ?? `db-${randomUUID()}`;\n this.isPg = this.exec.dialect === 'postgres';\n }\n\n // ── dialect helpers ──────────────────────────────────────────────────────────────────────────\n /** Positional placeholder for param `i` (1-based): `$i` on Postgres, `?` elsewhere. */\n private ph(i: number): string {\n return this.isPg ? `$${i}` : '?';\n }\n private q(id: string): string {\n return this.exec.escapeId(id);\n }\n private now(): number {\n return Math.floor(Date.now());\n }\n\n // ── schema ───────────────────────────────────────────────────────────────────────────────────\n private ensureSchema(): Promise<void> {\n if (!this.schemaReady) {\n // A failed attempt is not cached: the next call tries again instead of failing forever.\n this.schemaReady = this.createTables().catch((err) => {\n this.schemaReady = undefined;\n throw err;\n });\n }\n return this.schemaReady;\n }\n\n /**\n * Run one `CREATE … IF NOT EXISTS`. Two instances booting on a fresh database race it, and Postgres\n * can reject the loser with a unique violation on its catalog even with `IF NOT EXISTS`; the\n * statement is simply re-run, by which point the table exists and it is a no-op.\n */\n private async createIfMissing(sql: string): Promise<void> {\n try {\n await this.exec.query(sql);\n } catch {\n await this.exec.query(sql);\n }\n }\n\n private async createTables(): Promise<void> {\n if (!this.autoCreate) return;\n const str = 'varchar(191)';\n const txt = this.isPg ? 'text' : 'longtext';\n const tasks = this.q(this.tasksTable);\n const results = this.q(this.resultsTable);\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${tasks} (\n ${this.q('step_id')} ${str} PRIMARY KEY,\n ${this.q('run_id')} ${str} NOT NULL,\n ${this.q('seq')} integer NOT NULL,\n ${this.q('name')} ${str} NOT NULL,\n ${this.q('grp')} ${str} NOT NULL,\n ${this.q('input')} ${txt},\n ${this.q('attempt')} integer NOT NULL,\n ${this.q('status')} varchar(32) NOT NULL,\n ${this.q('claimed_by')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${results} (\n ${this.q('step_id')} ${str} PRIMARY KEY,\n ${this.q('run_id')} ${str} NOT NULL,\n ${this.q('seq')} integer NOT NULL,\n ${this.q('status')} varchar(32) NOT NULL,\n ${this.q('output')} ${txt},\n ${this.q('error')} ${txt},\n ${this.q('started_at')} bigint,\n ${this.q('claimed_by')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.createIfMissing(\n `CREATE TABLE IF NOT EXISTS ${this.q(this.runsTable)} (\n ${this.q('run_id')} ${str} PRIMARY KEY,\n ${this.q('ns')} ${str} NOT NULL,\n ${this.q('priority')} integer NOT NULL,\n ${this.q('wake')} integer NOT NULL,\n ${this.q('claimed_by')} ${str},\n ${this.q('claim_token')} ${str},\n ${this.q('claimed_at')} bigint,\n ${this.q('created_at')} bigint NOT NULL\n )`,\n );\n await this.exec\n .query(\n `CREATE INDEX ${this.q(`${this.runsTable}_ns_idx`)} ON ${this.q(this.runsTable)} (${this.q('ns')}, ${this.q('priority')}, ${this.q('created_at')})`,\n )\n .catch(() => {});\n // Best-effort index for the group poll (MySQL has no CREATE INDEX IF NOT EXISTS < 8.0.13).\n await this.exec\n .query(\n `CREATE INDEX ${this.q(`${this.tasksTable}_grp_idx`)} ON ${tasks} (${this.q('grp')}, ${this.q('status')}, ${this.q('created_at')})`,\n )\n .catch(() => {});\n }\n\n // ── engine → worker ────────────────────────────────────────────────────────────────────────\n async dispatch(task: RemoteTask): Promise<void> {\n await this.dispatchMany([task]);\n }\n\n /** {@link dispatch} for many tasks: one multi-row insert per {@link TASKS_INSERT_CHUNK} tasks. */\n async dispatchMany(tasks: RemoteTask[]): Promise<void> {\n if (tasks.length === 0) return;\n await this.ensureSchema();\n const t = this.q(this.tasksTable);\n const cols = [\n 'step_id',\n 'run_id',\n 'seq',\n 'name',\n 'grp',\n 'input',\n 'attempt',\n 'status',\n 'created_at',\n ];\n for (let at = 0; at < tasks.length; at += TASKS_INSERT_CHUNK) {\n const chunk = tasks.slice(at, at + TASKS_INSERT_CHUNK);\n const values = chunk\n .map((_, r) => `(${cols.map((__, c) => this.ph(r * cols.length + c + 1)).join(', ')})`)\n .join(', ');\n const into = `${t} (${cols.map((c) => this.q(c)).join(', ')}) VALUES ${values}`;\n // Idempotent: a redelivered dispatch for the same step_id is ignored (the row already exists).\n const sql = this.isPg\n ? `INSERT INTO ${into} ON CONFLICT (${this.q('step_id')}) DO NOTHING`\n : `INSERT IGNORE INTO ${into}`;\n const now = this.now();\n await this.exec.query(\n sql,\n chunk.flatMap((task) => [\n task.stepId,\n task.runId,\n task.seq,\n task.name,\n task.group,\n JSON.stringify(task.input ?? null),\n task.attempt,\n 'pending',\n now,\n ]),\n );\n }\n }\n\n // ── worker → engine ────────────────────────────────────────────────────────────────────────\n handle(name: string, fn: StepHandler): void {\n if (!this.group) throw new Error('DbTransport needs a `group` to register handlers');\n this.handlers.set(name, fn);\n if (!this.taskLoop) this.taskLoop = this.loop(() => this.drainTasks(this.group as string));\n }\n\n onResult(handler: (result: StepResult) => Promise<void>): void {\n if (!this.resultLoop) this.resultLoop = this.loop(() => this.drainResults(handler));\n }\n\n onHeartbeat(_handler: (beat: Heartbeat) => Promise<void>): void {\n // Not modelled; the lease/reclaim on `claimed_at` is the liveness mechanism.\n }\n\n // ── run queue (engine ↔ engine) ─────────────────────────────────────────────────────────────\n /**\n * Queue `pending` runs for whichever engine instance polls the run queue first: one row per run,\n * keyed by run id, so a run already waiting is not queued twice. A run whose row an instance holds\n * right now is released for another take, so a dispatch landing while the run's previous entry is\n * being handled is not lost when that entry is deleted. Up to {@link RUNS_INSERT_CHUNK} rows per\n * insert.\n */\n async dispatchRuns(runs: readonly RunDispatch[], opts?: RunQueueOptions): Promise<void> {\n if (runs.length === 0) return;\n await this.ensureSchema();\n // One row per run (an upsert may not touch the same row twice in one statement); a wake among\n // a run's dispatches makes its row a wake.\n const byRun = new Map<string, RunDispatch>();\n for (const run of runs) {\n if (!byRun.has(run.runId) || run.reason === 'wake') byRun.set(run.runId, run);\n }\n // In run-id order: two dispatches touching overlapping rows lock them in the same order.\n const unique = [...byRun.values()].sort((a, b) =>\n a.runId < b.runId ? -1 : a.runId > b.runId ? 1 : 0,\n );\n const t = this.q(this.runsTable);\n const cols = ['run_id', 'ns', 'priority', 'wake', 'created_at'];\n const now = this.now();\n for (let at = 0; at < unique.length; at += RUNS_INSERT_CHUNK) {\n const chunk = unique.slice(at, at + RUNS_INSERT_CHUNK);\n const values = chunk\n .map((_, r) => `(${cols.map((__, c) => this.ph(r * cols.length + c + 1)).join(', ')})`)\n .join(', ');\n const insert = `INTO ${t} (${cols.map((c) => this.q(c)).join(', ')}) VALUES ${values}`;\n const release = ['claimed_by', 'claim_token', 'claimed_at']\n .map((c) => `${this.q(c)} = NULL`)\n .join(', ');\n const wake = this.q('wake');\n const sql = this.isPg\n ? `INSERT ${insert} ON CONFLICT (${this.q('run_id')}) DO UPDATE SET ${release}, ${wake} = GREATEST(${t}.${wake}, EXCLUDED.${wake})`\n : `INSERT ${insert} ON DUPLICATE KEY UPDATE ${release}, ${wake} = GREATEST(${wake}, VALUES(${wake}))`;\n await this.retryOnDeadlock(() =>\n this.exec.query(\n sql,\n chunk.flatMap((run, r) => [\n run.runId,\n opts?.namespace ?? '',\n run.priority ?? 0,\n run.reason === 'wake' ? 1 : 0,\n now + r,\n ]),\n ),\n );\n }\n }\n\n /**\n * Poll the run queue, at most `opts.concurrency` runs in flight on this instance. Rows are claimed\n * with `FOR UPDATE SKIP LOCKED` (instances never take the same run), highest priority then oldest\n * first; a row is deleted once its handler settles, and its lease is renewed meanwhile, so a row is\n * re-taken only after the instance holding it went down. The returned stop takes no more rows and\n * resolves once the runs in flight settled.\n */\n onDispatchedRun(\n handler: (run: RunDispatch) => Promise<void>,\n opts: RunConsumerOptions,\n ): () => Promise<void> {\n const consumer: RunConsumer = {\n stopped: false,\n inflight: new Set(),\n slotFreed: undefined,\n loop: Promise.resolve(),\n };\n const concurrency = Math.max(1, Math.floor(opts.concurrency));\n const ns = opts.namespace ?? '';\n consumer.loop = this.consumeRuns(consumer, handler, concurrency, ns).catch((err) => {\n this.onError(err);\n });\n this.runConsumers.add(consumer);\n return () => this.stopRunConsumer(consumer);\n }\n\n private async stopRunConsumer(consumer: RunConsumer): Promise<void> {\n consumer.stopped = true;\n consumer.slotFreed?.();\n await consumer.loop;\n this.runConsumers.delete(consumer);\n }\n\n private async consumeRuns(\n consumer: RunConsumer,\n handler: (run: RunDispatch) => Promise<void>,\n concurrency: number,\n ns: string,\n ): Promise<void> {\n const renew = setInterval(\n () => void this.renewRunLeases(consumer).catch(() => undefined),\n Math.max(1_000, Math.floor(this.leaseMs / 3)),\n );\n renew.unref?.();\n const settled = new Set<Promise<unknown>>();\n try {\n while (this.running && !consumer.stopped) {\n const free = concurrency - consumer.inflight.size;\n if (free <= 0) {\n await new Promise<void>((resolve) => {\n consumer.slotFreed = resolve;\n });\n consumer.slotFreed = undefined;\n continue;\n }\n let rows: ClaimedRun[] = [];\n try {\n await this.ensureSchema();\n rows = await this.retryOnDeadlock(() =>\n this.claimRuns(ns, Math.min(free, RUNS_CLAIM_MAX)),\n );\n } catch (err) {\n if (this.running && !consumer.stopped) this.onError(err);\n }\n for (const { run, token } of rows) {\n consumer.inflight.add(token);\n const done = handler(run)\n .catch(() => undefined)\n .then(() =>\n this.retryOnDeadlock(() =>\n this.exec.query(\n `DELETE FROM ${this.q(this.runsTable)} WHERE ${this.q('claim_token')} = ${this.ph(1)}`,\n [token],\n ),\n ).catch(() => undefined),\n )\n .finally(() => {\n consumer.inflight.delete(token);\n settled.delete(done);\n consumer.slotFreed?.();\n });\n settled.add(done);\n }\n if (rows.length === 0) await this.sleep(this.pollMs);\n }\n await Promise.allSettled([...settled]);\n } finally {\n clearInterval(renew);\n }\n }\n\n /**\n * Run `statement`, again on a deadlock: queueing a run (an upsert), taking one (`SKIP LOCKED`)\n * and deleting a handled one touch the same rows from different instances, and the database then\n * rolls one of them back rather than wait (InnoDB reports `ER_LOCK_DEADLOCK`, Postgres `40P01`).\n */\n private async retryOnDeadlock<T>(statement: () => Promise<T>): Promise<T> {\n for (let attempt = 1; ; attempt += 1) {\n try {\n return await statement();\n } catch (err) {\n if (attempt >= DEADLOCK_RETRIES || !isDeadlock(err)) throw err;\n await this.sleep(10 * attempt + Math.floor(Math.random() * 10));\n }\n }\n }\n\n private async claimRuns(ns: string, limit: number): Promise<ClaimedRun[]> {\n const t = this.q(this.runsTable);\n const staleBefore = this.now() - this.leaseMs;\n return this.exec.transaction(async (tx) => {\n const rows = await tx.query<RunRow>(\n `SELECT ${this.q('run_id')}, ${this.q('wake')} FROM ${t}\n WHERE ${this.q('ns')} = ${this.ph(1)}\n AND (${this.q('claimed_at')} IS NULL OR ${this.q('claimed_at')} < ${this.ph(2)})\n ORDER BY ${this.q('priority')} DESC, ${this.q('created_at')} ASC\n LIMIT ${Math.floor(limit)} FOR UPDATE SKIP LOCKED`,\n [ns, staleBefore],\n );\n const claimed = rows.map((r) => ({\n run: {\n runId: r.run_id,\n reason: Number(r.wake) === 1 ? ('wake' as const) : ('start' as const),\n },\n token: globalThis.crypto.randomUUID(),\n }));\n const now = this.now();\n for (const {\n run: { runId },\n token,\n } of claimed) {\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_by')} = ${this.ph(1)}, ${this.q('claim_token')} = ${this.ph(2)}, ${this.q('claimed_at')} = ${this.ph(3)}\n WHERE ${this.q('run_id')} = ${this.ph(4)}`,\n [this.instanceId, token, now, runId],\n );\n }\n return claimed;\n });\n }\n\n /** Keep the rows of the runs in flight leased, so a long turn is not re-taken by another instance. */\n private async renewRunLeases(consumer: RunConsumer): Promise<void> {\n const tokens = [...consumer.inflight];\n if (tokens.length === 0) return;\n await this.exec.query(\n `UPDATE ${this.q(this.runsTable)} SET ${this.q('claimed_at')} = ${this.ph(1)}\n WHERE ${this.q('claim_token')} IN (${tokens.map((_, i) => this.ph(i + 2)).join(', ')})`,\n [this.now(), ...tokens],\n );\n }\n\n // ── polling ──────────────────────────────────────────────────────────────────────────────────\n /** Run `tick` repeatedly; sleep `pollMs` when it reports an empty queue. Stops on `close()`. */\n private async loop(tick: () => Promise<number>): Promise<void> {\n while (this.running) {\n let processed = 0;\n try {\n await this.ensureSchema();\n processed = await tick();\n } catch (err) {\n if (this.running) this.onError(err);\n }\n if (processed === 0 && this.running) await this.sleep(this.pollMs);\n }\n }\n\n /**\n * One task-loop round: claim as many rows as there are free slots (never more — a claimed row that\n * isn't running is one an idle sibling can't run), and start each. With every slot busy, wait for\n * one to free instead of polling.\n */\n private async drainTasks(group: string): Promise<number> {\n const free = this.concurrency - this.runningTasks.size;\n if (free <= 0) {\n await Promise.race(this.runningTasks);\n return 1;\n }\n const { rows, token } = await this.claim<TaskRow>(\n this.tasksTable,\n Math.min(free, this.batchSize),\n `${this.q('grp')} = ${this.ph(1)}`,\n [group],\n );\n if (rows.length === 0) return 0;\n this.hold(this.tasksTable, token);\n let left = rows.length;\n for (const row of rows) {\n const run = this.runTask(row, group, token).finally(() => {\n this.runningTasks.delete(run);\n left -= 1;\n if (left === 0) this.release(this.tasksTable, token);\n });\n this.runningTasks.add(run);\n }\n return rows.length;\n }\n\n /**\n * Run one claimed task and settle it — only while this instance still holds the claim: the claim is\n * re-checked before running, and the task row is deleted and its result written in ONE transaction,\n * conditional on the claim token, so an instance whose claim was taken over neither deletes the new\n * holder's row nor publishes a second result.\n */\n private async runTask(row: TaskRow, group: string, token: string): Promise<void> {\n try {\n if (!(await this.stillHolds(row.step_id, token))) {\n this.onError(\n new Error(\n `[DbTransport] lost the claim on task ${row.step_id} before running it — another instance took it over; not running it here`,\n ),\n );\n return;\n }\n const task: RemoteTask = {\n runId: row.run_id,\n seq: Number(row.seq),\n stepId: row.step_id,\n name: row.name,\n group,\n input: row.input == null ? undefined : JSON.parse(row.input),\n attempt: Number(row.attempt),\n };\n const result = await runStepHandler(task, this.handlers.get(task.name));\n if (!(await this.settleTask(task.stepId, token, result))) {\n this.onError(\n new Error(\n `[DbTransport] lost the claim on task ${task.stepId} while running it (this instance went silent past leaseMs) — another instance took it over and may run it too; this run's result was dropped. Raise leaseMs if steps block the event loop`,\n ),\n );\n }\n } catch (err) {\n // A database error settling the row (or a malformed row): it stays claimed, stops being\n // renewed, and is reclaimed after the lease — at-least-once, like a crash.\n this.onError(err);\n }\n }\n\n /** Whether the claim on `stepId` is still `token`'s (re-stamping it if so). */\n private async stillHolds(stepId: string, token: string): Promise<boolean> {\n const t = this.q(this.tasksTable);\n const rows = await this.exec.transaction(async (tx) => {\n const held = await tx.query<{ step_id: string }>(\n `SELECT ${this.q('step_id')} FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)} FOR UPDATE`,\n [stepId, token],\n );\n if (held.length > 0) {\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_at')} = ${this.ph(1)} WHERE ${this.q('step_id')} = ${this.ph(2)} AND ${this.q('claimed_by')} = ${this.ph(3)}`,\n [this.now(), stepId, token],\n );\n }\n return held;\n });\n return rows.length > 0;\n }\n\n /** Delete the task row and publish its result in ONE transaction, only while `token` holds it. */\n private async settleTask(stepId: string, token: string, result: StepResult): Promise<boolean> {\n const t = this.q(this.tasksTable);\n return this.exec.transaction(async (tx) => {\n const held = await tx.query<{ step_id: string }>(\n `SELECT ${this.q('step_id')} FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)} FOR UPDATE`,\n [stepId, token],\n );\n if (held.length === 0) return false;\n await tx.query(\n `DELETE FROM ${t} WHERE ${this.q('step_id')} = ${this.ph(1)} AND ${this.q('claimed_by')} = ${this.ph(2)}`,\n [stepId, token],\n );\n await this.insertResult(result, tx);\n return true;\n });\n }\n\n /**\n * Claim a batch of results and hand them all to the engine at once, then delete the ones whose\n * handler resolved — only after it did (at-least-once), and only while this round still holds the\n * claim (a newer result for the same step that replaced the row resets the claim, so it is\n * delivered next instead of deleted). The next round is claimed while this one runs, up to\n * {@link RESULTS_IN_FLIGHT} results; the round's claim is renewed until it is done. A throwing\n * handler is reported and its row left for re-delivery after the lease.\n */\n private async drainResults(handler: (result: StepResult) => Promise<void>): Promise<number> {\n const free = RESULTS_IN_FLIGHT - this.resultsInFlight;\n if (free <= 0) {\n await Promise.race(this.runningResults);\n return 1;\n }\n const { rows, token } = await this.claim<ResultRow>(\n this.resultsTable,\n Math.min(free, Math.max(this.batchSize, RESULTS_CLAIM_MAX)),\n '1 = 1',\n [],\n );\n if (rows.length === 0) return 0;\n this.hold(this.resultsTable, token);\n this.resultsInFlight += rows.length;\n const round = this.handleResults(rows, token, handler).finally(() => {\n this.resultsInFlight -= rows.length;\n this.runningResults.delete(round);\n this.release(this.resultsTable, token);\n });\n this.runningResults.add(round);\n return rows.length;\n }\n\n /** Hand one claimed round of results to the engine, then delete the delivered ones in one statement. */\n private async handleResults(\n rows: ResultRow[],\n token: string,\n handler: (result: StepResult) => Promise<void>,\n ): Promise<void> {\n const delivered: string[] = [];\n await Promise.all(\n rows.map(async (row) => {\n try {\n await handler({\n runId: row.run_id,\n seq: Number(row.seq),\n stepId: row.step_id,\n status: row.status as StepResult['status'],\n output: row.output == null ? undefined : JSON.parse(row.output),\n error: row.error == null ? undefined : JSON.parse(row.error),\n startedAt: row.started_at == null ? undefined : Number(row.started_at),\n });\n delivered.push(row.step_id);\n } catch (err) {\n this.onError(err);\n }\n }),\n );\n if (delivered.length === 0) return;\n try {\n const ids = delivered.map((_, i) => this.ph(i + 2)).join(', ');\n await this.exec.query(\n `DELETE FROM ${this.q(this.resultsTable)} WHERE ${this.q('claimed_by')} = ${this.ph(1)} AND ${this.q('step_id')} IN (${ids})`,\n [token, ...delivered],\n );\n } catch (err) {\n // The rows stay and are re-delivered after the lease; the engine drops a settled step's copy.\n this.onError(err);\n }\n }\n\n /**\n * Atomically claim up to `limit` un-leased rows matching `where` and stamp them with a claim token\n * unique to this round (`<instanceId>:<uuid>`). `SELECT … FOR UPDATE SKIP LOCKED` inside the txn\n * means concurrent instances skip each other's locked rows instead of blocking, so no row is\n * claimed twice; the token is what every later delete/settle is conditional on.\n */\n private async claim<T extends { step_id: string }>(\n table: string,\n limit: number,\n where: string,\n whereParams: unknown[],\n ): Promise<{ rows: T[]; token: string }> {\n const t = this.q(table);\n const staleBefore = this.now() - this.leaseMs;\n const token = `${this.instanceId}:${randomUUID()}`;\n const rows = await this.exec.transaction(async (tx) => {\n const leaseClause = `(${this.q('claimed_at')} IS NULL OR ${this.q('claimed_at')} < ${this.ph(whereParams.length + 1)})`;\n const found = await tx.query<T>(\n `SELECT * FROM ${t} WHERE ${where} AND ${leaseClause}\n ORDER BY ${this.q('created_at')} ASC LIMIT ${Math.max(1, Math.floor(limit))} FOR UPDATE SKIP LOCKED`,\n [...whereParams, staleBefore],\n );\n if (found.length > 0) {\n const ids = found.map((r) => r.step_id);\n const placeholders = ids.map((_, i) => this.ph(i + 3)).join(', ');\n await tx.query(\n `UPDATE ${t} SET ${this.q('claimed_by')} = ${this.ph(1)}, ${this.q('claimed_at')} = ${this.ph(2)}\n WHERE ${this.q('step_id')} IN (${placeholders})`,\n [token, this.now(), ...ids],\n );\n }\n return found;\n });\n return { rows, token };\n }\n\n // ── claim renewal: every claim held, not only the row being run ─────────────────────────────\n private hold(table: string, token: string): void {\n let tokens = this.held.get(table);\n if (!tokens) {\n tokens = new Set();\n this.held.set(table, tokens);\n }\n tokens.add(token);\n if (this.renewTimer) return;\n const intervalMs = Math.max(10, Math.floor(this.leaseMs / 3));\n this.renewTimer = setInterval(() => {\n if (this.renewal) return; // a slow database never stacks renewal rounds\n const round = this.renewHeld()\n .catch((err) => this.onError(err))\n .finally(() => {\n if (this.renewal === round) this.renewal = undefined;\n });\n this.renewal = round;\n }, intervalMs);\n this.renewTimer.unref?.();\n }\n\n private release(table: string, token: string): void {\n const tokens = this.held.get(table);\n tokens?.delete(token);\n if (tokens?.size === 0) this.held.delete(table);\n if (this.held.size === 0 && this.renewTimer) {\n clearInterval(this.renewTimer);\n this.renewTimer = undefined;\n }\n }\n\n /** Re-stamp `claimed_at` on every row still claimed under a token this instance holds. */\n private async renewHeld(): Promise<void> {\n for (const [table, tokens] of this.held) {\n if (tokens.size === 0) continue;\n const list = [...tokens];\n const placeholders = list.map((_, i) => this.ph(i + 2)).join(', ');\n await this.exec.query(\n `UPDATE ${this.q(table)} SET ${this.q('claimed_at')} = ${this.ph(1)} WHERE ${this.q('claimed_by')} IN (${placeholders})`,\n [this.now(), ...list],\n );\n }\n }\n\n /**\n * Write a result row. A result already waiting for the same step (an earlier attempt's, not yet\n * deleted by the engine) is REPLACED and its claim reset, never kept: `step_id` is stable across\n * attempts, so keeping the old row would silently drop a retry's result.\n */\n private async insertResult(result: StepResult, tx: SqlTx = this.exec): Promise<void> {\n const t = this.q(this.resultsTable);\n const cols = [\n 'step_id',\n 'run_id',\n 'seq',\n 'status',\n 'output',\n 'error',\n 'started_at',\n 'created_at',\n ];\n const replace = ['run_id', 'seq', 'status', 'output', 'error', 'started_at', 'created_at'];\n const values = `(${cols.map((c) => this.q(c)).join(', ')}) VALUES (${cols.map((_, i) => this.ph(i + 1)).join(', ')})`;\n const sql = this.isPg\n ? `INSERT INTO ${t} ${values} ON CONFLICT (${this.q('step_id')}) DO UPDATE SET ${[\n ...replace.map((c) => `${this.q(c)} = EXCLUDED.${this.q(c)}`),\n `${this.q('claimed_by')} = NULL`,\n `${this.q('claimed_at')} = NULL`,\n ].join(', ')}`\n : `INSERT INTO ${t} ${values} ON DUPLICATE KEY UPDATE ${[\n ...replace.map((c) => `${this.q(c)} = VALUES(${this.q(c)})`),\n `${this.q('claimed_by')} = NULL`,\n `${this.q('claimed_at')} = NULL`,\n ].join(', ')}`;\n await tx.query(sql, [\n result.stepId,\n result.runId,\n result.seq,\n result.status,\n result.output === undefined ? null : JSON.stringify(result.output),\n result.error === undefined ? null : JSON.stringify(result.error),\n result.startedAt ?? null,\n this.now(),\n ]);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n }\n\n /** Stop the pollers and wait for the current ticks to settle. Does not close the shared DataSource. */\n async close(): Promise<void> {\n this.running = false;\n await Promise.allSettled([\n this.taskLoop,\n this.resultLoop,\n ...[...this.runConsumers].map((c) => this.stopRunConsumer(c)),\n ]);\n // Let running tasks and results settle (their claims keep being renewed until they do).\n await Promise.allSettled([...this.runningTasks, ...this.runningResults]);\n if (this.renewTimer) clearInterval(this.renewTimer);\n this.renewTimer = undefined;\n await this.renewal;\n }\n}\n","/**\n * The tiny SQL surface `DbTransport` needs, so it rides **whatever ORM/connection the app already\n * has** instead of forcing a specific one. Implement it for your ORM (adapters for TypeORM and\n * MikroORM ship below) and hand it to `new DbTransport({ executor })`. Mirrors how the durable\n * *store* is ORM-pluggable (store-typeorm / store-mikro-orm / …).\n */\nexport interface SqlExecutor {\n /** Drives placeholder style (`$n` vs `?`) and a couple of DDL type choices. */\n readonly dialect: 'mysql' | 'postgres';\n /** Quote/escape an identifier (table/column name) for this dialect. */\n escapeId(id: string): string;\n /** Run a statement outside a transaction. */\n query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;\n /** Run `fn` inside a transaction — the claim uses `FOR UPDATE SKIP LOCKED` within it. */\n transaction<T>(fn: (tx: SqlTx) => Promise<T>): Promise<T>;\n}\n\nexport interface SqlTx {\n query<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;\n}\n\n// ── TypeORM ──────────────────────────────────────────────────────────────────────────────────────\n\n/** Structural slice of a TypeORM `DataSource` — avoids a hard dependency on typeorm. */\ninterface TypeOrmDataSourceLike {\n options: { type: string };\n driver: { escape(id: string): string };\n query(sql: string, params?: unknown[]): Promise<unknown>;\n createQueryRunner(): {\n connect(): Promise<void>;\n startTransaction(): Promise<void>;\n commitTransaction(): Promise<void>;\n rollbackTransaction(): Promise<void>;\n release(): Promise<void>;\n query(sql: string, params?: unknown[]): Promise<unknown>;\n };\n}\n\n/** Build an executor from a TypeORM `DataSource` (e.g. the one the durable store already uses). */\nexport function typeOrmExecutor(ds: TypeOrmDataSourceLike): SqlExecutor {\n return {\n dialect: /postgres/.test(String(ds.options.type)) ? 'postgres' : 'mysql',\n escapeId: (id) => ds.driver.escape(id),\n query: <T>(sql: string, params?: unknown[]) => ds.query(sql, params) as Promise<T[]>,\n transaction: async <T>(fn: (tx: SqlTx) => Promise<T>) => {\n const qr = ds.createQueryRunner();\n await qr.connect();\n await qr.startTransaction();\n try {\n const result = await fn({\n query: <U>(sql: string, params?: unknown[]) => qr.query(sql, params) as Promise<U[]>,\n });\n await qr.commitTransaction();\n return result;\n } catch (err) {\n await qr.rollbackTransaction();\n throw err;\n } finally {\n await qr.release();\n }\n },\n };\n}\n\n// ── MikroORM ─────────────────────────────────────────────────────────────────────────────────────\n\n/** Structural slice of a MikroORM `EntityManager` — avoids a hard dependency on @mikro-orm/core. */\ninterface MikroOrmEmLike {\n getPlatform(): { constructor: { name: string }; quoteIdentifier(id: string): string };\n getConnection(): {\n execute(sql: string, params?: unknown[], method?: 'all', ctx?: unknown): Promise<unknown>;\n };\n getTransactionContext?(): unknown;\n transactional<T>(cb: (em: MikroOrmEmLike) => Promise<T>): Promise<T>;\n}\n\n/**\n * Build an executor from a MikroORM `EntityManager` — i.e. **the app's own configured ORM**. Runs\n * raw SQL through `em.getConnection().execute(...)` and wraps the claim in `em.transactional(...)`,\n * threading the transaction context so `FOR UPDATE SKIP LOCKED` locks within the tx.\n */\nexport function mikroOrmExecutor(em: MikroOrmEmLike): SqlExecutor {\n const platform = em.getPlatform();\n return {\n dialect: /postgre/i.test(platform.constructor.name) ? 'postgres' : 'mysql',\n escapeId: (id) => platform.quoteIdentifier(id),\n query: <T>(sql: string, params?: unknown[]) =>\n em.getConnection().execute(sql, params ?? [], 'all') as Promise<T[]>,\n transaction: <T>(fn: (tx: SqlTx) => Promise<T>) =>\n em.transactional((txEm) =>\n fn({\n query: <U>(sql: string, params?: unknown[]) =>\n txEm\n .getConnection()\n .execute(sql, params ?? [], 'all', txEm.getTransactionContext?.()) as Promise<U[]>,\n }),\n ),\n };\n}\n"],"mappings":"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","names":["randomUUID","runStepHandler","typeOrmExecutor","ds","dialect","test","String","options","type","escapeId","id","driver","escape","query","sql","params","transaction","fn","qr","createQueryRunner","connect","startTransaction","result","commitTransaction","err","rollbackTransaction","release","mikroOrmExecutor","em","platform","getPlatform","name","quoteIdentifier","getConnection","execute","transactional","txEm","getTransactionContext","RUNS_INSERT_CHUNK","DEADLOCK_RETRIES","isDeadlock","err","codes","code","errno","driverError","some","c","TASKS_INSERT_CHUNK","RUNS_CLAIM_MAX","RESULTS_IN_FLIGHT","RESULTS_CLAIM_MAX","numberOption","value","fallback","min","undefined","Number","isFinite","DbTransport","exec","group","tasksTable","resultsTable","runsTable","runConsumers","Set","pollMs","leaseMs","batchSize","concurrency","onError","autoCreate","instanceId","isPg","handlers","Map","running","schemaReady","taskLoop","resultLoop","runningTasks","runningResults","resultsInFlight","held","renewTimer","renewal","options","executor","prefix","Math","floor","console","error","randomUUID","dialect","ph","i","q","id","escapeId","now","Date","ensureSchema","createTables","catch","createIfMissing","sql","query","str","txt","tasks","results","dispatch","task","dispatchMany","length","t","cols","at","chunk","slice","values","map","_","r","__","join","into","flatMap","stepId","runId","seq","name","JSON","stringify","input","attempt","handle","fn","Error","set","loop","drainTasks","onResult","handler","drainResults","onHeartbeat","_handler","dispatchRuns","runs","opts","byRun","run","has","reason","unique","sort","a","b","insert","release","wake","retryOnDeadlock","namespace","priority","onDispatchedRun","consumer","stopped","inflight","slotFreed","Promise","resolve","max","ns","consumeRuns","add","stopRunConsumer","delete","renew","setInterval","renewRunLeases","unref","settled","free","size","rows","claimRuns","token","done","then","finally","sleep","allSettled","clearInterval","statement","random","limit","staleBefore","transaction","tx","claimed","run_id","globalThis","crypto","tokens","tick","processed","race","claim","hold","left","row","runTask","stillHolds","step_id","parse","result","runStepHandler","get","settleTask","insertResult","round","handleResults","delivered","all","status","output","startedAt","started_at","push","ids","table","where","whereParams","leaseClause","found","placeholders","intervalMs","renewHeld","list","replace","ms","setTimeout","close"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dudousxd/nestjs-durable-transport-db",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Database (SQL) Transport for nestjs-durable — DBOS-style: dispatch remote steps as rows in the durable DB, no broker",
5
5
  "license": "MIT",
6
6
  "author": "Davide Carvalho",
@@ -44,7 +44,7 @@
44
44
  "typeorm": "0.3.31",
45
45
  "typescript": "5.9.3",
46
46
  "zod": "3.25.76",
47
- "@dudousxd/nestjs-durable-core": "^0.85.3"
47
+ "@dudousxd/nestjs-durable-core": "^0.85.7"
48
48
  },
49
49
  "module": "./dist/index.js",
50
50
  "exports": {