@dudousxd/nestjs-durable 0.48.2 → 0.50.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,4 +1,4 @@
1
- import { WorkflowCtx, SearchAttributes, WorkflowClass, 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';
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
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';
3
3
  import { z } from 'zod';
4
4
  import { ConcurrencyOption, StartRunDeps, RunRedisWorkerOptions, RunningWorker, DurableWorkerRuntime } from '@dudousxd/durable-worker';
@@ -102,6 +102,17 @@ interface ContextAccessor {
102
102
  get(): ContextStore | undefined;
103
103
  }
104
104
 
105
+ /** A class-validator DTO class — validated with the same `plainToInstance` + `validate` NestJS's
106
+ * `ValidationPipe` runs. */
107
+ type DtoClass = new (...args: any[]) => object;
108
+ /**
109
+ * What `@Workflow({ input, output })` and `@Step({ input, output })` accept: a **Standard Schema**
110
+ * (https://standardschema.dev — zod 3.24+, valibot, arktype, …) or a **class-validator DTO class**.
111
+ * The two are told apart at runtime: a value with a `~standard` property is a Standard Schema, a
112
+ * constructor is a DTO. A zod schema older than 3.24 (no `~standard`) still works through its `parse`.
113
+ */
114
+ type IoSchema = StandardSchemaV1 | DtoClass | z.ZodType;
115
+
105
116
  declare const WORKFLOW_METADATA: unique symbol;
106
117
  interface WorkflowMeta {
107
118
  name: string;
@@ -121,8 +132,11 @@ interface WorkflowMeta {
121
132
  concurrency?: ConcurrencyConfig | undefined;
122
133
  /** Max wall-clock lifetime before a run is cancelled (e.g. `'2h'`). See `WorkflowOptions`. */
123
134
  executionTimeout?: string | number | undefined;
124
- /** class-validator DTO validated at start. See `WorkflowOptions`. */
125
- inputSchema?: (new (...args: any[]) => object) | undefined;
135
+ /** Input schema (Standard Schema or DTO class), parsed at start and before every turn. Holds
136
+ * `input` or its `inputSchema` alias. See `WorkflowOptions`. */
137
+ input?: IoSchema | undefined;
138
+ /** Output schema, parsed before the run completes. See `WorkflowOptions`. */
139
+ output?: IoSchema | undefined;
126
140
  /** Custom input validator (throws on invalid). See `WorkflowOptions`. */
127
141
  validateInput?: ((input: unknown) => void | Promise<void>) | undefined;
128
142
  /** Typed/validated `searchAttributes` shape. See `WorkflowOptions`. */
@@ -183,9 +197,11 @@ interface WorkflowOptions {
183
197
  */
184
198
  concurrency?: ConcurrencyConfig;
185
199
  /**
186
- * Max wall-clock lifetime for a run of this workflow (e.g. `'2h'`, `'7 days'`, or ms). A run that
187
- * outlives it is moved to `cancelled` (`execution_timeout`) by the timer poller — a backstop for
188
- * runs that get stuck or loop forever. Omit for no limit.
200
+ * Max wall-clock lifetime for a run of this workflow (e.g. `'2h'`, `'7 days'`, or ms), counted from
201
+ * the run's creation. A run that outlives it is moved to `cancelled` (`execution_timeout`) by the
202
+ * timer poller — whether it is `pending`, `blocked`, `running` or `suspended` — a backstop for runs
203
+ * that get stuck, loop forever or are never picked up. A run in `cancelling` finishes its saga undo
204
+ * instead. Omit for no limit.
189
205
  */
190
206
  executionTimeout?: string | number;
191
207
  /**
@@ -197,15 +213,36 @@ interface WorkflowOptions {
197
213
  */
198
214
  requires?: string[];
199
215
  /**
200
- * Validate the workflow input at `start` against a **class-validator DTO** (the same
201
- * `plainToInstance` + `validate` NestJS runs in controllers) — invalid input is rejected before any
202
- * run is created. Needs the optional peers `class-validator` + `class-transformer`.
203
- * `@Workflow({ inputSchema: CheckoutInput })`.
216
+ * The run's input schema — a **Standard Schema** (zod 3.24+, valibot, arktype, …) or a
217
+ * **class-validator DTO class** (the same `plainToInstance` + `validate` NestJS's `ValidationPipe`
218
+ * runs; needs the optional peers `class-validator` + `class-transformer`). The same option
219
+ * `@Step({ input })` takes.
220
+ *
221
+ * Parsed at `start`, so invalid input is rejected before any run is created. The run stores the
222
+ * input **as it was sent**; `run(ctx, input)` receives the **parsed** value (coerced/transformed —
223
+ * a `Date` from `z.coerce.date()`, a DTO instance), re-parsed from the stored input before every
224
+ * turn, so a replay sees the same value the first execution did. The schema must therefore be
225
+ * deterministic (no `default(() => Date.now())`), and changing it is a breaking change to the
226
+ * workflow — bump `version`. A stored input that no longer parses fails the run with
227
+ * `code: 'invalid_input'`.
228
+ *
229
+ * `@Workflow({ name: 'checkout', input: CheckoutInput })` or `input: z.object({ … })`.
230
+ */
231
+ input?: IoSchema;
232
+ /**
233
+ * The run's output schema (same forms as {@link input}). The value `run()` returns is parsed before
234
+ * the run completes, and the parsed value is what the run stores as its output. A mismatch fails the
235
+ * run with `code: 'invalid_output'`. The same option `@Step({ output })` takes.
204
236
  */
205
- inputSchema?: new (...args: any[]) => object;
237
+ output?: IoSchema;
206
238
  /**
207
- * Custom input validator (throws on invalid) — an escape hatch for zod/yup/etc. instead of
208
- * `inputSchema`. Takes precedence over `inputSchema` if both are set.
239
+ * Alias of {@link input}, kept for workflows declared before `input` existed. Setting both is an
240
+ * error.
241
+ */
242
+ inputSchema?: IoSchema;
243
+ /**
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.
209
246
  */
210
247
  validateInput?: (input: unknown) => void | Promise<void>;
211
248
  /**
@@ -268,9 +305,9 @@ interface DurableStepMeta {
268
305
  name: string;
269
306
  /** Opt-in runtime input schema, validated when the handler is served (see `scanSteps`). Absent on
270
307
  * a bare `@Step()` — compile-time types from the method signature only, no runtime check. */
271
- input?: z.ZodType | undefined;
308
+ input?: IoSchema | undefined;
272
309
  /** Opt-in runtime output schema, validated before the handler's result is handed back. */
273
- output?: z.ZodType | undefined;
310
+ output?: IoSchema | undefined;
274
311
  }
275
312
  /**
276
313
  * `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,
@@ -279,10 +316,12 @@ interface DurableStepMeta {
279
316
  interface StepOptions {
280
317
  /** Explicit routing name, overriding the derived `Class.method`. */
281
318
  name?: string;
282
- /** Runtime input schema, validated at the serve boundary (opt-in — a bare `@Step()` skips it). */
283
- input?: z.ZodType;
319
+ /** Runtime input schema, validated at the serve boundary (opt-in — a bare `@Step()` skips it). A
320
+ * Standard Schema (zod, valibot, …) or a class-validator DTO class — the same forms
321
+ * `@Workflow({ input })` takes. The method receives the parsed value. */
322
+ input?: IoSchema;
284
323
  /** Runtime output schema, validated before the result is returned (opt-in). */
285
- output?: z.ZodType;
324
+ output?: IoSchema;
286
325
  /**
287
326
  * Def-level durable-dispatch policy stamped under `DURABLE_STEP_CONFIG` and read by `ctx.step` at
288
327
  * the dispatch boundary (see `stepConfigOf`) — a per-call `ctx.step(ref, input, opts)` overrides
@@ -328,8 +367,8 @@ interface StepOptions {
328
367
  * - `@Step('custom:name')` — explicit name override (stable across refactors, or a cross-runtime
329
368
  * contract with a non-JS worker that has no `@Step` of its own).
330
369
  * - `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,
331
- * timeoutMs? })` — optional name override, opt-in RUNTIME zod schemas, and a def-level
332
- * durable-retry/liveness-timeout policy. `input` validates before the method runs; `output`
370
+ * timeoutMs? })` — optional name override, opt-in RUNTIME schemas (a Standard Schema or a DTO
371
+ * class), and a def-level durable-retry/liveness-timeout policy. `input` validates before the method runs; `output`
333
372
  * validates its return value before it's handed back (see `scanSteps`). `retries`/`backoff`/
334
373
  * `backoffMs`/`backoffMaxMs`/`jitter`/`timeoutMs` are the policy `ctx.step` reads off this method
335
374
  * reference (via `stepConfigOf`) to build the dispatched `StepDef`'s durable retry/backoff and
@@ -558,6 +597,19 @@ interface DurableModuleOptions {
558
597
  * See `WorkflowEngineDeps.reconcileMs`. Operator only.
559
598
  */
560
599
  reconcileMs?: number;
600
+ /**
601
+ * Idle time (ms) before a stranded in-flight run is reported to {@link onStalled} listeners.
602
+ * Defaults to 900000 (15 min), floored at 60000. See `WorkflowEngineDeps.stalledAfterMs`.
603
+ * Operator only.
604
+ */
605
+ stalledAfterMs?: number;
606
+ /**
607
+ * Pager hook for stranded runs, registered on the engine as `engine.onStalled(...)`: a run
608
+ * suspended with no wake timer, or parked on an old pending remote step nothing is leasing. The
609
+ * timer poller sweeps once a minute and reports each stranded episode once per instance.
610
+ * Equivalent to calling `engine.onStalled` from a provider; only a DRIVING operator sweeps.
611
+ */
612
+ onStalled?: (info: StalledRunInfo) => void;
561
613
  /**
562
614
  * The most due timers one timer-poll pass loads and drives, most overdue first, so a wave of
563
615
  * thousands of due runs is worked through in batches. Defaults to 500; a non-finite or sub-1 value
@@ -1321,4 +1373,4 @@ declare class WorkflowService {
1321
1373
  }): Promise<number>;
1322
1374
  }
1323
1375
 
1324
- export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA, DURABLE_WORKER_RUNNERS, DURABLE_WORKER_RUNTIME, DeadLetter, DurableModule, type DurableModuleAsyncOptions, type DurableModuleOptions, type DurableRetentionOptions, DurableStartClient, DurableStep, type DurableStepInterceptor, type DurableStepMeta, ENTITY_METADATA, ENTITY_ON_METADATA, Entity, EntityService, type ErrorClassWithCode, IN_APP_RUN_REDIS_WORKER, IN_APP_WORKER_BINDING, IN_APP_WORKER_RUNNERS, IN_APP_WORKER_RUNTIME, type InAppWorkerBinding, InAppWorkerBootstrap, ON_ERROR_METADATA, ON_EVENT_METADATA, On, OnDurableEvent, OnError, type OnErrorMethod, OnEvent, ProxyRunGateway, RUN_GATEWAY, RUN_REDIS_WORKER, type RunLookupStore, type RunRedisWorkerFn, RunRequestResponder, type RunRequestTransport, STEP_INTERCEPTOR_METADATA, Step, StepInterceptor, type StepOptions, StoreRunGateway, TenantEventRepublisher, ThinStepRegistrar, ThinWorkerBootstrap, ThinWorkflowRegistrar, UNSUPPORTED_RUN_REQUEST, type UserRef, WORKFLOW_METADATA, Workflow, type WorkflowAttributesOf, type WorkflowMeta, type WorkflowOptions, WorkflowService, attributesOf, entityConfigFor, getDurableStepMeta, getEntityMeta, getOnEvents, getWorkflowMeta, inAppWorkerProviders, isDeadLetterHandler, isDrivingOperator, isOperatorRole, isStepInterceptor, onErrorCodesOf, thinWorkerProviders, unavailableRunGateway };
1376
+ export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA, DURABLE_WORKER_RUNNERS, DURABLE_WORKER_RUNTIME, DeadLetter, type DtoClass, DurableModule, type DurableModuleAsyncOptions, type DurableModuleOptions, type DurableRetentionOptions, DurableStartClient, DurableStep, type DurableStepInterceptor, type DurableStepMeta, ENTITY_METADATA, ENTITY_ON_METADATA, Entity, EntityService, type ErrorClassWithCode, IN_APP_RUN_REDIS_WORKER, IN_APP_WORKER_BINDING, IN_APP_WORKER_RUNNERS, IN_APP_WORKER_RUNTIME, type InAppWorkerBinding, InAppWorkerBootstrap, type IoSchema, ON_ERROR_METADATA, ON_EVENT_METADATA, On, OnDurableEvent, OnError, type OnErrorMethod, OnEvent, ProxyRunGateway, RUN_GATEWAY, RUN_REDIS_WORKER, type RunLookupStore, type RunRedisWorkerFn, RunRequestResponder, type RunRequestTransport, STEP_INTERCEPTOR_METADATA, Step, StepInterceptor, type StepOptions, StoreRunGateway, TenantEventRepublisher, ThinStepRegistrar, ThinWorkerBootstrap, ThinWorkflowRegistrar, UNSUPPORTED_RUN_REQUEST, type UserRef, WORKFLOW_METADATA, Workflow, type WorkflowAttributesOf, type WorkflowMeta, type WorkflowOptions, WorkflowService, attributesOf, entityConfigFor, getDurableStepMeta, getEntityMeta, getOnEvents, getWorkflowMeta, inAppWorkerProviders, isDeadLetterHandler, isDrivingOperator, isOperatorRole, isStepInterceptor, onErrorCodesOf, thinWorkerProviders, unavailableRunGateway };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { WorkflowCtx, SearchAttributes, WorkflowClass, 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';
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
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';
3
3
  import { z } from 'zod';
4
4
  import { ConcurrencyOption, StartRunDeps, RunRedisWorkerOptions, RunningWorker, DurableWorkerRuntime } from '@dudousxd/durable-worker';
@@ -102,6 +102,17 @@ interface ContextAccessor {
102
102
  get(): ContextStore | undefined;
103
103
  }
104
104
 
105
+ /** A class-validator DTO class — validated with the same `plainToInstance` + `validate` NestJS's
106
+ * `ValidationPipe` runs. */
107
+ type DtoClass = new (...args: any[]) => object;
108
+ /**
109
+ * What `@Workflow({ input, output })` and `@Step({ input, output })` accept: a **Standard Schema**
110
+ * (https://standardschema.dev — zod 3.24+, valibot, arktype, …) or a **class-validator DTO class**.
111
+ * The two are told apart at runtime: a value with a `~standard` property is a Standard Schema, a
112
+ * constructor is a DTO. A zod schema older than 3.24 (no `~standard`) still works through its `parse`.
113
+ */
114
+ type IoSchema = StandardSchemaV1 | DtoClass | z.ZodType;
115
+
105
116
  declare const WORKFLOW_METADATA: unique symbol;
106
117
  interface WorkflowMeta {
107
118
  name: string;
@@ -121,8 +132,11 @@ interface WorkflowMeta {
121
132
  concurrency?: ConcurrencyConfig | undefined;
122
133
  /** Max wall-clock lifetime before a run is cancelled (e.g. `'2h'`). See `WorkflowOptions`. */
123
134
  executionTimeout?: string | number | undefined;
124
- /** class-validator DTO validated at start. See `WorkflowOptions`. */
125
- inputSchema?: (new (...args: any[]) => object) | undefined;
135
+ /** Input schema (Standard Schema or DTO class), parsed at start and before every turn. Holds
136
+ * `input` or its `inputSchema` alias. See `WorkflowOptions`. */
137
+ input?: IoSchema | undefined;
138
+ /** Output schema, parsed before the run completes. See `WorkflowOptions`. */
139
+ output?: IoSchema | undefined;
126
140
  /** Custom input validator (throws on invalid). See `WorkflowOptions`. */
127
141
  validateInput?: ((input: unknown) => void | Promise<void>) | undefined;
128
142
  /** Typed/validated `searchAttributes` shape. See `WorkflowOptions`. */
@@ -183,9 +197,11 @@ interface WorkflowOptions {
183
197
  */
184
198
  concurrency?: ConcurrencyConfig;
185
199
  /**
186
- * Max wall-clock lifetime for a run of this workflow (e.g. `'2h'`, `'7 days'`, or ms). A run that
187
- * outlives it is moved to `cancelled` (`execution_timeout`) by the timer poller — a backstop for
188
- * runs that get stuck or loop forever. Omit for no limit.
200
+ * Max wall-clock lifetime for a run of this workflow (e.g. `'2h'`, `'7 days'`, or ms), counted from
201
+ * the run's creation. A run that outlives it is moved to `cancelled` (`execution_timeout`) by the
202
+ * timer poller — whether it is `pending`, `blocked`, `running` or `suspended` — a backstop for runs
203
+ * that get stuck, loop forever or are never picked up. A run in `cancelling` finishes its saga undo
204
+ * instead. Omit for no limit.
189
205
  */
190
206
  executionTimeout?: string | number;
191
207
  /**
@@ -197,15 +213,36 @@ interface WorkflowOptions {
197
213
  */
198
214
  requires?: string[];
199
215
  /**
200
- * Validate the workflow input at `start` against a **class-validator DTO** (the same
201
- * `plainToInstance` + `validate` NestJS runs in controllers) — invalid input is rejected before any
202
- * run is created. Needs the optional peers `class-validator` + `class-transformer`.
203
- * `@Workflow({ inputSchema: CheckoutInput })`.
216
+ * The run's input schema — a **Standard Schema** (zod 3.24+, valibot, arktype, …) or a
217
+ * **class-validator DTO class** (the same `plainToInstance` + `validate` NestJS's `ValidationPipe`
218
+ * runs; needs the optional peers `class-validator` + `class-transformer`). The same option
219
+ * `@Step({ input })` takes.
220
+ *
221
+ * Parsed at `start`, so invalid input is rejected before any run is created. The run stores the
222
+ * input **as it was sent**; `run(ctx, input)` receives the **parsed** value (coerced/transformed —
223
+ * a `Date` from `z.coerce.date()`, a DTO instance), re-parsed from the stored input before every
224
+ * turn, so a replay sees the same value the first execution did. The schema must therefore be
225
+ * deterministic (no `default(() => Date.now())`), and changing it is a breaking change to the
226
+ * workflow — bump `version`. A stored input that no longer parses fails the run with
227
+ * `code: 'invalid_input'`.
228
+ *
229
+ * `@Workflow({ name: 'checkout', input: CheckoutInput })` or `input: z.object({ … })`.
230
+ */
231
+ input?: IoSchema;
232
+ /**
233
+ * The run's output schema (same forms as {@link input}). The value `run()` returns is parsed before
234
+ * the run completes, and the parsed value is what the run stores as its output. A mismatch fails the
235
+ * run with `code: 'invalid_output'`. The same option `@Step({ output })` takes.
204
236
  */
205
- inputSchema?: new (...args: any[]) => object;
237
+ output?: IoSchema;
206
238
  /**
207
- * Custom input validator (throws on invalid) — an escape hatch for zod/yup/etc. instead of
208
- * `inputSchema`. Takes precedence over `inputSchema` if both are set.
239
+ * Alias of {@link input}, kept for workflows declared before `input` existed. Setting both is an
240
+ * error.
241
+ */
242
+ inputSchema?: IoSchema;
243
+ /**
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.
209
246
  */
210
247
  validateInput?: (input: unknown) => void | Promise<void>;
211
248
  /**
@@ -268,9 +305,9 @@ interface DurableStepMeta {
268
305
  name: string;
269
306
  /** Opt-in runtime input schema, validated when the handler is served (see `scanSteps`). Absent on
270
307
  * a bare `@Step()` — compile-time types from the method signature only, no runtime check. */
271
- input?: z.ZodType | undefined;
308
+ input?: IoSchema | undefined;
272
309
  /** Opt-in runtime output schema, validated before the handler's result is handed back. */
273
- output?: z.ZodType | undefined;
310
+ output?: IoSchema | undefined;
274
311
  }
275
312
  /**
276
313
  * `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,
@@ -279,10 +316,12 @@ interface DurableStepMeta {
279
316
  interface StepOptions {
280
317
  /** Explicit routing name, overriding the derived `Class.method`. */
281
318
  name?: string;
282
- /** Runtime input schema, validated at the serve boundary (opt-in — a bare `@Step()` skips it). */
283
- input?: z.ZodType;
319
+ /** Runtime input schema, validated at the serve boundary (opt-in — a bare `@Step()` skips it). A
320
+ * Standard Schema (zod, valibot, …) or a class-validator DTO class — the same forms
321
+ * `@Workflow({ input })` takes. The method receives the parsed value. */
322
+ input?: IoSchema;
284
323
  /** Runtime output schema, validated before the result is returned (opt-in). */
285
- output?: z.ZodType;
324
+ output?: IoSchema;
286
325
  /**
287
326
  * Def-level durable-dispatch policy stamped under `DURABLE_STEP_CONFIG` and read by `ctx.step` at
288
327
  * the dispatch boundary (see `stepConfigOf`) — a per-call `ctx.step(ref, input, opts)` overrides
@@ -328,8 +367,8 @@ interface StepOptions {
328
367
  * - `@Step('custom:name')` — explicit name override (stable across refactors, or a cross-runtime
329
368
  * contract with a non-JS worker that has no `@Step` of its own).
330
369
  * - `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,
331
- * timeoutMs? })` — optional name override, opt-in RUNTIME zod schemas, and a def-level
332
- * durable-retry/liveness-timeout policy. `input` validates before the method runs; `output`
370
+ * timeoutMs? })` — optional name override, opt-in RUNTIME schemas (a Standard Schema or a DTO
371
+ * class), and a def-level durable-retry/liveness-timeout policy. `input` validates before the method runs; `output`
333
372
  * validates its return value before it's handed back (see `scanSteps`). `retries`/`backoff`/
334
373
  * `backoffMs`/`backoffMaxMs`/`jitter`/`timeoutMs` are the policy `ctx.step` reads off this method
335
374
  * reference (via `stepConfigOf`) to build the dispatched `StepDef`'s durable retry/backoff and
@@ -558,6 +597,19 @@ interface DurableModuleOptions {
558
597
  * See `WorkflowEngineDeps.reconcileMs`. Operator only.
559
598
  */
560
599
  reconcileMs?: number;
600
+ /**
601
+ * Idle time (ms) before a stranded in-flight run is reported to {@link onStalled} listeners.
602
+ * Defaults to 900000 (15 min), floored at 60000. See `WorkflowEngineDeps.stalledAfterMs`.
603
+ * Operator only.
604
+ */
605
+ stalledAfterMs?: number;
606
+ /**
607
+ * Pager hook for stranded runs, registered on the engine as `engine.onStalled(...)`: a run
608
+ * suspended with no wake timer, or parked on an old pending remote step nothing is leasing. The
609
+ * timer poller sweeps once a minute and reports each stranded episode once per instance.
610
+ * Equivalent to calling `engine.onStalled` from a provider; only a DRIVING operator sweeps.
611
+ */
612
+ onStalled?: (info: StalledRunInfo) => void;
561
613
  /**
562
614
  * The most due timers one timer-poll pass loads and drives, most overdue first, so a wave of
563
615
  * thousands of due runs is worked through in batches. Defaults to 500; a non-finite or sub-1 value
@@ -1321,4 +1373,4 @@ declare class WorkflowService {
1321
1373
  }): Promise<number>;
1322
1374
  }
1323
1375
 
1324
- export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA, DURABLE_WORKER_RUNNERS, DURABLE_WORKER_RUNTIME, DeadLetter, DurableModule, type DurableModuleAsyncOptions, type DurableModuleOptions, type DurableRetentionOptions, DurableStartClient, DurableStep, type DurableStepInterceptor, type DurableStepMeta, ENTITY_METADATA, ENTITY_ON_METADATA, Entity, EntityService, type ErrorClassWithCode, IN_APP_RUN_REDIS_WORKER, IN_APP_WORKER_BINDING, IN_APP_WORKER_RUNNERS, IN_APP_WORKER_RUNTIME, type InAppWorkerBinding, InAppWorkerBootstrap, ON_ERROR_METADATA, ON_EVENT_METADATA, On, OnDurableEvent, OnError, type OnErrorMethod, OnEvent, ProxyRunGateway, RUN_GATEWAY, RUN_REDIS_WORKER, type RunLookupStore, type RunRedisWorkerFn, RunRequestResponder, type RunRequestTransport, STEP_INTERCEPTOR_METADATA, Step, StepInterceptor, type StepOptions, StoreRunGateway, TenantEventRepublisher, ThinStepRegistrar, ThinWorkerBootstrap, ThinWorkflowRegistrar, UNSUPPORTED_RUN_REQUEST, type UserRef, WORKFLOW_METADATA, Workflow, type WorkflowAttributesOf, type WorkflowMeta, type WorkflowOptions, WorkflowService, attributesOf, entityConfigFor, getDurableStepMeta, getEntityMeta, getOnEvents, getWorkflowMeta, inAppWorkerProviders, isDeadLetterHandler, isDrivingOperator, isOperatorRole, isStepInterceptor, onErrorCodesOf, thinWorkerProviders, unavailableRunGateway };
1376
+ export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA, DURABLE_WORKER_RUNNERS, DURABLE_WORKER_RUNTIME, DeadLetter, type DtoClass, DurableModule, type DurableModuleAsyncOptions, type DurableModuleOptions, type DurableRetentionOptions, DurableStartClient, DurableStep, type DurableStepInterceptor, type DurableStepMeta, ENTITY_METADATA, ENTITY_ON_METADATA, Entity, EntityService, type ErrorClassWithCode, IN_APP_RUN_REDIS_WORKER, IN_APP_WORKER_BINDING, IN_APP_WORKER_RUNNERS, IN_APP_WORKER_RUNTIME, type InAppWorkerBinding, InAppWorkerBootstrap, type IoSchema, ON_ERROR_METADATA, ON_EVENT_METADATA, On, OnDurableEvent, OnError, type OnErrorMethod, OnEvent, ProxyRunGateway, RUN_GATEWAY, RUN_REDIS_WORKER, type RunLookupStore, type RunRedisWorkerFn, RunRequestResponder, type RunRequestTransport, STEP_INTERCEPTOR_METADATA, Step, StepInterceptor, type StepOptions, StoreRunGateway, TenantEventRepublisher, ThinStepRegistrar, ThinWorkerBootstrap, ThinWorkflowRegistrar, UNSUPPORTED_RUN_REQUEST, type UserRef, WORKFLOW_METADATA, Workflow, type WorkflowAttributesOf, type WorkflowMeta, type WorkflowOptions, WorkflowService, attributesOf, entityConfigFor, getDurableStepMeta, getEntityMeta, getOnEvents, getWorkflowMeta, inAppWorkerProviders, isDeadLetterHandler, isDrivingOperator, isOperatorRole, isStepInterceptor, onErrorCodesOf, thinWorkerProviders, unavailableRunGateway };
package/dist/index.js CHANGED
@@ -119,6 +119,9 @@ __name(fileOfFrame, "fileOfFrame");
119
119
  var WORKFLOW_METADATA = Symbol("nestjs-durable:workflow");
120
120
  function Workflow(options) {
121
121
  const declaredIn = declaringFile(Workflow);
122
+ if (options.input !== void 0 && options.inputSchema !== void 0) {
123
+ throw new Error(`@Workflow ${options.name}: set either \`input\` or its alias \`inputSchema\`, not both`);
124
+ }
122
125
  return (target) => {
123
126
  rememberDeclaringFile(target, declaredIn);
124
127
  const meta = {
@@ -130,7 +133,8 @@ function Workflow(options) {
130
133
  singleton: options.singleton,
131
134
  concurrency: options.concurrency,
132
135
  executionTimeout: options.executionTimeout,
133
- inputSchema: options.inputSchema,
136
+ input: options.input ?? options.inputSchema,
137
+ output: options.output,
134
138
  validateInput: options.validateInput,
135
139
  searchAttributes: options.searchAttributes,
136
140
  onEvent: options.onEvent,
@@ -388,6 +392,94 @@ import { DURABLE_OPTIONS_CANONICAL } from "@dudousxd/nestjs-durable-core";
388
392
  import { Inject, Injectable as Injectable2 } from "@nestjs/common";
389
393
  import { DiscoveryService, MetadataScanner } from "@nestjs/core";
390
394
 
395
+ // src/schema-parse.ts
396
+ import { FatalError } from "@dudousxd/nestjs-durable-core";
397
+ function schemaParser(schema, kind, label) {
398
+ const code = kind === "input" ? "invalid_input" : "invalid_output";
399
+ if (isStandardSchema(schema)) {
400
+ return async (value) => {
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
+ }
411
+ return result.value;
412
+ };
413
+ }
414
+ if (isZodLike(schema)) {
415
+ return async (value) => {
416
+ 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
+ }
424
+ return result.data;
425
+ };
426
+ }
427
+ if (typeof schema === "function") return classValidatorParser(schema, code, label);
428
+ throw new Error(`${label}: expected a Standard Schema (zod, valibot, \u2026) or a class-validator DTO class`);
429
+ }
430
+ __name(schemaParser, "schemaParser");
431
+ function classValidatorParser(cls, code, label) {
432
+ return async (value) => {
433
+ let cv;
434
+ let ct;
435
+ try {
436
+ cv = await import("class-validator");
437
+ ct = await import("class-transformer");
438
+ } catch {
439
+ throw new Error(`${label} uses a class-validator DTO, which needs the optional peers "class-validator" and "class-transformer" \u2014 install them, or use a Standard Schema (zod, valibot, \u2026) instead.`);
440
+ }
441
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
442
+ 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, [
445
+ {
446
+ message
447
+ }
448
+ ]);
449
+ }
450
+ const instance = ct.plainToInstance(cls, value);
451
+ const errors = await cv.validate(instance, {
452
+ whitelist: true
453
+ });
454
+ if (errors.length > 0) {
455
+ const issues = errors.map((e) => ({
456
+ message: Object.values(e.constraints ?? {
457
+ _: `${e.property} is invalid`
458
+ }).join(", "),
459
+ path: [
460
+ String(e.property)
461
+ ]
462
+ }));
463
+ const message = issues.map((i) => i.message).join("; ");
464
+ throw new FatalError(`invalid ${label}: ${message}`, code, issues);
465
+ }
466
+ return instance;
467
+ };
468
+ }
469
+ __name(classValidatorParser, "classValidatorParser");
470
+ function isStandardSchema(schema) {
471
+ return (typeof schema === "object" || typeof schema === "function") && schema !== null && "~standard" in schema;
472
+ }
473
+ __name(isStandardSchema, "isStandardSchema");
474
+ function isZodLike(schema) {
475
+ return typeof schema === "object" && schema !== null && typeof schema.safeParse === "function";
476
+ }
477
+ __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
+
391
483
  // src/discovery-helpers.ts
392
484
  function scanWorkflows(discovery, register) {
393
485
  for (const wrapper of discovery.getProviders()) {
@@ -414,10 +506,12 @@ function scanSteps(discovery, scanner, register) {
414
506
  const meta = getDurableStepMeta(method);
415
507
  if (!meta) continue;
416
508
  const boundMethod = method;
509
+ const parseInput = meta.input && schemaParser(meta.input, "input", `step "${meta.name}" input`);
510
+ const parseOutput = meta.output && schemaParser(meta.output, "output", `step "${meta.name}" output`);
417
511
  const handler = /* @__PURE__ */ __name(async (input, log) => {
418
- const validInput = meta.input ? meta.input.parse(input) : input;
512
+ const validInput = parseInput ? await parseInput(input) : input;
419
513
  const output = await boundMethod.call(instance, validInput, log);
420
- return meta.output ? meta.output.parse(output) : output;
514
+ return parseOutput ? parseOutput(output) : output;
421
515
  }, "handler");
422
516
  register(meta, handler);
423
517
  }
@@ -457,6 +551,24 @@ function onErrorHandlersOf(meta, instance, scanner) {
457
551
  }
458
552
  __name(onErrorHandlersOf, "onErrorHandlersOf");
459
553
 
554
+ // src/workflow-io.ts
555
+ function workflowIo(meta) {
556
+ const parseInput = meta.input && schemaParser(meta.input, "input", `workflow "${meta.name}" input`);
557
+ const parseOutput = meta.output && schemaParser(meta.output, "output", `workflow "${meta.name}" output`);
558
+ const validateInput = meta.validateInput;
559
+ return {
560
+ validateAtStart: parseInput || validateInput ? async (input) => {
561
+ if (parseInput) await parseInput(input);
562
+ await validateInput?.(input);
563
+ } : void 0,
564
+ wrap: /* @__PURE__ */ __name((body) => !parseInput && !parseOutput ? body : async (ctx, input) => {
565
+ const output = await body(ctx, parseInput ? await parseInput(input) : input);
566
+ return parseOutput ? parseOutput(output) : output;
567
+ }, "wrap")
568
+ };
569
+ }
570
+ __name(workflowIo, "workflowIo");
571
+
460
572
  // src/runtime-registration.ts
461
573
  function registerOnRuntime(runtime, scanner, meta, instance, origin) {
462
574
  const { handlers, routes } = onErrorHandlersOf(meta, instance, scanner);
@@ -466,7 +578,8 @@ function registerOnRuntime(runtime, scanner, meta, instance, origin) {
466
578
  origin
467
579
  } : {}
468
580
  };
469
- runtime.registerWorkflow(meta.name, (ctx, input) => instance.run(ctx, input), {
581
+ const body = workflowIo(meta).wrap((ctx, input) => instance.run(ctx, input));
582
+ runtime.registerWorkflow(meta.name, body, {
470
583
  ...announced,
471
584
  ...meta.requires !== void 0 ? {
472
585
  requires: meta.requires
@@ -2073,6 +2186,7 @@ var TimerPoller = class {
2073
2186
  await this.sweep("recoverIncomplete", () => this.engine.recoverIncomplete());
2074
2187
  await this.sweep("resumeDueTimers", () => this.engine.resumeDueTimers());
2075
2188
  await this.sweep("sweepTimeouts", () => this.engine.sweepTimeouts());
2189
+ await this.sweep("sweepStalled", async () => this.engine.sweepStalled?.());
2076
2190
  const schedules = this.options.schedules;
2077
2191
  if (schedules && schedules.length > 0) {
2078
2192
  await this.sweep("schedules", () => runSchedules(this.engine, schedules, Date.now()));
@@ -2111,31 +2225,6 @@ import { DURABLE_OPTIONS_CANONICAL as DURABLE_OPTIONS_CANONICAL6, RemoteWorkflow
2111
2225
  import { Inject as Inject7, Injectable as Injectable10 } from "@nestjs/common";
2112
2226
  import { DiscoveryService as DiscoveryService4, MetadataScanner as MetadataScanner4 } from "@nestjs/core";
2113
2227
 
2114
- // src/input-validation.ts
2115
- function classValidatorInput(cls) {
2116
- return async (input) => {
2117
- let cv;
2118
- let ct;
2119
- try {
2120
- cv = await import("class-validator");
2121
- ct = await import("class-transformer");
2122
- } catch {
2123
- throw new Error('@Workflow({ inputSchema }) needs the optional peers "class-validator" and "class-transformer" \u2014 install them, or pass a `validateInput` function instead.');
2124
- }
2125
- const instance = ct.plainToInstance(cls, input);
2126
- const errors = await cv.validate(instance, {
2127
- whitelist: true
2128
- });
2129
- if (errors.length > 0) {
2130
- const message = errors.map((e) => Object.values(e.constraints ?? {
2131
- _: e.property
2132
- }).join(", ")).join("; ");
2133
- throw new Error(`invalid input for workflow: ${message}`);
2134
- }
2135
- };
2136
- }
2137
- __name(classValidatorInput, "classValidatorInput");
2138
-
2139
2228
  // src/step-interceptor.ts
2140
2229
  import "reflect-metadata";
2141
2230
  var STEP_INTERCEPTOR_METADATA = Symbol("nestjs-durable:step-interceptor");
@@ -2238,7 +2327,8 @@ var WorkflowRegistrar = class {
2238
2327
  const unattributed = [];
2239
2328
  scanWorkflows(this.discovery, (meta, workflow, origin) => {
2240
2329
  if (origin === void 0) unattributed.push(meta.name);
2241
- const validateInput = meta.validateInput ?? (meta.inputSchema ? classValidatorInput(meta.inputSchema) : void 0);
2330
+ const io = workflowIo(meta);
2331
+ const body = io.wrap((ctx, input) => workflow.run(ctx, input));
2242
2332
  const eventBatch = meta.debounce ? {
2243
2333
  mode: "debounce",
2244
2334
  windowMs: parseDuration2(meta.debounce)
@@ -2254,7 +2344,7 @@ var WorkflowRegistrar = class {
2254
2344
  origin
2255
2345
  });
2256
2346
  }
2257
- this.engine.register(meta.name, meta.version, (ctx, input) => workflow.run(ctx, input), {
2347
+ this.engine.register(meta.name, meta.version, body, {
2258
2348
  // Stamped onto every run this workflow starts (see `WorkflowRun.origin`). Derived from the
2259
2349
  // class's declaring file, so a lib gets attributed without opting in; `undefined` when it
2260
2350
  // could not be derived, which the engine records as unknown rather than defaulting.
@@ -2264,7 +2354,7 @@ var WorkflowRegistrar = class {
2264
2354
  concurrency: meta.concurrency,
2265
2355
  executionTimeout: meta.executionTimeout,
2266
2356
  requires: meta.requires,
2267
- validateInput,
2357
+ validateInput: io.validateAtStart,
2268
2358
  searchAttributesSchema: meta.searchAttributes,
2269
2359
  onEvent: getOnEvents(meta, workflowCtor),
2270
2360
  eventBatch,
@@ -2795,6 +2885,7 @@ var DurableModule = class _DurableModule {
2795
2885
  remoteRedispatchMs: options.remoteRedispatchMs,
2796
2886
  remoteRedispatchMax: options.remoteRedispatchMax,
2797
2887
  reconcileMs: options.reconcileMs,
2888
+ stalledAfterMs: options.stalledAfterMs,
2798
2889
  timerBatchSize: options.timerBatchSize,
2799
2890
  eventBufferTtlMs: options.eventBufferTtlMs,
2800
2891
  blockedPollMs: options.blockedPollMs,
@@ -2818,6 +2909,7 @@ var DurableModule = class _DurableModule {
2818
2909
  } : void 0
2819
2910
  });
2820
2911
  for (const queue of options.queues ?? []) engine.registerQueue(queue);
2912
+ if (options.onStalled) engine.onStalled(options.onStalled);
2821
2913
  return engine;
2822
2914
  }, "useFactory"),
2823
2915
  inject: [