@dudousxd/nestjs-durable 0.15.0 → 0.16.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/LICENSE +21 -0
- package/dist/index.cjs +790 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +411 -0
- package/dist/index.d.ts +411 -8
- package/dist/index.js +744 -21
- package/dist/index.js.map +1 -1
- package/package.json +21 -7
- package/dist/context-accessor.d.ts +0 -31
- package/dist/context-accessor.d.ts.map +0 -1
- package/dist/context-accessor.js +0 -3
- package/dist/context-accessor.js.map +0 -1
- package/dist/decorators.d.ts +0 -146
- package/dist/decorators.d.ts.map +0 -1
- package/dist/decorators.js +0 -103
- package/dist/decorators.js.map +0 -1
- package/dist/durable-step.registrar.d.ts +0 -30
- package/dist/durable-step.registrar.d.ts.map +0 -1
- package/dist/durable-step.registrar.js +0 -85
- package/dist/durable-step.registrar.js.map +0 -1
- package/dist/durable.module.d.ts +0 -108
- package/dist/durable.module.d.ts.map +0 -1
- package/dist/durable.module.js +0 -207
- package/dist/durable.module.js.map +0 -1
- package/dist/entity.d.ts +0 -43
- package/dist/entity.d.ts.map +0 -1
- package/dist/entity.js +0 -94
- package/dist/entity.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/input-validation.d.ts +0 -9
- package/dist/input-validation.d.ts.map +0 -1
- package/dist/input-validation.js +0 -30
- package/dist/input-validation.js.map +0 -1
- package/dist/step-interceptor.d.ts +0 -20
- package/dist/step-interceptor.d.ts.map +0 -1
- package/dist/step-interceptor.js +0 -22
- package/dist/step-interceptor.js.map +0 -1
- package/dist/timer-poller.d.ts +0 -20
- package/dist/timer-poller.d.ts.map +0 -1
- package/dist/timer-poller.js +0 -81
- package/dist/timer-poller.js.map +0 -1
- package/dist/tokens.d.ts +0 -13
- package/dist/tokens.d.ts.map +0 -1
- package/dist/tokens.js +0 -16
- package/dist/tokens.js.map +0 -1
- package/dist/workflow.registrar.d.ts +0 -33
- package/dist/workflow.registrar.d.ts.map +0 -1
- package/dist/workflow.registrar.js +0 -169
- package/dist/workflow.registrar.js.map +0 -1
- package/dist/workflow.service.d.ts +0 -55
- package/dist/workflow.service.d.ts.map +0 -1
- package/dist/workflow.service.js +0 -58
- package/dist/workflow.service.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/decorators.ts","../src/durable.module.ts","../src/durable-step.registrar.ts","../src/entity.ts","../src/timer-poller.ts","../src/tokens.ts","../src/workflow.registrar.ts","../src/input-validation.ts","../src/step-interceptor.ts","../src/workflow.service.ts"],"sourcesContent":["export * from './context-accessor';\nexport * from './decorators';\nexport * from './durable.module';\nexport * from './entity';\nexport * from './step-interceptor';\nexport * from './tokens';\nexport * from './workflow.service';\n","import {\n type SingletonConfig,\n type StepError,\n WORKFLOW_NAME_KEY,\n type WorkflowRef,\n} from '@dudousxd/nestjs-durable-core';\nimport 'reflect-metadata';\n\nexport const WORKFLOW_METADATA = Symbol('nestjs-durable:workflow');\n\nexport interface WorkflowMeta {\n name: string;\n version: string;\n /** The workflow this workflow's dead runs route to (a name or a class). See `WorkflowOptions`. */\n deadLetterWorkflow?: WorkflowRef | undefined;\n /** Static searchable labels stamped on every run of this workflow. See `WorkflowOptions`. */\n tags?: string[] | undefined;\n /** Per-key serialization (a durable mutex). See `WorkflowOptions`. */\n singleton?: SingletonConfig | undefined;\n /** Max wall-clock lifetime before a run is cancelled (e.g. `'2h'`). See `WorkflowOptions`. */\n executionTimeout?: string | number | undefined;\n /** class-validator DTO validated at start. See `WorkflowOptions`. */\n inputSchema?:\n | (new (\n ...args: any[]\n ) => object)\n | undefined;\n /** Custom input validator (throws on invalid). See `WorkflowOptions`. */\n validateInput?: ((input: unknown) => void | Promise<void>) | undefined;\n /** Event names that start a fresh run of this workflow. See `WorkflowOptions`. */\n onEvent?: string[] | undefined;\n /** Debounce `onEvent` triggers — fire once it's quiet for this long. See `WorkflowOptions`. */\n debounce?: string | number | undefined;\n /** Batch `onEvent` triggers — fire on size or window. See `WorkflowOptions`. */\n batch?: { maxSize: number; within: string | number } | undefined;\n}\n\nexport interface WorkflowOptions {\n name: string;\n version?: string;\n /**\n * Route this workflow's dead-lettered runs to another **registered** workflow — by class\n * (`deadLetterWorkflow: CheckoutDlqWorkflow`, refactor-safe) or by name for a cross-runtime\n * handler. For a handler co-located on the same class, prefer an inline `@DeadLetter()` method\n * instead — it takes precedence, and declaring both is a boot-time error. The handler receives a\n * {@link DeadLetter} payload, idempotent by a `dlq:<runId>` id.\n */\n deadLetterWorkflow?: WorkflowRef;\n /**\n * Static labels stamped on **every run** of this workflow (e.g. `tags: ['etl', 'critical']`) and\n * merged with any per-run tags passed to `start`. Searchable/filterable in the dashboard.\n */\n tags?: string[];\n /**\n * Serialize runs of this workflow that share a key — a durable FIFO mutex. e.g.\n * `singleton: { key: (input) => `base:${input.baseId}` }` runs at most one pipeline per base at a\n * time; same-key runs queue (suspended) and admit in creation order as slots free. `limit` (default\n * 1) raises the concurrency.\n */\n singleton?: SingletonConfig;\n /**\n * Max wall-clock lifetime for a run of this workflow (e.g. `'2h'`, `'7 days'`, or ms). A run that\n * outlives it is moved to `cancelled` (`execution_timeout`) by the timer poller — a backstop for\n * runs that get stuck or loop forever. Omit for no limit.\n */\n executionTimeout?: string | number;\n /**\n * Validate the workflow input at `start` against a **class-validator DTO** (the same\n * `plainToInstance` + `validate` NestJS runs in controllers) — invalid input is rejected before any\n * run is created. Needs the optional peers `class-validator` + `class-transformer`.\n * `@Workflow({ inputSchema: CheckoutInput })`.\n */\n inputSchema?: new (\n ...args: any[]\n ) => object;\n /**\n * Custom input validator (throws on invalid) — an escape hatch for zod/yup/etc. instead of\n * `inputSchema`. Takes precedence over `inputSchema` if both are set.\n */\n validateInput?: (input: unknown) => void | Promise<void>;\n /**\n * Start a fresh run of this workflow whenever any of these events is published via\n * `publishEvent(name, payload)` — the payload becomes the run's input. e.g.\n * `onEvent: ['user.registered', 'user.invited']`. The same subscription can also be declared with\n * the `@OnEvent(...)` class decorator; the two are merged.\n */\n onEvent?: string[];\n /**\n * Coalesce `onEvent` triggers by **debouncing** — start one run only once events have been quiet\n * for this long (resets on each event), with the LAST payload. e.g. `debounce: '30s'`.\n */\n debounce?: string | number;\n /**\n * Coalesce `onEvent` triggers by **batching** — start one run with all payloads (`{ events: [...] }`)\n * once `maxSize` is reached or `within` elapses from the first event. e.g.\n * `batch: { maxSize: 100, within: '10s' }`.\n */\n batch?: { maxSize: number; within: string | number };\n}\n\n/**\n * Marks a provider class as a durable workflow. Its `run(ctx, input)` method becomes the\n * workflow function the engine executes and replays.\n */\nexport function Workflow(options: WorkflowOptions): ClassDecorator {\n return (target) => {\n const meta: WorkflowMeta = {\n name: options.name,\n version: options.version ?? '1',\n deadLetterWorkflow: options.deadLetterWorkflow,\n tags: options.tags,\n singleton: options.singleton,\n executionTimeout: options.executionTimeout,\n inputSchema: options.inputSchema,\n validateInput: options.validateInput,\n onEvent: options.onEvent,\n debounce: options.debounce,\n batch: options.batch,\n };\n Reflect.defineMetadata(WORKFLOW_METADATA, meta, target);\n // Stamp the registered name so this class can be used as a typed workflow ref (ctx.child,\n // engine.start, deadLetterWorkflow) and resolved back to its name via `workflowName`.\n Object.defineProperty(target, WORKFLOW_NAME_KEY, {\n value: options.name,\n configurable: true,\n });\n };\n}\n\n// biome-ignore lint/complexity/noBannedTypes: matches reflect-metadata's class target type\nexport function getWorkflowMeta(target: Function): WorkflowMeta | undefined {\n return Reflect.getMetadata(WORKFLOW_METADATA, target) as WorkflowMeta | undefined;\n}\n\nexport const DURABLE_STEP_METADATA = Symbol('nestjs-durable:step-handler');\n\nexport interface DurableStepMeta {\n name: string;\n}\n\n/**\n * Marks a provider method as the handler for a remote step `name`. An in-process transport\n * (e.g. the event-emitter transport) routes dispatched tasks to it. The method's single\n * argument is the step input; its return value is the step output.\n */\nexport function DurableStep(name: string): MethodDecorator {\n return (_target, _propertyKey, descriptor: PropertyDescriptor) => {\n const meta: DurableStepMeta = { name };\n Reflect.defineMetadata(DURABLE_STEP_METADATA, meta, descriptor.value as object);\n return descriptor;\n };\n}\n\n// biome-ignore lint/complexity/noBannedTypes: reflect-metadata reads from the method function\nexport function getDurableStepMeta(method: Function): DurableStepMeta | undefined {\n return Reflect.getMetadata(DURABLE_STEP_METADATA, method) as DurableStepMeta | undefined;\n}\n\nexport const DEAD_LETTER_METADATA = Symbol('nestjs-durable:dead-letter');\n\n/**\n * The payload a dead-letter handler receives: the dead run's id, its workflow, the input it was\n * started with (typed via `TInput`), and the failure that killed it.\n */\nexport interface DeadLetter<TInput = unknown> {\n /** Id of the run that was dead-lettered (inspectable + retriable in the dashboard). */\n deadRunId: string;\n /** Name of the workflow whose run died. */\n workflow: string;\n /** The original input the dead run was started with. */\n input: TInput;\n /** The structured error that moved the run to `dead`, when known. */\n error?: StepError;\n}\n\n/**\n * Marks a method on a `@Workflow` class as that workflow's **inline dead-letter handler**. When a\n * run of the workflow is moved to `dead` (exceeded `maxRecoveryAttempts`), this method runs — as a\n * durable workflow itself, auto-registered as `<workflow>.dlq` with a `dlq:<runId>` id — receiving a\n * {@link DeadLetter} payload. It shares the class's injected dependencies, so the handler lives in\n * the same file as the workflow it protects.\n *\n * Takes precedence over `@Workflow({ deadLetterWorkflow })` and the module-level `deadLetterWorkflow`\n * default. The method signature is `(ctx, dead)` — the same `ctx` a workflow `run` gets.\n */\nexport function DeadLetter(): MethodDecorator {\n return (_target, _propertyKey, descriptor: PropertyDescriptor) => {\n Reflect.defineMetadata(DEAD_LETTER_METADATA, true, descriptor.value as object);\n return descriptor;\n };\n}\n\n// biome-ignore lint/complexity/noBannedTypes: reflect-metadata reads from the method function\nexport function isDeadLetterHandler(method: Function): boolean {\n return Reflect.getMetadata(DEAD_LETTER_METADATA, method) === true;\n}\n\nexport const ON_EVENT_METADATA = Symbol('nestjs-durable:on-event');\n\n/**\n * Subscribe a `@Workflow` class to one or more events: when any of them is published via\n * `publishEvent(name, payload)`, a fresh run of the workflow starts with the payload as input.\n * Equivalent to `@Workflow({ onEvent })` — use whichever reads better; multiple `@OnEvent(...)`\n * decorators and the option are all merged.\n */\nexport function OnEvent(...events: string[]): ClassDecorator {\n return (target) => {\n const existing = (Reflect.getMetadata(ON_EVENT_METADATA, target) as string[] | undefined) ?? [];\n Reflect.defineMetadata(ON_EVENT_METADATA, [...existing, ...events], target);\n };\n}\n\n/** All events a workflow class subscribes to — the union of `@Workflow({ onEvent })` and `@OnEvent`. */\nexport function getOnEvents(meta: WorkflowMeta, target: object): string[] {\n const fromDecorator =\n (Reflect.getMetadata(ON_EVENT_METADATA, target) as string[] | undefined) ?? [];\n return [...new Set([...(meta.onEvent ?? []), ...fromDecorator])];\n}\n","import {\n type ControlPlane,\n DURABLE_OPTIONS,\n type NamedTransport,\n type QueueConfig,\n STATE_STORE,\n type ScheduledWorkflow,\n type StateStore,\n TRANSPORT,\n type Transport,\n WorkflowEngine,\n type WorkflowRef,\n} from '@dudousxd/nestjs-durable-core';\nimport { type DynamicModule, type InjectionToken, Module, type Provider } from '@nestjs/common';\nimport { DiscoveryModule, ModuleRef } from '@nestjs/core';\nimport type { ContextAccessor } from './context-accessor';\nimport { DurableStepRegistrar } from './durable-step.registrar';\nimport { EntityService } from './entity';\nimport { TimerPoller } from './timer-poller';\nimport { CONTEXT_ACCESSOR } from './tokens';\nimport { WorkflowRegistrar } from './workflow.registrar';\nimport { WorkflowService } from './workflow.service';\n\n/**\n * Locate the context accessor non-strictly: an accessor provided by ANY module (a global\n * `ContextModule` from `@dudousxd/nestjs-context`, or the app root) is found via {@link ModuleRef},\n * because DurableModule is `global` and its engine factory can't see another module's local\n * providers directly. Returns undefined when the optional peer isn't installed/bound.\n */\nfunction resolveAccessor(moduleRef: ModuleRef): ContextAccessor | undefined {\n try {\n return moduleRef.get<ContextAccessor>(CONTEXT_ACCESSOR, { strict: false });\n } catch {\n return undefined;\n }\n}\n\n/**\n * Build an opaque context carrier from a {@link ContextAccessor} — the auto-feed default for the\n * engine's `context` option when `@dudousxd/nestjs-context` is present and the app passed no own\n * reader. Drops fields the accessor doesn't populate, so an empty/anonymous request yields `{}`.\n */\nfunction carrierFromAccessor(accessor: ContextAccessor): Record<string, unknown> | undefined {\n const carrier: Record<string, unknown> = {};\n const traceId = accessor.traceId();\n if (traceId !== undefined) carrier.traceId = traceId;\n const tenantId = accessor.tenantId();\n if (tenantId !== undefined) carrier.tenantId = tenantId;\n const userRef = accessor.userRef();\n if (userRef !== undefined) carrier.userRef = userRef;\n return carrier;\n}\n\n/**\n * The slice of `@dudousxd/nestjs-context`'s module-level `Context` singleton we use to re-hydrate the\n * originating context around a local step body. Structural — we never import the package's types (it\n * is an OPTIONAL peer); the guarded dynamic import below captures whatever the installed package\n * exports as `Context`.\n */\ninterface ContextRuntime {\n /** Re-hydrate a context from an opaque carrier and run `fn` inside its ALS scope. */\n deserialize<T>(carrier: Record<string, unknown>, fn: () => T): T;\n}\n\nfunction isContextRuntime(x: unknown): x is ContextRuntime {\n return !!x && typeof (x as ContextRuntime).deserialize === 'function';\n}\n\n/**\n * Resolve `@dudousxd/nestjs-context`'s runtime `Context` singleton via a GUARDED dynamic import — used\n * to build the engine's `rehydrate` bridge. The runtime `Context` (its `deserialize`/ALS `run`) is a\n * module-level singleton, NOT a DI provider behind {@link CONTEXT_ACCESSOR}, so it can't be resolved\n * through ModuleRef — we import it directly. We do NOT add a hard/static import (the peer is optional):\n * a failure (peer not installed) returns undefined and the engine falls back to passthrough re-hydration.\n */\nasync function resolveContextRuntime(): Promise<ContextRuntime | undefined> {\n try {\n // Indirect the specifier through a variable so the compiler does not try to STATICALLY resolve\n // the optional peer (it may be uninstalled). This stays a runtime guarded import — a missing\n // module rejects and is caught below.\n const specifier = '@dudousxd/nestjs-context';\n const mod = (await import(specifier)) as { Context?: unknown };\n return isContextRuntime(mod.Context) ? mod.Context : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** True when `x` can act as a control plane (broadcast pub/sub) — e.g. a broadcast-capable transport. */\nfunction isControlPlane(x: unknown): x is ControlPlane {\n return (\n !!x &&\n typeof (x as ControlPlane).publishControl === 'function' &&\n typeof (x as ControlPlane).onControl === 'function'\n );\n}\n\nexport interface DurableModuleOptions {\n store: StateStore;\n transport?: Transport;\n /**\n * An ordered pool of named transports for failover / multi-broker setups. The engine dispatches on\n * the first and fails over to the next; a step pins one via `ctx.call(step, input, { transport })`.\n * Use instead of `transport`.\n */\n transports?: NamedTransport[];\n /**\n * Cross-instance broadcast pub/sub (lifecycle events + cancellation). Defaults to the (first)\n * transport when it can broadcast (event-emitter, BullMQ); set explicitly to use a dedicated one.\n */\n controlPlane?: ControlPlane;\n /** Interval (ms) for the durable-timer poller. `0` disables it. Defaults to 1000. */\n timerPollMs?: number;\n /**\n * Auto-create the durable tables on boot via `store.ensureSchema()`. Defaults to true. Turn\n * off in production and call the store adapter's `ensure*DurableSchema()` from a migration.\n */\n autoSchema?: boolean;\n /**\n * Multi-instance recovery lease, in ms — how long an instance owns a run it picked up before\n * another may take over. Defaults to 30000. Set above your longest synchronous run.\n */\n leaseMs?: number;\n /** Unique id for this instance (for leases). Defaults to a random id. */\n instanceId?: string;\n /**\n * Cap recovery attempts before a still-`running` run is moved to the `dead` dead-letter state\n * (a poison pill that crashes the process every boot). Omit for unlimited.\n */\n maxRecoveryAttempts?: number;\n /**\n * The **default** workflow to route dead-lettered runs to, for workflows that don't declare their\n * own. When a run is moved to `dead` (exceeded `maxRecoveryAttempts`), the started handler gets a\n * `DeadLetter` payload `{ deadRunId, workflow, input, error }` (idempotent by a `dlq:<runId>` id) —\n * it can alert, compensate, or queue for review. Resolution per dead run: the workflow's inline\n * `@DeadLetter()` method → its `@Workflow({ deadLetterWorkflow })` reference → this default. Omit\n * everything to just leave dead runs parked (inspectable + retriable from the dashboard). Accepts a\n * workflow class (refactor-safe) or a name (cross-runtime).\n */\n deadLetterWorkflow?: WorkflowRef;\n /**\n * Whether this instance plays the **worker** role: register `@DurableStep` handlers (consume the\n * transport), recover incomplete runs on boot, and poll due timers. Defaults to `true`. Set\n * `false` for a **dashboard/dispatch-only** instance (e.g. an API pod) that mounts the control\n * plane and reads the store but must not process or recover workflows — leave that to the workers.\n */\n worker?: boolean;\n /** Max ms to wait for in-flight runs on shutdown before exiting. Defaults to 10000. */\n shutdownTimeoutMs?: number;\n /**\n * Recurring workflows to start on a schedule (fixed interval or cron). The timer poller fires\n * them each tick on **worker** instances only; `engine.start` is idempotent by the schedule's\n * time-bucket run id, so racing instances start each window exactly once. Cron schedules need the\n * optional `cron-parser` peer dependency.\n */\n schedules?: ScheduledWorkflow[];\n /**\n * Build the public callback URL for a `ctx.webhook()` token, e.g.\n * ``(t) => `https://api.example.com/durable/api/webhooks/${t}` ``. Populates `DurableWebhook.url`\n * so a step can hand the URL to a third party. The dashboard's `POST webhooks/:token` receives the\n * callback. Omit to build URLs yourself from the token.\n */\n webhookUrl?: (token: string) => string;\n /**\n * Flow-control queues for remote steps, registered on the engine at startup. Reference one from a\n * workflow with `ctx.call(step, input, { queue: name })` to cap its concurrency / admission rate.\n */\n queues?: QueueConfig[];\n /**\n * Provide the current W3C `traceparent` to stamp on dispatched remote tasks, so workers continue\n * the distributed trace. Pass `otelTraceparent` from `@dudousxd/nestjs-durable-otel`.\n */\n traceparent?: () => string | undefined;\n /**\n * Provide an opaque context carrier (tenant / user / correlation ids) to stamp on dispatched remote\n * tasks, so workers re-expose it to the step handler alongside the `traceparent`. The engine never\n * inspects its shape.\n *\n * **Auto-feed**: if you omit this AND `@dudousxd/nestjs-context` is installed (its accessor is bound\n * to the shared `CONTEXT_ACCESSOR` token), DurableModule defaults this to a reader that builds\n * `{ traceId, tenantId, userRef }` from the accessor — so a workflow dispatched within a request\n * automatically carries the originating context across process boundaries. Pass your own reader to\n * override the auto-feed; with neither, the carrier is omitted (unchanged behavior).\n *\n * Re-evaluated at each (re)dispatch — including a retry or a crash/scale-down resume that the engine\n * drives OUTSIDE the originating request scope, where this reader may return empty or stale values.\n * Treat the carrier as best-effort correlation/propagation metadata only — do NOT treat it as an\n * authorization boundary.\n */\n context?: () => Record<string, unknown> | undefined;\n /** Attempts for each saga compensation when a run fails. Default 1 (no retry). Idempotent undos. */\n compensationRetries?: number;\n}\n\nexport interface DurableModuleAsyncOptions {\n useFactory: (...args: never[]) => DurableModuleOptions | Promise<DurableModuleOptions>;\n inject?: InjectionToken[];\n}\n\n@Module({})\nexport class DurableModule {\n static forRoot(options: DurableModuleOptions): DynamicModule {\n return DurableModule.build({ provide: DURABLE_OPTIONS, useValue: options });\n }\n\n static forRootAsync(options: DurableModuleAsyncOptions): DynamicModule {\n return DurableModule.build({\n provide: DURABLE_OPTIONS,\n useFactory: options.useFactory,\n inject: options.inject ?? [],\n });\n }\n\n private static build(optionsProvider: Provider): DynamicModule {\n return {\n module: DurableModule,\n global: true,\n imports: [DiscoveryModule],\n providers: [\n optionsProvider,\n {\n provide: STATE_STORE,\n useFactory: (options: DurableModuleOptions) => options.store,\n inject: [DURABLE_OPTIONS],\n },\n {\n provide: TRANSPORT,\n useFactory: (options: DurableModuleOptions) => options.transport ?? null,\n inject: [DURABLE_OPTIONS],\n },\n {\n provide: WorkflowEngine,\n useFactory: async (\n store: StateStore,\n transport: Transport | null,\n opts: DurableModuleOptions,\n // The accessor from `@dudousxd/nestjs-context` is resolved at construction via ModuleRef\n // (shared CONTEXT_ACCESSOR symbol — no hard import). Absent → unchanged behavior.\n moduleRef: ModuleRef,\n ) => {\n // The control-plane default is the primary task transport (single, or the pool's first)\n // when it can broadcast.\n const primary = transport ?? opts.transports?.[0]?.transport;\n // Auto-feed the carrier from nestjs-context when an accessor is present AND the app didn't\n // pass its own `context` reader. The app's own reader always wins; with no accessor the\n // carrier stays `undefined` (unchanged behavior — `traceparent` etc. still work).\n const accessor = resolveAccessor(moduleRef);\n const context =\n opts.context ?? (accessor ? () => carrierFromAccessor(accessor) : undefined);\n // Consume side: when nestjs-context is present (an accessor is bound), re-hydrate the\n // originating context AROUND each local step body, so a `@DurableStep` reader sees the\n // tenant/user/trace ids ambiently via nestjs-context's ALS — no consumer wrapping needed.\n // The runtime `Context` is a module-level singleton (not the accessor token), resolved\n // ONCE here via a guarded dynamic import (optional peer — failure leaves the default\n // passthrough). Best-effort: an empty/undefined carrier just runs the handler normally.\n const runtime = accessor ? await resolveContextRuntime() : undefined;\n const rehydrate =\n runtime &&\n (<T>(carrier: Record<string, unknown> | undefined, fn: () => T): T =>\n carrier && Object.keys(carrier).length > 0\n ? runtime.deserialize(carrier, fn)\n : fn());\n const engine = new WorkflowEngine({\n store,\n transport: transport ?? undefined,\n transports: opts.transports,\n controlPlane: opts.controlPlane ?? (isControlPlane(primary) ? primary : undefined),\n leaseMs: opts.leaseMs,\n maxRecoveryAttempts: opts.maxRecoveryAttempts,\n instanceId: opts.instanceId,\n webhookUrl: opts.webhookUrl,\n traceparent: opts.traceparent,\n context,\n rehydrate: rehydrate || undefined,\n compensationRetries: opts.compensationRetries,\n // A non-worker (API/dashboard) instance must not run workflows: enqueue-only, leaving\n // each `pending` run in the store for a worker's `runPending` poll. Workers use the\n // default in-process dispatcher (execute locally).\n runDispatcher: opts.worker === false ? { dispatch: () => {} } : undefined,\n });\n for (const queue of opts.queues ?? []) engine.registerQueue(queue);\n // Dead-letter routing (per-workflow `@DeadLetter()` / `deadLetterWorkflow` + this global\n // default) is wired by the WorkflowRegistrar, which owns the `@Workflow` metadata.\n return engine;\n },\n inject: [STATE_STORE, TRANSPORT, DURABLE_OPTIONS, ModuleRef],\n },\n WorkflowService,\n EntityService,\n WorkflowRegistrar,\n DurableStepRegistrar,\n TimerPoller,\n ],\n exports: [WorkflowService, EntityService, WorkflowEngine, STATE_STORE, TRANSPORT],\n };\n }\n}\n","import {\n DURABLE_OPTIONS,\n type StepLogger,\n TRANSPORT,\n type Transport,\n} from '@dudousxd/nestjs-durable-core';\nimport { Inject, Injectable, type OnModuleInit } from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport { getDurableStepMeta } from './decorators';\nimport type { DurableModuleOptions } from './durable.module';\n\n/** A transport that can run step handlers in-process (e.g. the event-emitter transport). */\ninterface LocalTaskHandling {\n handle(name: string, fn: (input: unknown, log: StepLogger) => Promise<unknown>): void;\n}\n\nfunction supportsHandle(transport: unknown): transport is LocalTaskHandling {\n return typeof (transport as LocalTaskHandling | null)?.handle === 'function';\n}\n\n/**\n * Discovers `@DurableStep` methods and registers them as step handlers on the configured\n * transport, when that transport runs handlers in-process. With a queue/remote transport the\n * handlers live in the worker process instead, so there is nothing to wire here.\n *\n * **Context re-hydration (consume side) is the consumer's responsibility.** DurableModule's\n * produce side auto-feeds an opaque carrier (`{ traceId, tenantId, userRef }`) from\n * `@dudousxd/nestjs-context` onto each dispatched `RemoteTask.context` (see DurableModule). A worker\n * that wants its `@DurableStep` reads to SEE the originating context must re-establish it from\n * `task.context`. We do NOT do this here because: (a) re-hydration needs nestjs-context's module-level\n * `Context.deserialize(carrier, fn)` at runtime, and it is an OPTIONAL peer we must not hard-import;\n * and (b) the in-process `StepHandler` contract is `(input, log)` — the engine never surfaces\n * `task.context` to the handler closure, so wrapping it here would require widening the core\n * transport/handler signature (a non-additive cross-cutting change). Instead, a worker that consumes\n * the carrier should wrap its handler with `Context.deserialize(task.context, () => handler(...))`\n * from its own transport/worker bootstrap, where `task` is in scope.\n */\n@Injectable()\nexport class DurableStepRegistrar implements OnModuleInit {\n constructor(\n private readonly discovery: DiscoveryService,\n private readonly metadataScanner: MetadataScanner,\n @Inject(TRANSPORT) private readonly transport: Transport | null,\n @Inject(DURABLE_OPTIONS) private readonly options: DurableModuleOptions,\n ) {}\n\n onModuleInit(): void {\n // A dashboard/dispatch-only instance (`worker: false`) must not consume the queue — receiving\n // and running step tasks is exactly the worker role we're switching off here.\n if (this.options.worker === false) return;\n if (!supportsHandle(this.transport)) return;\n const transport = this.transport;\n\n for (const wrapper of this.discovery.getProviders()) {\n const { instance } = wrapper;\n if (!instance || typeof instance !== 'object') continue;\n const prototype = Object.getPrototypeOf(instance);\n for (const methodName of this.metadataScanner.getAllMethodNames(prototype)) {\n const method = instance[methodName];\n if (typeof method !== 'function') continue;\n const meta = getDurableStepMeta(method);\n if (!meta) continue;\n // Forward the step logger as a second arg; methods that only declare `(input)` ignore it.\n transport.handle(meta.name, (input, log) => instance[methodName](input, log));\n }\n }\n }\n}\n","import { type EntityHandler, WorkflowEngine } from '@dudousxd/nestjs-durable-core';\nimport { Injectable } from '@nestjs/common';\nimport 'reflect-metadata';\n\nexport const ENTITY_METADATA = Symbol('nestjs-durable:entity');\nexport const ENTITY_ON_METADATA = Symbol('nestjs-durable:entity-on');\n\n/**\n * Marks an `@Injectable()` class as a **durable entity** (a virtual object): its `@On(op)` methods run\n * serialized per key over the instance's fields as durable state. e.g.\n *\n * ```ts\n * @Entity({ name: 'cart' }) @Injectable()\n * class Cart { items: Item[] = []; @On('add') add(i: Item) { this.items.push(i); } @On('list') list() { return this.items; } }\n * ```\n *\n * The class must be **constructible with no arguments** (a fresh instance is the initial state per key)\n * — keep entities pure state, no DI. Drive them with `EntityService` or `ctx.signalEntity`/`callEntity`.\n */\nexport function Entity(options: { name: string }): ClassDecorator {\n return (target) => {\n Reflect.defineMetadata(ENTITY_METADATA, options, target);\n };\n}\n\n/** Marks an entity method as the handler for operation `op`. */\nexport function On(op: string): MethodDecorator {\n return (target, propertyKey) => {\n // biome-ignore lint/complexity/noBannedTypes: reflect-metadata keys on the class constructor\n const ctor = (target as { constructor: Function }).constructor;\n const ops = (Reflect.getMetadata(ENTITY_ON_METADATA, ctor) as Map<string, string>) ?? new Map();\n ops.set(op, propertyKey as string);\n Reflect.defineMetadata(ENTITY_ON_METADATA, ops, ctor);\n };\n}\n\n// biome-ignore lint/complexity/noBannedTypes: matches reflect-metadata's class target type\nexport function getEntityMeta(target: Function): { name: string } | undefined {\n return Reflect.getMetadata(ENTITY_METADATA, target) as { name: string } | undefined;\n}\n\n/**\n * Build the engine `EntityConfig` for a discovered `@Entity` class: a fresh instance per key, and\n * handlers that rehydrate the class prototype onto the (serialized) state before dispatching the op,\n * so methods work after replay.\n */\n// biome-ignore lint/complexity/noBannedTypes: a class constructor\nexport function entityConfigFor(ctor: Function): {\n initialState: () => object;\n handlers: Record<string, EntityHandler>;\n} {\n const ops = (Reflect.getMetadata(ENTITY_ON_METADATA, ctor) as Map<string, string>) ?? new Map();\n const Cls = ctor as new () => Record<string, (arg: unknown) => unknown>;\n const handlers: Record<string, EntityHandler> = {};\n for (const [op, method] of ops) {\n handlers[op] = (state, arg) => {\n Object.setPrototypeOf(state as object, Cls.prototype); // re-attach methods to the plain state\n const fn = (state as Record<string, ((a: unknown) => unknown) | undefined>)[method];\n if (typeof fn !== 'function') throw new Error(`entity handler \"${method}\" is not a method`);\n return fn.call(state, arg);\n };\n }\n return { initialState: () => new Cls(), handlers };\n}\n\n/** Inject this to drive durable entities from outside a workflow. */\n@Injectable()\nexport class EntityService {\n constructor(private readonly engine: WorkflowEngine) {}\n\n /** Send an operation to an entity (fire-and-forget; ordered + exactly-once per key). */\n signal(name: string, key: string, op: string, arg?: unknown): Promise<void> {\n return this.engine.signalEntity(name, key, op, arg);\n }\n\n /** Read an entity's current durable state (or undefined if it has none yet). */\n getState<S = unknown>(name: string, key: string): Promise<S | undefined> {\n return this.engine.getEntityState<S>(name, key);\n }\n}\n","import { DURABLE_OPTIONS, WorkflowEngine, runSchedules } from '@dudousxd/nestjs-durable-core';\nimport {\n Inject,\n Injectable,\n type OnApplicationBootstrap,\n type OnModuleDestroy,\n} from '@nestjs/common';\nimport type { DurableModuleOptions } from './durable.module';\n\n/**\n * Resumes suspended runs whose durable timer (`ctx.sleep`) is due, and fires any configured\n * recurring `schedules` — once on boot, then on an interval. Set `timerPollMs` to `0` to disable\n * the interval (e.g. when an external scheduler drives `WorkflowEngine.resumeDueTimers`).\n */\n@Injectable()\nexport class TimerPoller implements OnApplicationBootstrap, OnModuleDestroy {\n private timer?: ReturnType<typeof setInterval>;\n private polling = false;\n private unsubscribeEnqueued?: () => void;\n\n constructor(\n private readonly engine: WorkflowEngine,\n @Inject(DURABLE_OPTIONS) private readonly options: DurableModuleOptions,\n ) {}\n\n async onApplicationBootstrap(): Promise<void> {\n // Only the worker role drives suspended runs forward. A dashboard-only instance\n // (`worker: false`) must not resume timers — leave that to the workers.\n if (this.options.worker === false) return;\n // Low-latency dispatch: when a run is enqueued elsewhere (e.g. an API pod), pick it up at once\n // over the control plane instead of waiting for the next poll tick. Leasing dedups across workers.\n this.unsubscribeEnqueued = this.engine.onEnqueued((runId) => void this.engine.runOne(runId));\n await this.poll();\n const intervalMs = this.options.timerPollMs ?? 1_000;\n if (intervalMs > 0) {\n this.timer = setInterval(() => void this.poll(), intervalMs);\n this.timer.unref?.();\n }\n }\n\n onModuleDestroy(): void {\n if (this.timer) clearInterval(this.timer);\n this.unsubscribeEnqueued?.();\n }\n\n private async poll(): Promise<void> {\n if (this.polling) return; // never overlap two sweeps\n this.polling = true;\n try {\n // Pick up runs enqueued elsewhere (an API pod's `start`, or another worker) still `pending`,\n // reclaim runs orphaned by a crashed worker (lease expired — a live worker renews its lease so\n // only dead ones are reclaimed), then resume due timers and sweep execution timeouts.\n await this.engine.runPending();\n await this.engine.recoverIncomplete();\n await this.engine.resumeDueTimers();\n await this.engine.sweepTimeouts();\n const schedules = this.options.schedules;\n if (schedules && schedules.length > 0) {\n await runSchedules(this.engine, schedules, Date.now());\n }\n } finally {\n this.polling = false;\n }\n }\n}\n","/**\n * Cross-lib injection token for the current-request context accessor, owned by\n * `@dudousxd/nestjs-context`. We do NOT import nestjs-context (it is an OPTIONAL\n * peer dependency) — instead we share its well-known token by value so DI\n * resolves the same provider when nestjs-context is installed and present.\n *\n * `Symbol.for(key)` uses the global symbol registry, so this resolves to the\n * SAME symbol instance as nestjs-context's `tokens.ts` (and the identical token\n * declared by `@dudousxd/nestjs-authz`) without any import. The key MUST stay\n * byte-identical with nestjs-context's export.\n */\nexport const CONTEXT_ACCESSOR = Symbol.for('@dudousxd/nestjs-context:accessor');\n","import {\n DURABLE_OPTIONS,\n STATE_STORE,\n type StateStore,\n type WorkflowCtx,\n WorkflowEngine,\n type WorkflowRun,\n parseDuration,\n workflowName,\n} from '@dudousxd/nestjs-durable-core';\nimport {\n Inject,\n Injectable,\n type OnApplicationBootstrap,\n type OnApplicationShutdown,\n type OnModuleInit,\n} from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport { getOnEvents, getWorkflowMeta, isDeadLetterHandler } from './decorators';\nimport type { DurableModuleOptions } from './durable.module';\nimport { entityConfigFor, getEntityMeta } from './entity';\nimport { classValidatorInput } from './input-validation';\nimport { type DurableStepInterceptor, isStepInterceptor } from './step-interceptor';\n\ninterface WorkflowInstance {\n run(ctx: WorkflowCtx, input: unknown): Promise<unknown>;\n}\n\ntype WorkflowFn = (ctx: WorkflowCtx, input: unknown) => Promise<unknown>;\n\n/**\n * Ensures the schema (auto-schema) and registers `@Workflow` providers on init, resumes runs left\n * incomplete by a previous process once booted, and drains in-flight runs on shutdown.\n *\n * For the shutdown drain to fire, enable Nest's shutdown hooks: `app.enableShutdownHooks()`.\n */\n@Injectable()\nexport class WorkflowRegistrar\n implements OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown\n{\n constructor(\n private readonly discovery: DiscoveryService,\n private readonly metadataScanner: MetadataScanner,\n private readonly engine: WorkflowEngine,\n @Inject(STATE_STORE) private readonly store: StateStore,\n @Inject(DURABLE_OPTIONS) private readonly options: DurableModuleOptions,\n ) {}\n\n async onApplicationBootstrap(): Promise<void> {\n // Only the worker role recovers runs left incomplete by a crash/deploy. A dashboard-only\n // instance (`worker: false`) must not pick up and re-run workflows — leave that to the workers.\n if (this.options.worker === false) return;\n await this.engine.recoverIncomplete();\n }\n\n /** On deploy/shutdown: stop picking up new runs and wait for in-flight ones to settle, then close\n * the transport(s) so the broker workers stop consuming and connections are released. Closing\n * AFTER the drain keeps the transport alive while in-flight runs dispatch/await their remote steps. */\n async onApplicationShutdown(): Promise<void> {\n await this.engine.drain(this.options.shutdownTimeoutMs);\n const transports = [\n this.options.transport,\n ...(this.options.transports ?? []).map((t) => t.transport),\n ];\n await Promise.allSettled(transports.map((t) => t?.close?.()));\n }\n\n async onModuleInit(): Promise<void> {\n if (this.options.autoSchema !== false) {\n await this.store.ensureSchema?.();\n }\n // Maps a workflow name to the workflow its dead runs route to. Built from each `@Workflow`'s\n // inline `@DeadLetter()` method (preferred) or its `deadLetterWorkflow` reference; the\n // module-level `deadLetterWorkflow` is the fallback applied in the onDead listener below.\n const deadLetterByWorkflow = new Map<string, string>();\n\n for (const wrapper of this.discovery.getProviders()) {\n const { instance } = wrapper;\n if (!instance || typeof instance !== 'object') continue;\n if (isStepInterceptor(instance.constructor)) {\n const interceptor = instance as DurableStepInterceptor;\n this.engine.use((invocation, next) => interceptor.intercept(invocation, next));\n }\n const entityMeta = getEntityMeta(instance.constructor);\n if (entityMeta) {\n this.engine.registerEntity(entityMeta.name, entityConfigFor(instance.constructor));\n continue; // an @Entity is not a @Workflow\n }\n const meta = getWorkflowMeta(instance.constructor);\n if (!meta) continue;\n const workflow = instance as WorkflowInstance;\n if (typeof workflow.run !== 'function') {\n throw new Error(`@Workflow ${meta.name} must define a run(ctx, input) method`);\n }\n // Input validation: a custom `validateInput` wins; otherwise build one from the class-validator\n // `inputSchema` DTO (lazy — class-validator is only required if a workflow uses inputSchema).\n const validateInput =\n meta.validateInput ??\n (meta.inputSchema ? classValidatorInput(meta.inputSchema) : undefined);\n const eventBatch = meta.debounce\n ? ({ mode: 'debounce', windowMs: parseDuration(meta.debounce) } as const)\n : meta.batch\n ? ({\n mode: 'batch',\n maxSize: meta.batch.maxSize,\n windowMs: parseDuration(meta.batch.within),\n } as const)\n : undefined;\n this.engine.register(meta.name, meta.version, (ctx, input) => workflow.run(ctx, input), {\n tags: meta.tags,\n singleton: meta.singleton,\n executionTimeout: meta.executionTimeout,\n validateInput,\n onEvent: getOnEvents(meta, instance.constructor),\n eventBatch,\n });\n\n const inline = this.findDeadLetterHandler(instance);\n if (inline && meta.deadLetterWorkflow) {\n // Two dead-letter targets for one workflow is ambiguous config, not a precedence question —\n // fail fast at boot rather than silently picking one.\n throw new Error(\n `@Workflow ${meta.name} declares both an inline @DeadLetter() method and a deadLetterWorkflow option. Use one: the inline handler, or the reference.`,\n );\n }\n if (inline) {\n // The inline handler is itself a durable workflow, registered as `<name>.dlq`, so it gets\n // checkpointing and a dashboard run linked to the dead run via the `dlq:<runId>` id.\n const dlqName = `${meta.name}.dlq`;\n this.engine.register(dlqName, meta.version, inline);\n deadLetterByWorkflow.set(meta.name, dlqName);\n } else if (meta.deadLetterWorkflow) {\n deadLetterByWorkflow.set(meta.name, workflowName(meta.deadLetterWorkflow));\n }\n }\n\n this.installDeadLetterRouting(deadLetterByWorkflow);\n }\n\n /** Returns the instance's `@DeadLetter()` method bound to the instance, or undefined if none. */\n private findDeadLetterHandler(instance: object): WorkflowFn | undefined {\n const prototype = Object.getPrototypeOf(instance);\n for (const methodName of this.metadataScanner.getAllMethodNames(prototype)) {\n const method = (instance as Record<string, unknown>)[methodName];\n if (typeof method === 'function' && isDeadLetterHandler(method)) {\n return (ctx, input) => method.call(instance, ctx, input);\n }\n }\n return undefined;\n }\n\n /**\n * Installs a single onDead listener that routes a dead run to its workflow's handler (from the\n * map) or the module-level `deadLetterWorkflow` default. The handler is started idempotently with\n * a `dlq:<runId>` id, so re-recovery never double-dispatches.\n */\n private installDeadLetterRouting(byWorkflow: Map<string, string>): void {\n const fallback = this.options.deadLetterWorkflow\n ? workflowName(this.options.deadLetterWorkflow)\n : undefined;\n if (byWorkflow.size === 0 && !fallback) return;\n this.engine.onDead((run: WorkflowRun) => {\n const target = byWorkflow.get(run.workflow) ?? fallback;\n if (!target) return;\n void this.engine\n .start(\n target,\n { deadRunId: run.id, workflow: run.workflow, input: run.input, error: run.error },\n `dlq:${run.id}`,\n )\n .catch(() => undefined);\n });\n }\n}\n","type ClassCtor = new (...args: any[]) => object;\n\n/**\n * Build a `validateInput` from a class-validator DTO class — the same `plainToInstance` + `validate`\n * NestJS runs in controllers. `class-validator` and `class-transformer` are lazy-required optional\n * peers (only needed if you use `@Workflow({ inputSchema })`), so they stay out of the type graph.\n */\nexport function classValidatorInput(cls: ClassCtor): (input: unknown) => Promise<void> {\n let cv: any;\n let ct: any;\n try {\n cv = require('class-validator');\n ct = require('class-transformer');\n } catch {\n throw new Error(\n '@Workflow({ inputSchema }) needs the optional peers \"class-validator\" and \"class-transformer\" — install them, or pass a `validateInput` function instead.',\n );\n }\n return async (input: unknown) => {\n const instance = ct.plainToInstance(cls, input);\n const errors = await cv.validate(instance, { whitelist: true });\n if (errors.length > 0) {\n const message = errors\n .map((e: any) => Object.values(e.constraints ?? { _: e.property }).join(', '))\n .join('; ');\n throw new Error(`invalid input for workflow: ${message}`);\n }\n };\n}\n","import type { StepInvocation } from '@dudousxd/nestjs-durable-core';\nimport 'reflect-metadata';\n\nexport const STEP_INTERCEPTOR_METADATA = Symbol('nestjs-durable:step-interceptor');\n\n/**\n * The shape a `@StepInterceptor()` provider must implement: `intercept(invocation, next)` wraps the\n * real execution of every local `ctx.step` (call `next()` to run the step body / next interceptor,\n * and return — or transform — its result). The engine-level {@link StepInterceptor} primitive, with\n * NestJS dependency injection.\n */\nexport interface DurableStepInterceptor {\n intercept(invocation: StepInvocation, next: () => Promise<unknown>): Promise<unknown>;\n}\n\n/**\n * Marks an `@Injectable()` class as a durable step interceptor. The module discovers it on boot and\n * registers its `intercept` method with the engine (so it can inject loggers/tracers/etc.). First\n * declared is outermost. Interceptors fire only when a step actually executes, never on replay.\n */\nexport function StepInterceptor(): ClassDecorator {\n return (target) => {\n Reflect.defineMetadata(STEP_INTERCEPTOR_METADATA, true, target);\n };\n}\n\n// biome-ignore lint/complexity/noBannedTypes: matches reflect-metadata's class target type\nexport function isStepInterceptor(target: Function): boolean {\n return Reflect.getMetadata(STEP_INTERCEPTOR_METADATA, target) === true;\n}\n","import { randomUUID } from 'node:crypto';\nimport {\n type RunResult,\n type StartOptions,\n type WorkflowClass,\n WorkflowEngine,\n type WorkflowInputOf,\n} from '@dudousxd/nestjs-durable-core';\nimport { Injectable } from '@nestjs/common';\n\n/** Public entry point for starting and resuming workflow runs. */\n@Injectable()\nexport class WorkflowService {\n constructor(private readonly engine: WorkflowEngine) {}\n\n /**\n * Enqueue a workflow run — it creates the run (`pending`) and returns `{ runId, status: 'pending' }`\n * immediately; a worker executes the body (so the caller never blocks on workflow logic). Use\n * {@link waitForRun} when you need the outcome. Pass the workflow's **class**\n * (`start(CheckoutWorkflow, input)`) for a typed input + refactor-safety, or a **name** string for a\n * cross-runtime workflow. `runId` defaults to a random id; pass your own to make the start idempotent\n * (a redelivery returns the existing run). `opts.tags` are merged with the workflow's static\n * `@Workflow({ tags })`; `opts.searchAttributes` stamp typed, queryable run data.\n */\n start<C extends WorkflowClass>(\n workflow: C,\n input: WorkflowInputOf<C>,\n runId?: string,\n opts?: StartOptions,\n ): Promise<RunResult>;\n start(workflow: string, input: unknown, runId?: string, opts?: StartOptions): Promise<RunResult>;\n start(\n workflow: string,\n input: unknown,\n runId: string = randomUUID(),\n opts?: StartOptions,\n ): Promise<RunResult> {\n return this.engine.start(workflow, input, runId, opts);\n }\n\n resume(runId: string): Promise<RunResult> {\n return this.engine.resume(runId);\n }\n\n /**\n * Resolve once a run settles — terminal (completed/failed/cancelled/dead) or suspended. `start`\n * only enqueues (a worker runs the body), so pair them when a request needs the outcome:\n * `const { runId } = await svc.start(...); const result = await svc.waitForRun(runId)`.\n */\n waitForRun(runId: string, opts?: { timeoutMs?: number }): Promise<RunResult> {\n return this.engine.waitForRun(runId, opts);\n }\n\n /** Deliver an external signal (e.g. from a webhook) to the run waiting on `token`. */\n signal(token: string, payload: unknown): Promise<RunResult | null> {\n return this.engine.signal(token, payload);\n }\n\n /**\n * Ensure a run exists for `runId`, then deliver a signal to it — race-free (the signal is buffered\n * until the run reaches its `waitForSignal`). The durable-entity / accumulator pattern: one\n * long-lived run per key fed events by many calls. See {@link WorkflowEngine.signalWithStart}.\n */\n signalWithStart<C extends WorkflowClass>(\n workflow: C,\n input: WorkflowInputOf<C>,\n runId: string,\n signal: { token: string; payload?: unknown },\n opts?: StartOptions,\n ): Promise<{ runId: string }>;\n signalWithStart(\n workflow: string,\n input: unknown,\n runId: string,\n signal: { token: string; payload?: unknown },\n opts?: StartOptions,\n ): Promise<{ runId: string }>;\n signalWithStart(\n workflow: string,\n input: unknown,\n runId: string,\n signal: { token: string; payload?: unknown },\n opts?: StartOptions,\n ): Promise<{ runId: string }> {\n return this.engine.signalWithStart(workflow, input, runId, signal, opts);\n }\n\n /**\n * Publish a named event. Resumes runs waiting on it via `ctx.waitForEvent(name, { match })` and\n * starts a fresh run of every workflow subscribed via `@Workflow({ onEvent })` / `@OnEvent` (the\n * payload becomes its input). Pass `opts.id` to dedupe redeliveries. Returns how many runs it\n * touched (resumed + started).\n */\n publishEvent(name: string, payload: unknown, opts?: { id?: string }): Promise<number> {\n return this.engine.publishEvent(name, payload, opts);\n }\n}\n"],"mappings":"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","names":["WORKFLOW_METADATA","Symbol","Workflow","options","target","meta","name","version","deadLetterWorkflow","tags","singleton","executionTimeout","inputSchema","validateInput","onEvent","debounce","batch","Reflect","defineMetadata","Object","defineProperty","WORKFLOW_NAME_KEY","value","configurable","getWorkflowMeta","getMetadata","DURABLE_STEP_METADATA","DurableStep","_target","_propertyKey","descriptor","getDurableStepMeta","method","DEAD_LETTER_METADATA","DeadLetter","isDeadLetterHandler","ON_EVENT_METADATA","OnEvent","events","existing","getOnEvents","fromDecorator","Set","import_nestjs_durable_core","import_common","import_core","import_nestjs_durable_core","supportsHandle","transport","handle","DurableStepRegistrar","discovery","metadataScanner","options","onModuleInit","worker","wrapper","getProviders","instance","prototype","Object","getPrototypeOf","methodName","getAllMethodNames","method","meta","getDurableStepMeta","name","input","log","import_nestjs_durable_core","import_common","import_reflect_metadata","ENTITY_METADATA","Symbol","ENTITY_ON_METADATA","Entity","options","target","Reflect","defineMetadata","On","op","propertyKey","ctor","ops","getMetadata","Map","set","getEntityMeta","entityConfigFor","Cls","handlers","method","state","arg","Object","setPrototypeOf","prototype","fn","Error","call","initialState","EntityService","engine","signal","name","key","signalEntity","getState","getEntityState","import_nestjs_durable_core","import_common","TimerPoller","timer","polling","unsubscribeEnqueued","engine","options","onApplicationBootstrap","worker","onEnqueued","runId","runOne","poll","intervalMs","timerPollMs","setInterval","unref","onModuleDestroy","clearInterval","runPending","recoverIncomplete","resumeDueTimers","sweepTimeouts","schedules","length","runSchedules","Date","now","CONTEXT_ACCESSOR","Symbol","for","import_nestjs_durable_core","import_common","import_core","classValidatorInput","cls","cv","ct","require","Error","input","instance","plainToInstance","errors","validate","whitelist","length","message","map","e","Object","values","constraints","_","property","join","import_reflect_metadata","STEP_INTERCEPTOR_METADATA","Symbol","StepInterceptor","target","Reflect","defineMetadata","isStepInterceptor","getMetadata","WorkflowRegistrar","discovery","metadataScanner","engine","store","options","onApplicationBootstrap","worker","recoverIncomplete","onApplicationShutdown","drain","shutdownTimeoutMs","transports","transport","map","t","Promise","allSettled","close","onModuleInit","autoSchema","ensureSchema","deadLetterByWorkflow","Map","wrapper","getProviders","instance","isStepInterceptor","interceptor","use","invocation","next","intercept","entityMeta","getEntityMeta","registerEntity","name","entityConfigFor","meta","getWorkflowMeta","workflow","run","Error","validateInput","inputSchema","classValidatorInput","undefined","eventBatch","debounce","mode","windowMs","parseDuration","batch","maxSize","within","register","version","ctx","input","tags","singleton","executionTimeout","onEvent","getOnEvents","inline","findDeadLetterHandler","deadLetterWorkflow","dlqName","set","workflowName","installDeadLetterRouting","prototype","Object","getPrototypeOf","methodName","getAllMethodNames","method","isDeadLetterHandler","call","byWorkflow","fallback","size","onDead","target","get","start","deadRunId","id","error","catch","import_nestjs_durable_core","import_common","WorkflowService","engine","start","workflow","input","runId","randomUUID","opts","resume","waitForRun","signal","token","payload","signalWithStart","publishEvent","name","resolveAccessor","moduleRef","get","CONTEXT_ACCESSOR","strict","undefined","carrierFromAccessor","accessor","carrier","traceId","tenantId","userRef","isContextRuntime","x","deserialize","resolveContextRuntime","specifier","mod","Context","isControlPlane","publishControl","onControl","DurableModule","forRoot","options","build","provide","DURABLE_OPTIONS","useValue","forRootAsync","useFactory","inject","optionsProvider","module","global","imports","DiscoveryModule","providers","STATE_STORE","store","TRANSPORT","transport","WorkflowEngine","opts","primary","transports","context","runtime","rehydrate","fn","Object","keys","length","engine","controlPlane","leaseMs","maxRecoveryAttempts","instanceId","webhookUrl","traceparent","compensationRetries","runDispatcher","worker","dispatch","queue","queues","registerQueue","ModuleRef","WorkflowService","EntityService","WorkflowRegistrar","DurableStepRegistrar","TimerPoller","exports"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { StepError, WorkflowRef, SingletonConfig, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, QueueConfig, WorkflowEngine, EntityHandler, StepInvocation, WorkflowClass, WorkflowInputOf, StartOptions, RunResult } from '@dudousxd/nestjs-durable-core';
|
|
2
|
+
import { DynamicModule, InjectionToken } from '@nestjs/common';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Local, structural mirror of `@dudousxd/nestjs-context`'s public accessor
|
|
6
|
+
* (`packages/core/src/accessor.ts`).
|
|
7
|
+
*
|
|
8
|
+
* We deliberately do NOT import nestjs-context (it is an OPTIONAL peer). Instead
|
|
9
|
+
* we declare the same shape here and inject it via the shared
|
|
10
|
+
* {@link CONTEXT_ACCESSOR} token with `@Optional()`. Any object that structurally
|
|
11
|
+
* satisfies this interface — including nestjs-context's real accessor — works.
|
|
12
|
+
*
|
|
13
|
+
* Kept byte-aligned with nestjs-context's `ContextAccessor` (and the identical
|
|
14
|
+
* mirror in `@dudousxd/nestjs-authz`): `traceId()` / `tenantId()` are REQUIRED
|
|
15
|
+
* (the real accessor always provides them) and `get()` is included, so the
|
|
16
|
+
* structural match stays exact when DurableModule auto-feeds the carrier.
|
|
17
|
+
*/
|
|
18
|
+
interface UserRef {
|
|
19
|
+
type: string;
|
|
20
|
+
id: string | number;
|
|
21
|
+
}
|
|
22
|
+
/** Opaque shape of the context store. durable never reads it; mirrors the upstream surface. */
|
|
23
|
+
type ContextStore = Record<string, unknown>;
|
|
24
|
+
interface ContextAccessor {
|
|
25
|
+
/** Trace id for the current request, or `undefined` when unavailable. */
|
|
26
|
+
traceId(): string | undefined;
|
|
27
|
+
/** Current tenant id, or `undefined` when no multi-tenant context is populated. */
|
|
28
|
+
tenantId(): string | undefined;
|
|
29
|
+
/** Reference to the current user, or `undefined` when unauthenticated. */
|
|
30
|
+
userRef(): UserRef | undefined;
|
|
31
|
+
/** The raw context store for the current request, or `undefined`. */
|
|
32
|
+
get(): ContextStore | undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
declare const WORKFLOW_METADATA: unique symbol;
|
|
36
|
+
interface WorkflowMeta {
|
|
37
|
+
name: string;
|
|
38
|
+
version: string;
|
|
39
|
+
/** The workflow this workflow's dead runs route to (a name or a class). See `WorkflowOptions`. */
|
|
40
|
+
deadLetterWorkflow?: WorkflowRef | undefined;
|
|
41
|
+
/** Static searchable labels stamped on every run of this workflow. See `WorkflowOptions`. */
|
|
42
|
+
tags?: string[] | undefined;
|
|
43
|
+
/** Per-key serialization (a durable mutex). See `WorkflowOptions`. */
|
|
44
|
+
singleton?: SingletonConfig | undefined;
|
|
45
|
+
/** Max wall-clock lifetime before a run is cancelled (e.g. `'2h'`). See `WorkflowOptions`. */
|
|
46
|
+
executionTimeout?: string | number | undefined;
|
|
47
|
+
/** class-validator DTO validated at start. See `WorkflowOptions`. */
|
|
48
|
+
inputSchema?: (new (...args: any[]) => object) | undefined;
|
|
49
|
+
/** Custom input validator (throws on invalid). See `WorkflowOptions`. */
|
|
50
|
+
validateInput?: ((input: unknown) => void | Promise<void>) | undefined;
|
|
51
|
+
/** Event names that start a fresh run of this workflow. See `WorkflowOptions`. */
|
|
52
|
+
onEvent?: string[] | undefined;
|
|
53
|
+
/** Debounce `onEvent` triggers — fire once it's quiet for this long. See `WorkflowOptions`. */
|
|
54
|
+
debounce?: string | number | undefined;
|
|
55
|
+
/** Batch `onEvent` triggers — fire on size or window. See `WorkflowOptions`. */
|
|
56
|
+
batch?: {
|
|
57
|
+
maxSize: number;
|
|
58
|
+
within: string | number;
|
|
59
|
+
} | undefined;
|
|
60
|
+
}
|
|
61
|
+
interface WorkflowOptions {
|
|
62
|
+
name: string;
|
|
63
|
+
version?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Route this workflow's dead-lettered runs to another **registered** workflow — by class
|
|
66
|
+
* (`deadLetterWorkflow: CheckoutDlqWorkflow`, refactor-safe) or by name for a cross-runtime
|
|
67
|
+
* handler. For a handler co-located on the same class, prefer an inline `@DeadLetter()` method
|
|
68
|
+
* instead — it takes precedence, and declaring both is a boot-time error. The handler receives a
|
|
69
|
+
* {@link DeadLetter} payload, idempotent by a `dlq:<runId>` id.
|
|
70
|
+
*/
|
|
71
|
+
deadLetterWorkflow?: WorkflowRef;
|
|
72
|
+
/**
|
|
73
|
+
* Static labels stamped on **every run** of this workflow (e.g. `tags: ['etl', 'critical']`) and
|
|
74
|
+
* merged with any per-run tags passed to `start`. Searchable/filterable in the dashboard.
|
|
75
|
+
*/
|
|
76
|
+
tags?: string[];
|
|
77
|
+
/**
|
|
78
|
+
* Serialize runs of this workflow that share a key — a durable FIFO mutex. e.g.
|
|
79
|
+
* `singleton: { key: (input) => `base:${input.baseId}` }` runs at most one pipeline per base at a
|
|
80
|
+
* time; same-key runs queue (suspended) and admit in creation order as slots free. `limit` (default
|
|
81
|
+
* 1) raises the concurrency.
|
|
82
|
+
*/
|
|
83
|
+
singleton?: SingletonConfig;
|
|
84
|
+
/**
|
|
85
|
+
* Max wall-clock lifetime for a run of this workflow (e.g. `'2h'`, `'7 days'`, or ms). A run that
|
|
86
|
+
* outlives it is moved to `cancelled` (`execution_timeout`) by the timer poller — a backstop for
|
|
87
|
+
* runs that get stuck or loop forever. Omit for no limit.
|
|
88
|
+
*/
|
|
89
|
+
executionTimeout?: string | number;
|
|
90
|
+
/**
|
|
91
|
+
* Validate the workflow input at `start` against a **class-validator DTO** (the same
|
|
92
|
+
* `plainToInstance` + `validate` NestJS runs in controllers) — invalid input is rejected before any
|
|
93
|
+
* run is created. Needs the optional peers `class-validator` + `class-transformer`.
|
|
94
|
+
* `@Workflow({ inputSchema: CheckoutInput })`.
|
|
95
|
+
*/
|
|
96
|
+
inputSchema?: new (...args: any[]) => object;
|
|
97
|
+
/**
|
|
98
|
+
* Custom input validator (throws on invalid) — an escape hatch for zod/yup/etc. instead of
|
|
99
|
+
* `inputSchema`. Takes precedence over `inputSchema` if both are set.
|
|
100
|
+
*/
|
|
101
|
+
validateInput?: (input: unknown) => void | Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Start a fresh run of this workflow whenever any of these events is published via
|
|
104
|
+
* `publishEvent(name, payload)` — the payload becomes the run's input. e.g.
|
|
105
|
+
* `onEvent: ['user.registered', 'user.invited']`. The same subscription can also be declared with
|
|
106
|
+
* the `@OnEvent(...)` class decorator; the two are merged.
|
|
107
|
+
*/
|
|
108
|
+
onEvent?: string[];
|
|
109
|
+
/**
|
|
110
|
+
* Coalesce `onEvent` triggers by **debouncing** — start one run only once events have been quiet
|
|
111
|
+
* for this long (resets on each event), with the LAST payload. e.g. `debounce: '30s'`.
|
|
112
|
+
*/
|
|
113
|
+
debounce?: string | number;
|
|
114
|
+
/**
|
|
115
|
+
* Coalesce `onEvent` triggers by **batching** — start one run with all payloads (`{ events: [...] }`)
|
|
116
|
+
* once `maxSize` is reached or `within` elapses from the first event. e.g.
|
|
117
|
+
* `batch: { maxSize: 100, within: '10s' }`.
|
|
118
|
+
*/
|
|
119
|
+
batch?: {
|
|
120
|
+
maxSize: number;
|
|
121
|
+
within: string | number;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Marks a provider class as a durable workflow. Its `run(ctx, input)` method becomes the
|
|
126
|
+
* workflow function the engine executes and replays.
|
|
127
|
+
*/
|
|
128
|
+
declare function Workflow(options: WorkflowOptions): ClassDecorator;
|
|
129
|
+
declare function getWorkflowMeta(target: Function): WorkflowMeta | undefined;
|
|
130
|
+
declare const DURABLE_STEP_METADATA: unique symbol;
|
|
131
|
+
interface DurableStepMeta {
|
|
132
|
+
name: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Marks a provider method as the handler for a remote step `name`. An in-process transport
|
|
136
|
+
* (e.g. the event-emitter transport) routes dispatched tasks to it. The method's single
|
|
137
|
+
* argument is the step input; its return value is the step output.
|
|
138
|
+
*/
|
|
139
|
+
declare function DurableStep(name: string): MethodDecorator;
|
|
140
|
+
declare function getDurableStepMeta(method: Function): DurableStepMeta | undefined;
|
|
141
|
+
declare const DEAD_LETTER_METADATA: unique symbol;
|
|
142
|
+
/**
|
|
143
|
+
* The payload a dead-letter handler receives: the dead run's id, its workflow, the input it was
|
|
144
|
+
* started with (typed via `TInput`), and the failure that killed it.
|
|
145
|
+
*/
|
|
146
|
+
interface DeadLetter<TInput = unknown> {
|
|
147
|
+
/** Id of the run that was dead-lettered (inspectable + retriable in the dashboard). */
|
|
148
|
+
deadRunId: string;
|
|
149
|
+
/** Name of the workflow whose run died. */
|
|
150
|
+
workflow: string;
|
|
151
|
+
/** The original input the dead run was started with. */
|
|
152
|
+
input: TInput;
|
|
153
|
+
/** The structured error that moved the run to `dead`, when known. */
|
|
154
|
+
error?: StepError;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Marks a method on a `@Workflow` class as that workflow's **inline dead-letter handler**. When a
|
|
158
|
+
* run of the workflow is moved to `dead` (exceeded `maxRecoveryAttempts`), this method runs — as a
|
|
159
|
+
* durable workflow itself, auto-registered as `<workflow>.dlq` with a `dlq:<runId>` id — receiving a
|
|
160
|
+
* {@link DeadLetter} payload. It shares the class's injected dependencies, so the handler lives in
|
|
161
|
+
* the same file as the workflow it protects.
|
|
162
|
+
*
|
|
163
|
+
* Takes precedence over `@Workflow({ deadLetterWorkflow })` and the module-level `deadLetterWorkflow`
|
|
164
|
+
* default. The method signature is `(ctx, dead)` — the same `ctx` a workflow `run` gets.
|
|
165
|
+
*/
|
|
166
|
+
declare function DeadLetter(): MethodDecorator;
|
|
167
|
+
declare function isDeadLetterHandler(method: Function): boolean;
|
|
168
|
+
declare const ON_EVENT_METADATA: unique symbol;
|
|
169
|
+
/**
|
|
170
|
+
* Subscribe a `@Workflow` class to one or more events: when any of them is published via
|
|
171
|
+
* `publishEvent(name, payload)`, a fresh run of the workflow starts with the payload as input.
|
|
172
|
+
* Equivalent to `@Workflow({ onEvent })` — use whichever reads better; multiple `@OnEvent(...)`
|
|
173
|
+
* decorators and the option are all merged.
|
|
174
|
+
*/
|
|
175
|
+
declare function OnEvent(...events: string[]): ClassDecorator;
|
|
176
|
+
/** All events a workflow class subscribes to — the union of `@Workflow({ onEvent })` and `@OnEvent`. */
|
|
177
|
+
declare function getOnEvents(meta: WorkflowMeta, target: object): string[];
|
|
178
|
+
|
|
179
|
+
interface DurableModuleOptions {
|
|
180
|
+
store: StateStore;
|
|
181
|
+
transport?: Transport;
|
|
182
|
+
/**
|
|
183
|
+
* An ordered pool of named transports for failover / multi-broker setups. The engine dispatches on
|
|
184
|
+
* the first and fails over to the next; a step pins one via `ctx.call(step, input, { transport })`.
|
|
185
|
+
* Use instead of `transport`.
|
|
186
|
+
*/
|
|
187
|
+
transports?: NamedTransport[];
|
|
188
|
+
/**
|
|
189
|
+
* Cross-instance broadcast pub/sub (lifecycle events + cancellation). Defaults to the (first)
|
|
190
|
+
* transport when it can broadcast (event-emitter, BullMQ); set explicitly to use a dedicated one.
|
|
191
|
+
*/
|
|
192
|
+
controlPlane?: ControlPlane;
|
|
193
|
+
/** Interval (ms) for the durable-timer poller. `0` disables it. Defaults to 1000. */
|
|
194
|
+
timerPollMs?: number;
|
|
195
|
+
/**
|
|
196
|
+
* Auto-create the durable tables on boot via `store.ensureSchema()`. Defaults to true. Turn
|
|
197
|
+
* off in production and call the store adapter's `ensure*DurableSchema()` from a migration.
|
|
198
|
+
*/
|
|
199
|
+
autoSchema?: boolean;
|
|
200
|
+
/**
|
|
201
|
+
* Multi-instance recovery lease, in ms — how long an instance owns a run it picked up before
|
|
202
|
+
* another may take over. Defaults to 30000. Set above your longest synchronous run.
|
|
203
|
+
*/
|
|
204
|
+
leaseMs?: number;
|
|
205
|
+
/** Unique id for this instance (for leases). Defaults to a random id. */
|
|
206
|
+
instanceId?: string;
|
|
207
|
+
/**
|
|
208
|
+
* Cap recovery attempts before a still-`running` run is moved to the `dead` dead-letter state
|
|
209
|
+
* (a poison pill that crashes the process every boot). Omit for unlimited.
|
|
210
|
+
*/
|
|
211
|
+
maxRecoveryAttempts?: number;
|
|
212
|
+
/**
|
|
213
|
+
* The **default** workflow to route dead-lettered runs to, for workflows that don't declare their
|
|
214
|
+
* own. When a run is moved to `dead` (exceeded `maxRecoveryAttempts`), the started handler gets a
|
|
215
|
+
* `DeadLetter` payload `{ deadRunId, workflow, input, error }` (idempotent by a `dlq:<runId>` id) —
|
|
216
|
+
* it can alert, compensate, or queue for review. Resolution per dead run: the workflow's inline
|
|
217
|
+
* `@DeadLetter()` method → its `@Workflow({ deadLetterWorkflow })` reference → this default. Omit
|
|
218
|
+
* everything to just leave dead runs parked (inspectable + retriable from the dashboard). Accepts a
|
|
219
|
+
* workflow class (refactor-safe) or a name (cross-runtime).
|
|
220
|
+
*/
|
|
221
|
+
deadLetterWorkflow?: WorkflowRef;
|
|
222
|
+
/**
|
|
223
|
+
* Whether this instance plays the **worker** role: register `@DurableStep` handlers (consume the
|
|
224
|
+
* transport), recover incomplete runs on boot, and poll due timers. Defaults to `true`. Set
|
|
225
|
+
* `false` for a **dashboard/dispatch-only** instance (e.g. an API pod) that mounts the control
|
|
226
|
+
* plane and reads the store but must not process or recover workflows — leave that to the workers.
|
|
227
|
+
*/
|
|
228
|
+
worker?: boolean;
|
|
229
|
+
/** Max ms to wait for in-flight runs on shutdown before exiting. Defaults to 10000. */
|
|
230
|
+
shutdownTimeoutMs?: number;
|
|
231
|
+
/**
|
|
232
|
+
* Recurring workflows to start on a schedule (fixed interval or cron). The timer poller fires
|
|
233
|
+
* them each tick on **worker** instances only; `engine.start` is idempotent by the schedule's
|
|
234
|
+
* time-bucket run id, so racing instances start each window exactly once. Cron schedules need the
|
|
235
|
+
* optional `cron-parser` peer dependency.
|
|
236
|
+
*/
|
|
237
|
+
schedules?: ScheduledWorkflow[];
|
|
238
|
+
/**
|
|
239
|
+
* Build the public callback URL for a `ctx.webhook()` token, e.g.
|
|
240
|
+
* ``(t) => `https://api.example.com/durable/api/webhooks/${t}` ``. Populates `DurableWebhook.url`
|
|
241
|
+
* so a step can hand the URL to a third party. The dashboard's `POST webhooks/:token` receives the
|
|
242
|
+
* callback. Omit to build URLs yourself from the token.
|
|
243
|
+
*/
|
|
244
|
+
webhookUrl?: (token: string) => string;
|
|
245
|
+
/**
|
|
246
|
+
* Flow-control queues for remote steps, registered on the engine at startup. Reference one from a
|
|
247
|
+
* workflow with `ctx.call(step, input, { queue: name })` to cap its concurrency / admission rate.
|
|
248
|
+
*/
|
|
249
|
+
queues?: QueueConfig[];
|
|
250
|
+
/**
|
|
251
|
+
* Provide the current W3C `traceparent` to stamp on dispatched remote tasks, so workers continue
|
|
252
|
+
* the distributed trace. Pass `otelTraceparent` from `@dudousxd/nestjs-durable-otel`.
|
|
253
|
+
*/
|
|
254
|
+
traceparent?: () => string | undefined;
|
|
255
|
+
/**
|
|
256
|
+
* Provide an opaque context carrier (tenant / user / correlation ids) to stamp on dispatched remote
|
|
257
|
+
* tasks, so workers re-expose it to the step handler alongside the `traceparent`. The engine never
|
|
258
|
+
* inspects its shape.
|
|
259
|
+
*
|
|
260
|
+
* **Auto-feed**: if you omit this AND `@dudousxd/nestjs-context` is installed (its accessor is bound
|
|
261
|
+
* to the shared `CONTEXT_ACCESSOR` token), DurableModule defaults this to a reader that builds
|
|
262
|
+
* `{ traceId, tenantId, userRef }` from the accessor — so a workflow dispatched within a request
|
|
263
|
+
* automatically carries the originating context across process boundaries. Pass your own reader to
|
|
264
|
+
* override the auto-feed; with neither, the carrier is omitted (unchanged behavior).
|
|
265
|
+
*
|
|
266
|
+
* Re-evaluated at each (re)dispatch — including a retry or a crash/scale-down resume that the engine
|
|
267
|
+
* drives OUTSIDE the originating request scope, where this reader may return empty or stale values.
|
|
268
|
+
* Treat the carrier as best-effort correlation/propagation metadata only — do NOT treat it as an
|
|
269
|
+
* authorization boundary.
|
|
270
|
+
*/
|
|
271
|
+
context?: () => Record<string, unknown> | undefined;
|
|
272
|
+
/** Attempts for each saga compensation when a run fails. Default 1 (no retry). Idempotent undos. */
|
|
273
|
+
compensationRetries?: number;
|
|
274
|
+
}
|
|
275
|
+
interface DurableModuleAsyncOptions {
|
|
276
|
+
useFactory: (...args: never[]) => DurableModuleOptions | Promise<DurableModuleOptions>;
|
|
277
|
+
inject?: InjectionToken[];
|
|
278
|
+
}
|
|
279
|
+
declare class DurableModule {
|
|
280
|
+
static forRoot(options: DurableModuleOptions): DynamicModule;
|
|
281
|
+
static forRootAsync(options: DurableModuleAsyncOptions): DynamicModule;
|
|
282
|
+
private static build;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
declare const ENTITY_METADATA: unique symbol;
|
|
286
|
+
declare const ENTITY_ON_METADATA: unique symbol;
|
|
287
|
+
/**
|
|
288
|
+
* Marks an `@Injectable()` class as a **durable entity** (a virtual object): its `@On(op)` methods run
|
|
289
|
+
* serialized per key over the instance's fields as durable state. e.g.
|
|
290
|
+
*
|
|
291
|
+
* ```ts
|
|
292
|
+
* @Entity({ name: 'cart' }) @Injectable()
|
|
293
|
+
* class Cart { items: Item[] = []; @On('add') add(i: Item) { this.items.push(i); } @On('list') list() { return this.items; } }
|
|
294
|
+
* ```
|
|
295
|
+
*
|
|
296
|
+
* The class must be **constructible with no arguments** (a fresh instance is the initial state per key)
|
|
297
|
+
* — keep entities pure state, no DI. Drive them with `EntityService` or `ctx.signalEntity`/`callEntity`.
|
|
298
|
+
*/
|
|
299
|
+
declare function Entity(options: {
|
|
300
|
+
name: string;
|
|
301
|
+
}): ClassDecorator;
|
|
302
|
+
/** Marks an entity method as the handler for operation `op`. */
|
|
303
|
+
declare function On(op: string): MethodDecorator;
|
|
304
|
+
declare function getEntityMeta(target: Function): {
|
|
305
|
+
name: string;
|
|
306
|
+
} | undefined;
|
|
307
|
+
/**
|
|
308
|
+
* Build the engine `EntityConfig` for a discovered `@Entity` class: a fresh instance per key, and
|
|
309
|
+
* handlers that rehydrate the class prototype onto the (serialized) state before dispatching the op,
|
|
310
|
+
* so methods work after replay.
|
|
311
|
+
*/
|
|
312
|
+
declare function entityConfigFor(ctor: Function): {
|
|
313
|
+
initialState: () => object;
|
|
314
|
+
handlers: Record<string, EntityHandler>;
|
|
315
|
+
};
|
|
316
|
+
/** Inject this to drive durable entities from outside a workflow. */
|
|
317
|
+
declare class EntityService {
|
|
318
|
+
private readonly engine;
|
|
319
|
+
constructor(engine: WorkflowEngine);
|
|
320
|
+
/** Send an operation to an entity (fire-and-forget; ordered + exactly-once per key). */
|
|
321
|
+
signal(name: string, key: string, op: string, arg?: unknown): Promise<void>;
|
|
322
|
+
/** Read an entity's current durable state (or undefined if it has none yet). */
|
|
323
|
+
getState<S = unknown>(name: string, key: string): Promise<S | undefined>;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
declare const STEP_INTERCEPTOR_METADATA: unique symbol;
|
|
327
|
+
/**
|
|
328
|
+
* The shape a `@StepInterceptor()` provider must implement: `intercept(invocation, next)` wraps the
|
|
329
|
+
* real execution of every local `ctx.step` (call `next()` to run the step body / next interceptor,
|
|
330
|
+
* and return — or transform — its result). The engine-level {@link StepInterceptor} primitive, with
|
|
331
|
+
* NestJS dependency injection.
|
|
332
|
+
*/
|
|
333
|
+
interface DurableStepInterceptor {
|
|
334
|
+
intercept(invocation: StepInvocation, next: () => Promise<unknown>): Promise<unknown>;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Marks an `@Injectable()` class as a durable step interceptor. The module discovers it on boot and
|
|
338
|
+
* registers its `intercept` method with the engine (so it can inject loggers/tracers/etc.). First
|
|
339
|
+
* declared is outermost. Interceptors fire only when a step actually executes, never on replay.
|
|
340
|
+
*/
|
|
341
|
+
declare function StepInterceptor(): ClassDecorator;
|
|
342
|
+
declare function isStepInterceptor(target: Function): boolean;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Cross-lib injection token for the current-request context accessor, owned by
|
|
346
|
+
* `@dudousxd/nestjs-context`. We do NOT import nestjs-context (it is an OPTIONAL
|
|
347
|
+
* peer dependency) — instead we share its well-known token by value so DI
|
|
348
|
+
* resolves the same provider when nestjs-context is installed and present.
|
|
349
|
+
*
|
|
350
|
+
* `Symbol.for(key)` uses the global symbol registry, so this resolves to the
|
|
351
|
+
* SAME symbol instance as nestjs-context's `tokens.ts` (and the identical token
|
|
352
|
+
* declared by `@dudousxd/nestjs-authz`) without any import. The key MUST stay
|
|
353
|
+
* byte-identical with nestjs-context's export.
|
|
354
|
+
*/
|
|
355
|
+
declare const CONTEXT_ACCESSOR: unique symbol;
|
|
356
|
+
|
|
357
|
+
/** Public entry point for starting and resuming workflow runs. */
|
|
358
|
+
declare class WorkflowService {
|
|
359
|
+
private readonly engine;
|
|
360
|
+
constructor(engine: WorkflowEngine);
|
|
361
|
+
/**
|
|
362
|
+
* Enqueue a workflow run — it creates the run (`pending`) and returns `{ runId, status: 'pending' }`
|
|
363
|
+
* immediately; a worker executes the body (so the caller never blocks on workflow logic). Use
|
|
364
|
+
* {@link waitForRun} when you need the outcome. Pass the workflow's **class**
|
|
365
|
+
* (`start(CheckoutWorkflow, input)`) for a typed input + refactor-safety, or a **name** string for a
|
|
366
|
+
* cross-runtime workflow. `runId` defaults to a random id; pass your own to make the start idempotent
|
|
367
|
+
* (a redelivery returns the existing run). `opts.tags` are merged with the workflow's static
|
|
368
|
+
* `@Workflow({ tags })`; `opts.searchAttributes` stamp typed, queryable run data.
|
|
369
|
+
*/
|
|
370
|
+
start<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, runId?: string, opts?: StartOptions): Promise<RunResult>;
|
|
371
|
+
start(workflow: string, input: unknown, runId?: string, opts?: StartOptions): Promise<RunResult>;
|
|
372
|
+
resume(runId: string): Promise<RunResult>;
|
|
373
|
+
/**
|
|
374
|
+
* Resolve once a run settles — terminal (completed/failed/cancelled/dead) or suspended. `start`
|
|
375
|
+
* only enqueues (a worker runs the body), so pair them when a request needs the outcome:
|
|
376
|
+
* `const { runId } = await svc.start(...); const result = await svc.waitForRun(runId)`.
|
|
377
|
+
*/
|
|
378
|
+
waitForRun(runId: string, opts?: {
|
|
379
|
+
timeoutMs?: number;
|
|
380
|
+
}): Promise<RunResult>;
|
|
381
|
+
/** Deliver an external signal (e.g. from a webhook) to the run waiting on `token`. */
|
|
382
|
+
signal(token: string, payload: unknown): Promise<RunResult | null>;
|
|
383
|
+
/**
|
|
384
|
+
* Ensure a run exists for `runId`, then deliver a signal to it — race-free (the signal is buffered
|
|
385
|
+
* until the run reaches its `waitForSignal`). The durable-entity / accumulator pattern: one
|
|
386
|
+
* long-lived run per key fed events by many calls. See {@link WorkflowEngine.signalWithStart}.
|
|
387
|
+
*/
|
|
388
|
+
signalWithStart<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, runId: string, signal: {
|
|
389
|
+
token: string;
|
|
390
|
+
payload?: unknown;
|
|
391
|
+
}, opts?: StartOptions): Promise<{
|
|
392
|
+
runId: string;
|
|
393
|
+
}>;
|
|
394
|
+
signalWithStart(workflow: string, input: unknown, runId: string, signal: {
|
|
395
|
+
token: string;
|
|
396
|
+
payload?: unknown;
|
|
397
|
+
}, opts?: StartOptions): Promise<{
|
|
398
|
+
runId: string;
|
|
399
|
+
}>;
|
|
400
|
+
/**
|
|
401
|
+
* Publish a named event. Resumes runs waiting on it via `ctx.waitForEvent(name, { match })` and
|
|
402
|
+
* starts a fresh run of every workflow subscribed via `@Workflow({ onEvent })` / `@OnEvent` (the
|
|
403
|
+
* payload becomes its input). Pass `opts.id` to dedupe redeliveries. Returns how many runs it
|
|
404
|
+
* touched (resumed + started).
|
|
405
|
+
*/
|
|
406
|
+
publishEvent(name: string, payload: unknown, opts?: {
|
|
407
|
+
id?: string;
|
|
408
|
+
}): Promise<number>;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA, DeadLetter, DurableModule, type DurableModuleAsyncOptions, type DurableModuleOptions, DurableStep, type DurableStepInterceptor, type DurableStepMeta, ENTITY_METADATA, ENTITY_ON_METADATA, Entity, EntityService, ON_EVENT_METADATA, On, OnEvent, STEP_INTERCEPTOR_METADATA, StepInterceptor, type UserRef, WORKFLOW_METADATA, Workflow, type WorkflowMeta, type WorkflowOptions, WorkflowService, entityConfigFor, getDurableStepMeta, getEntityMeta, getOnEvents, getWorkflowMeta, isDeadLetterHandler, isStepInterceptor };
|