@dudousxd/nestjs-durable-core 0.39.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1280,7 +1280,8 @@ function createWorkflowCtx(host, runId, compensations, workflow = "") {
1280
1280
  await store.putSignalWaiter({
1281
1281
  token: `child:${childId}`,
1282
1282
  runId,
1283
- seq
1283
+ seq,
1284
+ parallelGroup: group
1284
1285
  });
1285
1286
  if (!await store.getRun(childId)) host.startChild(name, inputs[i], childId);
1286
1287
  if (!cp) {
@@ -1512,6 +1513,14 @@ function isNewerVersion(a, b) {
1512
1513
  }) > 0;
1513
1514
  }
1514
1515
  __name(isNewerVersion, "isNewerVersion");
1516
+ var NamespaceMismatch = class NamespaceMismatch2 extends Error {
1517
+ static {
1518
+ __name(this, "NamespaceMismatch");
1519
+ }
1520
+ constructor() {
1521
+ super("namespace-mismatch");
1522
+ }
1523
+ };
1515
1524
  var WorkflowEngine = class {
1516
1525
  static {
1517
1526
  __name(this, "WorkflowEngine");
@@ -1522,6 +1531,7 @@ var WorkflowEngine = class {
1522
1531
  controlPlane;
1523
1532
  clock;
1524
1533
  instanceId;
1534
+ namespace;
1525
1535
  leaseMs;
1526
1536
  maxRecoveryAttempts;
1527
1537
  remoteAdvanceSilenceMs;
@@ -1588,6 +1598,7 @@ var WorkflowEngine = class {
1588
1598
  this.admission = deps.admission ?? new InMemoryAdmissionBackend(this.clock);
1589
1599
  this.admission.onFreed?.((queue) => this.wakeQueueWaiters(queue));
1590
1600
  this.instanceId = deps.instanceId ?? globalThis.crypto.randomUUID();
1601
+ this.namespace = deps.namespace ?? "default";
1591
1602
  this.leaseMs = deps.leaseMs ?? 3e4;
1592
1603
  this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
1593
1604
  this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
@@ -1908,6 +1919,7 @@ var WorkflowEngine = class {
1908
1919
  workflow: name,
1909
1920
  workflowVersion: registered.version,
1910
1921
  status: "pending",
1922
+ namespace: this.namespace,
1911
1923
  input,
1912
1924
  tags,
1913
1925
  searchAttributes: opts?.searchAttributes,
@@ -1944,6 +1956,10 @@ var WorkflowEngine = class {
1944
1956
  error: run.error
1945
1957
  };
1946
1958
  }
1959
+ if (run.namespace !== void 0 && run.namespace !== this.namespace) {
1960
+ await this.store.releaseRunLock(runId);
1961
+ throw new NamespaceMismatch();
1962
+ }
1947
1963
  const registered = this.workflows.get(versionKey(run.workflow, run.workflowVersion)) ?? await this.findInheritedRegistration(run);
1948
1964
  if (!registered) {
1949
1965
  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)`);
@@ -2062,11 +2078,13 @@ var WorkflowEngine = class {
2062
2078
  const inflight = [
2063
2079
  ...await this.store.listRuns({
2064
2080
  workflow: reg.name,
2065
- status: "running"
2081
+ status: "running",
2082
+ namespace: this.namespace
2066
2083
  }),
2067
2084
  ...await this.store.listRuns({
2068
2085
  workflow: reg.name,
2069
- status: "suspended"
2086
+ status: "suspended",
2087
+ namespace: this.namespace
2070
2088
  })
2071
2089
  ];
2072
2090
  for (const run of inflight) {
@@ -2096,7 +2114,7 @@ var WorkflowEngine = class {
2096
2114
  async recoverIncomplete(nowMs = this.clock()) {
2097
2115
  if (this.draining) return [];
2098
2116
  const results = [];
2099
- for (const run of await this.store.listIncompleteRuns()) {
2117
+ for (const run of await this.store.listIncompleteRuns(this.namespace)) {
2100
2118
  const acquired = await this.store.tryLockRun(run.id, this.instanceId, nowMs + this.leaseMs, nowMs);
2101
2119
  if (!acquired) continue;
2102
2120
  const settled = await this.countRecovery(run);
@@ -2166,7 +2184,7 @@ var WorkflowEngine = class {
2166
2184
  * boot. A run still not due re-suspends cheaply without running new work.
2167
2185
  */
2168
2186
  async resumeDueTimers(nowMs = this.clock()) {
2169
- return this.resumeLeased(await this.store.listDueTimers(nowMs), nowMs);
2187
+ return this.resumeLeased(await this.store.listDueTimers(nowMs, this.namespace), nowMs);
2170
2188
  }
2171
2189
  /**
2172
2190
  * Lease and execute one run by id — the worker side of dispatch. Acquires the recovery lease (so
@@ -2179,7 +2197,10 @@ var WorkflowEngine = class {
2179
2197
  const nowMs = this.clock();
2180
2198
  const acquired = await this.store.tryLockRun(runId, this.instanceId, nowMs + this.leaseMs, nowMs);
2181
2199
  if (!acquired) return null;
2182
- return this.resume(runId);
2200
+ return this.resume(runId).catch((err) => {
2201
+ if (err instanceof NamespaceMismatch) return null;
2202
+ throw err;
2203
+ });
2183
2204
  }
2184
2205
  /**
2185
2206
  * Re-enqueue a run for a worker to (re-)execute — the dispatch-model **retry**. Sets it back to
@@ -2225,7 +2246,7 @@ var WorkflowEngine = class {
2225
2246
  * {@link resumeDueTimers}.
2226
2247
  */
2227
2248
  async runPending(nowMs = this.clock()) {
2228
- return this.resumeLeased(await this.store.listPendingRuns(100), nowMs);
2249
+ return this.resumeLeased(await this.store.listPendingRuns(100, this.namespace), nowMs);
2229
2250
  }
2230
2251
  /**
2231
2252
  * Resolve once `runId` reaches a settled state — terminal (completed/failed/cancelled/dead) or
@@ -2335,9 +2356,17 @@ var WorkflowEngine = class {
2335
2356
  seq: waiter.seq,
2336
2357
  name: `signal:${token}`,
2337
2358
  kind: "signal",
2338
- output: payload
2359
+ output: payload,
2360
+ // Carry the awaiting command's fan group (set when a `ctx.gather_children`/`ctx.all` fan-out
2361
+ // registered this waiter) onto the resolving `signal:child:<id>` checkpoint, so the dashboard
2362
+ // groups the child fan-out vertically instead of rendering it as a sequential chain. Undefined
2363
+ // for an ordinary (non-fan) signal/child await.
2364
+ parallelGroup: waiter.parallelGroup
2339
2365
  }));
2340
- return this.resume(waiter.runId);
2366
+ return this.resume(waiter.runId).catch((err) => {
2367
+ if (err instanceof NamespaceMismatch) return null;
2368
+ throw err;
2369
+ });
2341
2370
  }
2342
2371
  /**
2343
2372
  * Ensure a run exists for `runId`, then deliver a signal to it — atomically race-free thanks to
@@ -3102,7 +3131,12 @@ var WorkflowEngine = class {
3102
3131
  await this.store.putSignalWaiter({
3103
3132
  token: `child:${childId}`,
3104
3133
  runId: run.id,
3105
- seq: cmd.seq
3134
+ seq: cmd.seq,
3135
+ // A `ctx.gather_children`/`ctx.all` fan-out stamps every `startChild` in the fan with the same
3136
+ // group. Thread it onto the waiter so the child's terminal `signal:child:<id>` checkpoint
3137
+ // (written by engine.signal when the child notifies the parent) carries the group and the
3138
+ // dashboard renders the fan as one parallel group. Undefined for a lone (non-fan) child.
3139
+ parallelGroup: cmd.parallelGroup
3106
3140
  });
3107
3141
  if (!await this.store.getRun(childId)) {
3108
3142
  queueMicrotask(() => void this.start(cmd.workflow, cmd.input, childId).catch(() => void 0));
@@ -3491,7 +3525,10 @@ var WorkflowEngine = class {
3491
3525
  queueMs: startedAt.getTime() - cp.enqueuedAt.getTime(),
3492
3526
  durationMs: finishedAt.getTime() - startedAt.getTime()
3493
3527
  });
3494
- await this.resume(result.runId);
3528
+ await this.resume(result.runId).catch((err) => {
3529
+ if (err instanceof NamespaceMismatch) return null;
3530
+ throw err;
3531
+ });
3495
3532
  }
3496
3533
  /**
3497
3534
  * A slot freed on `queue` (a fleet-wide signal): resume this instance's runs blocked on it so they
@@ -3789,14 +3826,14 @@ var CodecStateStore = class {
3789
3826
  saveCheckpoint: /* @__PURE__ */ __name((cp) => tx.saveCheckpoint(this.encCp(cp)), "saveCheckpoint")
3790
3827
  }));
3791
3828
  }
3792
- async listIncompleteRuns() {
3793
- return (await this.inner.listIncompleteRuns()).map((r) => this.decRun(r));
3829
+ async listIncompleteRuns(namespace) {
3830
+ return (await this.inner.listIncompleteRuns(namespace)).map((r) => this.decRun(r));
3794
3831
  }
3795
- async listPendingRuns(limit) {
3796
- return (await this.inner.listPendingRuns(limit)).map((r) => this.decRun(r));
3832
+ async listPendingRuns(limit, namespace) {
3833
+ return (await this.inner.listPendingRuns(limit, namespace)).map((r) => this.decRun(r));
3797
3834
  }
3798
- async listDueTimers(nowMs) {
3799
- return (await this.inner.listDueTimers(nowMs)).map((r) => this.decRun(r));
3835
+ async listDueTimers(nowMs, namespace) {
3836
+ return (await this.inner.listDueTimers(nowMs, namespace)).map((r) => this.decRun(r));
3800
3837
  }
3801
3838
  tryLockRun(runId, owner, leaseUntilMs, nowMs) {
3802
3839
  return this.inner.tryLockRun(runId, owner, leaseUntilMs, nowMs);
@@ -4151,7 +4188,8 @@ var InMemoryStateStore = class {
4151
4188
  }
4152
4189
  async createRun(run) {
4153
4190
  this.runs.set(run.id, {
4154
- ...run
4191
+ ...run,
4192
+ namespace: run.namespace ?? "default"
4155
4193
  });
4156
4194
  this.reindexAttributes(run.id, run.searchAttributes);
4157
4195
  }
@@ -4192,24 +4230,24 @@ var InMemoryStateStore = class {
4192
4230
  ...checkpoint
4193
4231
  });
4194
4232
  }
4195
- async listIncompleteRuns() {
4233
+ async listIncompleteRuns(namespace) {
4196
4234
  return [
4197
4235
  ...this.runs.values()
4198
- ].filter((r) => r.status === "running" || r.status === "cancelling").map((r) => ({
4236
+ ].filter((r) => (r.status === "running" || r.status === "cancelling") && (namespace === void 0 || r.namespace === namespace)).map((r) => ({
4199
4237
  ...r
4200
4238
  }));
4201
4239
  }
4202
- async listPendingRuns(limit) {
4240
+ async listPendingRuns(limit, namespace) {
4203
4241
  return [
4204
4242
  ...this.runs.values()
4205
- ].filter((r) => r.status === "pending").sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime() || a.id.localeCompare(b.id)).slice(0, limit).map((r) => ({
4243
+ ].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) => ({
4206
4244
  ...r
4207
4245
  }));
4208
4246
  }
4209
- async listDueTimers(nowMs) {
4247
+ async listDueTimers(nowMs, namespace) {
4210
4248
  return [
4211
4249
  ...this.runs.values()
4212
- ].filter((r) => r.status === "suspended" && r.wakeAt !== void 0 && r.wakeAt <= nowMs).map((r) => ({
4250
+ ].filter((r) => r.status === "suspended" && r.wakeAt !== void 0 && r.wakeAt <= nowMs && (namespace === void 0 || r.namespace === namespace)).map((r) => ({
4213
4251
  ...r
4214
4252
  }));
4215
4253
  }
@@ -4324,6 +4362,7 @@ var InMemoryStateStore = class {
4324
4362
  if (query.status) runs = runs.filter((r) => r.status === query.status);
4325
4363
  if (query.statuses) runs = runs.filter((r) => query.statuses?.includes(r.status));
4326
4364
  if (query.tag) runs = runs.filter((r) => r.tags?.includes(query.tag));
4365
+ if (query.namespace !== void 0) runs = runs.filter((r) => r.namespace === query.namespace);
4327
4366
  if (query.attributes?.length) {
4328
4367
  let candidates = null;
4329
4368
  for (const f of query.attributes) {