@dudousxd/nestjs-durable-core 0.39.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -246,6 +246,13 @@ interface WorkflowRun {
246
246
  tags?: string[] | undefined;
247
247
  /** Typed, queryable run data (e.g. `{ amount: 200, tier: 'pro' }`) — see {@link RunQuery.attributes}. */
248
248
  searchAttributes?: SearchAttributes | undefined;
249
+ /**
250
+ * The worker-pool partition this run belongs to. Stamped at creation from the creating engine's
251
+ * `namespace` (default `'default'`). A worker only picks up / recovers / resumes-timers-for /
252
+ * times-out runs in its own namespace. `undefined` on a run created before this field existed; the
253
+ * store persists it as `'default'`. Read paths (dashboard, `getRun`) are NOT namespace-scoped.
254
+ */
255
+ namespace?: string | undefined;
249
256
  /**
250
257
  * Dispatch priority for a REMOTE run (one advanced by a {@link WorkflowExecutor} over a broker):
251
258
  * carried onto each {@link WorkflowTask} so an urgent child workflow can jump ahead of enqueued
@@ -433,11 +440,11 @@ interface StateStore {
433
440
  */
434
441
  saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
435
442
  /** Used by recovery on boot to find runs to resume (crashed, left `running`). */
436
- listIncompleteRuns(): Promise<WorkflowRun[]>;
443
+ listIncompleteRuns(namespace?: string): Promise<WorkflowRun[]>;
437
444
  /** The oldest `pending` runs awaiting dispatch (FIFO, by `createdAt`), capped at `limit`. */
438
- listPendingRuns(limit: number): Promise<WorkflowRun[]>;
445
+ listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
439
446
  /** Suspended runs whose durable timer is due (`wakeAt <= nowMs`), ready to resume. */
440
- listDueTimers(nowMs: number): Promise<WorkflowRun[]>;
447
+ listDueTimers(nowMs: number, namespace?: string): Promise<WorkflowRun[]>;
441
448
  /**
442
449
  * Atomically acquire the recovery lease on a run for `owner` until `leaseUntilMs`, but only if
443
450
  * it is currently unlocked or its lease has expired (`<= nowMs`). Returns whether it was
@@ -549,6 +556,8 @@ interface RunQuery {
549
556
  * `workflow`/`status`/`tag` to bound the scan on large stores.
550
557
  */
551
558
  attributes?: AttributeFilter[] | undefined;
559
+ /** Restrict to runs in this namespace (exact match), ANDed with the other predicates. */
560
+ namespace?: string | undefined;
552
561
  limit?: number | undefined;
553
562
  offset?: number | undefined;
554
563
  }
@@ -1336,6 +1345,13 @@ interface WorkflowEngineDeps {
1336
1345
  admission?: AdmissionBackend | undefined;
1337
1346
  /** Unique id for this engine instance, used for recovery leases. Defaults to a random id. */
1338
1347
  instanceId?: string | undefined;
1348
+ /**
1349
+ * Worker-pool partition for this engine. Stamped on every run it creates; the poll paths
1350
+ * (`runPending`/`recoverIncomplete`/`resumeDueTimers`/`sweepTimeouts`) only act on runs in this
1351
+ * namespace. Default `'default'` — byte-identical to a single-pool deployment. Set distinct values
1352
+ * to safely share ONE state store across non-interchangeable pools (e.g. local dev vs a cluster).
1353
+ */
1354
+ namespace?: string | undefined;
1339
1355
  /** Recovery lease duration in ms — how long this instance owns a run it picked up. Default 30s. */
1340
1356
  leaseMs?: number | undefined;
1341
1357
  /**
@@ -1427,6 +1443,7 @@ declare class WorkflowEngine {
1427
1443
  private readonly controlPlane?;
1428
1444
  private readonly clock;
1429
1445
  private readonly instanceId;
1446
+ private readonly namespace;
1430
1447
  private readonly leaseMs;
1431
1448
  private readonly maxRecoveryAttempts?;
1432
1449
  private readonly remoteAdvanceSilenceMs?;
@@ -2146,9 +2163,9 @@ declare class CodecStateStore implements StateStore {
2146
2163
  raw: unknown;
2147
2164
  saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
2148
2165
  }) => Promise<T>): Promise<T>;
2149
- listIncompleteRuns(): Promise<WorkflowRun[]>;
2150
- listPendingRuns(limit: number): Promise<WorkflowRun[]>;
2151
- listDueTimers(nowMs: number): Promise<WorkflowRun[]>;
2166
+ listIncompleteRuns(namespace?: string): Promise<WorkflowRun[]>;
2167
+ listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
2168
+ listDueTimers(nowMs: number, namespace?: string): Promise<WorkflowRun[]>;
2152
2169
  tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
2153
2170
  releaseRunLock(runId: string): Promise<void>;
2154
2171
  renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
@@ -2410,9 +2427,9 @@ declare class InMemoryStateStore implements StateStore {
2410
2427
  deleteRun(runId: string): Promise<void>;
2411
2428
  getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
2412
2429
  saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
2413
- listIncompleteRuns(): Promise<WorkflowRun[]>;
2414
- listPendingRuns(limit: number): Promise<WorkflowRun[]>;
2415
- listDueTimers(nowMs: number): Promise<WorkflowRun[]>;
2430
+ listIncompleteRuns(namespace?: string): Promise<WorkflowRun[]>;
2431
+ listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
2432
+ listDueTimers(nowMs: number, namespace?: string): Promise<WorkflowRun[]>;
2416
2433
  tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
2417
2434
  releaseRunLock(runId: string): Promise<void>;
2418
2435
  renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
package/dist/index.d.ts CHANGED
@@ -246,6 +246,13 @@ interface WorkflowRun {
246
246
  tags?: string[] | undefined;
247
247
  /** Typed, queryable run data (e.g. `{ amount: 200, tier: 'pro' }`) — see {@link RunQuery.attributes}. */
248
248
  searchAttributes?: SearchAttributes | undefined;
249
+ /**
250
+ * The worker-pool partition this run belongs to. Stamped at creation from the creating engine's
251
+ * `namespace` (default `'default'`). A worker only picks up / recovers / resumes-timers-for /
252
+ * times-out runs in its own namespace. `undefined` on a run created before this field existed; the
253
+ * store persists it as `'default'`. Read paths (dashboard, `getRun`) are NOT namespace-scoped.
254
+ */
255
+ namespace?: string | undefined;
249
256
  /**
250
257
  * Dispatch priority for a REMOTE run (one advanced by a {@link WorkflowExecutor} over a broker):
251
258
  * carried onto each {@link WorkflowTask} so an urgent child workflow can jump ahead of enqueued
@@ -433,11 +440,11 @@ interface StateStore {
433
440
  */
434
441
  saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
435
442
  /** Used by recovery on boot to find runs to resume (crashed, left `running`). */
436
- listIncompleteRuns(): Promise<WorkflowRun[]>;
443
+ listIncompleteRuns(namespace?: string): Promise<WorkflowRun[]>;
437
444
  /** The oldest `pending` runs awaiting dispatch (FIFO, by `createdAt`), capped at `limit`. */
438
- listPendingRuns(limit: number): Promise<WorkflowRun[]>;
445
+ listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
439
446
  /** Suspended runs whose durable timer is due (`wakeAt <= nowMs`), ready to resume. */
440
- listDueTimers(nowMs: number): Promise<WorkflowRun[]>;
447
+ listDueTimers(nowMs: number, namespace?: string): Promise<WorkflowRun[]>;
441
448
  /**
442
449
  * Atomically acquire the recovery lease on a run for `owner` until `leaseUntilMs`, but only if
443
450
  * it is currently unlocked or its lease has expired (`<= nowMs`). Returns whether it was
@@ -549,6 +556,8 @@ interface RunQuery {
549
556
  * `workflow`/`status`/`tag` to bound the scan on large stores.
550
557
  */
551
558
  attributes?: AttributeFilter[] | undefined;
559
+ /** Restrict to runs in this namespace (exact match), ANDed with the other predicates. */
560
+ namespace?: string | undefined;
552
561
  limit?: number | undefined;
553
562
  offset?: number | undefined;
554
563
  }
@@ -1336,6 +1345,13 @@ interface WorkflowEngineDeps {
1336
1345
  admission?: AdmissionBackend | undefined;
1337
1346
  /** Unique id for this engine instance, used for recovery leases. Defaults to a random id. */
1338
1347
  instanceId?: string | undefined;
1348
+ /**
1349
+ * Worker-pool partition for this engine. Stamped on every run it creates; the poll paths
1350
+ * (`runPending`/`recoverIncomplete`/`resumeDueTimers`/`sweepTimeouts`) only act on runs in this
1351
+ * namespace. Default `'default'` — byte-identical to a single-pool deployment. Set distinct values
1352
+ * to safely share ONE state store across non-interchangeable pools (e.g. local dev vs a cluster).
1353
+ */
1354
+ namespace?: string | undefined;
1339
1355
  /** Recovery lease duration in ms — how long this instance owns a run it picked up. Default 30s. */
1340
1356
  leaseMs?: number | undefined;
1341
1357
  /**
@@ -1427,6 +1443,7 @@ declare class WorkflowEngine {
1427
1443
  private readonly controlPlane?;
1428
1444
  private readonly clock;
1429
1445
  private readonly instanceId;
1446
+ private readonly namespace;
1430
1447
  private readonly leaseMs;
1431
1448
  private readonly maxRecoveryAttempts?;
1432
1449
  private readonly remoteAdvanceSilenceMs?;
@@ -2146,9 +2163,9 @@ declare class CodecStateStore implements StateStore {
2146
2163
  raw: unknown;
2147
2164
  saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
2148
2165
  }) => Promise<T>): Promise<T>;
2149
- listIncompleteRuns(): Promise<WorkflowRun[]>;
2150
- listPendingRuns(limit: number): Promise<WorkflowRun[]>;
2151
- listDueTimers(nowMs: number): Promise<WorkflowRun[]>;
2166
+ listIncompleteRuns(namespace?: string): Promise<WorkflowRun[]>;
2167
+ listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
2168
+ listDueTimers(nowMs: number, namespace?: string): Promise<WorkflowRun[]>;
2152
2169
  tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
2153
2170
  releaseRunLock(runId: string): Promise<void>;
2154
2171
  renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
@@ -2410,9 +2427,9 @@ declare class InMemoryStateStore implements StateStore {
2410
2427
  deleteRun(runId: string): Promise<void>;
2411
2428
  getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
2412
2429
  saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
2413
- listIncompleteRuns(): Promise<WorkflowRun[]>;
2414
- listPendingRuns(limit: number): Promise<WorkflowRun[]>;
2415
- listDueTimers(nowMs: number): Promise<WorkflowRun[]>;
2430
+ listIncompleteRuns(namespace?: string): Promise<WorkflowRun[]>;
2431
+ listPendingRuns(limit: number, namespace?: string): Promise<WorkflowRun[]>;
2432
+ listDueTimers(nowMs: number, namespace?: string): Promise<WorkflowRun[]>;
2416
2433
  tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
2417
2434
  releaseRunLock(runId: string): Promise<void>;
2418
2435
  renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
package/dist/index.js CHANGED
@@ -1440,6 +1440,14 @@ function isNewerVersion(a, b) {
1440
1440
  }) > 0;
1441
1441
  }
1442
1442
  __name(isNewerVersion, "isNewerVersion");
1443
+ var NamespaceMismatch = class NamespaceMismatch2 extends Error {
1444
+ static {
1445
+ __name(this, "NamespaceMismatch");
1446
+ }
1447
+ constructor() {
1448
+ super("namespace-mismatch");
1449
+ }
1450
+ };
1443
1451
  var WorkflowEngine = class {
1444
1452
  static {
1445
1453
  __name(this, "WorkflowEngine");
@@ -1450,6 +1458,7 @@ var WorkflowEngine = class {
1450
1458
  controlPlane;
1451
1459
  clock;
1452
1460
  instanceId;
1461
+ namespace;
1453
1462
  leaseMs;
1454
1463
  maxRecoveryAttempts;
1455
1464
  remoteAdvanceSilenceMs;
@@ -1516,6 +1525,7 @@ var WorkflowEngine = class {
1516
1525
  this.admission = deps.admission ?? new InMemoryAdmissionBackend(this.clock);
1517
1526
  this.admission.onFreed?.((queue) => this.wakeQueueWaiters(queue));
1518
1527
  this.instanceId = deps.instanceId ?? globalThis.crypto.randomUUID();
1528
+ this.namespace = deps.namespace ?? "default";
1519
1529
  this.leaseMs = deps.leaseMs ?? 3e4;
1520
1530
  this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
1521
1531
  this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
@@ -1836,6 +1846,7 @@ var WorkflowEngine = class {
1836
1846
  workflow: name,
1837
1847
  workflowVersion: registered.version,
1838
1848
  status: "pending",
1849
+ namespace: this.namespace,
1839
1850
  input,
1840
1851
  tags,
1841
1852
  searchAttributes: opts?.searchAttributes,
@@ -1872,6 +1883,10 @@ var WorkflowEngine = class {
1872
1883
  error: run.error
1873
1884
  };
1874
1885
  }
1886
+ if (run.namespace !== void 0 && run.namespace !== this.namespace) {
1887
+ await this.store.releaseRunLock(runId);
1888
+ throw new NamespaceMismatch();
1889
+ }
1875
1890
  const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? await this.findInheritedRegistration(run);
1876
1891
  if (!registered) {
1877
1892
  throw new Error(`workflow ${run.workflow}@${run.workflowVersion} is not registered \u2014 keep the prior version deployed so in-flight runs can drain (skew protection)`);
@@ -1990,11 +2005,13 @@ var WorkflowEngine = class {
1990
2005
  const inflight = [
1991
2006
  ...await this.store.listRuns({
1992
2007
  workflow: reg.name,
1993
- status: "running"
2008
+ status: "running",
2009
+ namespace: this.namespace
1994
2010
  }),
1995
2011
  ...await this.store.listRuns({
1996
2012
  workflow: reg.name,
1997
- status: "suspended"
2013
+ status: "suspended",
2014
+ namespace: this.namespace
1998
2015
  })
1999
2016
  ];
2000
2017
  for (const run of inflight) {
@@ -2024,7 +2041,7 @@ var WorkflowEngine = class {
2024
2041
  async recoverIncomplete(nowMs = this.clock()) {
2025
2042
  if (this.draining) return [];
2026
2043
  const results = [];
2027
- for (const run of await this.store.listIncompleteRuns()) {
2044
+ for (const run of await this.store.listIncompleteRuns(this.namespace)) {
2028
2045
  const acquired = await this.store.tryLockRun(run.id, this.instanceId, nowMs + this.leaseMs, nowMs);
2029
2046
  if (!acquired) continue;
2030
2047
  const settled = await this.countRecovery(run);
@@ -2094,7 +2111,7 @@ var WorkflowEngine = class {
2094
2111
  * boot. A run still not due re-suspends cheaply without running new work.
2095
2112
  */
2096
2113
  async resumeDueTimers(nowMs = this.clock()) {
2097
- return this.resumeLeased(await this.store.listDueTimers(nowMs), nowMs);
2114
+ return this.resumeLeased(await this.store.listDueTimers(nowMs, this.namespace), nowMs);
2098
2115
  }
2099
2116
  /**
2100
2117
  * Lease and execute one run by id — the worker side of dispatch. Acquires the recovery lease (so
@@ -2107,7 +2124,10 @@ var WorkflowEngine = class {
2107
2124
  const nowMs = this.clock();
2108
2125
  const acquired = await this.store.tryLockRun(runId, this.instanceId, nowMs + this.leaseMs, nowMs);
2109
2126
  if (!acquired) return null;
2110
- return this.resume(runId);
2127
+ return this.resume(runId).catch((err) => {
2128
+ if (err instanceof NamespaceMismatch) return null;
2129
+ throw err;
2130
+ });
2111
2131
  }
2112
2132
  /**
2113
2133
  * Re-enqueue a run for a worker to (re-)execute — the dispatch-model **retry**. Sets it back to
@@ -2153,7 +2173,7 @@ var WorkflowEngine = class {
2153
2173
  * {@link resumeDueTimers}.
2154
2174
  */
2155
2175
  async runPending(nowMs = this.clock()) {
2156
- return this.resumeLeased(await this.store.listPendingRuns(100), nowMs);
2176
+ return this.resumeLeased(await this.store.listPendingRuns(100, this.namespace), nowMs);
2157
2177
  }
2158
2178
  /**
2159
2179
  * Resolve once `runId` reaches a settled state — terminal (completed/failed/cancelled/dead) or
@@ -2265,7 +2285,10 @@ var WorkflowEngine = class {
2265
2285
  kind: "signal",
2266
2286
  output: payload
2267
2287
  }));
2268
- return this.resume(waiter.runId);
2288
+ return this.resume(waiter.runId).catch((err) => {
2289
+ if (err instanceof NamespaceMismatch) return null;
2290
+ throw err;
2291
+ });
2269
2292
  }
2270
2293
  /**
2271
2294
  * Ensure a run exists for `runId`, then deliver a signal to it — atomically race-free thanks to
@@ -3419,7 +3442,10 @@ var WorkflowEngine = class {
3419
3442
  queueMs: startedAt.getTime() - cp.enqueuedAt.getTime(),
3420
3443
  durationMs: finishedAt.getTime() - startedAt.getTime()
3421
3444
  });
3422
- await this.resume(result.runId);
3445
+ await this.resume(result.runId).catch((err) => {
3446
+ if (err instanceof NamespaceMismatch) return null;
3447
+ throw err;
3448
+ });
3423
3449
  }
3424
3450
  /**
3425
3451
  * A slot freed on `queue` (a fleet-wide signal): resume this instance's runs blocked on it so they
@@ -3717,14 +3743,14 @@ var CodecStateStore = class {
3717
3743
  saveCheckpoint: /* @__PURE__ */ __name((cp) => tx.saveCheckpoint(this.encCp(cp)), "saveCheckpoint")
3718
3744
  }));
3719
3745
  }
3720
- async listIncompleteRuns() {
3721
- return (await this.inner.listIncompleteRuns()).map((r) => this.decRun(r));
3746
+ async listIncompleteRuns(namespace) {
3747
+ return (await this.inner.listIncompleteRuns(namespace)).map((r) => this.decRun(r));
3722
3748
  }
3723
- async listPendingRuns(limit) {
3724
- return (await this.inner.listPendingRuns(limit)).map((r) => this.decRun(r));
3749
+ async listPendingRuns(limit, namespace) {
3750
+ return (await this.inner.listPendingRuns(limit, namespace)).map((r) => this.decRun(r));
3725
3751
  }
3726
- async listDueTimers(nowMs) {
3727
- return (await this.inner.listDueTimers(nowMs)).map((r) => this.decRun(r));
3752
+ async listDueTimers(nowMs, namespace) {
3753
+ return (await this.inner.listDueTimers(nowMs, namespace)).map((r) => this.decRun(r));
3728
3754
  }
3729
3755
  tryLockRun(runId, owner, leaseUntilMs, nowMs) {
3730
3756
  return this.inner.tryLockRun(runId, owner, leaseUntilMs, nowMs);
@@ -4079,7 +4105,8 @@ var InMemoryStateStore = class {
4079
4105
  }
4080
4106
  async createRun(run) {
4081
4107
  this.runs.set(run.id, {
4082
- ...run
4108
+ ...run,
4109
+ namespace: run.namespace ?? "default"
4083
4110
  });
4084
4111
  this.reindexAttributes(run.id, run.searchAttributes);
4085
4112
  }
@@ -4120,24 +4147,24 @@ var InMemoryStateStore = class {
4120
4147
  ...checkpoint
4121
4148
  });
4122
4149
  }
4123
- async listIncompleteRuns() {
4150
+ async listIncompleteRuns(namespace) {
4124
4151
  return [
4125
4152
  ...this.runs.values()
4126
- ].filter((r) => r.status === "running" || r.status === "cancelling").map((r) => ({
4153
+ ].filter((r) => (r.status === "running" || r.status === "cancelling") && (namespace === void 0 || r.namespace === namespace)).map((r) => ({
4127
4154
  ...r
4128
4155
  }));
4129
4156
  }
4130
- async listPendingRuns(limit) {
4157
+ async listPendingRuns(limit, namespace) {
4131
4158
  return [
4132
4159
  ...this.runs.values()
4133
- ].filter((r) => r.status === "pending").sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime() || a.id.localeCompare(b.id)).slice(0, limit).map((r) => ({
4160
+ ].filter((r) => r.status === "pending" && (namespace === void 0 || r.namespace === namespace)).sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime() || a.id.localeCompare(b.id)).slice(0, limit).map((r) => ({
4134
4161
  ...r
4135
4162
  }));
4136
4163
  }
4137
- async listDueTimers(nowMs) {
4164
+ async listDueTimers(nowMs, namespace) {
4138
4165
  return [
4139
4166
  ...this.runs.values()
4140
- ].filter((r) => r.status === "suspended" && r.wakeAt !== void 0 && r.wakeAt <= nowMs).map((r) => ({
4167
+ ].filter((r) => r.status === "suspended" && r.wakeAt !== void 0 && r.wakeAt <= nowMs && (namespace === void 0 || r.namespace === namespace)).map((r) => ({
4141
4168
  ...r
4142
4169
  }));
4143
4170
  }
@@ -4252,6 +4279,7 @@ var InMemoryStateStore = class {
4252
4279
  if (query.status) runs = runs.filter((r) => r.status === query.status);
4253
4280
  if (query.statuses) runs = runs.filter((r) => query.statuses?.includes(r.status));
4254
4281
  if (query.tag) runs = runs.filter((r) => r.tags?.includes(query.tag));
4282
+ if (query.namespace !== void 0) runs = runs.filter((r) => r.namespace === query.namespace);
4255
4283
  if (query.attributes?.length) {
4256
4284
  let candidates = null;
4257
4285
  for (const f of query.attributes) {