@dudousxd/nestjs-durable-core 0.72.0 → 0.72.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
@@ -46,6 +46,7 @@ __export(index_exports, {
46
46
  InMemoryTransport: () => InMemoryTransport,
47
47
  LEGACY_V1_CAPABILITIES: () => LEGACY_V1_CAPABILITIES,
48
48
  LEGACY_V1_PROTOCOL: () => LEGACY_V1_PROTOCOL,
49
+ NestedWorkflowCallError: () => NestedWorkflowCallError,
49
50
  NonDeterminismError: () => NonDeterminismError,
50
51
  QueueController: () => QueueController,
51
52
  RUN_VALUE_FACET_LIMIT: () => RUN_VALUE_FACET_LIMIT,
@@ -92,6 +93,7 @@ __export(index_exports, {
92
93
  coveredTokens: () => coveredTokens,
93
94
  createStepLogger: () => createStepLogger,
94
95
  currentStep: () => currentStep,
96
+ currentStepScope: () => currentStepScope,
95
97
  currentWorkflowCtx: () => currentWorkflowCtx,
96
98
  debug: () => debug,
97
99
  describeWorker: () => describeWorker,
@@ -133,7 +135,9 @@ __export(index_exports, {
133
135
  resolveRunWaiting: () => resolveRunWaiting,
134
136
  runDurableExecution: () => runDurableExecution,
135
137
  runInStepLogger: () => runInStepLogger,
138
+ runInStepScope: () => runInStepScope,
136
139
  runInWorkflowCtx: () => runInWorkflowCtx,
140
+ runOutsideWorkflowCtx: () => runOutsideWorkflowCtx,
137
141
  runSchedules: () => runSchedules,
138
142
  runStepHandler: () => runStepHandler,
139
143
  runValueFacetsFromRuns: () => runValueFacetsFromRuns,
@@ -324,6 +328,27 @@ function currentWorkflowCtx() {
324
328
  return storage.getStore();
325
329
  }
326
330
  __name(currentWorkflowCtx, "currentWorkflowCtx");
331
+ var STEP_SCOPE_KEY = Symbol.for("nestjs-durable:ambient-step-scope");
332
+ var stepScopeRef = globalThis;
333
+ if (!stepScopeRef[STEP_SCOPE_KEY]) {
334
+ stepScopeRef[STEP_SCOPE_KEY] = new import_node_async_hooks.AsyncLocalStorage();
335
+ }
336
+ var stepScopeStorage = stepScopeRef[STEP_SCOPE_KEY];
337
+ function runInStepScope(owner, step, fn) {
338
+ return stepScopeStorage.run({
339
+ owner,
340
+ step
341
+ }, fn);
342
+ }
343
+ __name(runInStepScope, "runInStepScope");
344
+ function currentStepScope() {
345
+ return stepScopeStorage.getStore();
346
+ }
347
+ __name(currentStepScope, "currentStepScope");
348
+ function runOutsideWorkflowCtx(fn) {
349
+ return storage.exit(() => stepScopeStorage.exit(fn));
350
+ }
351
+ __name(runOutsideWorkflowCtx, "runOutsideWorkflowCtx");
327
352
 
328
353
  // src/ambient-step.ts
329
354
  var import_node_async_hooks2 = require("async_hooks");
@@ -486,6 +511,21 @@ var NonDeterminismError = class extends Error {
486
511
  this.seq = seq;
487
512
  }
488
513
  };
514
+ var NestedWorkflowCallError = class extends FatalError {
515
+ static {
516
+ __name(this, "NestedWorkflowCallError");
517
+ }
518
+ runId;
519
+ primitive;
520
+ step;
521
+ constructor(runId, primitive, step) {
522
+ super(`ctx.${primitive}() was called inside the body of step "${step}" (run ${runId}). Workflow primitives (child/startChild/step/sleep/signals/sideEffect/\u2026, and MyWorkflow.start()/.execute() via the ambient ctx) must be called from the workflow body, never from inside a step body: the step's checkpoint replays without re-running it, so the nested call would shift every later journal position and corrupt the run on replay. Move the call out of the step.`, "nested_workflow_call");
523
+ this.name = "NestedWorkflowCallError";
524
+ this.runId = runId;
525
+ this.primitive = primitive;
526
+ this.step = step;
527
+ }
528
+ };
489
529
  var GatherError = class extends Error {
490
530
  static {
491
531
  __name(this, "GatherError");
@@ -842,14 +882,19 @@ async function runStepHandler(task, handler) {
842
882
  } : result, "withEvents");
843
883
  try {
844
884
  const logger = createStepLogger(events, Date.now);
845
- const output = await runDurableExecution({
846
- unit: "step",
847
- runId: task.runId,
848
- workflow: void 0,
849
- name: task.name,
850
- seq: task.seq,
851
- attempt: task.attempt
852
- }, async () => runInStepLogger(logger, () => handler(task.input, logger)));
885
+ const output = await runDurableExecution(
886
+ {
887
+ unit: "step",
888
+ runId: task.runId,
889
+ workflow: void 0,
890
+ name: task.name,
891
+ seq: task.seq,
892
+ attempt: task.attempt
893
+ },
894
+ // Outside any ambient workflow ctx: an in-process transport runs the handler on the parent
895
+ // body's async path, but a handler is not the body (see `runOutsideWorkflowCtx`).
896
+ async () => runOutsideWorkflowCtx(() => runInStepLogger(logger, () => handler(task.input, logger)))
897
+ );
853
898
  return withEvents({
854
899
  ...base,
855
900
  status: "completed",
@@ -2753,10 +2798,15 @@ var Position = class Position2 {
2753
2798
  rewind() {
2754
2799
  this.seq -= 1;
2755
2800
  }
2801
+ /** The position the NEXT primitive would claim, without claiming it. */
2802
+ peek() {
2803
+ return this.seq + 1;
2804
+ }
2756
2805
  };
2757
2806
  function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttributesSchema) {
2758
2807
  const { store, replay } = host;
2759
2808
  const pos = new Position();
2809
+ const scopeOwner = {};
2760
2810
  const readCheckpoint = /* @__PURE__ */ __name((seq) => {
2761
2811
  const hit = replay?.get(seq);
2762
2812
  if (hit !== void 0) return hit;
@@ -2802,7 +2852,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
2802
2852
  };
2803
2853
  const body = /* @__PURE__ */ __name(() => {
2804
2854
  const logger = createStepLogger(events, host.clock);
2805
- return runInStepLogger(logger, () => fn(logger));
2855
+ return runInStepScope(scopeOwner, name, () => runInStepLogger(logger, () => fn(logger)));
2806
2856
  }, "body");
2807
2857
  const output = host.interceptStep ? await host.interceptStep(invocation, body) : await body();
2808
2858
  await host.completeStep({
@@ -2855,7 +2905,7 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
2855
2905
  }
2856
2906
  if (existing && existing.status === "completed") return existing.output;
2857
2907
  return store.transaction(async (tx) => {
2858
- const output = await fn(tx.raw);
2908
+ const output = await runInStepScope(scopeOwner, name, () => fn(tx.raw));
2859
2909
  const cp = instantCheckpoint({
2860
2910
  runId,
2861
2911
  seq: current,
@@ -3364,30 +3414,49 @@ function createWorkflowCtx(host, runId, compensations, workflow = "", searchAttr
3364
3414
  });
3365
3415
  }
3366
3416
  __name(step, "step");
3417
+ const nestedStep = /* @__PURE__ */ __name(() => {
3418
+ const scope = currentStepScope();
3419
+ return scope !== void 0 && scope.owner === scopeOwner ? scope.step : void 0;
3420
+ }, "nestedStep");
3421
+ const guard = /* @__PURE__ */ __name((primitive, fn) => (...args) => {
3422
+ const stepName = nestedStep();
3423
+ if (stepName === void 0) return fn(...args);
3424
+ return (async () => {
3425
+ if (await readCheckpoint(pos.peek())) return fn(...args);
3426
+ throw new NestedWorkflowCallError(runId, primitive, stepName);
3427
+ })();
3428
+ }, "guard");
3429
+ const guardedWebhook = /* @__PURE__ */ __name(() => {
3430
+ const stepName = nestedStep();
3431
+ if (stepName !== void 0 && !replay?.get(pos.peek())) {
3432
+ throw new NestedWorkflowCallError(runId, "webhook", stepName);
3433
+ }
3434
+ return webhook();
3435
+ }, "guardedWebhook");
3367
3436
  return {
3368
3437
  runId,
3369
- step,
3370
- localStep,
3371
- upsertSearchAttributes,
3372
- transaction,
3373
- callEntity,
3374
- signalEntity,
3375
- sleep,
3376
- sleepUntil,
3438
+ step: guard("step", step),
3439
+ localStep: guard("localStep", localStep),
3440
+ upsertSearchAttributes: guard("upsertSearchAttributes", upsertSearchAttributes),
3441
+ transaction: guard("transaction", transaction),
3442
+ callEntity: guard("callEntity", callEntity),
3443
+ signalEntity: guard("signalEntity", signalEntity),
3444
+ sleep: guard("sleep", sleep),
3445
+ sleepUntil: guard("sleepUntil", sleepUntil),
3377
3446
  continueAsNew,
3378
- waitForSignal,
3379
- waitForEvent,
3380
- task,
3381
- child,
3382
- all,
3383
- startChild,
3384
- breakpoint,
3385
- webhook,
3386
- setEvent,
3387
- onUpdate,
3388
- patched,
3389
- now,
3390
- sideEffect
3447
+ waitForSignal: guard("waitForSignal", waitForSignal),
3448
+ waitForEvent: guard("waitForEvent", waitForEvent),
3449
+ task: guard("task", task),
3450
+ child: guard("child", child),
3451
+ all: guard("all", all),
3452
+ startChild: guard("startChild", startChild),
3453
+ breakpoint: guard("breakpoint", breakpoint),
3454
+ webhook: guardedWebhook,
3455
+ setEvent: guard("setEvent", setEvent),
3456
+ onUpdate: guard("onUpdate", onUpdate),
3457
+ patched: guard("patched", patched),
3458
+ now: guard("now", now),
3459
+ sideEffect: guard("sideEffect", sideEffect)
3391
3460
  };
3392
3461
  }
3393
3462
  __name(createWorkflowCtx, "createWorkflowCtx");
@@ -3989,10 +4058,30 @@ var WorkflowEngine = class {
3989
4058
  getRun(runId) {
3990
4059
  return this.store.getRun(runId);
3991
4060
  }
3992
- async resume(runId) {
4061
+ /**
4062
+ * Re-execute a run's body (replaying its checkpoints). This is the EXPLICIT entry point: besides
4063
+ * driving a live run, it re-drives a `failed` run — the manual retry (prefer {@link requeue}, which
4064
+ * also resets the failure state so replay re-attempts the failed parts). Completed / cancelled /
4065
+ * dead runs are never re-executed. The engine's own implicit wake-ups (signals, step results,
4066
+ * timers, redelivered dispatches) go through {@link resumeImplicit}, which treats `failed` as
4067
+ * terminal too.
4068
+ */
4069
+ resume(runId) {
4070
+ return this.resumeRun(runId, true);
4071
+ }
4072
+ /**
4073
+ * {@link resume} for every IMPLICIT wake-up — a delivered signal/child completion, a late step
4074
+ * result, a due timer, a redelivered dispatch, a queue-slot wake. A `failed` run is terminal to
4075
+ * these: only an explicit retry ({@link requeue} / {@link resume}) may re-drive it. Otherwise a
4076
+ * stale waiter or a late result left behind by a failed run silently re-executes its body.
4077
+ */
4078
+ resumeImplicit(runId) {
4079
+ return this.resumeRun(runId, false);
4080
+ }
4081
+ async resumeRun(runId, allowFailed) {
3993
4082
  const run = await this.store.getRun(runId);
3994
4083
  if (!run) throw new Error(`run ${runId} not found`);
3995
- if (run.status === "cancelled" || run.status === "completed" || run.status === "dead") {
4084
+ if (run.status === "cancelled" || run.status === "completed" || run.status === "dead" || run.status === "failed" && !allowFailed) {
3996
4085
  return {
3997
4086
  runId,
3998
4087
  status: run.status,
@@ -4441,7 +4530,7 @@ var WorkflowEngine = class {
4441
4530
  const nowMs = this.clock();
4442
4531
  const acquired = await this.store.tryLockRun(runId, this.instanceId, nowMs + this.leaseMs, nowMs);
4443
4532
  if (!acquired) return null;
4444
- return this.resume(runId).catch((err) => {
4533
+ return this.resumeImplicit(runId).catch((err) => {
4445
4534
  if (err instanceof NamespaceMismatch) return null;
4446
4535
  throw err;
4447
4536
  });
@@ -4662,7 +4751,7 @@ var WorkflowEngine = class {
4662
4751
  if (!acquired) continue;
4663
4752
  try {
4664
4753
  const settled = onLocked ? await onLocked(run) : void 0;
4665
- results.push(settled ?? await this.resume(run.id));
4754
+ results.push(settled ?? await this.resumeImplicit(run.id));
4666
4755
  } catch (error2) {
4667
4756
  console.error(`[nestjs-durable] could not resume run ${run.id} (${run.workflow}@${run.workflowVersion}); leaving it for another instance:`, error2);
4668
4757
  }
@@ -4759,7 +4848,7 @@ var WorkflowEngine = class {
4759
4848
  // for an ordinary (non-fan) signal/child await.
4760
4849
  parallelGroup: waiter.parallelGroup
4761
4850
  }));
4762
- return this.resume(waiter.runId).catch((err) => {
4851
+ return this.resumeImplicit(waiter.runId).catch((err) => {
4763
4852
  if (err instanceof NamespaceMismatch) return null;
4764
4853
  throw err;
4765
4854
  });
@@ -4875,7 +4964,7 @@ var WorkflowEngine = class {
4875
4964
  async cancel(runId, opts) {
4876
4965
  const run = await this.store.getRun(runId);
4877
4966
  if (!run) return null;
4878
- if (run.status === "completed" || run.status === "cancelled" || run.status === "dead") {
4967
+ if (TERMINAL_RUN_STATUSES.includes(run.status)) {
4879
4968
  return {
4880
4969
  runId,
4881
4970
  status: run.status,
@@ -4904,7 +4993,7 @@ var WorkflowEngine = class {
4904
4993
  }).catch(() => void 0);
4905
4994
  }
4906
4995
  queueMicrotask(() => {
4907
- void this.resume(runId).then(() => this.notifyCancelled(runId)).catch(() => void 0);
4996
+ void this.resumeImplicit(runId).then(() => this.notifyCancelled(runId)).catch(() => void 0);
4908
4997
  });
4909
4998
  await this.cancelChildren(runId, opts);
4910
4999
  return {
@@ -5739,7 +5828,7 @@ var WorkflowEngine = class {
5739
5828
  const checkpoint = await this.store.getCheckpoint(runId, cmd.seq);
5740
5829
  if (checkpoint && checkpoint.status !== "pending") {
5741
5830
  setTimeout(() => {
5742
- void this.resume(runId).catch(() => void 0);
5831
+ void this.resumeImplicit(runId).catch(() => void 0);
5743
5832
  }, 0).unref?.();
5744
5833
  return;
5745
5834
  }
@@ -6081,7 +6170,7 @@ var WorkflowEngine = class {
6081
6170
  kind: "signal",
6082
6171
  output: payload
6083
6172
  }));
6084
- setTimeout(() => void this.resume(run.id).catch(() => void 0), 0);
6173
+ setTimeout(() => void this.resumeImplicit(run.id).catch(() => void 0), 0);
6085
6174
  }, "deliverBuffered");
6086
6175
  const buffered = await this.store.takeBufferedSignal(cmd.signal);
6087
6176
  if (buffered) {
@@ -6895,7 +6984,7 @@ var WorkflowEngine = class {
6895
6984
  queueMs: startedAt.getTime() - cp.enqueuedAt.getTime(),
6896
6985
  durationMs: finishedAt.getTime() - startedAt.getTime()
6897
6986
  });
6898
- await this.resume(result.runId).catch((err) => {
6987
+ await this.resumeImplicit(result.runId).catch((err) => {
6899
6988
  if (err instanceof NamespaceMismatch) return null;
6900
6989
  throw err;
6901
6990
  });
@@ -6996,7 +7085,7 @@ var WorkflowEngine = class {
6996
7085
  ...waiters
6997
7086
  ];
6998
7087
  waiters.clear();
6999
- for (const runId of runIds) void this.resume(runId).catch(() => void 0);
7088
+ for (const runId of runIds) void this.resumeImplicit(runId).catch(() => void 0);
7000
7089
  }
7001
7090
  /** Release the flow-control slot a dispatched step held (if any), by its stepId. */
7002
7091
  async releaseQueueSlot(id) {
@@ -8149,6 +8238,7 @@ var InMemoryTransport = class {
8149
8238
  InMemoryTransport,
8150
8239
  LEGACY_V1_CAPABILITIES,
8151
8240
  LEGACY_V1_PROTOCOL,
8241
+ NestedWorkflowCallError,
8152
8242
  NonDeterminismError,
8153
8243
  QueueController,
8154
8244
  RUN_VALUE_FACET_LIMIT,
@@ -8195,6 +8285,7 @@ var InMemoryTransport = class {
8195
8285
  coveredTokens,
8196
8286
  createStepLogger,
8197
8287
  currentStep,
8288
+ currentStepScope,
8198
8289
  currentWorkflowCtx,
8199
8290
  debug,
8200
8291
  describeWorker,
@@ -8236,7 +8327,9 @@ var InMemoryTransport = class {
8236
8327
  resolveRunWaiting,
8237
8328
  runDurableExecution,
8238
8329
  runInStepLogger,
8330
+ runInStepScope,
8239
8331
  runInWorkflowCtx,
8332
+ runOutsideWorkflowCtx,
8240
8333
  runSchedules,
8241
8334
  runStepHandler,
8242
8335
  runValueFacetsFromRuns,