@dudousxd/nestjs-durable 0.49.0 → 0.51.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
@@ -1,5 +1,5 @@
1
- import { WorkflowCtx, SearchAttributes, WorkflowClass, StandardSchemaV1, StepError, OnErrorInput, WorkflowRef, SingletonConfig, ConcurrencyConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetailOptions, RunDetail, FanPageQuery, FanPage, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, WorkflowRun, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
- export { AttributeFilter, DynamicWorkflowCtx, EngineEvent, InferSearchAttributes, OnErrorInput, RunDetail, RunGateway, RunListItem, RunQuery, RunStatus, RunWaiting, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, readSearchAttributes } from '@dudousxd/nestjs-durable-core';
1
+ import { WorkflowCtx, SearchAttributes, WorkflowClass, StandardSchemaV1, StepError, OnErrorInput, WorkflowRef, SingletonConfig, ConcurrencyConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, StalledRunInfo, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetailOptions, RunDetail, FanPageQuery, FanPage, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, WorkflowRun, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
+ export { AttributeFilter, DynamicWorkflowCtx, EngineEvent, InferSearchAttributes, OnErrorInput, RunDetail, RunGateway, RunListItem, RunQuery, RunSchedulesOptions, RunStatus, RunWaiting, ScheduledWorkflow, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, prevCronFireMs, readSearchAttributes, runSchedules, scheduleRunIdAt, scheduledRunId } from '@dudousxd/nestjs-durable-core';
3
3
  import { z } from 'zod';
4
4
  import { ConcurrencyOption, StartRunDeps, RunRedisWorkerOptions, RunningWorker, DurableWorkerRuntime } from '@dudousxd/durable-worker';
5
5
  import { DynamicModule, InjectionToken, OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown, Provider } from '@nestjs/common';
@@ -242,14 +242,16 @@ interface WorkflowOptions {
242
242
  inputSchema?: IoSchema;
243
243
  /**
244
244
  * A custom check at `start` (throw to reject) for a rule a schema can't express — a cross-field
245
- * invariant, an async lookup. It sees the input as it was sent, and runs after `input` parses.
245
+ * invariant, an async lookup. It runs after `input` parses and receives the parsed value (the input
246
+ * as sent when there is no `input` schema).
246
247
  */
247
248
  validateInput?: (input: unknown) => void | Promise<void>;
248
249
  /**
249
250
  * Validate this workflow's {@link WorkflowCtx.upsertSearchAttributes} writes against a **Standard
250
251
  * Schema** (https://standardschema.dev — zod 3.24+, valibot, arktype, …). `ctx.upsertSearchAttributes`
251
252
  * validates the MERGED result (existing attributes shallow-merged with the patch) on every call —
252
- * an invalid merge throws, naming this workflow, the offending key(s), and the schema's issues. The
253
+ * an invalid merge throws a `SchemaValidationError` (code `schema_validation`) naming this workflow,
254
+ * with the schema's issues. The
253
255
  * schema's inferred output must be search-attribute-shaped (flat `string`/`number`/`boolean` values
254
256
  * only) — a schema whose output has a nested object/array is a compile-time error here. Omit for
255
257
  * the prior unvalidated behavior.
@@ -597,6 +599,19 @@ interface DurableModuleOptions {
597
599
  * See `WorkflowEngineDeps.reconcileMs`. Operator only.
598
600
  */
599
601
  reconcileMs?: number;
602
+ /**
603
+ * Idle time (ms) before a stranded in-flight run is reported to {@link onStalled} listeners.
604
+ * Defaults to 900000 (15 min), floored at 60000. See `WorkflowEngineDeps.stalledAfterMs`.
605
+ * Operator only.
606
+ */
607
+ stalledAfterMs?: number;
608
+ /**
609
+ * Pager hook for stranded runs, registered on the engine as `engine.onStalled(...)`: a run
610
+ * suspended with no wake timer, or parked on an old pending remote step nothing is leasing. The
611
+ * timer poller sweeps once a minute and reports each stranded episode once per instance.
612
+ * Equivalent to calling `engine.onStalled` from a provider; only a DRIVING operator sweeps.
613
+ */
614
+ onStalled?: (info: StalledRunInfo) => void;
600
615
  /**
601
616
  * The most due timers one timer-poll pass loads and drives, most overdue first, so a wave of
602
617
  * thousands of due runs is worked through in batches. Defaults to 500; a non-finite or sub-1 value
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { WorkflowCtx, SearchAttributes, WorkflowClass, StandardSchemaV1, StepError, OnErrorInput, WorkflowRef, SingletonConfig, ConcurrencyConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetailOptions, RunDetail, FanPageQuery, FanPage, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, WorkflowRun, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
- export { AttributeFilter, DynamicWorkflowCtx, EngineEvent, InferSearchAttributes, OnErrorInput, RunDetail, RunGateway, RunListItem, RunQuery, RunStatus, RunWaiting, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, readSearchAttributes } from '@dudousxd/nestjs-durable-core';
1
+ import { WorkflowCtx, SearchAttributes, WorkflowClass, StandardSchemaV1, StepError, OnErrorInput, WorkflowRef, SingletonConfig, ConcurrencyConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, StalledRunInfo, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetailOptions, RunDetail, FanPageQuery, FanPage, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, WorkflowRun, TenantEvent } from '@dudousxd/nestjs-durable-core';
2
+ export { AttributeFilter, DynamicWorkflowCtx, EngineEvent, InferSearchAttributes, OnErrorInput, RunDetail, RunGateway, RunListItem, RunQuery, RunSchedulesOptions, RunStatus, RunWaiting, ScheduledWorkflow, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, prevCronFireMs, readSearchAttributes, runSchedules, scheduleRunIdAt, scheduledRunId } from '@dudousxd/nestjs-durable-core';
3
3
  import { z } from 'zod';
4
4
  import { ConcurrencyOption, StartRunDeps, RunRedisWorkerOptions, RunningWorker, DurableWorkerRuntime } from '@dudousxd/durable-worker';
5
5
  import { DynamicModule, InjectionToken, OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown, Provider } from '@nestjs/common';
@@ -242,14 +242,16 @@ interface WorkflowOptions {
242
242
  inputSchema?: IoSchema;
243
243
  /**
244
244
  * A custom check at `start` (throw to reject) for a rule a schema can't express — a cross-field
245
- * invariant, an async lookup. It sees the input as it was sent, and runs after `input` parses.
245
+ * invariant, an async lookup. It runs after `input` parses and receives the parsed value (the input
246
+ * as sent when there is no `input` schema).
246
247
  */
247
248
  validateInput?: (input: unknown) => void | Promise<void>;
248
249
  /**
249
250
  * Validate this workflow's {@link WorkflowCtx.upsertSearchAttributes} writes against a **Standard
250
251
  * Schema** (https://standardschema.dev — zod 3.24+, valibot, arktype, …). `ctx.upsertSearchAttributes`
251
252
  * validates the MERGED result (existing attributes shallow-merged with the patch) on every call —
252
- * an invalid merge throws, naming this workflow, the offending key(s), and the schema's issues. The
253
+ * an invalid merge throws a `SchemaValidationError` (code `schema_validation`) naming this workflow,
254
+ * with the schema's issues. The
253
255
  * schema's inferred output must be search-attribute-shaped (flat `string`/`number`/`boolean` values
254
256
  * only) — a schema whose output has a nested object/array is a compile-time error here. Omit for
255
257
  * the prior unvalidated behavior.
@@ -597,6 +599,19 @@ interface DurableModuleOptions {
597
599
  * See `WorkflowEngineDeps.reconcileMs`. Operator only.
598
600
  */
599
601
  reconcileMs?: number;
602
+ /**
603
+ * Idle time (ms) before a stranded in-flight run is reported to {@link onStalled} listeners.
604
+ * Defaults to 900000 (15 min), floored at 60000. See `WorkflowEngineDeps.stalledAfterMs`.
605
+ * Operator only.
606
+ */
607
+ stalledAfterMs?: number;
608
+ /**
609
+ * Pager hook for stranded runs, registered on the engine as `engine.onStalled(...)`: a run
610
+ * suspended with no wake timer, or parked on an old pending remote step nothing is leasing. The
611
+ * timer poller sweeps once a minute and reports each stranded episode once per instance.
612
+ * Equivalent to calling `engine.onStalled` from a provider; only a DRIVING operator sweeps.
613
+ */
614
+ onStalled?: (info: StalledRunInfo) => void;
600
615
  /**
601
616
  * The most due timers one timer-poll pass loads and drives, most overdue first, so a wave of
602
617
  * thousands of due runs is worked through in batches. Defaults to 500; a non-finite or sub-1 value
package/dist/index.js CHANGED
@@ -393,34 +393,20 @@ import { Inject, Injectable as Injectable2 } from "@nestjs/common";
393
393
  import { DiscoveryService, MetadataScanner } from "@nestjs/core";
394
394
 
395
395
  // src/schema-parse.ts
396
- import { FatalError } from "@dudousxd/nestjs-durable-core";
396
+ import { SchemaValidationError } from "@dudousxd/nestjs-durable-core";
397
397
  function schemaParser(schema, kind, label) {
398
398
  const code = kind === "input" ? "invalid_input" : "invalid_output";
399
399
  if (isStandardSchema(schema)) {
400
400
  return async (value) => {
401
401
  const result = await schema["~standard"].validate(value);
402
- if (result.issues) {
403
- const issues = result.issues.map((i) => ({
404
- message: i.message,
405
- ...i.path ? {
406
- path: i.path.map((p) => String(typeof p === "object" ? p.key : p))
407
- } : {}
408
- }));
409
- throw new FatalError(`invalid ${label}: ${formatIssues(issues)}`, code, issues);
410
- }
402
+ if (result.issues) throw new SchemaValidationError(label, result.issues, code);
411
403
  return result.value;
412
404
  };
413
405
  }
414
406
  if (isZodLike(schema)) {
415
407
  return async (value) => {
416
408
  const result = schema.safeParse(value);
417
- if (!result.success) {
418
- const issues = result.error.issues.map((i) => ({
419
- message: i.message,
420
- path: i.path.map(String)
421
- }));
422
- throw new FatalError(`invalid ${label}: ${formatIssues(issues)}`, code, issues);
423
- }
409
+ if (!result.success) throw new SchemaValidationError(label, result.error.issues, code);
424
410
  return result.data;
425
411
  };
426
412
  }
@@ -440,12 +426,11 @@ function classValidatorParser(cls, code, label) {
440
426
  }
441
427
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
442
428
  const got = value === null ? "null" : Array.isArray(value) ? "an array" : typeof value;
443
- const message = `expected an object, got ${got}`;
444
- throw new FatalError(`invalid ${label}: ${message}`, code, [
429
+ throw new SchemaValidationError(label, [
445
430
  {
446
- message
431
+ message: `expected an object, got ${got}`
447
432
  }
448
- ]);
433
+ ], code);
449
434
  }
450
435
  const instance = ct.plainToInstance(cls, value);
451
436
  const errors = await cv.validate(instance, {
@@ -460,8 +445,8 @@ function classValidatorParser(cls, code, label) {
460
445
  String(e.property)
461
446
  ]
462
447
  }));
463
- const message = issues.map((i) => i.message).join("; ");
464
- throw new FatalError(`invalid ${label}: ${message}`, code, issues);
448
+ const summary = issues.map((i) => i.message).join("; ");
449
+ throw new SchemaValidationError(label, issues, code, summary);
465
450
  }
466
451
  return instance;
467
452
  };
@@ -475,10 +460,6 @@ function isZodLike(schema) {
475
460
  return typeof schema === "object" && schema !== null && typeof schema.safeParse === "function";
476
461
  }
477
462
  __name(isZodLike, "isZodLike");
478
- function formatIssues(issues) {
479
- return issues.map((i) => i.path && i.path.length > 0 ? `${i.path.join(".")}: ${i.message}` : i.message).join("; ");
480
- }
481
- __name(formatIssues, "formatIssues");
482
463
 
483
464
  // src/discovery-helpers.ts
484
465
  function scanWorkflows(discovery, register) {
@@ -558,8 +539,8 @@ function workflowIo(meta) {
558
539
  const validateInput = meta.validateInput;
559
540
  return {
560
541
  validateAtStart: parseInput || validateInput ? async (input) => {
561
- if (parseInput) await parseInput(input);
562
- await validateInput?.(input);
542
+ const parsed = parseInput ? await parseInput(input) : input;
543
+ await validateInput?.(parsed);
563
544
  } : void 0,
564
545
  wrap: /* @__PURE__ */ __name((body) => !parseInput && !parseOutput ? body : async (ctx, input) => {
565
546
  const output = await body(ctx, parseInput ? await parseInput(input) : input);
@@ -2076,7 +2057,7 @@ var TenantEventRepublisher = class {
2076
2057
  };
2077
2058
 
2078
2059
  // src/timer-poller.ts
2079
- import { DURABLE_OPTIONS_CANONICAL as DURABLE_OPTIONS_CANONICAL5, WorkflowEngine as WorkflowEngine3, runSchedules } from "@dudousxd/nestjs-durable-core";
2060
+ import { DURABLE_OPTIONS_CANONICAL as DURABLE_OPTIONS_CANONICAL5, WorkflowEngine as WorkflowEngine3, runSchedules, workflowName as workflowName3 } from "@dudousxd/nestjs-durable-core";
2080
2061
  import { Inject as Inject6, Injectable as Injectable9 } from "@nestjs/common";
2081
2062
  function _ts_decorate9(decorators, target, key, desc) {
2082
2063
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -2120,6 +2101,7 @@ var TimerPoller = class {
2120
2101
  this.options = options;
2121
2102
  }
2122
2103
  async onApplicationBootstrap() {
2104
+ for (const s of this.options.schedules ?? []) workflowName3(s.workflow);
2123
2105
  if (!isDrivingOperator(this.options)) return;
2124
2106
  this.unsubscribeEnqueued = this.engine.onEnqueued((runId) => {
2125
2107
  void this.engine.runOne(runId).catch((error) => {
@@ -2186,6 +2168,7 @@ var TimerPoller = class {
2186
2168
  await this.sweep("recoverIncomplete", () => this.engine.recoverIncomplete());
2187
2169
  await this.sweep("resumeDueTimers", () => this.engine.resumeDueTimers());
2188
2170
  await this.sweep("sweepTimeouts", () => this.engine.sweepTimeouts());
2171
+ await this.sweep("sweepStalled", async () => this.engine.sweepStalled?.());
2189
2172
  const schedules = this.options.schedules;
2190
2173
  if (schedules && schedules.length > 0) {
2191
2174
  await this.sweep("schedules", () => runSchedules(this.engine, schedules, Date.now()));
@@ -2220,7 +2203,7 @@ var CONTEXT_ACCESSOR = Symbol.for("@dudousxd/nestjs-context:accessor");
2220
2203
  var RUN_GATEWAY = RunGateway;
2221
2204
 
2222
2205
  // src/workflow.registrar.ts
2223
- import { DURABLE_OPTIONS_CANONICAL as DURABLE_OPTIONS_CANONICAL6, RemoteWorkflowExecutor, STATE_STORE_CANONICAL as STATE_STORE_CANONICAL3, WorkflowEngine as WorkflowEngine4, bindWorkflowClass, parseDuration as parseDuration2, sanitizeQueueToken, tenantGroup, workflowName as workflowName3 } from "@dudousxd/nestjs-durable-core";
2206
+ import { DURABLE_OPTIONS_CANONICAL as DURABLE_OPTIONS_CANONICAL6, RemoteWorkflowExecutor, STATE_STORE_CANONICAL as STATE_STORE_CANONICAL3, WorkflowEngine as WorkflowEngine4, bindWorkflowClass, parseDuration as parseDuration2, sanitizeQueueToken, tenantGroup, workflowName as workflowName4 } from "@dudousxd/nestjs-durable-core";
2224
2207
  import { Inject as Inject7, Injectable as Injectable10 } from "@nestjs/common";
2225
2208
  import { DiscoveryService as DiscoveryService4, MetadataScanner as MetadataScanner4 } from "@nestjs/core";
2226
2209
 
@@ -2387,7 +2370,7 @@ var WorkflowRegistrar = class {
2387
2370
  });
2388
2371
  deadLetterByWorkflow.set(meta.name, dlqName);
2389
2372
  } else if (meta.deadLetterWorkflow) {
2390
- deadLetterByWorkflow.set(meta.name, workflowName3(meta.deadLetterWorkflow));
2373
+ deadLetterByWorkflow.set(meta.name, workflowName4(meta.deadLetterWorkflow));
2391
2374
  }
2392
2375
  });
2393
2376
  this.reportUnattributed(unattributed);
@@ -2435,7 +2418,7 @@ var WorkflowRegistrar = class {
2435
2418
  * a `dlq:<runId>` id, so re-recovery never double-dispatches.
2436
2419
  */
2437
2420
  installDeadLetterRouting(byWorkflow) {
2438
- const fallback = this.options.deadLetterWorkflow ? workflowName3(this.options.deadLetterWorkflow) : void 0;
2421
+ const fallback = this.options.deadLetterWorkflow ? workflowName4(this.options.deadLetterWorkflow) : void 0;
2439
2422
  if (byWorkflow.size === 0 && !fallback) return;
2440
2423
  this.engine.onDead((run) => {
2441
2424
  const target = byWorkflow.get(run.workflow) ?? fallback;
@@ -2884,6 +2867,7 @@ var DurableModule = class _DurableModule {
2884
2867
  remoteRedispatchMs: options.remoteRedispatchMs,
2885
2868
  remoteRedispatchMax: options.remoteRedispatchMax,
2886
2869
  reconcileMs: options.reconcileMs,
2870
+ stalledAfterMs: options.stalledAfterMs,
2887
2871
  timerBatchSize: options.timerBatchSize,
2888
2872
  eventBufferTtlMs: options.eventBufferTtlMs,
2889
2873
  blockedPollMs: options.blockedPollMs,
@@ -2907,6 +2891,7 @@ var DurableModule = class _DurableModule {
2907
2891
  } : void 0
2908
2892
  });
2909
2893
  for (const queue of options.queues ?? []) engine.registerQueue(queue);
2894
+ if (options.onStalled) engine.onStalled(options.onStalled);
2910
2895
  return engine;
2911
2896
  }, "useFactory"),
2912
2897
  inject: [
@@ -2969,7 +2954,7 @@ DurableModule = _ts_decorate12([
2969
2954
  ], DurableModule);
2970
2955
 
2971
2956
  // src/index.ts
2972
- import { readSearchAttributes as readSearchAttributes2, RunGateway as RunGateway3, WorkflowEngine as WorkflowEngine7 } from "@dudousxd/nestjs-durable-core";
2957
+ import { prevCronFireMs, readSearchAttributes as readSearchAttributes2, RunGateway as RunGateway3, runSchedules as runSchedules2, scheduledRunId, scheduleRunIdAt, WorkflowEngine as WorkflowEngine7 } from "@dudousxd/nestjs-durable-core";
2973
2958
  export {
2974
2959
  CONTEXT_ACCESSOR,
2975
2960
  DEAD_LETTER_METADATA,
@@ -3025,7 +3010,11 @@ export {
3025
3010
  isOperatorRole,
3026
3011
  isStepInterceptor,
3027
3012
  onErrorCodesOf,
3013
+ prevCronFireMs,
3028
3014
  readSearchAttributes2 as readSearchAttributes,
3015
+ runSchedules2 as runSchedules,
3016
+ scheduleRunIdAt,
3017
+ scheduledRunId,
3029
3018
  thinWorkerProviders,
3030
3019
  unavailableRunGateway
3031
3020
  };