@dudousxd/nestjs-durable-core 0.75.2 → 0.76.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
@@ -3867,6 +3867,12 @@ var WorkflowEngine = class {
3867
3867
  * {@link redriveSettledSteps}).
3868
3868
  */
3869
3869
  awaitedRemoteSteps = /* @__PURE__ */ new Map();
3870
+ /**
3871
+ * When THIS instance settled a remote step from its result, by `runId:seq`. That result's resume
3872
+ * runs here, and `execute` lets it into a lease this instance holds, so its wake is never the
3873
+ * dropped one {@link redriveSettledSteps} recovers. Pruned by age (see {@link noteSettledHere}).
3874
+ */
3875
+ settledHere = /* @__PURE__ */ new Map();
3870
3876
  /** Flow-control admission backend for remote steps (see {@link registerQueue}). */
3871
3877
  admission;
3872
3878
  /** Runs on THIS instance blocked on admission, by queue — woken early on a freed-slot signal. */
@@ -4341,7 +4347,18 @@ var WorkflowEngine = class {
4341
4347
  createdAt: now,
4342
4348
  updatedAt: now
4343
4349
  };
4344
- await this.store.createRun(run);
4350
+ try {
4351
+ await this.store.createRun(run);
4352
+ } catch (err) {
4353
+ const winner = await this.store.getRun(runId).catch(() => null);
4354
+ if (!winner) throw err;
4355
+ return {
4356
+ runId,
4357
+ status: winner.status,
4358
+ output: winner.output,
4359
+ error: winner.error
4360
+ };
4361
+ }
4345
4362
  await this.runDispatcher.dispatch(runId);
4346
4363
  if (this.controlPlane) {
4347
4364
  void this.controlPlane.publishControl({
@@ -5283,6 +5300,16 @@ var WorkflowEngine = class {
5283
5300
  for (const seq of seqs) {
5284
5301
  const cp = await this.store.getCheckpoint(runId, seq);
5285
5302
  if (cp && (cp.status === "completed" || cp.status === "failed")) {
5303
+ if (this.settledHere.has(`${runId}:${seq}`)) return;
5304
+ const nowMs = this.clock();
5305
+ if (!await this.store.tryLockRun(runId, this.instanceId, nowMs + this.leaseMs, nowMs)) {
5306
+ return;
5307
+ }
5308
+ const run = await this.store.getRun(runId);
5309
+ if (run?.status !== "suspended") {
5310
+ await this.store.releaseRunLock(runId);
5311
+ return;
5312
+ }
5286
5313
  await this.resumeImplicit(runId);
5287
5314
  return;
5288
5315
  }
@@ -7254,10 +7281,12 @@ var WorkflowEngine = class {
7254
7281
  if (this.remoteRedispatchMs == null) throw new WorkflowSuspended();
7255
7282
  if (existing.wakeAt == null) {
7256
7283
  const wakeAt = this.clock() + this.remoteRedispatchMs;
7257
- await this.store.saveCheckpoint({
7284
+ if (!await this.savePendingCheckpoint({
7258
7285
  ...existing,
7259
7286
  wakeAt
7260
- });
7287
+ })) {
7288
+ return this.callRemote(runId, seq, step, input, queue, transport, void 0, admission, routing);
7289
+ }
7261
7290
  throw new WorkflowSuspended(wakeAt);
7262
7291
  }
7263
7292
  if (this.clock() < existing.wakeAt) throw new WorkflowSuspended(existing.wakeAt);
@@ -7270,7 +7299,7 @@ var WorkflowEngine = class {
7270
7299
  const reAttempt = existing.attempts + 1;
7271
7300
  const nextDeadline = this.clock() + this.remoteRedispatchMs;
7272
7301
  const reEnqueuedAt = /* @__PURE__ */ new Date();
7273
- await this.store.saveCheckpoint({
7302
+ const claimed = await this.savePendingCheckpoint({
7274
7303
  ...existing,
7275
7304
  attempts: reAttempt,
7276
7305
  wakeAt: nextDeadline,
@@ -7284,6 +7313,9 @@ var WorkflowEngine = class {
7284
7313
  lostStepEvent(this.clock(), reAttempt, "redispatched")
7285
7314
  ]
7286
7315
  });
7316
+ if (!claimed) {
7317
+ return this.callRemote(runId, seq, step, input, queue, transport, void 0, admission, routing);
7318
+ }
7287
7319
  await this.dispatchRemoteTask({
7288
7320
  runId,
7289
7321
  seq,
@@ -7374,6 +7406,21 @@ var WorkflowEngine = class {
7374
7406
  this.awaitedRemoteSteps.get(runId)?.add(seq);
7375
7407
  throw new WorkflowSuspended();
7376
7408
  }
7409
+ /** Remember a step settled here, forgetting entries older than a lease so the map stays small. */
7410
+ noteSettledHere(key) {
7411
+ const now = this.clock();
7412
+ this.settledHere.set(key, now);
7413
+ if (this.settledHere.size < 1024) return;
7414
+ for (const [k, at] of this.settledHere) {
7415
+ if (now - at > this.leaseMs) this.settledHere.delete(k);
7416
+ }
7417
+ }
7418
+ /** {@link StateStore.saveCheckpointIfPending}, or a plain save on a store without it. */
7419
+ async savePendingCheckpoint(checkpoint) {
7420
+ if (this.store.saveCheckpointIfPending) return this.store.saveCheckpointIfPending(checkpoint);
7421
+ await this.store.saveCheckpoint(checkpoint);
7422
+ return true;
7423
+ }
7377
7424
  /** Enqueue a remote step task to its worker group via the transport pool. Shared by the initial
7378
7425
  * dispatch, the opt-in self-heal re-dispatch (see `remoteRedispatchMs`), and the explicit
7379
7426
  * {@link redispatchPending}. Carries the current trace/context so a re-dispatch is traceable. */
@@ -7404,7 +7451,7 @@ var WorkflowEngine = class {
7404
7451
  if (run && (run.status === "cancelled" || run.status === "completed")) return;
7405
7452
  const finishedAt = /* @__PURE__ */ new Date();
7406
7453
  const startedAt = result.startedAt ? new Date(result.startedAt) : cp.startedAt;
7407
- await this.store.saveCheckpoint({
7454
+ const claimed = await this.savePendingCheckpoint({
7408
7455
  ...cp,
7409
7456
  status: result.status,
7410
7457
  output: result.status === "completed" ? result.output : cp.output,
@@ -7413,6 +7460,8 @@ var WorkflowEngine = class {
7413
7460
  startedAt,
7414
7461
  finishedAt
7415
7462
  });
7463
+ if (!claimed) return;
7464
+ this.noteSettledHere(`${result.runId}:${result.seq}`);
7416
7465
  this.emit({
7417
7466
  type: result.status === "completed" ? "step.completed" : "step.failed",
7418
7467
  runId: result.runId,
@@ -7805,6 +7854,9 @@ var CodecStateStore = class {
7805
7854
  /** Forwarded verbatim (deletes touch no payload). Bound only when the inner store has it, so the
7806
7855
  * engine's per-run fallback still kicks in through the wrapper. */
7807
7856
  deleteRuns;
7857
+ /** Encodes like {@link saveCheckpoint}. Bound only when the inner store has it, so the engine's
7858
+ * plain-save fallback still kicks in through the wrapper. */
7859
+ saveCheckpointIfPending;
7808
7860
  /** Forwarded verbatim — retention selects by status/updatedAt/scope, never by payload. Bound only
7809
7861
  * when the inner store prunes, so the retention poller still sees (and warns about) its absence. */
7810
7862
  pruneTerminalRuns;
@@ -7832,6 +7884,8 @@ var CodecStateStore = class {
7832
7884
  if (inner.tryAdmitSingleton) this.tryAdmitSingleton = inner.tryAdmitSingleton.bind(inner);
7833
7885
  const deleteRuns = inner.deleteRuns;
7834
7886
  if (deleteRuns) this.deleteRuns = (ids) => deleteRuns.call(inner, ids);
7887
+ const saveIfPending = inner.saveCheckpointIfPending;
7888
+ if (saveIfPending) this.saveCheckpointIfPending = (cp) => saveIfPending.call(inner, this.encCp(cp));
7835
7889
  const prune = inner.pruneTerminalRuns;
7836
7890
  if (prune) this.pruneTerminalRuns = (policy, now, limit) => prune.call(inner, policy, now, limit);
7837
7891
  const count = inner.countRuns;
@@ -8395,6 +8449,14 @@ var InMemoryStateStore = class {
8395
8449
  ...checkpoint
8396
8450
  });
8397
8451
  }
8452
+ async saveCheckpointIfPending(checkpoint) {
8453
+ const key = this.key(checkpoint.runId, checkpoint.seq);
8454
+ if (this.checkpoints.get(key)?.status !== "pending") return false;
8455
+ this.checkpoints.set(key, {
8456
+ ...checkpoint
8457
+ });
8458
+ return true;
8459
+ }
8398
8460
  async listIncompleteRuns(namespace) {
8399
8461
  return [
8400
8462
  ...this.runs.values()