@dudousxd/nestjs-durable-core 0.33.0 → 0.34.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.cjs CHANGED
@@ -1502,6 +1502,7 @@ var WorkflowEngine = class {
1502
1502
  instanceId;
1503
1503
  leaseMs;
1504
1504
  maxRecoveryAttempts;
1505
+ remoteAdvanceSilenceMs;
1505
1506
  webhookUrl;
1506
1507
  traceparent;
1507
1508
  context;
@@ -1526,7 +1527,9 @@ var WorkflowEngine = class {
1526
1527
  accumulators;
1527
1528
  /** In-flight remote steps awaiting a worker result, keyed by stepId. */
1528
1529
  pending = /* @__PURE__ */ new Map();
1529
- /** Per-step "reset the liveness timer" callbacks, called when a heartbeat arrives. */
1530
+ /** "Reset the liveness timer" callbacks keyed by the beat's target — a stepId (in-flight remote step)
1531
+ * or a runId (in-flight remote workflow turn) — invoked when a matching heartbeat arrives. The two id
1532
+ * shapes don't collide in practice: a stepId is `${runId}:${seq}`, a run key is the bare runId. */
1530
1533
  heartbeatResets = /* @__PURE__ */ new Map();
1531
1534
  listeners = /* @__PURE__ */ new Set();
1532
1535
  /** Step interceptors (onion middleware around real local-step execution), first = outermost. */
@@ -1565,6 +1568,7 @@ var WorkflowEngine = class {
1565
1568
  this.instanceId = deps.instanceId ?? globalThis.crypto.randomUUID();
1566
1569
  this.leaseMs = deps.leaseMs ?? 3e4;
1567
1570
  this.maxRecoveryAttempts = deps.maxRecoveryAttempts;
1571
+ this.remoteAdvanceSilenceMs = deps.remoteAdvanceSilenceMs;
1568
1572
  this.webhookUrl = deps.webhookUrl;
1569
1573
  this.traceparent = deps.traceparent;
1570
1574
  this.context = deps.context;
@@ -1601,9 +1605,11 @@ var WorkflowEngine = class {
1601
1605
  }
1602
1606
  await this.completeRemoteResult(result);
1603
1607
  },
1604
- // A heartbeat for an in-flight long step resets its liveness window (see callRemote).
1608
+ // A heartbeat resets the liveness window for whatever it targets: an in-flight long STEP (keyed
1609
+ // by stepId, see callRemote) or — when stepId is absent — the in-flight workflow TURN (keyed by
1610
+ // runId, see runRemoteExecution's heartbeat-rearmed advance).
1605
1611
  async (beat) => {
1606
- this.heartbeatResets.get(beat.stepId)?.();
1612
+ this.heartbeatResets.get(beat.stepId ?? beat.runId)?.();
1607
1613
  },
1608
1614
  // A remote workflow worker streams each local step's lifecycle (running → completed/failed) so
1609
1615
  // it's checkpointed live, not all-at-once when the long turn ends.
@@ -2695,7 +2701,9 @@ var WorkflowEngine = class {
2695
2701
  const history = await this.remoteHistory(run.id);
2696
2702
  let decision;
2697
2703
  try {
2698
- decision = await remote.executor.advance(run, history);
2704
+ const silenceMs = this.remoteAdvanceSilenceMs;
2705
+ const advance = remote.executor.advance(run, history);
2706
+ decision = silenceMs != null ? await this.awaitWithLivenessDeadline(run.id, advance, silenceMs, () => new RemoteWorkflowTimeout(run.id, silenceMs)) : await advance;
2699
2707
  } catch (err) {
2700
2708
  if (err instanceof RemoteWorkflowTimeout) {
2701
2709
  await this.store.releaseRunLock(run.id);
@@ -3356,11 +3364,13 @@ var WorkflowEngine = class {
3356
3364
  }
3357
3365
  }
3358
3366
  /**
3359
- * Await a remote result, but reject with `RemoteStepTimeout` if neither the result nor a heartbeat
3360
- * arrives within `timeoutMs`. Each heartbeat (delivered via `transport.onHeartbeat`) rearms the
3361
- * window, so a worker that keeps beating stays alive past `timeoutMs`.
3367
+ * Await `resultPromise`, but reject with `onTimeout()` if neither it settles nor a heartbeat for `id`
3368
+ * arrives within `timeoutMs`. Each heartbeat (delivered via `transport.onHeartbeat`, looked up in
3369
+ * `heartbeatResets` by `id`) REARMS the window, so a worker that keeps beating stays alive past
3370
+ * `timeoutMs`. Shared by the remote-STEP liveness (keyed by stepId) and the remote-WORKFLOW advance
3371
+ * liveness (keyed by runId); the caller supplies the timeout error and any extra cleanup via `onTimeout`.
3362
3372
  */
3363
- awaitWithHeartbeat(id, resultPromise, timeoutMs) {
3373
+ awaitWithLivenessDeadline(id, resultPromise, timeoutMs, onTimeout) {
3364
3374
  return new Promise((resolve, reject) => {
3365
3375
  let timer;
3366
3376
  const cleanup = /* @__PURE__ */ __name(() => {
@@ -3370,8 +3380,7 @@ var WorkflowEngine = class {
3370
3380
  const arm = /* @__PURE__ */ __name(() => {
3371
3381
  timer = setTimeout(() => {
3372
3382
  cleanup();
3373
- this.pending.delete(id);
3374
- reject(new RemoteStepTimeout(id, timeoutMs));
3383
+ reject(onTimeout());
3375
3384
  }, timeoutMs);
3376
3385
  timer.unref?.();
3377
3386
  }, "arm");
@@ -3389,6 +3398,14 @@ var WorkflowEngine = class {
3389
3398
  });
3390
3399
  });
3391
3400
  }
3401
+ /** Remote-STEP liveness: await a worker result, timing out (with the step's pending-map cleanup) to
3402
+ * `RemoteStepTimeout` if it neither lands nor beats within `timeoutMs`. See {@link awaitWithLivenessDeadline}. */
3403
+ awaitWithHeartbeat(id, resultPromise, timeoutMs) {
3404
+ return this.awaitWithLivenessDeadline(id, resultPromise, timeoutMs, () => {
3405
+ this.pending.delete(id);
3406
+ return new RemoteStepTimeout(id, timeoutMs);
3407
+ });
3408
+ }
3392
3409
  };
3393
3410
  var RemoteStepError = class extends Error {
3394
3411
  static {
@@ -4154,6 +4171,7 @@ var InMemoryTransport = class {
4154
4171
  }
4155
4172
  handlers = /* @__PURE__ */ new Map();
4156
4173
  resultHandler;
4174
+ heartbeatHandler;
4157
4175
  stepEventHandler;
4158
4176
  controlHandlers = /* @__PURE__ */ new Set();
4159
4177
  /** Register a fake worker handler for a step name. */
@@ -4168,7 +4186,12 @@ var InMemoryTransport = class {
4168
4186
  onResult(handler) {
4169
4187
  this.resultHandler = handler;
4170
4188
  }
4171
- onHeartbeat(_handler) {
4189
+ onHeartbeat(handler) {
4190
+ this.heartbeatHandler = handler;
4191
+ }
4192
+ /** Test hook: deliver a liveness heartbeat to the engine, as a real broker would on its channel. */
4193
+ async emitHeartbeat(beat) {
4194
+ await this.heartbeatHandler?.(beat);
4172
4195
  }
4173
4196
  /** Workflow step lifecycle: deliver to the engine's handler (async, mirroring a real broker). */
4174
4197
  async dispatchStepEvent(event) {