@dudousxd/nestjs-durable 0.38.0 → 0.39.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/attributes-of.ts","../src/decorators.ts","../src/durable-start-client.ts","../src/durable-worker.module.ts","../src/discovery-helpers.ts","../src/durable.module.ts","../src/durable-step.registrar.ts","../src/role.ts","../src/entity.ts","../src/in-app-worker.ts","../src/proxy-run-gateway.ts","../src/retention-poller.ts","../src/run-request-responder.ts","../src/store-run-gateway.ts","../src/tenant-event-republisher.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 './attributes-of';\nexport * from './context-accessor';\nexport * from './decorators';\nexport { DurableStartClient } from './durable-start-client';\nexport * from './durable-worker.module';\nexport * from './durable.module';\nexport * from './entity';\nexport * from './in-app-worker';\nexport * from './proxy-run-gateway';\nexport * from './role';\nexport * from './run-request-responder';\nexport * from './step-interceptor';\nexport * from './store-run-gateway';\nexport * from './tenant-event-republisher';\nexport * from './tokens';\nexport * from './workflow.service';\n\n// Facade re-exports: the everyday `@dudousxd/nestjs-durable-core` surface a consumer touches\n// alongside this package's own decorators/module/tokens, so e.g. the `Workflow` decorator (here)\n// and `WorkflowEngine`/`WorkflowCtx` (core), or `RUN_GATEWAY` (here) and its `RunGateway` type\n// (core), no longer require importing both packages to pick up the paired symbol. Additive only —\n// re-exports only what already exists on core's public index; core's own new exports (e.g. typed\n// search-attribute helpers) are surfaced there, not duplicated here.\nexport type {\n AttributeFilter,\n EngineEvent,\n InferSearchAttributes,\n RunDetail,\n RunGateway,\n RunListItem,\n RunQuery,\n RunStatus,\n RunWaiting,\n SearchAttributes,\n SearchAttributesSchema,\n StepCheckpoint,\n StepEvent,\n StepLogger,\n WorkflowCtx,\n WorkflowHandler,\n WorkflowRun,\n} from '@dudousxd/nestjs-durable-core';\nexport { readSearchAttributes, WorkflowEngine } from '@dudousxd/nestjs-durable-core';\n","import {\n type SearchAttributes,\n type WorkflowClass,\n type WorkflowCtx,\n readSearchAttributes,\n} from '@dudousxd/nestjs-durable-core';\nimport { getWorkflowMeta } from './decorators';\n\n/**\n * The search-attributes shape a `@Workflow` class declares — extracted from its `run` method's\n * `ctx: WorkflowCtx<A>` annotation, mirroring the `WorkflowInputOf`/`WorkflowOutputOf` structural-\n * typing idiom in core's `workflow-ref.ts` (which extract `run`'s `input`/return type the same way).\n * A class whose `run` leaves `ctx` untyped (or types it as the bare `WorkflowCtx`) resolves to the\n * untyped `SearchAttributes` default — matching `WorkflowCtx` itself.\n */\nexport type WorkflowAttributesOf<C> = C extends abstract new (\n ...args: never[]\n) => {\n run(ctx: WorkflowCtx<infer A>, input: never): unknown;\n}\n ? A\n : SearchAttributes;\n\n/**\n * Read a run's search attributes **by workflow class**, with the schema resolved from that class's\n * `@Workflow({ searchAttributes })` decorator metadata — the same single-source-of-truth idiom as\n * triggering a workflow by class (`ctx.child(ShippingWorkflow, input)`, `engine.start(CheckoutWorkflow,\n * input)`): the decorator is the one place the schema lives, and every reader references the\n * WORKFLOW, never re-imports or re-declares the schema itself. The return type is inferred\n * structurally from the class's `run(ctx: WorkflowCtx<A>, …)` annotation (see\n * {@link WorkflowAttributesOf}), so a valid read is typed to `A` with no explicit type argument.\n *\n * Delegates to core's `readSearchAttributes(schema, run)` for the actual read, so the same lenient\n * safe-parse semantics apply: a run whose stored `searchAttributes` predate the schema, or fail it,\n * reads back as `{}` rather than throwing (see `readSearchAttributes`'s doc comment).\n *\n * @throws if `workflow` isn't a `@Workflow`-decorated class (no metadata at all — nothing to resolve\n * the schema from).\n * @throws if `workflow` is a `@Workflow` class that never declared a `searchAttributes` schema —\n * reading attributes by class needs one to resolve against.\n *\n * @example\n * ```ts\n * import { z } from 'zod';\n * import { Injectable } from '@nestjs/common';\n * import {\n * attributesOf,\n * InferSearchAttributes,\n * Workflow,\n * WorkflowCtx,\n * WorkflowHandler,\n * } from '@dudousxd/nestjs-durable';\n *\n * const orderAttrs = z.object({ tier: z.enum(['free', 'pro']), amount: z.number() });\n * type OrderAttrs = InferSearchAttributes<typeof orderAttrs>;\n *\n * @Workflow({ name: 'checkout', searchAttributes: orderAttrs })\n * class CheckoutWorkflow implements WorkflowHandler<{ orderId: string }, void, OrderAttrs> {\n * async run(ctx: WorkflowCtx<OrderAttrs>, input: { orderId: string }): Promise<void> {\n * await ctx.upsertSearchAttributes({ tier: 'pro', amount: 100 });\n * }\n * }\n *\n * @Injectable()\n * class CheckoutDashboardService {\n * constructor(private readonly store: StateStore) {}\n *\n * async tierOf(runId: string) {\n * const run = await this.store.getRun(runId);\n * const attrs = attributesOf(CheckoutWorkflow, run ?? {}); // OrderAttrs\n * return attrs.tier;\n * }\n * }\n * ```\n */\nexport function attributesOf<C extends WorkflowClass>(\n workflow: C,\n run: { searchAttributes?: SearchAttributes | null | undefined },\n): WorkflowAttributesOf<C> {\n // biome-ignore lint/complexity/noBannedTypes: getWorkflowMeta reads reflect-metadata off the class target, same contract as workflowName/bindWorkflowClass\n const meta = getWorkflowMeta(workflow as unknown as Function);\n if (!meta) {\n throw new Error(\n `attributesOf: ${workflow.name} is not a @Workflow class — is it decorated with @Workflow({ name, searchAttributes })?`,\n );\n }\n if (!meta.searchAttributes) {\n throw new Error(\n `attributesOf: workflow '${meta.name}' declares no searchAttributes schema — reading attributes by class requires the workflow to declare its schema: @Workflow({ name: '${meta.name}', searchAttributes: mySchema }).`,\n );\n }\n return readSearchAttributes(meta.searchAttributes, run) as WorkflowAttributesOf<C>;\n}\n","import {\n DURABLE_STEP_CONFIG,\n DURABLE_STEP_NAME,\n type SearchAttributesSchema,\n type SingletonConfig,\n type StepConfig,\n type StepError,\n WORKFLOW_NAME_KEY,\n type WorkflowRef,\n} from '@dudousxd/nestjs-durable-core';\nimport 'reflect-metadata';\nimport type { z } from 'zod';\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?: (new (...args: any[]) => object) | undefined;\n /** Custom input validator (throws on invalid). See `WorkflowOptions`. */\n validateInput?: ((input: unknown) => void | Promise<void>) | undefined;\n /** Typed/validated `searchAttributes` shape. See `WorkflowOptions`. */\n searchAttributes?: SearchAttributesSchema | 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 /** Capabilities a live worker must advertise to run this workflow's turns (handshake §7.5). See\n * `WorkflowOptions`. */\n requires?: string[] | 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 * Capabilities a live worker MUST advertise to run this workflow's turns (handshake design §7.5).\n * Only meaningful for a group-served / remote workflow (an in-app worker or a cross-runtime body):\n * the control-plane dispatches a turn only to a capability-capable + protocol-compatible worker, and\n * if descriptors are published on its group but none qualifies the run parks `blocked` until the\n * recovery poll finds one. Absent/empty = \"runs anywhere\" (a legacy fleet skips the guard, §7.7).\n */\n requires?: string[];\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 * Validate this workflow's {@link WorkflowCtx.upsertSearchAttributes} writes against a **Standard\n * Schema** (https://standardschema.dev — zod 3.24+, valibot, arktype, …). `ctx.upsertSearchAttributes`\n * validates the MERGED result (existing attributes shallow-merged with the patch) on every call —\n * an invalid merge throws, naming this workflow, the offending key(s), and the schema's issues. The\n * schema's inferred output must be search-attribute-shaped (flat `string`/`number`/`boolean` values\n * only) — a schema whose output has a nested object/array is a compile-time error here. Omit for\n * the prior unvalidated behavior.\n *\n * ```ts\n * import { z } from 'zod';\n *\n * const orderAttrs = z.object({\n * tier: z.enum(['free', 'pro']),\n * amount: z.number(),\n * });\n *\n * @Workflow({ name: 'checkout', searchAttributes: orderAttrs })\n * class CheckoutWorkflow {\n * async run(ctx: WorkflowCtx<InferSearchAttributes<typeof orderAttrs>>, input: CheckoutInput) {\n * await ctx.upsertSearchAttributes({ tier: input.tier, amount: input.total });\n * }\n * }\n * ```\n */\n searchAttributes?: SearchAttributesSchema;\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 `@OnDurableEvent(...)` 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 searchAttributes: options.searchAttributes,\n onEvent: options.onEvent,\n debounce: options.debounce,\n batch: options.batch,\n requires: options.requires,\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 /** The resolved routing name — derived (`Class.method`) or explicit, always present. */\n name: string;\n /** Opt-in runtime input schema, validated when the handler is served (see `scanSteps`). Absent on\n * a bare `@Step()` — compile-time types from the method signature only, no runtime check. */\n input?: z.ZodType | undefined;\n /** Opt-in runtime output schema, validated before the handler's result is handed back. */\n output?: z.ZodType | undefined;\n}\n\n/**\n * `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,\n * timeoutMs? })` — the object call form. See {@link Step}.\n */\nexport interface StepOptions {\n /** Explicit routing name, overriding the derived `Class.method`. */\n name?: string;\n /** Runtime input schema, validated at the serve boundary (opt-in — a bare `@Step()` skips it). */\n input?: z.ZodType;\n /** Runtime output schema, validated before the result is returned (opt-in). */\n output?: z.ZodType;\n /**\n * Def-level durable-dispatch policy stamped under `DURABLE_STEP_CONFIG` and read by `ctx.step` at\n * the dispatch boundary (see `stepConfigOf`) — a per-call `ctx.step(ref, input, opts)` overrides\n * these field-by-field. Max attempts before the step (and run) fails.\n */\n retries?: number;\n /** How the delay between retries grows: `fixed` (constant) or `exp` (doubles each attempt). */\n backoff?: 'fixed' | 'exp';\n /** Base delay in ms between retries. Omit (or 0) to retry with no delay. */\n backoffMs?: number;\n /** Upper bound on the (exponential) backoff delay. */\n backoffMaxMs?: number;\n /** Add random jitter (50–100% of the computed delay) to avoid thundering-herd retries. */\n jitter?: boolean;\n /**\n * Liveness window for a dispatched step: no result/heartbeat within this many ms presumes the\n * worker dead and fails the dispatch with a `RemoteStepTimeout` (retryable — re-dispatches per\n * `retries`). Omit to wait indefinitely.\n */\n timeoutMs?: number;\n /**\n * Capabilities a live worker MUST advertise to run this step (handshake design §7.5). The\n * control-plane routes the step only to workers whose descriptor advertises every name here; if\n * descriptors are published on the step's group but none is capability-capable + protocol-compatible,\n * the run parks `blocked` (never a silent hang) until the recovery poll finds one. Absent/empty =\n * \"runs anywhere\" (the default — a legacy fleet publishing no descriptors skips the guard, §7.7). A\n * per-call `ctx.step(ref, input, { requires })` overrides this.\n */\n requires?: string[];\n}\n\n/** Build the {@link StepConfig} to stamp under `DURABLE_STEP_CONFIG` from `@Step` options — omitting\n * every unset field, so a bare `@Step()` (or one with only `name`/`input`/`output`) stamps `undefined`\n * and leaves `ctx.step` reading nothing but a per-call `opts` override. */\nfunction stepConfigFrom(options: StepOptions): StepConfig | undefined {\n const config: StepConfig = {\n retries: options.retries,\n backoff: options.backoff,\n backoffMs: options.backoffMs,\n backoffMaxMs: options.backoffMaxMs,\n jitter: options.jitter,\n timeoutMs: options.timeoutMs,\n requires: options.requires,\n };\n const hasAnyField = Object.values(config).some((value) => value !== undefined);\n return hasAnyField ? config : undefined;\n}\n\n/**\n * Marks a provider method as a durable step handler. An in-process transport (e.g. the\n * event-emitter transport) or a co-located/thin worker runtime routes a dispatched task to it BY\n * NAME — `ctx.step(this.svc.method, input)` reads that same name off the method reference (stamped\n * via the shared, cross-package `DURABLE_STEP_NAME` symbol from `@dudousxd/nestjs-durable-core`), so\n * there's no separately-declared def linking a call site to this handler. The method's single\n * argument is the step input (plus an optional `StepLogger` second arg); its return value is the\n * step output.\n *\n * Three call forms:\n * - `@Step()` — bare: the routing name is DERIVED from the method as `` `${ClassName}.${method}` ``\n * (e.g. `ExtractionService.runExtractionPage`) — refactor-safe, no magic string.\n * - `@Step('custom:name')` — explicit name override (stable across refactors, or a cross-runtime\n * contract with a non-JS worker that has no `@Step` of its own).\n * - `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,\n * timeoutMs? })` — optional name override, opt-in RUNTIME zod schemas, and a def-level\n * durable-retry/liveness-timeout policy. `input` validates before the method runs; `output`\n * validates its return value before it's handed back (see `scanSteps`). `retries`/`backoff`/\n * `backoffMs`/`backoffMaxMs`/`jitter`/`timeoutMs` are the policy `ctx.step` reads off this method\n * reference (via `stepConfigOf`) to build the dispatched `StepDef`'s durable retry/backoff and\n * remote-liveness timeout — a per-call `ctx.step(ref, input, opts)` overrides them field-by-field.\n * A bare `@Step()` carries none of this and skips validation/retry/timeout — compile-time types\n * from the method signature are the only check, and the step dispatches with no retry/timeout.\n */\nexport function Step(nameOrOptions?: string | StepOptions): MethodDecorator {\n return (target, propertyKey, descriptor: PropertyDescriptor) => {\n const options =\n typeof nameOrOptions === 'string' ? { name: nameOrOptions } : (nameOrOptions ?? {});\n const derivedName = options.name ?? `${target.constructor.name}.${String(propertyKey)}`;\n const meta: DurableStepMeta = {\n name: derivedName,\n input: options.input,\n output: options.output,\n };\n Reflect.defineMetadata(DURABLE_STEP_METADATA, meta, descriptor.value as object);\n // Stamp the SAME resolved name core's `ctx.step` reads off a method reference (`stepNameOf`) —\n // the cross-package contract letting `ctx.step(this.svc.method, input)` route without a\n // separately-declared def. `DURABLE_STEP_NAME` is core's `Symbol.for`-keyed constant, so a\n // duplicate copy of core still reads back the same key (see `step-name-symbol.ts`).\n (descriptor.value as { [DURABLE_STEP_NAME]?: string })[DURABLE_STEP_NAME] = derivedName;\n // Stamp the def-level dispatch policy (retries/backoff/timeoutMs) core's `ctx.step` reads via\n // `stepConfigOf`, under the same `Symbol.for`-keyed contract as `DURABLE_STEP_NAME`. Omitted\n // entirely when no policy field is set, so a bare `@Step()` stamps nothing extra.\n const config = stepConfigFrom(options);\n if (config !== undefined) {\n (descriptor.value as { [DURABLE_STEP_CONFIG]?: StepConfig })[DURABLE_STEP_CONFIG] = config;\n }\n return descriptor;\n };\n}\n\n/**\n * @deprecated Use `@Step` instead. `@DurableStep` is a back-compat alias of {@link Step} and\n * writes the same `DURABLE_STEP_METADATA`, so discovery/registrars treat them identically.\n */\nexport const DurableStep = Step;\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\n * `@OnDurableEvent(...)` decorators and the option are all merged.\n *\n * Named \"durable\" on purpose: `@nestjs/event-emitter` exports an `@OnEvent` decorator, and in an\n * app using both libs an auto-import picking the wrong one fails silently in either direction.\n */\nexport function OnDurableEvent(...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/** @deprecated Renamed to `OnDurableEvent` — this alias clashes with `@nestjs/event-emitter`'s `@OnEvent` and will be removed in the next minor. */\nexport const OnEvent = OnDurableEvent;\n\n/** All events a workflow class subscribes to — the union of `@Workflow({ onEvent })` and `@OnDurableEvent`. */\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 { type StartRunDeps, startRun } from '@dudousxd/durable-worker';\nimport {\n type RunResult,\n type StartOptions,\n type WorkflowClass,\n type WorkflowInputOf,\n workflowName,\n} from '@dudousxd/nestjs-durable-core';\nimport { Injectable } from '@nestjs/common';\nimport type { DurableModuleOptions } from './durable.module';\n\n/**\n * The **store-less `engine.start` facade** for a tenant worker. Provided under the `WorkflowEngine`\n * DI token by {@link import('./durable.module').DurableModule} (`forRoot({ connection })`, no\n * `store`), so tenant code calls `engine.start(...)` UNCHANGED — it has no idea it is a tenant.\n * Instead of touching a DB, `start` publishes a `StartRunMessage` on the SHARED `durable-start-run`\n * queue (Option B: tenant rides as message DATA, never as wire segmentation). The operator (control\n * plane, `namespace: undefined`) consumes it, stamps the run's namespace from `tenant`, and routes\n * the run's task to `<workflow>@<tenant>` — the partition THIS tenant's worker serves.\n *\n * `cancel`/`deleteRun` need the store/driver a tenant does not have; they throw. No wire message\n * exists for them (the operator owns cancellation/retention).\n */\n@Injectable()\nexport class DurableStartClient {\n private readonly tenant: string;\n\n constructor(\n private readonly options: DurableModuleOptions,\n private readonly deps?: StartRunDeps,\n ) {\n this.tenant = options.partition ?? 'default';\n }\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 async start(\n workflow: string,\n input: unknown,\n runId: string = globalThis.crypto.randomUUID(),\n opts?: StartOptions,\n ): Promise<RunResult> {\n const name = workflowName(workflow);\n await startRun(this.options.connection, {\n tenant: this.tenant,\n workflow: name,\n input,\n runId,\n // Option B: DO NOT pass `namespace` — the start-run queue stays the shared\n // `durable-start-run` the operator consumes; tenant rides only as message data.\n ...(this.options.prefix !== undefined ? { prefix: this.options.prefix } : {}),\n ...(opts?.tags !== undefined ? { tags: opts.tags } : {}),\n ...(opts?.searchAttributes !== undefined ? { searchAttributes: opts.searchAttributes } : {}),\n ...(this.deps !== undefined ? { deps: this.deps } : {}),\n });\n return { runId, status: 'pending' };\n }\n\n cancel(_runId: string): Promise<void> {\n return tenantUnsupported('cancel');\n }\n\n deleteRun(_runId: string): Promise<void> {\n return tenantUnsupported('deleteRun');\n }\n\n // — everything below rejects: a tenant worker holds no store/driver, only the start channel. —\n\n // The remaining WorkflowEngine surface WorkflowService delegates to (resume/waitForRun/signal/\n // signalWithStart/publishEvent) all need the store or driver a tenant does not have. A tenant only\n // ever calls `start`; these exist so a mistaken call fails with a CLEAR, named tenant error instead\n // of a cryptic `this.engine.X is not a function` — the facade is honest about what it cannot do.\n // Params mirror the WorkflowEngine surface being faced; every method rejects without reading them.\n resume(_runId: string): Promise<void> {\n return tenantUnsupported('resume');\n }\n\n waitForRun(_runId: string, _opts?: { timeoutMs?: number }): Promise<void> {\n return tenantUnsupported('waitForRun');\n }\n\n signal(_token: string, _payload: unknown): Promise<void> {\n return tenantUnsupported('signal');\n }\n\n signalWithStart(\n _workflow: string,\n _input: unknown,\n _runId: string,\n _signal: { token: string; payload?: unknown },\n _opts?: StartOptions,\n ): Promise<void> {\n return tenantUnsupported('signalWithStart');\n }\n\n publishEvent(\n _name: string,\n _payload: unknown,\n _opts?: { id?: string; buffer?: boolean },\n ): Promise<void> {\n return tenantUnsupported('publishEvent');\n }\n}\n\n/** Reject with a clear, named tenant error for an operation that needs a store/driver. */\nfunction tenantUnsupported(method: string): Promise<void> {\n return Promise.reject(\n new Error(\n `${method}() is not available on a tenant worker (no store). Use the control plane for it.`,\n ),\n );\n}\n","import {\n DurableWorkerRuntime,\n type RunRedisWorkerOptions,\n type RunningWorker,\n runRedisWorker as defaultRunRedisWorker,\n} from '@dudousxd/durable-worker';\nimport {\n DURABLE_OPTIONS_CANONICAL,\n type DurableTopology,\n type EngineEvent,\n type GroupHealth,\n type RunDetail,\n type RunGateway,\n type RunListItem,\n type RunQuery,\n type RunResult,\n type RunWaiting,\n} from '@dudousxd/nestjs-durable-core';\nimport {\n Inject,\n Injectable,\n type OnApplicationBootstrap,\n type OnApplicationShutdown,\n type OnModuleInit,\n type Provider,\n} from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport { scanSteps, scanWorkflows } from './discovery-helpers';\nimport type { DurableModuleOptions } from './durable.module';\n\n/**\n * The `runRedisWorker` function the module uses to start each partition's BullMQ consumer.\n * Defaults to the real one from `@dudousxd/durable-worker`; tests `overrideProvider` it with a\n * fake so no real Redis is needed.\n */\nexport const RUN_REDIS_WORKER = Symbol('nestjs-durable:run-redis-worker');\n\n/** The list of started {@link RunningWorker} handles (the single handle {@link ThinWorkerBootstrap}\n * starts), closed on shutdown. */\nexport const DURABLE_WORKER_RUNNERS = Symbol('nestjs-durable:worker-runners');\n\n/** The signature of `runRedisWorker` — injected behind {@link RUN_REDIS_WORKER}. */\nexport type RunRedisWorkerFn = (opts: RunRedisWorkerOptions) => Promise<RunningWorker>;\n\n/** True for the **pure thin-worker** role — `connection` set, `store` unset. `DurableModule`\n * keeps {@link ThinWorkflowRegistrar}/{@link ThinStepRegistrar}/{@link ThinWorkerBootstrap} inert\n * (registered but no-op) outside this role — an operator (`store` set) drives its own bodies\n * inline or, with `connection` also set, through the separate in-app-worker mechanics instead. */\nfunction isPureThinWorker(options: DurableModuleOptions): boolean {\n return options.store === undefined && options.connection !== undefined;\n}\n\n/**\n * Discovers every provider carrying `@Workflow` metadata and registers its `run(ctx, input)` on the\n * thin {@link DurableWorkerRuntime}. Mirrors the engine-side `WorkflowRegistrar`, but registers on\n * the runner-core runtime instead of a `WorkflowEngine` — the runtime drives `run` with the thin\n * `WorkflowContext` (which `implements WorkflowCtx`), so the body runs unchanged. NO store/engine.\n * Inert (skips registration) outside the pure thin-worker role — see {@link isPureThinWorker}.\n */\n@Injectable()\nexport class ThinWorkflowRegistrar implements OnModuleInit {\n constructor(\n private readonly discovery: DiscoveryService,\n private readonly runtime: DurableWorkerRuntime,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n onModuleInit(): void {\n if (!isPureThinWorker(this.options)) return;\n scanWorkflows(this.discovery, (meta, instance) =>\n this.runtime.registerWorkflow(meta.name, (ctx, input) => instance.run(ctx, input)),\n );\n }\n}\n\n/**\n * Discovers every `@Step` method and registers it as a step handler on the thin\n * {@link DurableWorkerRuntime}. Mirrors the engine-side `DurableStepRegistrar`, but always registers\n * on the runtime (a thin worker IS the consumer — there is no in-process-vs-queue branch). Inert\n * outside the pure thin-worker role — see {@link isPureThinWorker}.\n */\n@Injectable()\nexport class ThinStepRegistrar implements OnModuleInit {\n constructor(\n private readonly discovery: DiscoveryService,\n private readonly metadataScanner: MetadataScanner,\n private readonly runtime: DurableWorkerRuntime,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n onModuleInit(): void {\n if (!isPureThinWorker(this.options)) return;\n scanSteps(this.discovery, this.metadataScanner, (meta, handler) =>\n this.runtime.registerStep(meta.name, handler),\n );\n }\n}\n\n/**\n * Starts ONE `runRedisWorker` call on bootstrap — after both registrars have run, so every handler\n * is registered before any task is consumed — and closes it on shutdown. The runner (`@dudousxd/\n * durable-worker`) derives its subscription from `runtime.registeredNames()` (one queue per\n * registered `@Workflow`/`@Step` name), not a hand-declared group list. Inert outside the pure\n * thin-worker role — see {@link isPureThinWorker}: an operator (`store` set) never starts this\n * consumer, and `store + connection` starts its OWN co-located consumer instead (`in-app-worker.ts`).\n *\n * For the shutdown close to fire, enable Nest's shutdown hooks: `app.enableShutdownHooks()`.\n */\n@Injectable()\nexport class ThinWorkerBootstrap implements OnApplicationBootstrap, OnApplicationShutdown {\n private readonly runners: RunningWorker[] = [];\n\n constructor(\n private readonly runtime: DurableWorkerRuntime,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n @Inject(RUN_REDIS_WORKER) private readonly runRedisWorker: RunRedisWorkerFn,\n @Inject(DURABLE_WORKER_RUNNERS) private readonly runnersSink: RunningWorker[],\n ) {}\n\n async onApplicationBootstrap(): Promise<void> {\n const options = this.options;\n if (!isPureThinWorker(options) || options.connection === undefined) return;\n // ONE call: the runner subscribes one queue per name registered on `this.runtime` — populated by\n // `ThinWorkflowRegistrar`/`ThinStepRegistrar`'s `onModuleInit`, which Nest's lifecycle guarantees\n // run (across the whole app) before any `onApplicationBootstrap` hook.\n const handle = await this.runRedisWorker({\n runtime: this.runtime,\n connection: options.connection,\n ...(options.partition !== undefined ? { partition: options.partition } : {}),\n ...(options.prefix !== undefined ? { prefix: options.prefix } : {}),\n ...(options.instanceId !== undefined ? { instanceId: options.instanceId } : {}),\n ...(options.concurrency !== undefined ? { concurrency: options.concurrency } : {}),\n });\n this.runners.push(handle);\n this.runnersSink.push(handle);\n }\n\n async onApplicationShutdown(): Promise<void> {\n await Promise.allSettled(this.runners.map((h) => h.close()));\n }\n}\n\n/**\n * The thin-worker slice of {@link import('./durable.module').DurableModule}'s unified provider set —\n * active for the pure thin-worker role (`connection` set, `store` unset): registers discovered\n * `@Workflow`/`@Step` on a store-less {@link DurableWorkerRuntime} and starts ONE `runRedisWorker`\n * consumer, one queue PER REGISTERED NAME. Always present in the provider list (so a single\n * `DurableModule.forRootAsync` can serve any role once its options resolve); every class/factory here\n * is inert (see {@link isPureThinWorker}) outside that role.\n */\nexport function thinWorkerProviders(): Provider[] {\n return [\n {\n provide: DurableWorkerRuntime,\n // The runtime's `WorkflowWorker` falls back to its own `group` ctor param as the WORKFLOW's\n // `workflowPartition` for any `ctx.step` call (see `workflow-context.ts`'s `resolveCallGroup`)\n // — that fallback MUST equal this module's own `partition`, or a dispatched step's decision\n // carries a mismatched token and the engine dispatches it to a queue nothing here subscribes\n // to. Explicit `''` (never\n // `undefined`) so it does NOT fall through to `WorkflowWorker`'s unrelated `'workflows'`\n // default parameter.\n useFactory: (options: DurableModuleOptions) =>\n new DurableWorkerRuntime({ workflowGroup: options.partition ?? '' }),\n inject: [DURABLE_OPTIONS_CANONICAL],\n },\n { provide: RUN_REDIS_WORKER, useValue: defaultRunRedisWorker },\n { provide: DURABLE_WORKER_RUNNERS, useValue: [] as RunningWorker[] },\n ThinWorkflowRegistrar,\n ThinStepRegistrar,\n ThinWorkerBootstrap,\n ];\n}\n\n/** Reject with a clear, named tenant error for a `RunGateway` call made without a `transport`. The\n * call site pins `T` from its own declared return type (e.g. `Promise<RunDetail | null>`); this\n * helper only ever rejects, so it never actually produces a `T` value. */\nfunction tenantGatewayUnavailable<T>(method: string): Promise<T> {\n return Promise.reject(\n new Error(`RunGateway.${method}() is not available — pass \\`transport\\` to use RunGateway.`),\n );\n}\n\n/**\n * The no-transport fallback bound to `RUN_GATEWAY` for a thin worker (`connection` set, `store`\n * unset) that didn't also pass a `transport` — mirrors `DurableStartClient`'s tenant-error idiom\n * (`tenantUnsupported`): every method rejects with a clear, named error instead of a cryptic\n * `this.gateway.X is not a function`. `subscribe` is synchronous on the `RunGateway` port, so it\n * throws synchronously (same message) rather than returning a rejected promise.\n */\nexport function unavailableRunGateway(): RunGateway {\n return {\n // Topology is local metadata, always answerable — a thin worker with no transport is still a tenant.\n topology(): DurableTopology {\n return { role: 'tenant' };\n },\n getRunDetail(_runId: string): Promise<RunDetail | null> {\n return tenantGatewayUnavailable<RunDetail | null>('getRunDetail');\n },\n listRuns(_query: RunQuery): Promise<RunListItem[]> {\n return tenantGatewayUnavailable<RunListItem[]>('listRuns');\n },\n waitingFor(_runIds: string[]): Promise<Record<string, RunWaiting>> {\n return tenantGatewayUnavailable<Record<string, RunWaiting>>('waitingFor');\n },\n workerHealth(): Promise<GroupHealth[]> {\n return tenantGatewayUnavailable<GroupHealth[]>('workerHealth');\n },\n cancel(_runId: string): Promise<RunResult | null> {\n return tenantGatewayUnavailable<RunResult | null>('cancel');\n },\n retry(_runId: string): Promise<RunResult | null> {\n return tenantGatewayUnavailable<RunResult | null>('retry');\n },\n continue(_runId: string): Promise<RunResult | null> {\n return tenantGatewayUnavailable<RunResult | null>('continue');\n },\n retryWithInput(_runId: string, _input: unknown): Promise<{ runId: string } | null> {\n return tenantGatewayUnavailable<{ runId: string } | null>('retryWithInput');\n },\n redispatchPending(_runId: string): Promise<(RunResult & { redispatched: number }) | null> {\n return tenantGatewayUnavailable<(RunResult & { redispatched: number }) | null>(\n 'redispatchPending',\n );\n },\n subscribe(_runId: string, _onEvent: (event: EngineEvent) => void): () => void {\n throw new Error(\n 'RunGateway.subscribe() is not available — pass `transport` to use RunGateway.',\n );\n },\n };\n}\n","import type { StepLogger, WorkflowCtx } from '@dudousxd/nestjs-durable-core';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport { getDurableStepMeta, getWorkflowMeta } from './decorators';\nimport type { DurableStepMeta, WorkflowMeta } from './decorators';\n\nexport interface WorkflowInstance {\n run(ctx: WorkflowCtx, input: unknown): Promise<unknown>;\n}\n\n/**\n * Walks every provider that carries `@Workflow` metadata and invokes `register(meta, instance)`.\n *\n * Guards applied (same as both registrars):\n * - null / non-object provider instances are skipped\n * - providers without `@Workflow` metadata are skipped\n * - a `@Workflow` provider that has no `run` method throws at boot\n */\nexport function scanWorkflows(\n discovery: DiscoveryService,\n register: (meta: WorkflowMeta, instance: WorkflowInstance) => void,\n): void {\n for (const wrapper of discovery.getProviders()) {\n const { instance } = wrapper;\n if (!instance || typeof instance !== 'object') continue;\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 register(meta, workflow);\n }\n}\n\n/**\n * Walks every provider method that carries `@Step` metadata and invokes\n * `register(meta, boundHandler)`.\n *\n * Guards applied (same as both registrars):\n * - null / non-object provider instances are skipped\n * - non-function methods are skipped\n * - methods without `@Step` metadata are skipped\n *\n * The handler passed to `register` is already bound to its instance, and is the ONE place a `@Step`\n * method actually runs with real input/output in this package — every registrar\n * (`DurableStepRegistrar`, `InAppWorkerBootstrap`, `ThinStepRegistrar`) funnels through here — so\n * this is where `@Step`'s optional `input`/`output` zod schemas are enforced: `input` validates\n * before the method runs, `output` validates its return value before it's handed back. A bare\n * `@Step()` (no schemas attached) skips both checks — `meta.input`/`meta.output` are `undefined`.\n */\nexport function scanSteps(\n discovery: DiscoveryService,\n scanner: MetadataScanner,\n register: (\n meta: DurableStepMeta,\n handler: (input: unknown, log: StepLogger) => Promise<unknown>,\n ) => void,\n): void {\n for (const wrapper of discovery.getProviders()) {\n const { instance } = wrapper;\n if (!instance || typeof instance !== 'object') continue;\n const prototype = Object.getPrototypeOf(instance);\n for (const methodName of scanner.getAllMethodNames(prototype)) {\n const method = (instance as Record<string, unknown>)[methodName];\n if (typeof method !== 'function') continue;\n const meta = getDurableStepMeta(method);\n if (!meta) continue;\n const boundMethod = method as (input: unknown, log: StepLogger) => unknown;\n const handler = async (input: unknown, log: StepLogger): Promise<unknown> => {\n const validInput = meta.input ? meta.input.parse(input) : input;\n const output = await boundMethod.call(instance, validInput, log);\n return meta.output ? meta.output.parse(output) : output;\n };\n register(meta, handler);\n }\n }\n}\n","import type { ConcurrencyOption } from '@dudousxd/durable-worker';\nimport {\n type AdmissionBackend,\n type ControlPlane,\n DURABLE_OPTIONS,\n DURABLE_OPTIONS_CANONICAL,\n type NamedTransport,\n type QueueConfig,\n type RetentionPolicy,\n RunGateway,\n STATE_STORE,\n STATE_STORE_CANONICAL,\n type ScheduledWorkflow,\n type StateStore,\n TRANSPORT,\n TRANSPORT_CANONICAL,\n type Transport,\n WorkflowEngine,\n type WorkflowRef,\n} from '@dudousxd/nestjs-durable-core';\nimport {\n type DynamicModule,\n Inject,\n Injectable,\n type InjectionToken,\n Module,\n type OnApplicationBootstrap,\n type OnModuleDestroy,\n type Provider,\n} from '@nestjs/common';\nimport { DiscoveryModule, ModuleRef } from '@nestjs/core';\nimport type { ContextAccessor } from './context-accessor';\nimport { DurableStartClient } from './durable-start-client';\nimport { DurableStepRegistrar } from './durable-step.registrar';\nimport { thinWorkerProviders, unavailableRunGateway } from './durable-worker.module';\nimport { EntityService } from './entity';\nimport { inAppWorkerProviders } from './in-app-worker';\nimport { ProxyRunGateway } from './proxy-run-gateway';\nimport { RetentionPoller } from './retention-poller';\nimport { isDrivingOperator } from './role';\nimport { RunRequestResponder, type RunRequestTransport } from './run-request-responder';\nimport { StoreRunGateway } from './store-run-gateway';\nimport { TenantEventRepublisher } from './tenant-event-republisher';\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\n/** A store that can derive a tenant-namespace-scoped view of itself (the MikroORM adapter). */\ninterface ScopeableStore {\n /** Derive a store confined to `scope.namespace`, sharing the same backing connection. */\n withScope(scope: { namespace?: string }): StateStore;\n}\n\n/**\n * True when `store` can produce a namespace-scoped view of itself. Structural check (same idiom as\n * {@link isControlPlane}) so the store-agnostic module never imports a concrete adapter — the\n * pre-built `store` is only re-scoped when it actually carries the capability.\n */\nfunction isScopeableStore(store: StateStore): store is StateStore & ScopeableStore {\n return typeof (store as StateStore & Partial<ScopeableStore>).withScope === 'function';\n}\n\n/**\n * Apply opt-in tenant read scoping to a pre-built store. The module receives `store` already\n * instantiated (it is store-agnostic — no MikroORM import), so it cannot reconstruct it; instead it\n * asks the store for a scoped view via the optional {@link ScopeableStore.withScope} capability. When\n * `scopeReads` is off, `namespace` is unset, or the store can't scope itself, the original store is\n * returned unchanged (the operator view) — the caller can always pass an already-scoped store.\n */\nfunction scopedStore(store: StateStore, options: DurableModuleOptions): StateStore {\n if (options.scopeReads !== true || options.namespace === undefined) return store;\n if (!isScopeableStore(store)) return store;\n return store.withScope({ namespace: options.namespace });\n}\n\n/**\n * Retention config for the {@link RetentionPoller}. One or more {@link RetentionPolicy policies}\n * (status sets must be disjoint — validated at boot), swept together on a shared interval.\n *\n * ```ts\n * retention: {\n * sweepInterval: '1m',\n * batchSize: 1_000,\n * policies: [\n * { statuses: ['completed', 'cancelled'], maxAge: '14d', maxCount: 200 },\n * { statuses: ['failed'], maxAge: '90d' }, // keep failures longer for debugging\n * ],\n * }\n * ```\n */\nexport interface DurableRetentionOptions {\n /** The retention rules, one per (disjoint) status group. */\n policies: RetentionPolicy[];\n /**\n * How often to run the prune sweep. A number is milliseconds; a string is an `ms`-style duration\n * (`'1m'`, `'5m'` — `'m'` is minutes). `0` runs it once on boot only. Defaults to 60000 (1 minute).\n */\n sweepInterval?: number | string;\n /** Max runs hard-deleted per batch (per policy, looped until drained). Defaults to 1000. */\n batchSize?: number;\n}\n\n/**\n * Options for `DurableModule.forRoot`/`forRootAsync`. The **role is inferred** from which of `store`/\n * `connection` are set:\n *\n * - `{ store, transport }` — **operator**: a real `WorkflowEngine` + `StoreRunGateway` + drivers/\n * timer/retention/registrars, executing registered bodies INLINE. Driven by {@link drive} (default\n * `true`).\n * - `{ connection }` (no `store`) — **thin worker**: `WorkflowEngine` resolves to a store-less\n * `DurableStartClient`; `RUN_GATEWAY` is a `ProxyRunGateway` when `transport` is also given, else\n * every method rejects with a clear error. No store/timer/retention/entity — just discovered\n * `@Workflow`/`@Step` handlers served over ONE `runRedisWorker` consumer.\n * - `{ store, transport, connection }` — an **operator that also runs a co-located worker**: every\n * `@Workflow` is registered GROUP-SERVED (dispatched over the transport, PER WORKFLOW NAME, instead\n * of inline) and a co-located consumer (one `runRedisWorker` call) replays the same bodies.\n *\n * `forRoot` throws when neither `store` nor `connection` is set, and when `store` is set without a\n * `transport`.\n *\n * Set {@link DurableModuleOptions.topology} to name the role explicitly instead of relying on this\n * inference — it additionally VALIDATES the axes above (`namespace` vs `partition`) that this prose\n * is otherwise the only source of truth for. Omitting it changes nothing.\n */\nexport interface DurableModuleOptions {\n /** State store — set this to play the **operator** role (see the interface doc for the full role\n * matrix). Omit for a store-less **thin worker** (`connection` only). */\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.step(handler, 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. Operator only. */\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 * Operator only.\n */\n autoSchema?: boolean;\n /**\n * Worker-pool namespace for this instance (forwarded to the engine). The poll paths\n * (`runPending`/`recoverIncomplete`/`resumeDueTimers`/`sweepTimeouts`) only act on runs in this\n * namespace. Set distinct values to safely share ONE state store across non-interchangeable\n * pools — e.g. a developer's local instance vs the deployed cluster. **Omit it to make this\n * instance an OPERATOR** — an unset namespace drives/recovers/resumes runs of EVERY namespace and\n * leaves the transport on its bare prefix. See `WorkflowEngineDeps.namespace`. Not to be confused\n * with {@link partition} (the co-located/thin-worker QUEUE routing suffix).\n */\n namespace?: string;\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. Operator only.\n */\n leaseMs?: number;\n /** Unique id for this instance (for leases, and the co-located/thin-worker consumer's heartbeats). */\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. Operator only.\n */\n maxRecoveryAttempts?: number;\n /**\n * Opt-in liveness deadline (ms) for a remote (polyglot) workflow `advance`. If the worker neither\n * returns a decision nor sends a run-scoped heartbeat within this window, the engine presumes it dead\n * and lets recovery re-drive; each heartbeat rearms the window so a slow-but-alive worker is never\n * re-driven. Pair with a worker SDK that emits run-scoped heartbeats (`@dudousxd/durable-worker` ≥ the\n * release that ships them, and the Python `durable-worker`). Omit for the prior unbounded await.\n * Operator only.\n */\n remoteAdvanceSilenceMs?: 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). Operator only.\n */\n deadLetterWorkflow?: WorkflowRef;\n /**\n * Whether an **operator** instance actively DRIVES runs — polls pending, recovers crashed\n * (`recoverIncomplete`), resumes due timers, sweeps timeouts, prunes retention, and consumes local\n * steps — as opposed to a read-only/dashboard replica. Defaults to `true`. Set `false` for a\n * **dashboard/dispatch-only** instance (e.g. an API pod) that mounts the store/dashboard but must\n * not process or recover workflows — leave that to another driving instance. `drive: false` also\n * installs the engine's no-op run dispatcher, so a freshly `start()`ed run stays enqueue-only until\n * a driving instance's poll picks it up. Ignored (irrelevant) for a thin worker (no `store`).\n */\n drive?: boolean;\n /** Max ms to wait for in-flight runs on shutdown before exiting. Defaults to 10000. Operator only. */\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 **driving** 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. Operator only.\n */\n schedules?: ScheduledWorkflow[];\n /**\n * Hard-prune terminal run history on an interval so `durable_workflow_runs` (and its child tables)\n * stays bounded — without it, completed/failed/cancelled runs accumulate forever and the timer\n * poller's per-tick status scans get linearly slower. Driving instances only. Omit to keep all\n * history (the default). Requires a store adapter that implements `pruneTerminalRuns` (the\n * MikroORM adapter does); other adapters no-op with a warning. See {@link DurableRetentionOptions}.\n * Operator only.\n */\n retention?: DurableRetentionOptions;\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. Operator only.\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.step(handler, input, { queue: name })` to cap its concurrency / admission rate.\n * Operator only.\n */\n queues?: QueueConfig[];\n /**\n * Admission backend for the flow-control `queues`. Defaults to in-process (per-instance) caps. Pass\n * a `RedisAdmissionBackend` (from `@dudousxd/nestjs-durable-admission-redis`) to make concurrency /\n * rate-limit / priority ordering GLOBAL across every engine replica. Operator only.\n */\n admission?: AdmissionBackend;\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`. Operator only.\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. Operator only.\n */\n context?: () => Record<string, unknown> | undefined;\n /** Attempts for each saga compensation when a run fails. Default 1 (no retry). Idempotent undos.\n * Operator only. */\n compensationRetries?: number;\n /**\n * Opt into tenant read scoping: when `true` AND {@link namespace} is set, the module confines the\n * store's reads to that namespace (a tenant-boundary view) instead of the operator view that sees\n * all namespaces. Default `false` — the control plane (e.g. flip's `/ctrl` operator screens) stays\n * unscoped. Requires a store that exposes the `withScope` capability (the MikroORM adapter does);\n * a pre-built store without it is used as-is (construct it already-scoped instead). Operator only.\n */\n scopeReads?: boolean;\n /**\n * ioredis connection (string or options) for a **thin worker** (`connection` only) or the\n * **co-located worker** consumer (`store` + `connection`). Set this to play a worker role (see the\n * interface doc for the full role matrix). Omit to keep every `@Workflow` on the operator's inline\n * fast path with zero dispatch round-trips.\n */\n connection?: string | Record<string, unknown>;\n /**\n * The isolation partition a worker role serves — a thin worker's or co-located worker's queue\n * subscription, AND (for a co-located worker) the suffix each `@Workflow`'s dispatch token carries.\n * Each handler's queue token is `tenantGroup(sanitizeQueueToken(name), partition)`\n * (`@dudousxd/nestjs-durable-core`), so `undefined`, `''`, or `'default'` stays byte-identical to\n * the bare (sanitized) name (single-tenant deployment unchanged), and any other partition serves\n * `<name>@<partition>` — matching the queue name an operator's convention dispatch routes that\n * tenant's runs to (`tenantGroup(run.workflow, run.namespace)` on the engine side). Not to be\n * confused with {@link namespace} (the operator's own poll-scoping axis). Ignored for a plain\n * operator (no `connection`).\n */\n partition?: string;\n /** Key prefix namespacing the durable queues for a worker role's consumer. Defaults to `durable`\n * (matches the transport). Ignored for a plain operator (no `connection`). */\n prefix?: string;\n /**\n * How many tasks a worker role's co-located/thin consumer runs concurrently PER SUBSCRIBED QUEUE\n * (BullMQ Worker concurrency — the same limit is applied to every per-name queue the single\n * `runRedisWorker` call starts). Defaults to 1. Raise it so a fanned-out batch (e.g. the N remote\n * steps of a `gather`) runs in parallel instead of serially.\n *\n * Pass `'adaptive'` (or `{ mode:'adaptive', ... }`) to let the consumer self-tune its concurrency\n * (latency gradient + RAM brake + backpressure) and publish a live status on its heartbeat. Ignored\n * for a plain operator (no `connection`).\n */\n concurrency?: ConcurrencyOption;\n /**\n * Per-handler concurrency override, keyed by workflow/step NAME. Falls back to {@link concurrency}\n * (then 1). NOT YET WIRED THROUGH: `runRedisWorker` (`@dudousxd/durable-worker`) currently applies\n * one {@link concurrency} limit uniformly across every per-name queue it subscribes to from a\n * single call — reserved here for when per-name concurrency lands there.\n */\n concurrencyByHandler?: Record<string, ConcurrencyOption>;\n /** Timeout for a thin worker's `RunGateway` round-trip over `transport` before it rejects. Defaults\n * to 10_000ms inside `ProxyRunGateway`. Ignored for an operator (bound to `StoreRunGateway`). */\n runGatewayTimeoutMs?: number;\n /**\n * An explicit preset that NAMES the deployment role instead of leaving it to `store`/`connection`\n * inference, and VALIDATES the axes that are otherwise only prose (see the interface doc's role\n * matrix, and {@link namespace} vs {@link partition}). Omit to keep the existing inference —\n * `topology` is entirely additive and changes nothing when unset.\n *\n * **`namespace` vs `partition` — the two axes this preset locks down:**\n * - `namespace` is the OPERATOR's poll-scoping axis: which runs a control-plane instance\n * drives/recovers/resumes (`runPending`/`recoverIncomplete`/`resumeDueTimers`/`sweepTimeouts`).\n * Unset makes the instance see (drive) EVERY namespace.\n * `{ role: 'control-plane' }` is the only preset that allows it.\n * - `partition` is the WORKER's queue-routing suffix: which queue a thin/co-located worker's\n * consumer subscribes to (`<name>@<partition>`), matching the queue an operator's convention\n * dispatch routes a run's `namespace` to. `{ role: 'tenant' }` sets this FOR you from `tenant` —\n * you never set `partition` directly under `topology`.\n *\n * **One `tenant` word, two axes — the preset maps it to the right one per role:** on a\n * `control-plane` an optional `tenant` scopes the OPERATOR to its own runs (maps to `namespace` —\n * a self-contained local stack sharing a broker with a deployed cluster names itself so neither\n * drives the other's runs; leave it unset on a deployed operator to drive every tenant). On a\n * `tenant` role it is required and maps to `partition` (the worker's queue suffix).\n *\n * ```ts\n * // Control plane: owns the store, dispatches over the transport, prunes old runs. The optional\n * // `tenant` (e.g. from an env var) scopes this operator to its own runs — undefined on a\n * // deployed operator (drives everything), set on a local stack sharing the broker.\n * DurableModule.forRoot({\n * topology: { role: 'control-plane', tenant: process.env.DURABLE_TENANT },\n * store,\n * transport,\n * retention: { policies: [{ statuses: ['completed', 'cancelled'], maxAge: '14d' }] },\n * });\n *\n * // Tenant: store-less worker scoped to its own partition — `tenant` maps to `partition` for you.\n * DurableModule.forRoot({\n * topology: { role: 'tenant', tenant: 'acme-corp' },\n * connection: process.env.REDIS_URL,\n * });\n * ```\n *\n * Validated at `forRoot`/`forRootAsync` resolution time — see {@link DurableModule} for the exact\n * error messages, which double as the axis primer above.\n */\n topology?: { role: 'control-plane'; tenant?: string } | { role: 'tenant'; tenant: string };\n}\n\nexport interface DurableModuleAsyncOptions {\n // `never[]` (not `object[]`) is intentional: a contravariant bottom-type rest param is what lets a\n // consumer supply a factory with concrete injected args, e.g. `(config: ConfigService) => …`.\n useFactory: (...args: never[]) => DurableModuleOptions | Promise<DurableModuleOptions>;\n inject?: InjectionToken[];\n}\n\n/** Throws the exact role-inference contract errors documented on {@link DurableModuleOptions}. */\nfunction assertValidRole(options: DurableModuleOptions): void {\n const hasStore = options.store !== undefined;\n const hasConnection = options.connection !== undefined;\n if (!hasStore && !hasConnection) {\n throw new Error(\n 'a durable module needs either a `store` (operator) or a `connection` (worker)',\n );\n }\n if (hasStore && options.transport === undefined && options.transports === undefined) {\n throw new Error('an operator (`store`) needs a `transport` (or `transports`)');\n }\n}\n\n/**\n * Validates the {@link DurableModuleOptions.topology} preset (a no-op when it's unset — existing\n * inference is untouched). Each thrown message NAMES the role, the offending option, and a one-line\n * explanation of the axis it protects — these messages are the feature: they teach `namespace` (the\n * operator's poll-scoping axis) apart from `partition`/`tenant` (the worker's queue-routing axis),\n * which today are only reconciled in consumer-app comments.\n */\nfunction assertValidTopology(options: DurableModuleOptions): void {\n const topology = options.topology;\n if (topology === undefined) return;\n\n if (topology.role === 'control-plane') {\n if (\n options.store === undefined ||\n (options.transport === undefined && options.transports === undefined)\n ) {\n throw new Error(\n \"topology: { role: 'control-plane' } needs `store` AND (`transport` or `transports`).\\n\" +\n 'A control-plane node is the durable operator: it owns the state store and dispatches ' +\n 'runs to workers over a transport — without both, there is nothing for it to operate.',\n );\n }\n if (options.partition !== undefined) {\n throw new Error(\n \"topology: { role: 'control-plane' } forbids `partition`.\\n\" +\n 'partition is the WORKER queue-routing suffix — on a control-plane node, runs are ' +\n \"dispatched to tenant partitions via each run's namespace, not via this option. Set \" +\n \"`partition` (or `topology: { role: 'tenant', tenant }`) on the WORKER node instead.\",\n );\n }\n if (\n topology.tenant !== undefined &&\n options.namespace !== undefined &&\n options.namespace !== topology.tenant\n ) {\n throw new Error(\n `topology: { role: 'control-plane', tenant: '${topology.tenant}' } conflicts with \\`namespace: '${options.namespace}'\\`.\\ntenant maps 1:1 onto namespace (the operator's poll-scoping axis) — set only \\`tenant\\`, or set \\`namespace\\` to the same value.`,\n );\n }\n return;\n }\n\n if (options.connection === undefined) {\n throw new Error(\n `topology: { role: 'tenant', tenant: '${topology.tenant}' } needs \\`connection\\`.\\nA tenant is a store-less worker: it connects to the broker directly to pull its own partition of tasks instead of polling a store it does not have.`,\n );\n }\n if (options.store !== undefined) {\n throw new Error(\n \"topology: { role: 'tenant' } forbids `store`.\\n\" +\n 'A tenant is store-less BY DEFINITION — a `store` present means you actually wanted ' +\n \"`topology: { role: 'control-plane' }` (the store-owning role).\",\n );\n }\n if (options.namespace !== undefined) {\n throw new Error(\n \"topology: { role: 'tenant' } forbids `namespace`.\\n\" +\n \"namespace is the OPERATOR's poll-scoping axis (which runs a control-plane instance \" +\n 'drives/recovers/resumes) — a tenant worker has no poll loop for it to scope. Use this ' +\n \"preset's `tenant` field for the worker's own routing axis instead.\",\n );\n }\n if (options.partition !== undefined && options.partition !== topology.tenant) {\n throw new Error(\n `topology: { role: 'tenant', tenant: '${topology.tenant}' } conflicts with \\`partition: '${options.partition}'\\`.\\ntenant maps 1:1 onto partition (the worker queue-routing suffix) — set only \\`tenant\\`, or set \\`partition\\` to the same value.`,\n );\n }\n}\n\n/**\n * Applies the {@link DurableModuleOptions.topology} preset's `tenant` mapping — per role: onto\n * `partition` (worker queue-routing) for `{ role: 'tenant' }`, onto `namespace` (operator\n * poll-scoping) for `{ role: 'control-plane' }`. Validated already by {@link assertValidTopology}\n * (an equal explicit value is a no-op; a mismatched one throws before this runs). A no-op for an\n * unset `topology` (existing behavior, zero change).\n */\nfunction resolveTopology(options: DurableModuleOptions): DurableModuleOptions {\n const topology = options.topology;\n if (topology === undefined) return options;\n if (topology.role === 'control-plane') {\n // An optional tenant scopes the operator to its own runs — maps onto `namespace` (undefined =\n // global operator, drives every tenant; validated against an explicit mismatched `namespace`).\n // The namespace stays a STORE-side axis: the engine stamps runs with it and routes their work\n // via `@<tenant>` GROUP suffixes on the transport's own prefix (the cross-SDK convention every\n // tenant worker speaks natively) — it never re-scopes the transport keyspace.\n if (topology.tenant === undefined || options.namespace !== undefined) return options;\n return { ...options, namespace: topology.tenant };\n }\n if (options.partition !== undefined) return options;\n return { ...options, partition: topology.tenant };\n}\n\n/**\n * Boot-time wiring for the tenant run gateway's OPERATOR side, gated by {@link isDrivingOperator} (an\n * operator instance drives by default; `drive: false` — or a thin worker with no `store` at all —\n * does not). Two independent capabilities, each wired only when the transport carries it:\n *\n * 1. **`RunRequestResponder`** — answers a tenant's {@link RunRequest} against the bound\n * {@link RUN_GATEWAY}, tenant-scoped. Wired when the transport implements both\n * `onRunRequest`/`publishRunReply` (only broker transports carry the protocol).\n * 2. **Tenant-event re-publisher** ({@link TenantEventRepublisher}) — mirrors every engine\n * lifecycle event onto its run's per-tenant channel via `publishTenantEvent`, so a store-less\n * tenant worker can live-tail its OWN runs. See that class for the namespace-resolution and\n * cache-eviction details.\n */\n@Injectable()\nclass RunGatewayBootstrap implements OnApplicationBootstrap, OnModuleDestroy {\n private unsubscribe?: () => void;\n\n constructor(\n private readonly engine: WorkflowEngine,\n @Inject(TRANSPORT_CANONICAL) private readonly transport: Transport | null,\n private readonly gateway: RunGateway,\n @Inject(STATE_STORE_CANONICAL) private readonly store: StateStore | null,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n onApplicationBootstrap(): void {\n if (!isDrivingOperator(this.options) || !this.transport) return;\n const store = this.store;\n if (!store) return; // unreachable when isDrivingOperator is true — keeps types honest\n\n const { onRunRequest, publishRunReply, publishTenantEvent } = this.transport;\n if (typeof onRunRequest === 'function' && typeof publishRunReply === 'function') {\n // Bind to the transport: these are real methods that touch the transport's private fields\n // (e.g. BullMQTransport's `this.#runRequestName()`), so they must keep their receiver — passing\n // them destructured/unbound makes `this` the literal below and throws \"Receiver must be an\n // instance of class\" on first use. (Mirrors the `publishTenantEvent.bind` just below.)\n const runRequestTransport: RunRequestTransport = {\n onRunRequest: onRunRequest.bind(this.transport),\n publishRunReply: publishRunReply.bind(this.transport),\n };\n new RunRequestResponder(runRequestTransport, this.gateway).start();\n }\n\n if (typeof publishTenantEvent === 'function') {\n const republisher = new TenantEventRepublisher(\n store,\n publishTenantEvent.bind(this.transport),\n );\n this.unsubscribe = this.engine.subscribe((event) => {\n void republisher.handle(event);\n });\n }\n }\n\n onModuleDestroy(): void {\n this.unsubscribe?.();\n }\n}\n\n@Module({})\nexport class DurableModule {\n static forRoot(options: DurableModuleOptions): DynamicModule {\n DurableModule.assertValid(options);\n return DurableModule.build({\n provide: DURABLE_OPTIONS_CANONICAL,\n useValue: resolveTopology(options),\n });\n }\n\n static forRootAsync(options: DurableModuleAsyncOptions): DynamicModule {\n return DurableModule.build({\n provide: DURABLE_OPTIONS_CANONICAL,\n useFactory: async (...args: Parameters<DurableModuleAsyncOptions['useFactory']>) => {\n const resolved = await options.useFactory(...args);\n DurableModule.assertValid(resolved);\n return resolveTopology(resolved);\n },\n inject: options.inject ?? [],\n });\n }\n\n /**\n * Validates role/axis constraints, in resolution order. When {@link DurableModuleOptions.topology}\n * is set, it OWNS validation — {@link assertValidTopology}'s own store/transport/connection checks\n * are strictly stronger than {@link assertValidRole}'s, so the topology-specific, axis-teaching\n * message is what a `topology`-opted-in consumer sees (not the older generic one). Falls back to\n * {@link assertValidRole} unchanged when `topology` is absent — zero behavior change.\n */\n private static assertValid(options: DurableModuleOptions): void {\n if (options.topology !== undefined) {\n assertValidTopology(options);\n return;\n }\n assertValidRole(options);\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_CANONICAL,\n useFactory: (options: DurableModuleOptions): StateStore | null =>\n options.store !== undefined ? scopedStore(options.store, options) : null,\n inject: [DURABLE_OPTIONS_CANONICAL],\n },\n {\n provide: TRANSPORT_CANONICAL,\n // With a POOL (`transports`) and no singular `transport`, the canonical transport is the\n // pool's PRIMARY — the same one the engine dispatches on first. Leaving it null starved\n // the step registrar / in-app worker of a transport, so a pool-configured operator\n // registered NO step handlers and its own steps parked in `wait` with no consumer.\n useFactory: (options: DurableModuleOptions) =>\n options.transport ?? options.transports?.[0]?.transport ?? null,\n inject: [DURABLE_OPTIONS_CANONICAL],\n },\n // Legacy back-compat aliases (deprecated tokens still resolve to the same instances):\n { provide: STATE_STORE, useExisting: STATE_STORE_CANONICAL },\n { provide: TRANSPORT, useExisting: TRANSPORT_CANONICAL },\n { provide: DURABLE_OPTIONS, useExisting: DURABLE_OPTIONS_CANONICAL },\n {\n // Shared token, bound EXACTLY once: a real engine for the operator role, or a store-less\n // `DurableStartClient` facade for a thin worker (no `store`) — either way, `WorkflowService`\n // and app code call `engine.start(...)` unchanged.\n provide: WorkflowEngine,\n useFactory: async (\n options: DurableModuleOptions,\n store: StateStore | null,\n transport: Transport | null,\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 if (options.store === undefined) {\n return new DurableStartClient(options);\n }\n if (!store) {\n throw new Error(\n 'unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set',\n );\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 ?? options.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 options.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 `@Step` 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: options.transports,\n controlPlane: options.controlPlane ?? (isControlPlane(primary) ? primary : undefined),\n leaseMs: options.leaseMs,\n admission: options.admission,\n maxRecoveryAttempts: options.maxRecoveryAttempts,\n remoteAdvanceSilenceMs: options.remoteAdvanceSilenceMs,\n instanceId: options.instanceId,\n namespace: options.namespace,\n webhookUrl: options.webhookUrl,\n traceparent: options.traceparent,\n context,\n rehydrate: rehydrate || undefined,\n compensationRetries: options.compensationRetries,\n // A non-driving (dashboard/API) operator must not run workflows: enqueue-only, leaving\n // each `pending` run in the store for a DRIVING instance's poll. A driving operator gets\n // the engine's default in-process dispatcher: for a body registered inline, it runs\n // locally; for one registered GROUP-SERVED (co-located worker) or left unregistered, the\n // SAME default dispatcher routes it out remotely (group-served executor, or convention\n // dispatch — always on) instead.\n runDispatcher: options.drive === false ? { dispatch: () => {} } : undefined,\n });\n for (const queue of options.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: [\n DURABLE_OPTIONS_CANONICAL,\n STATE_STORE_CANONICAL,\n TRANSPORT_CANONICAL,\n ModuleRef,\n ],\n },\n WorkflowService,\n EntityService,\n WorkflowRegistrar,\n DurableStepRegistrar,\n TimerPoller,\n RetentionPoller,\n // Tenant run gateway: bound EXACTLY once — the store-backed `StoreRunGateway` for an operator,\n // or (for a thin worker, no `store`) a `ProxyRunGateway` over `transport` when given, else a\n // gateway whose every method rejects with a clear tenant error.\n {\n provide: RunGateway,\n useFactory: (\n options: DurableModuleOptions,\n store: StateStore | null,\n engine: WorkflowEngine,\n ): RunGateway => {\n if (options.store !== undefined) {\n if (!store) {\n throw new Error(\n 'unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set',\n );\n }\n return new StoreRunGateway(store, engine);\n }\n return options.transport\n ? new ProxyRunGateway(\n options.transport,\n options.partition ?? 'default',\n options.runGatewayTimeoutMs,\n )\n : unavailableRunGateway();\n },\n inject: [DURABLE_OPTIONS_CANONICAL, STATE_STORE_CANONICAL, WorkflowEngine],\n },\n RunGatewayBootstrap,\n // Co-located in-app worker (uniform dispatch): inert unless BOTH `store` and `connection` are\n // set — see `in-app-worker.ts`.\n ...inAppWorkerProviders(),\n // Pure thin-worker consumer: inert unless `connection` is set WITHOUT `store` — see\n // `durable-worker.module.ts`.\n ...thinWorkerProviders(),\n ],\n exports: [\n WorkflowService,\n EntityService,\n WorkflowEngine,\n STATE_STORE,\n STATE_STORE_CANONICAL,\n TRANSPORT,\n TRANSPORT_CANONICAL,\n DURABLE_OPTIONS_CANONICAL,\n RunGateway,\n ],\n };\n }\n}\n","import {\n DURABLE_OPTIONS_CANONICAL,\n type StepLogger,\n TRANSPORT_CANONICAL,\n type Transport,\n} from '@dudousxd/nestjs-durable-core';\nimport { Inject, Injectable, type OnModuleInit } from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport { scanSteps } from './discovery-helpers';\nimport type { DurableModuleOptions } from './durable.module';\nimport { isDrivingOperator } from './role';\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> | 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 `@Step` methods and registers them — under their RESOLVED name (derived `Class.method`\n * for a bare `@Step()`, or the explicit override) — as step handlers on the configured transport,\n * when that transport runs handlers in-process. With a queue/remote transport the handlers live in\n * 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 `@Step` 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_CANONICAL) private readonly transport: Transport | null,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n onModuleInit(): void {\n // A non-driving operator (`drive: false`) or a thin worker (no `store`) must not consume the\n // queue — receiving and running step tasks locally is exactly the driving-operator role we're\n // switching off here (a thin worker registers its steps via `ThinStepRegistrar` instead).\n if (!isDrivingOperator(this.options)) return;\n if (!supportsHandle(this.transport)) return;\n const transport = this.transport;\n\n // Forward the step logger as a second arg; methods that only declare `(input)` ignore it.\n scanSteps(this.discovery, this.metadataScanner, (meta, handler) =>\n transport.handle(meta.name, handler),\n );\n }\n}\n","import type { DurableModuleOptions } from './durable.module';\n\n/**\n * True for the **operator** role — `store` is set (with or without a co-located `connection`). See\n * {@link DurableModuleOptions} for the full role matrix. Kept in its own module (rather than\n * `durable.module.ts`) so the poller/registrar classes can import it as a plain VALUE without a\n * circular value dependency on the module that also imports them as providers.\n */\nexport function isOperatorRole(options: DurableModuleOptions): boolean {\n return options.store !== undefined;\n}\n\n/**\n * True when an operator instance actively DRIVES runs — polls pending, recovers crashed, resumes due\n * timers, sweeps timeouts, prunes retention, consumes local steps — as opposed to a read-only/\n * dashboard replica (`drive: false`) that mounts the store/dashboard but leaves driving to another\n * instance. Meaningless (`false`) outside the operator role. Defaults to `true` for an operator.\n */\nexport function isDrivingOperator(options: DurableModuleOptions): boolean {\n return isOperatorRole(options) && options.drive !== false;\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 {\n DurableWorkerRuntime,\n type RunningWorker,\n runRedisWorker as defaultRunRedisWorker,\n} from '@dudousxd/durable-worker';\nimport {\n DURABLE_OPTIONS_CANONICAL,\n TRANSPORT_CANONICAL,\n type Transport,\n} from '@dudousxd/nestjs-durable-core';\nimport {\n Inject,\n Injectable,\n type OnApplicationBootstrap,\n type OnApplicationShutdown,\n type OnModuleInit,\n type Provider,\n} from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport { scanSteps, scanWorkflows } from './discovery-helpers';\nimport type { RunRedisWorkerFn } from './durable-worker.module';\nimport type { DurableModuleOptions } from './durable.module';\n\n/**\n * The **co-located in-app worker** (uniform dispatch): active whenever an app supplies BOTH `store`\n * AND `connection` (`DurableModule.forRoot({ store, transport, connection, partition? })`) — the same\n * process runs the engine AND serves its own discovered `@Workflow`/`@Step`. The engine registers each\n * `@Workflow` GROUP-SERVED — its turns are dispatched, PER WORKFLOW NAME, to\n * `tenantGroup(sanitizeQueueToken(name), partition)` over the transport via a per-workflow\n * `RemoteWorkflowExecutor` instead of run inline — and a co-located {@link DurableWorkerRuntime}\n * subscribes one queue per discovered name (via `runRedisWorker`) and replays the very same TS bodies.\n * This is the uniform-dispatch \"one app, both roles\" shape — every turn pays a transport round-trip\n * even though the worker is the same process. Requires a transport that carries workflow tasks\n * (BullMQ); an in-process-only transport cannot dispatch a `WorkflowExecutor`.\n *\n * Distinct from the PURE thin-worker role (`connection` set, `store` unset — see\n * `durable-worker.module.ts`'s `ThinWorkflowRegistrar`/`ThinStepRegistrar`/`ThinWorkerBootstrap`),\n * which has no engine/store of its own at all.\n */\nexport const IN_APP_WORKER_BINDING = Symbol('nestjs-durable:in-app-worker-binding');\n\n/** The co-located worker's {@link DurableWorkerRuntime} (the consumer half). */\nexport const IN_APP_WORKER_RUNTIME = Symbol('nestjs-durable:in-app-worker-runtime');\n\n/** `runRedisWorker`, injected so tests can substitute a fake (no real Redis). Defaults to the real one. */\nexport const IN_APP_RUN_REDIS_WORKER = Symbol('nestjs-durable:in-app-run-redis-worker');\n\n/** Started {@link RunningWorker} handles for the in-app worker, closed on shutdown. */\nexport const IN_APP_WORKER_RUNNERS = Symbol('nestjs-durable:in-app-worker-runners');\n\n/** The group-served binding shape resolved behind {@link IN_APP_WORKER_BINDING}. */\nexport interface InAppWorkerBinding {\n transport: Transport;\n partition?: string;\n}\n\n/** True for the **co-located** role — both `store` AND `connection` set. */\nfunction isCoLocatedWorker(options: DurableModuleOptions): boolean {\n return options.store !== undefined && options.connection !== undefined;\n}\n\n/**\n * Builds the {@link IN_APP_WORKER_BINDING}: when this app is co-located (`store` + `connection`), the\n * engine's transport plus the configured partition — the registrar builds a PER-WORKFLOW\n * `RemoteWorkflowExecutor` from these, one per discovered name; otherwise `null` (inline default).\n * Fails fast if co-located but the transport can't carry workflow tasks — a group-served run would\n * otherwise dead-end at dispatch.\n */\nfunction inAppWorkerBinding(\n transport: Transport | null,\n options: DurableModuleOptions,\n): InAppWorkerBinding | null {\n if (!isCoLocatedWorker(options)) return null;\n if (!transport?.dispatchWorkflowTask || !transport.onDecision) {\n throw new Error(\n 'a co-located worker (store + connection) requires a transport that carries workflow tasks (dispatchWorkflowTask + onDecision), e.g. BullMQTransport. An in-process transport cannot serve a group-served workflow.',\n );\n }\n return {\n transport,\n ...(options.partition !== undefined ? { partition: options.partition } : {}),\n };\n}\n\n/**\n * The consumer half of the in-app worker: on init it registers every discovered `@Workflow`/`@Step`\n * on a {@link DurableWorkerRuntime} (the SAME bodies the engine registered group-served), and on\n * bootstrap it starts one `runRedisWorker` call that subscribes one queue per discovered name,\n * suffixed by the configured partition, closing it on shutdown. A no-op outside the co-located role\n * (see {@link isCoLocatedWorker}). Mirrors the thin {@link\n * import('./durable-worker.module').ThinWorkerBootstrap}, but co-located with a full engine.\n */\n@Injectable()\nexport class InAppWorkerBootstrap\n implements OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown\n{\n private readonly runners: RunningWorker[] = [];\n\n constructor(\n private readonly discovery: DiscoveryService,\n private readonly metadataScanner: MetadataScanner,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n @Inject(IN_APP_WORKER_RUNTIME) private readonly runtime: DurableWorkerRuntime,\n @Inject(IN_APP_RUN_REDIS_WORKER) private readonly runRedisWorker: RunRedisWorkerFn,\n @Inject(IN_APP_WORKER_RUNNERS) private readonly runnersSink: RunningWorker[],\n ) {}\n\n onModuleInit(): void {\n if (!isCoLocatedWorker(this.options)) return;\n // Register the same TS bodies the engine serves group-served, so the consumer can replay them.\n scanWorkflows(this.discovery, (meta, instance) =>\n this.runtime.registerWorkflow(meta.name, (ctx, input) => instance.run(ctx, input)),\n );\n scanSteps(this.discovery, this.metadataScanner, (meta, handler) =>\n this.runtime.registerStep(meta.name, handler),\n );\n }\n\n async onApplicationBootstrap(): Promise<void> {\n const options = this.options;\n if (!isCoLocatedWorker(options) || options.connection === undefined) return;\n const handle = await this.runRedisWorker({\n runtime: this.runtime,\n connection: options.connection,\n ...(options.partition !== undefined ? { partition: options.partition } : {}),\n ...(options.prefix !== undefined ? { prefix: options.prefix } : {}),\n ...(options.instanceId !== undefined ? { instanceId: options.instanceId } : {}),\n ...(options.concurrency !== undefined ? { concurrency: options.concurrency } : {}),\n });\n this.runners.push(handle);\n this.runnersSink.push(handle);\n }\n\n async onApplicationShutdown(): Promise<void> {\n await Promise.allSettled(this.runners.map((handle) => handle.close()));\n }\n}\n\n/**\n * The providers that stand up the co-located in-app worker, added to {@link\n * import('./durable.module').DurableModule}'s unified provider set. All are inert outside the\n * co-located role (`store` + `connection` both set — see {@link isCoLocatedWorker}), so a plain\n * operator (`store` only) or pure thin worker (`connection` only) is unaffected.\n */\nexport function inAppWorkerProviders(): Provider[] {\n return [\n {\n provide: IN_APP_WORKER_BINDING,\n useFactory: (transport: Transport | null, options: DurableModuleOptions) =>\n inAppWorkerBinding(transport, options),\n inject: [TRANSPORT_CANONICAL, DURABLE_OPTIONS_CANONICAL],\n },\n {\n provide: IN_APP_WORKER_RUNTIME,\n // The runtime's `WorkflowWorker` uses its `group` ctor param as the WORKFLOW's\n // `workflowPartition` for any `ctx.step` call (see `workflow-context.ts`'s `resolveCallGroup`)\n // — that fallback MUST equal this app's own `partition`, or a dispatched step's decision\n // carries a mismatched token and the engine dispatches it to a queue nothing here subscribes\n // to. Explicit `''` (never\n // `undefined`) so it does NOT fall through to `WorkflowWorker`'s unrelated `'workflows'`\n // default parameter.\n useFactory: (options: DurableModuleOptions) =>\n new DurableWorkerRuntime({ workflowGroup: options.partition ?? '' }),\n inject: [DURABLE_OPTIONS_CANONICAL],\n },\n { provide: IN_APP_RUN_REDIS_WORKER, useValue: defaultRunRedisWorker },\n { provide: IN_APP_WORKER_RUNNERS, useValue: [] as RunningWorker[] },\n InAppWorkerBootstrap,\n ];\n}\n","import type {\n DurableTopology,\n EngineEvent,\n GroupHealth,\n RunDetail,\n RunGateway,\n RunListItem,\n RunQuery,\n RunReply,\n RunRequestKind,\n RunResult,\n RunWaiting,\n Transport,\n} from '@dudousxd/nestjs-durable-core';\nimport { Injectable } from '@nestjs/common';\n\n/**\n * Method-shorthand signatures (not arrow-function-typed properties) are deliberate: TypeScript\n * checks method parameters bivariantly, so a `Promise<T>`'s own `resolve`/`reject` (typed for that\n * call's concrete `T`) can be stored here erased to `unknown` with no cast — the one place the\n * generic `T` from {@link ProxyRunGateway.request} crosses into the untyped bookkeeping map.\n */\ninterface PendingRequest {\n resolve(data: unknown): void;\n reject(err: Error): void;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Tenant-side `RunGateway` — round-trips every verb as a `RunRequest`/`RunReply` pair over the\n * transport, correlated by a minted `requestId`, and bridges `subscribe` onto the transport's\n * per-tenant event stream. Bound under `RUN_GATEWAY` by `DurableModule`'s thin-worker role\n * (`connection` set, no `store`) when the app supplies a `transport` (see `unavailableRunGateway`\n * for the no-transport fallback). The counterpart to the operator-side `StoreRunGateway`: a tenant\n * worker never touches a store/driver directly, only this proxy.\n */\n@Injectable()\nexport class ProxyRunGateway implements RunGateway {\n private readonly pending = new Map<string, PendingRequest>();\n\n constructor(\n private readonly transport: Transport,\n private readonly tenant: string,\n private readonly timeoutMs = 10_000,\n ) {\n this.transport.onRunReply?.((reply: RunReply) => this.handleReply(reply));\n }\n\n private handleReply(reply: RunReply): void {\n const pending = this.pending.get(reply.requestId);\n if (!pending) return;\n clearTimeout(pending.timer);\n this.pending.delete(reply.requestId);\n if (reply.result.ok) {\n pending.resolve(reply.result.data);\n } else {\n pending.reject(new Error(reply.result.error.message));\n }\n }\n\n private request<T>(body: RunRequestKind): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const requestId = globalThis.crypto.randomUUID();\n const timer = setTimeout(() => {\n this.pending.delete(requestId);\n reject(\n new Error(`control plane did not respond to ${body.kind} within ${this.timeoutMs}ms`),\n );\n }, this.timeoutMs);\n this.pending.set(requestId, { resolve, reject, timer });\n this.transport\n .dispatchRunRequest?.({ requestId, tenant: this.tenant, body })\n .catch((error: unknown) => {\n const stillPending = this.pending.get(requestId);\n if (!stillPending) return;\n clearTimeout(stillPending.timer);\n this.pending.delete(requestId);\n stillPending.reject(error instanceof Error ? error : new Error(String(error)));\n });\n });\n }\n\n topology(): DurableTopology {\n return { role: 'tenant', tenant: this.tenant };\n }\n\n getRunDetail(runId: string): Promise<RunDetail | null> {\n return this.request<RunDetail | null>({ kind: 'getRunDetail', runId });\n }\n\n /** The control plane's `StoreRunGateway` resolves each run's `waiting` descriptor; the reply carries\n * it through as plain JSON, so a tenant's list rows name the wait too. */\n listRuns(query: RunQuery): Promise<RunListItem[]> {\n return this.request<RunListItem[]>({ kind: 'listRuns', query });\n }\n\n /** Round-trips to the operator, which scopes the result to this tenant's own `@<tenant>` groups. */\n workerHealth(): Promise<GroupHealth[]> {\n return this.request<GroupHealth[]>({ kind: 'workerHealth' });\n }\n\n /** Bulk, one request for the whole id list (like `listRuns`, not one request per id). The operator\n * filters the reply to runs this tenant actually owns (see `RunRequestResponder`). */\n waitingFor(runIds: string[]): Promise<Record<string, RunWaiting>> {\n return this.request<Record<string, RunWaiting>>({ kind: 'waitingFor', runIds });\n }\n\n cancel(runId: string, opts?: { compensate?: boolean }): Promise<RunResult | null> {\n return this.request<RunResult | null>(\n opts === undefined ? { kind: 'cancel', runId } : { kind: 'cancel', runId, opts },\n );\n }\n\n retry(runId: string): Promise<RunResult | null> {\n return this.request<RunResult | null>({ kind: 'retry', runId });\n }\n\n continue(runId: string): Promise<RunResult | null> {\n return this.request<RunResult | null>({ kind: 'continue', runId });\n }\n\n retryWithInput(runId: string, input: unknown): Promise<{ runId: string } | null> {\n return this.request<{ runId: string } | null>({ kind: 'retryWithInput', runId, input });\n }\n\n redispatchPending(runId: string): Promise<(RunResult & { redispatched: number }) | null> {\n return this.request<(RunResult & { redispatched: number }) | null>({\n kind: 'redispatch',\n runId,\n });\n }\n\n subscribe(runId: string, onEvent: (event: EngineEvent) => void): () => void {\n const unsubscribe = this.transport.onTenantEvent?.(this.tenant, (evt) => {\n if (evt.event.runId === runId) onEvent(evt.event);\n });\n return unsubscribe ?? (() => {});\n }\n}\n","import {\n DURABLE_OPTIONS_CANONICAL,\n STATE_STORE_CANONICAL,\n type StateStore,\n TERMINAL_RUN_STATUSES,\n parseDuration,\n} from '@dudousxd/nestjs-durable-core';\nimport {\n Inject,\n Injectable,\n type OnApplicationBootstrap,\n type OnModuleDestroy,\n} from '@nestjs/common';\nimport type { DurableModuleOptions, DurableRetentionOptions } from './durable.module';\nimport { isDrivingOperator } from './role';\n\nconst DEFAULT_SWEEP_INTERVAL_MS = 60_000;\nconst DEFAULT_BATCH_SIZE = 1_000;\n// Backstop so a single sweep can never loop unbounded. At the default batch size this still drains up\n// to 100k runs per policy per sweep before yielding the event loop to the next tick.\nconst MAX_BATCHES_PER_POLICY = 100;\n\n/**\n * Reject a retention config that would silently misbehave: every policy must set at least one bound,\n * list only terminal statuses (pruning a live status would race the engine), and the status sets must\n * be disjoint (so \"most recent N\" is unambiguous per status group). Throws on the first violation.\n */\nexport function validateRetention(retention: DurableRetentionOptions): void {\n const seen = new Set<string>();\n for (const policy of retention.policies) {\n if (policy.statuses.length === 0) {\n throw new Error('durable retention: each policy must list at least one status');\n }\n if (policy.maxAge == null && policy.maxCount == null) {\n throw new Error('durable retention: each policy must set maxAge and/or maxCount');\n }\n // Fail fast on a typo'd duration string ('7days', '1 month') at boot rather than silently never pruning.\n if (policy.maxAge != null) parseDuration(policy.maxAge);\n for (const status of policy.statuses) {\n if (!TERMINAL_RUN_STATUSES.includes(status)) {\n throw new Error(\n `durable retention: status \"${status}\" is not terminal; only ${TERMINAL_RUN_STATUSES.join(\n ', ',\n )} can be pruned`,\n );\n }\n if (seen.has(status)) {\n throw new Error(\n `durable retention: status \"${status}\" appears in more than one policy; status sets must be disjoint`,\n );\n }\n seen.add(status);\n }\n }\n}\n\n/**\n * Hard-prunes terminal run history on an interval per the configured `retention` policies, keeping the\n * `durable_workflow_runs` table (and its children) bounded so the poller's per-tick scans stay cheap.\n *\n * Worker-only (a dashboard-only instance never prunes), separate from the 1s timer poll (defaults to a\n * 60s sweep), and self-draining: each policy is swept in `batchSize` chunks until a batch comes back\n * short. No-ops with a warning if the store adapter doesn't implement `pruneTerminalRuns`.\n */\n@Injectable()\nexport class RetentionPoller implements OnApplicationBootstrap, OnModuleDestroy {\n private timer?: ReturnType<typeof setInterval>;\n private sweeping = false;\n\n constructor(\n @Inject(STATE_STORE_CANONICAL) private readonly store: StateStore | null,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n /** Non-null once past the `isDrivingOperator` guard (which requires `options.store` to be set). */\n private requireStore(): StateStore {\n if (!this.store) {\n throw new Error(\n 'unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set',\n );\n }\n return this.store;\n }\n\n async onApplicationBootstrap(): Promise<void> {\n // Only a DRIVING operator prunes — a dashboard/dispatch-only instance, or a thin worker with no\n // `store` at all, must not delete history.\n if (!isDrivingOperator(this.options)) return;\n const retention = this.options.retention;\n if (!retention || retention.policies.length === 0) return;\n validateRetention(retention);\n if (typeof this.requireStore().pruneTerminalRuns !== 'function') {\n console.warn(\n '[nestjs-durable] `retention` is configured but the store adapter does not implement pruneTerminalRuns; retention is disabled.',\n );\n return;\n }\n await this.sweep();\n const intervalMs =\n retention.sweepInterval != null\n ? parseDuration(retention.sweepInterval)\n : DEFAULT_SWEEP_INTERVAL_MS;\n if (intervalMs > 0) {\n this.timer = setInterval(() => void this.sweep(), intervalMs);\n this.timer.unref?.();\n }\n }\n\n onModuleDestroy(): void {\n if (this.timer) clearInterval(this.timer);\n }\n\n private async sweep(): Promise<void> {\n if (this.sweeping) return; // never overlap two sweeps on this instance\n const retention = this.options.retention;\n const store = this.requireStore();\n const prune = store.pruneTerminalRuns;\n if (!retention || typeof prune !== 'function') return;\n this.sweeping = true;\n try {\n const batchSize = retention.batchSize ?? DEFAULT_BATCH_SIZE;\n const now = Date.now();\n for (const policy of retention.policies) {\n for (let batch = 0; batch < MAX_BATCHES_PER_POLICY; batch++) {\n const deleted = await prune.call(store, policy, now, batchSize);\n if (deleted < batchSize) break; // backlog drained for this policy\n }\n }\n } finally {\n this.sweeping = false;\n }\n }\n}\n","import type { RunGateway, RunReply, RunRequest, RunWaiting } from '@dudousxd/nestjs-durable-core';\n\n/** The narrow slice of `Transport` the responder needs — a tenant's read/control request in, a\n * correlated reply out. Both are OPTIONAL on the full `Transport` interface (only broker\n * transports carry the run-request/reply protocol); the caller capability-checks before wiring\n * this up (see `durable.module.ts`). */\nexport interface RunRequestTransport {\n onRunRequest(handler: (msg: RunRequest) => Promise<void>): void;\n publishRunReply(reply: RunReply): Promise<void>;\n}\n\n/**\n * Operator-side consumer of a tenant's {@link RunRequest}s: answers each one against a\n * `RunGateway`, enforcing the tenant boundary before touching the run. For every runId-bearing\n * verb it loads the run via `getRunDetail` FIRST and compares `run.namespace` to the requesting\n * `msg.tenant` — a mismatch short-circuits into a `cross-tenant` error reply WITHOUT calling the\n * verb, so a tenant can never read or act on another tenant's run. `listRuns` is scoped by\n * overwriting the query's `namespace` with the requester's tenant, ignoring whatever the client\n * sent. This is the security boundary of the tenant run gateway — do not weaken it.\n */\nexport class RunRequestResponder {\n constructor(\n private readonly transport: RunRequestTransport,\n private readonly gateway: RunGateway,\n ) {}\n\n /** Register the consumer on the transport. Each request is answered independently; a handler\n * failure never throws back into the transport (errors are captured into an error reply). */\n start(): void {\n this.transport.onRunRequest(async (msg) => {\n const reply = await this.handle(msg);\n await this.transport.publishRunReply(reply);\n });\n }\n\n private async handle(msg: RunRequest): Promise<RunReply> {\n const { body } = msg;\n if (body.kind === 'listRuns') {\n // Force the namespace to the requester's tenant — the client-supplied value is discarded,\n // never merely validated, so a tenant can't widen its own query into another's namespace.\n const data = await this.gateway.listRuns({ ...body.query, namespace: msg.tenant });\n return { requestId: msg.requestId, result: { ok: true, data } };\n }\n\n if (body.kind === 'workerHealth') {\n // Not runId-bearing, so it can't ride the getRunDetail namespace check below. Scope by the\n // group-name convention instead: a tenant's queues are suffixed `<name>@<tenant>`, so keep only\n // groups ending in the requester's `@<tenant>` — the operator's own bare groups and every other\n // tenant's are dropped, so a tenant's Workers panel only ever sees ITS OWN queues.\n const all = await this.gateway.workerHealth();\n const data = all.filter((h) => h.group.endsWith(`@${msg.tenant}`));\n return { requestId: msg.requestId, result: { ok: true, data } };\n }\n\n if (body.kind === 'waitingFor') {\n // Bulk, like `listRuns` — but unlike `listRuns` (which forces the query's namespace and so\n // scopes itself) the caller supplies arbitrary ids, which could probe another tenant's runs. The\n // gateway's own `waitingFor` has no namespace to filter by, so verify ownership per MATCHED entry\n // (bounded by how many of the requested ids are actually waiting, never by `runIds.length`) the\n // same way every other runId-bearing verb does below: `getRunDetail` + a `namespace` check.\n const all = await this.gateway.waitingFor(body.runIds);\n const owned = await Promise.all(\n Object.entries(all).map(async ([runId, waiting]) => {\n const runDetail = await this.gateway.getRunDetail(runId);\n return runDetail && runDetail.run.namespace === msg.tenant\n ? ([runId, waiting] as const)\n : undefined;\n }),\n );\n const data: Record<string, RunWaiting> = {};\n for (const entry of owned) {\n if (entry) data[entry[0]] = entry[1];\n }\n return { requestId: msg.requestId, result: { ok: true, data } };\n }\n\n // Every remaining verb is runId-bearing. Load the run FIRST — before calling the verb — so a\n // cross-tenant request never reaches the gateway's mutating methods (cancel/retry/continue/redispatch).\n const detail = await this.gateway.getRunDetail(body.runId);\n if (detail && detail.run.namespace !== msg.tenant) {\n return {\n requestId: msg.requestId,\n result: {\n ok: false,\n error: { message: 'run belongs to another tenant', code: 'cross-tenant' },\n },\n };\n }\n\n if (body.kind === 'getRunDetail') {\n return { requestId: msg.requestId, result: { ok: true, data: detail } };\n }\n\n try {\n const data = await this.callVerb(body);\n return { requestId: msg.requestId, result: { ok: true, data } };\n } catch (err) {\n return {\n requestId: msg.requestId,\n result: { ok: false, error: { message: err instanceof Error ? err.message : String(err) } },\n };\n }\n }\n\n private callVerb(\n body: Exclude<\n RunRequest['body'],\n | { kind: 'listRuns' }\n | { kind: 'getRunDetail' }\n | { kind: 'workerHealth' }\n | { kind: 'waitingFor' }\n >,\n ): Promise<unknown> {\n switch (body.kind) {\n case 'cancel':\n return this.gateway.cancel(body.runId, body.opts);\n case 'retry':\n return this.gateway.retry(body.runId);\n case 'continue':\n return this.gateway.continue(body.runId);\n case 'retryWithInput':\n return this.gateway.retryWithInput(body.runId, body.input);\n case 'redispatch':\n return this.gateway.redispatchPending(body.runId);\n }\n }\n}\n","import {\n type DurableTopology,\n type EngineEvent,\n type GroupHealth,\n type RunDetail,\n type RunGateway,\n type RunListItem,\n type RunQuery,\n type RunResult,\n type RunWaiting,\n STATE_STORE_CANONICAL,\n type StateStore,\n WorkflowEngine,\n indexWaitersByRun,\n resolveRunWaiting,\n} from '@dudousxd/nestjs-durable-core';\nimport { Inject, Injectable } from '@nestjs/common';\n\n/**\n * Store-backed `RunGateway` — the operator-side implementation, bound to {@link RUN_GATEWAY} on\n * worker/drive instances. Reuses `DashboardService`'s six read/control method bodies verbatim\n * (`dashboard.service.ts:76-172`), so a consumer that only needs the bounded `RunGateway` surface\n * (the `RunRequestResponder`, a thin controller) doesn't have to depend on the dashboard package.\n */\n@Injectable()\nexport class StoreRunGateway implements RunGateway {\n constructor(\n @Inject(STATE_STORE_CANONICAL) private readonly store: StateStore,\n private readonly engine: WorkflowEngine,\n ) {}\n\n topology(): DurableTopology {\n return { role: 'control-plane' };\n }\n\n async getRunDetail(runId: string): Promise<RunDetail | null> {\n const run = await this.store.getRun(runId);\n if (!run) return null;\n const [timeline, children] = await Promise.all([\n this.store.listCheckpoints(runId),\n this.engine.getRunChildren(runId), // canonical parent→children edge (shared with cancel cascade)\n ]);\n return { run, timeline, children };\n }\n\n async listRuns(query: RunQuery): Promise<RunListItem[]> {\n const runs = await this.store.listRuns(query);\n // ONE bulk scan of the signal-waiter table (indexed by runId) resolves what each suspended run is\n // parked on — signal / webhook / child — with no per-run timeline fetch. A timer wait falls back\n // to the run's own `wakeAt`. Non-suspended (and remote-step-in-flight) runs carry no `waiting`.\n const waiterByRun = indexWaitersByRun(await this.store.listSignalWaiters(''));\n return runs.map((run) => {\n const waiting = resolveRunWaiting(run, waiterByRun);\n return waiting ? { ...run, waiting } : run;\n });\n }\n\n /**\n * Bulk-resolve what each of `runIds` is currently parked on — for a consumer with its own filtered/\n * paginated run listing (e.g. \"which of MY suspended runs are stuck at a breakpoint\") without\n * re-deriving `listRuns`' waiter scan or querying `durable_step_checkpoints` directly. Mirrors\n * `listRuns`' waiting computation (the SAME bulk signal-waiter scan + `resolveRunWaiting`), but ALSO\n * bulk-fetches the currently-suspended runs to check real status: `engine.cancel` (the non-compensate\n * path) doesn't clear a run's signal waiter row, so a cancelled run can leave an ORPHANED waiter\n * behind — trusting waiter presence alone would wrongly report a terminal run as still waiting.\n * Two bulk scans total (never one query per requested id), same as `listRuns`.\n */\n async waitingFor(runIds: string[]): Promise<Record<string, RunWaiting>> {\n if (runIds.length === 0) return {};\n const idSet = new Set(runIds);\n const [suspended, waiters] = await Promise.all([\n this.store.listRuns({ statuses: ['suspended'] }),\n this.store.listSignalWaiters(''),\n ]);\n const waiterByRun = indexWaitersByRun(waiters);\n const result: Record<string, RunWaiting> = {};\n for (const run of suspended) {\n if (!idSet.has(run.id)) continue;\n const waiting = resolveRunWaiting(run, waiterByRun);\n if (waiting) result[run.id] = waiting;\n }\n return result;\n }\n\n /** Every group the engine knows about — unscoped. A tenant proxy's request is scoped by the\n * `RunRequestResponder` (to the requester's `@<tenant>` groups); the operator's own UI sees all. */\n workerHealth(): Promise<GroupHealth[]> {\n return this.engine.workerHealth();\n }\n\n cancel(runId: string, opts?: { compensate?: boolean }): Promise<RunResult | null> {\n return this.engine.cancel(runId, opts);\n }\n\n /** Re-enqueue (dispatch model) instead of resuming inline — a worker picks the run up and replays it. */\n retry(runId: string): Promise<RunResult | null> {\n return this.engine.requeue(runId);\n }\n\n continue(runId: string): Promise<RunResult | null> {\n return this.engine.continue(runId);\n }\n\n retryWithInput(runId: string, input: unknown): Promise<{ runId: string } | null> {\n return this.engine.retryWithInput(runId, input);\n }\n\n redispatchPending(runId: string): Promise<(RunResult & { redispatched: number }) | null> {\n return this.engine.redispatchPending(runId);\n }\n\n subscribe(runId: string, onEvent: (event: EngineEvent) => void): () => void {\n return this.engine.subscribe((event) => {\n if (event.runId === runId) onEvent(event);\n });\n }\n}\n","import type { EngineEvent, StateStore, TenantEvent } from '@dudousxd/nestjs-durable-core';\n\n/** The narrow slice of `StateStore` the republisher needs — the `step.*`-event namespace fallback\n * (see {@link TenantEventRepublisher.namespaceFor}). Mirrors the `RunRequestTransport` narrowing\n * convention (`run-request-responder.ts`): depend on the smallest surface, not the whole store. */\nexport type RunLookupStore = Pick<StateStore, 'getRun'>;\n\n/** `EngineEvent` types that end a run's lifecycle — no further event for that `runId` will ever\n * follow one of these, so it is the safe point to drop the run from `TenantEventRepublisher`'s\n * namespace cache. Matches the terminal members of `EngineEventType` (`packages/core/src/interfaces.ts`)\n * exactly: `run.suspended` is NOT terminal (a suspended run resumes and emits more events), and\n * there is no separate `run.cancelled`/`run.dead` event type — both cancellation and dead-lettering\n * are published as `run.failed`. */\nfunction isTerminalRunEvent(event: EngineEvent): boolean {\n return event.type === 'run.completed' || event.type === 'run.failed';\n}\n\n/**\n * Re-publishes engine lifecycle events onto a run's per-tenant channel via `publish` (bound from\n * `Transport.publishTenantEvent`), so a store-less tenant worker can live-tail its OWN runs.\n *\n * The run's namespace is read straight off `event.namespace` (stamped by `engine.ts`'s `emit()` on\n * every `run.*` lifecycle event, where the run is already in hand); a `step.*` event doesn't carry\n * it, so those fall back to `store.getRun`. EVERY run's resolved namespace is memoized in\n * `runNamespaces` — including a bare/`default` run — so the store is read at most ONCE per run id\n * regardless of how many `step.*` events that run emits; a `null` cache entry means \"resolved, not\n * a tenant\" (distinct from \"not yet resolved\", i.e. absent from the map). A run's entry is deleted\n * the moment its terminal event is handled, so the cache stays bounded to in-flight runs (tenant and\n * default alike) — the same order of magnitude it was already paying for tenant runs alone, in\n * exchange for turning a per-event store read into a per-run one.\n */\nexport class TenantEventRepublisher {\n private readonly runNamespaces = new Map<string, string | null>();\n\n constructor(\n private readonly store: RunLookupStore,\n private readonly publish: (event: TenantEvent) => Promise<void>,\n ) {}\n\n async handle(event: EngineEvent): Promise<void> {\n const namespace = await this.namespaceFor(event);\n // A terminal run emits no further events — drop its cache entry now regardless of whether this\n // event was itself re-published, so the slot is reclaimed even for a bare/default run.\n if (isTerminalRunEvent(event)) this.runNamespaces.delete(event.runId);\n // Skip bare/default runs — no real tenant to re-publish to, and it would just be noise on an\n // unpartitioned operator.\n if (!namespace || namespace === 'default') return;\n await this.publish({ tenant: namespace, event }).catch(() => undefined);\n }\n\n private async namespaceFor(event: EngineEvent): Promise<string | undefined> {\n if (this.runNamespaces.has(event.runId)) {\n const cached = this.runNamespaces.get(event.runId);\n return cached === null ? undefined : cached;\n }\n if (event.namespace !== undefined) {\n this.runNamespaces.set(event.runId, event.namespace === 'default' ? null : event.namespace);\n return event.namespace;\n }\n const run = await this.store.getRun(event.runId);\n const tenantNamespace =\n run?.namespace !== undefined && run.namespace !== 'default' ? run.namespace : null;\n this.runNamespaces.set(event.runId, tenantNamespace);\n return tenantNamespace === null ? undefined : tenantNamespace;\n }\n}\n","import {\n DURABLE_OPTIONS_CANONICAL,\n WorkflowEngine,\n runSchedules,\n} 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';\nimport { isDrivingOperator } from './role';\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_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n async onApplicationBootstrap(): Promise<void> {\n // Drive suspended runs forward when this operator instance is DRIVING (defaults to true) — it\n // may execute locally or dispatch remotely (group-served / convention), either way it drives. A\n // non-driving (dashboard-only) operator, or a thin worker with no `store` at all, must not\n // resume timers — leave that to a driving instance.\n if (!isDrivingOperator(this.options)) 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","import { RunGateway } from '@dudousxd/nestjs-durable-core';\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\n/**\n * @deprecated Inject the `RunGateway` abstract class directly (it is its own DI token now):\n * `constructor(private readonly gateway: RunGateway)`, provider `{ provide: RunGateway, useClass }`.\n * This symbol is kept as a back-compat alias — it points at the `RunGateway` class, so existing\n * `@Inject(RUN_GATEWAY)` sites resolve the very same token — and will be removed in a future major.\n *\n * `RunGateway` is owned by `@dudousxd/nestjs-durable-core` (a required peer dep of both this package\n * and the dashboard), so the abstract class is a single shared token across packages without the\n * previous `Symbol.for` value-sharing hack.\n */\nexport const RUN_GATEWAY = RunGateway;\n","import {\n DURABLE_OPTIONS_CANONICAL,\n RemoteWorkflowExecutor,\n STATE_STORE_CANONICAL,\n type StateStore,\n type WorkflowCtx,\n WorkflowEngine,\n type WorkflowRun,\n bindWorkflowClass,\n parseDuration,\n sanitizeQueueToken,\n tenantGroup,\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, isDeadLetterHandler } from './decorators';\nimport { scanWorkflows } from './discovery-helpers';\nimport type { DurableModuleOptions } from './durable.module';\nimport { entityConfigFor, getEntityMeta } from './entity';\nimport { IN_APP_WORKER_BINDING, type InAppWorkerBinding } from './in-app-worker';\nimport { classValidatorInput } from './input-validation';\nimport { isDrivingOperator, isOperatorRole } from './role';\nimport { type DurableStepInterceptor, isStepInterceptor } from './step-interceptor';\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_CANONICAL) private readonly store: StateStore | null,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n // Group-served binding when the app opted into an in-app worker (uniform dispatch); null = inline.\n @Inject(IN_APP_WORKER_BINDING) private readonly inAppWorker: InAppWorkerBinding | null,\n ) {}\n\n async onApplicationBootstrap(): Promise<void> {\n // Recover runs left incomplete by a crash/deploy when this operator instance is DRIVING\n // (defaults to true). A non-driving (dashboard-only) operator, or a thin worker with no `store`\n // at all, must not pick up and re-run workflows — leave that to a driving instance.\n if (!isDrivingOperator(this.options)) 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 * Operator only — a thin worker (no `store`) holds no engine to drain. */\n async onApplicationShutdown(): Promise<void> {\n if (!isOperatorRole(this.options)) return;\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 // Operator only — a thin worker (no `store`) registers its bodies on a `DurableWorkerRuntime`\n // instead (see `durable-worker.module.ts`'s `ThinWorkflowRegistrar`), never on this engine.\n if (!isOperatorRole(this.options)) return;\n const store = this.store;\n if (!store) {\n throw new Error(\n 'unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set',\n );\n }\n if (this.options.autoSchema !== false) {\n await 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 // Pre-pass: wire interceptors and register @Entity providers (engine-only concerns).\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 }\n }\n\n // Workflow registration pass — shared scan, engine-specific registration callback.\n scanWorkflows(this.discovery, (meta, workflow) => {\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 // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const workflowCtor: object = (workflow as any).constructor as object;\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 requires: meta.requires,\n validateInput,\n searchAttributesSchema: meta.searchAttributes,\n onEvent: getOnEvents(meta, workflowCtor),\n eventBatch,\n // Uniform dispatch (opt-in): when an in-app worker is configured, register the body GROUP-SERVED\n // so the engine dispatches its turns over the transport instead of running it inline; the\n // co-located worker consumer (Task 5: one queue PER REGISTERED NAME) replays the same body.\n // The routing token — and the executor that dispatches under it — MUST be keyed by THIS\n // workflow's own name (`tenantGroup(sanitizeQueueToken(meta.name), partition)`), not a single\n // group shared across every discovered `@Workflow`: a fixed shared token would dispatch every\n // workflow's turns to one queue while the co-located worker subscribes one queue per name,\n // so a turn dispatched under the wrong token would never be consumed. Absent → the inline fast\n // path (unchanged).\n ...(this.inAppWorker\n ? {\n group: tenantGroup(sanitizeQueueToken(meta.name), this.inAppWorker.partition),\n executor: new RemoteWorkflowExecutor(\n this.inAppWorker.transport,\n meta.name,\n this.inAppWorker.partition,\n ),\n }\n : {}),\n });\n\n // Class-first statics (`MyWorkflow.start()` / `.execute()`): bind the class to THIS engine,\n // so the statics resolve the engine that registered it — no global engine singleton.\n bindWorkflowClass(workflowCtor as new () => object, {\n start: (name, input, runId, opts) => this.engine.start(name, input, runId, opts),\n waitForRun: (runId, opts) => this.engine.waitForRun(runId, opts),\n });\n\n const inline = this.findDeadLetterHandler(workflow as unknown as object);\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 // Route the dead-letter handler to the dead run's OWN tenant so an operator dispatches it to\n // that tenant's worker group (`target@<tenant>`), not the bare group.\n { namespace: run.namespace },\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 })` / `@OnDurableEvent` (the\n * payload becomes its input). Pass `opts.id` to dedupe redeliveries. Returns how many runs it\n * touched (resumed + started).\n *\n * Reliable by default: a publish that touches NOBODY (no live waiter, no subscriber) buffers ONE\n * copy so a LATER `waitForEvent(name, { match })` still consumes it instead of it being dropped —\n * see {@link WorkflowEngine.publishEvent}'s full semantics doc. Pass `opts.buffer: false` to opt out.\n */\n publishEvent(\n name: string,\n payload: unknown,\n opts?: { id?: string; buffer?: boolean },\n ): Promise<number> {\n return this.engine.publishEvent(name, payload, opts);\n }\n}\n"],"mappings":"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","names":["import_nestjs_durable_core","WORKFLOW_METADATA","Symbol","Workflow","options","target","meta","name","version","deadLetterWorkflow","tags","singleton","executionTimeout","inputSchema","validateInput","searchAttributes","onEvent","debounce","batch","requires","Reflect","defineMetadata","Object","defineProperty","WORKFLOW_NAME_KEY","value","configurable","getWorkflowMeta","getMetadata","DURABLE_STEP_METADATA","stepConfigFrom","config","retries","backoff","backoffMs","backoffMaxMs","jitter","timeoutMs","hasAnyField","values","some","undefined","Step","nameOrOptions","propertyKey","descriptor","derivedName","String","input","output","DURABLE_STEP_NAME","DURABLE_STEP_CONFIG","DurableStep","getDurableStepMeta","method","DEAD_LETTER_METADATA","DeadLetter","_target","_propertyKey","isDeadLetterHandler","ON_EVENT_METADATA","OnDurableEvent","events","existing","OnEvent","getOnEvents","fromDecorator","Set","attributesOf","workflow","run","meta","getWorkflowMeta","Error","name","searchAttributes","readSearchAttributes","import_nestjs_durable_core","DurableStartClient","tenant","options","deps","partition","start","workflow","input","runId","globalThis","crypto","randomUUID","opts","name","workflowName","startRun","connection","prefix","undefined","tags","searchAttributes","status","cancel","_runId","tenantUnsupported","deleteRun","resume","waitForRun","_opts","signal","_token","_payload","signalWithStart","_workflow","_input","_signal","publishEvent","_name","method","Promise","reject","Error","import_durable_worker","import_nestjs_durable_core","import_common","scanWorkflows","discovery","register","wrapper","getProviders","instance","meta","getWorkflowMeta","workflow","run","Error","name","scanSteps","scanner","prototype","Object","getPrototypeOf","methodName","getAllMethodNames","method","getDurableStepMeta","boundMethod","handler","input","log","validInput","parse","output","call","RUN_REDIS_WORKER","Symbol","DURABLE_WORKER_RUNNERS","isPureThinWorker","options","store","undefined","connection","ThinWorkflowRegistrar","discovery","runtime","onModuleInit","scanWorkflows","meta","instance","registerWorkflow","name","ctx","input","run","ThinStepRegistrar","metadataScanner","scanSteps","handler","registerStep","ThinWorkerBootstrap","runners","runRedisWorker","runnersSink","onApplicationBootstrap","handle","partition","prefix","instanceId","concurrency","push","onApplicationShutdown","Promise","allSettled","map","h","close","thinWorkerProviders","provide","DurableWorkerRuntime","useFactory","workflowGroup","inject","DURABLE_OPTIONS_CANONICAL","useValue","defaultRunRedisWorker","tenantGatewayUnavailable","method","reject","Error","unavailableRunGateway","topology","role","getRunDetail","_runId","listRuns","_query","waitingFor","_runIds","workerHealth","cancel","retry","continue","retryWithInput","_input","redispatchPending","subscribe","_onEvent","import_nestjs_durable_core","import_common","import_core","import_nestjs_durable_core","import_common","import_core","isOperatorRole","options","store","undefined","isDrivingOperator","drive","supportsHandle","transport","handle","DurableStepRegistrar","discovery","metadataScanner","options","onModuleInit","isDrivingOperator","scanSteps","meta","handler","name","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_durable_worker","import_nestjs_durable_core","import_common","import_core","IN_APP_WORKER_BINDING","Symbol","IN_APP_WORKER_RUNTIME","IN_APP_RUN_REDIS_WORKER","IN_APP_WORKER_RUNNERS","isCoLocatedWorker","options","store","undefined","connection","inAppWorkerBinding","transport","dispatchWorkflowTask","onDecision","Error","partition","InAppWorkerBootstrap","runners","discovery","metadataScanner","runtime","runRedisWorker","runnersSink","onModuleInit","scanWorkflows","meta","instance","registerWorkflow","name","ctx","input","run","scanSteps","handler","registerStep","onApplicationBootstrap","handle","prefix","instanceId","concurrency","push","onApplicationShutdown","Promise","allSettled","map","close","inAppWorkerProviders","provide","useFactory","inject","TRANSPORT_CANONICAL","DURABLE_OPTIONS_CANONICAL","DurableWorkerRuntime","workflowGroup","useValue","defaultRunRedisWorker","import_common","ProxyRunGateway","pending","Map","transport","tenant","timeoutMs","onRunReply","reply","handleReply","get","requestId","clearTimeout","timer","delete","result","ok","resolve","data","reject","Error","error","message","request","body","Promise","globalThis","crypto","randomUUID","setTimeout","kind","set","dispatchRunRequest","catch","stillPending","String","topology","role","getRunDetail","runId","listRuns","query","workerHealth","waitingFor","runIds","cancel","opts","undefined","retry","continue","retryWithInput","input","redispatchPending","subscribe","onEvent","unsubscribe","onTenantEvent","evt","event","import_nestjs_durable_core","import_common","DEFAULT_SWEEP_INTERVAL_MS","DEFAULT_BATCH_SIZE","MAX_BATCHES_PER_POLICY","validateRetention","retention","seen","Set","policy","policies","statuses","length","Error","maxAge","maxCount","parseDuration","status","TERMINAL_RUN_STATUSES","includes","join","has","add","RetentionPoller","timer","sweeping","store","options","requireStore","onApplicationBootstrap","isDrivingOperator","pruneTerminalRuns","console","warn","sweep","intervalMs","sweepInterval","setInterval","unref","onModuleDestroy","clearInterval","prune","batchSize","now","Date","batch","deleted","call","RunRequestResponder","transport","gateway","start","onRunRequest","msg","reply","handle","publishRunReply","body","kind","data","listRuns","query","namespace","tenant","requestId","result","ok","all","workerHealth","filter","h","group","endsWith","waitingFor","runIds","owned","Promise","Object","entries","map","runId","waiting","runDetail","getRunDetail","run","undefined","entry","detail","error","message","code","callVerb","err","Error","String","cancel","opts","retry","continue","retryWithInput","input","redispatchPending","import_nestjs_durable_core","import_common","StoreRunGateway","store","engine","topology","role","getRunDetail","runId","run","getRun","timeline","children","Promise","all","listCheckpoints","getRunChildren","listRuns","query","runs","waiterByRun","indexWaitersByRun","listSignalWaiters","map","waiting","resolveRunWaiting","waitingFor","runIds","length","idSet","Set","suspended","waiters","statuses","result","has","id","workerHealth","cancel","opts","retry","requeue","continue","retryWithInput","input","redispatchPending","subscribe","onEvent","event","isTerminalRunEvent","event","type","TenantEventRepublisher","runNamespaces","Map","store","publish","handle","namespace","namespaceFor","delete","runId","tenant","catch","undefined","has","cached","get","set","run","getRun","tenantNamespace","import_nestjs_durable_core","import_common","TimerPoller","timer","polling","unsubscribeEnqueued","engine","options","onApplicationBootstrap","isDrivingOperator","onEnqueued","runId","runOne","poll","intervalMs","timerPollMs","setInterval","unref","onModuleDestroy","clearInterval","runPending","recoverIncomplete","resumeDueTimers","sweepTimeouts","schedules","length","runSchedules","Date","now","import_nestjs_durable_core","CONTEXT_ACCESSOR","Symbol","for","RUN_GATEWAY","RunGateway","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","inAppWorker","onApplicationBootstrap","isDrivingOperator","recoverIncomplete","onApplicationShutdown","isOperatorRole","drain","shutdownTimeoutMs","transports","transport","map","t","Promise","allSettled","close","onModuleInit","Error","autoSchema","ensureSchema","deadLetterByWorkflow","Map","wrapper","getProviders","instance","isStepInterceptor","interceptor","use","invocation","next","intercept","entityMeta","getEntityMeta","registerEntity","name","entityConfigFor","scanWorkflows","meta","workflow","validateInput","inputSchema","classValidatorInput","undefined","eventBatch","debounce","mode","windowMs","parseDuration","batch","maxSize","within","workflowCtor","register","version","ctx","input","run","tags","singleton","executionTimeout","requires","searchAttributesSchema","searchAttributes","onEvent","getOnEvents","group","tenantGroup","sanitizeQueueToken","partition","executor","RemoteWorkflowExecutor","bindWorkflowClass","start","runId","opts","waitForRun","inline","findDeadLetterHandler","deadLetterWorkflow","dlqName","set","workflowName","installDeadLetterRouting","prototype","Object","getPrototypeOf","methodName","getAllMethodNames","method","isDeadLetterHandler","call","byWorkflow","fallback","size","onDead","target","get","deadRunId","id","error","namespace","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","isScopeableStore","store","withScope","scopedStore","options","scopeReads","namespace","assertValidRole","hasStore","hasConnection","connection","Error","transport","transports","assertValidTopology","topology","role","partition","tenant","resolveTopology","RunGatewayBootstrap","unsubscribe","engine","gateway","onApplicationBootstrap","isDrivingOperator","onRunRequest","publishRunReply","publishTenantEvent","runRequestTransport","bind","RunRequestResponder","start","republisher","TenantEventRepublisher","subscribe","event","handle","onModuleDestroy","DurableModule","forRoot","assertValid","build","provide","DURABLE_OPTIONS_CANONICAL","useValue","forRootAsync","useFactory","args","resolved","inject","optionsProvider","module","global","imports","DiscoveryModule","providers","STATE_STORE_CANONICAL","TRANSPORT_CANONICAL","STATE_STORE","useExisting","TRANSPORT","DURABLE_OPTIONS","WorkflowEngine","DurableStartClient","primary","context","runtime","rehydrate","fn","Object","keys","length","controlPlane","leaseMs","admission","maxRecoveryAttempts","remoteAdvanceSilenceMs","instanceId","webhookUrl","traceparent","compensationRetries","runDispatcher","drive","dispatch","queue","queues","registerQueue","ModuleRef","WorkflowService","EntityService","WorkflowRegistrar","DurableStepRegistrar","TimerPoller","RetentionPoller","RunGateway","StoreRunGateway","ProxyRunGateway","runGatewayTimeoutMs","unavailableRunGateway","inAppWorkerProviders","thinWorkerProviders","exports","import_nestjs_durable_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/attributes-of.ts","../src/decorators.ts","../src/durable-start-client.ts","../src/durable-worker.module.ts","../src/discovery-helpers.ts","../src/durable.module.ts","../src/durable-step.registrar.ts","../src/role.ts","../src/entity.ts","../src/in-app-worker.ts","../src/proxy-run-gateway.ts","../src/retention-poller.ts","../src/run-request-responder.ts","../src/store-run-gateway.ts","../src/tenant-event-republisher.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 './attributes-of';\nexport * from './context-accessor';\nexport * from './decorators';\nexport { DurableStartClient } from './durable-start-client';\nexport * from './durable-worker.module';\nexport * from './durable.module';\nexport * from './entity';\nexport * from './in-app-worker';\nexport * from './proxy-run-gateway';\nexport * from './role';\nexport * from './run-request-responder';\nexport * from './step-interceptor';\nexport * from './store-run-gateway';\nexport * from './tenant-event-republisher';\nexport * from './tokens';\nexport * from './workflow.service';\n\n// Facade re-exports: the everyday `@dudousxd/nestjs-durable-core` surface a consumer touches\n// alongside this package's own decorators/module/tokens, so e.g. the `Workflow` decorator (here)\n// and `WorkflowEngine`/`WorkflowCtx` (core), or `RUN_GATEWAY` (here) and the `RunGateway` class\n// (core), no longer require importing both packages to pick up the paired symbol. Additive only —\n// re-exports only what already exists on core's public index; core's own new exports (e.g. typed\n// search-attribute helpers) are surfaced there, not duplicated here.\n//\n// Anything in core that is a CLASS — `RunGateway` and `WorkflowEngine` are both abstract classes\n// that double as their own DI token — has to be re-exported below as a VALUE. Putting one in the\n// `export type` block breaks consumers in a way neither `tsc` nor a build catches: the emitted\n// `.d.ts` rollup drops the `type` modifier, so the name type-checks as a value, while the JS\n// bundle correctly omits it — `import { RunGateway } from '@dudousxd/nestjs-durable'` compiles\n// green and is `undefined` at runtime, and `moduleRef.get(undefined)` fails only on the code path\n// that resolves it. Keep classes here, types above.\nexport type {\n AttributeFilter,\n EngineEvent,\n InferSearchAttributes,\n RunDetail,\n RunListItem,\n RunQuery,\n RunStatus,\n RunWaiting,\n SearchAttributes,\n SearchAttributesSchema,\n StepCheckpoint,\n StepEvent,\n StepLogger,\n WorkflowCtx,\n WorkflowHandler,\n WorkflowRun,\n} from '@dudousxd/nestjs-durable-core';\nexport { readSearchAttributes, RunGateway, WorkflowEngine } from '@dudousxd/nestjs-durable-core';\n","import {\n type SearchAttributes,\n type WorkflowClass,\n type WorkflowCtx,\n readSearchAttributes,\n} from '@dudousxd/nestjs-durable-core';\nimport { getWorkflowMeta } from './decorators';\n\n/**\n * The search-attributes shape a `@Workflow` class declares — extracted from its `run` method's\n * `ctx: WorkflowCtx<A>` annotation, mirroring the `WorkflowInputOf`/`WorkflowOutputOf` structural-\n * typing idiom in core's `workflow-ref.ts` (which extract `run`'s `input`/return type the same way).\n * A class whose `run` leaves `ctx` untyped (or types it as the bare `WorkflowCtx`) resolves to the\n * untyped `SearchAttributes` default — matching `WorkflowCtx` itself.\n */\nexport type WorkflowAttributesOf<C> = C extends abstract new (\n ...args: never[]\n) => {\n run(ctx: WorkflowCtx<infer A>, input: never): unknown;\n}\n ? A\n : SearchAttributes;\n\n/**\n * Read a run's search attributes **by workflow class**, with the schema resolved from that class's\n * `@Workflow({ searchAttributes })` decorator metadata — the same single-source-of-truth idiom as\n * triggering a workflow by class (`ctx.child(ShippingWorkflow, input)`, `engine.start(CheckoutWorkflow,\n * input)`): the decorator is the one place the schema lives, and every reader references the\n * WORKFLOW, never re-imports or re-declares the schema itself. The return type is inferred\n * structurally from the class's `run(ctx: WorkflowCtx<A>, …)` annotation (see\n * {@link WorkflowAttributesOf}), so a valid read is typed to `A` with no explicit type argument.\n *\n * Delegates to core's `readSearchAttributes(schema, run)` for the actual read, so the same lenient\n * safe-parse semantics apply: a run whose stored `searchAttributes` predate the schema, or fail it,\n * reads back as `{}` rather than throwing (see `readSearchAttributes`'s doc comment).\n *\n * @throws if `workflow` isn't a `@Workflow`-decorated class (no metadata at all — nothing to resolve\n * the schema from).\n * @throws if `workflow` is a `@Workflow` class that never declared a `searchAttributes` schema —\n * reading attributes by class needs one to resolve against.\n *\n * @example\n * ```ts\n * import { z } from 'zod';\n * import { Injectable } from '@nestjs/common';\n * import {\n * attributesOf,\n * InferSearchAttributes,\n * Workflow,\n * WorkflowCtx,\n * WorkflowHandler,\n * } from '@dudousxd/nestjs-durable';\n *\n * const orderAttrs = z.object({ tier: z.enum(['free', 'pro']), amount: z.number() });\n * type OrderAttrs = InferSearchAttributes<typeof orderAttrs>;\n *\n * @Workflow({ name: 'checkout', searchAttributes: orderAttrs })\n * class CheckoutWorkflow implements WorkflowHandler<{ orderId: string }, void, OrderAttrs> {\n * async run(ctx: WorkflowCtx<OrderAttrs>, input: { orderId: string }): Promise<void> {\n * await ctx.upsertSearchAttributes({ tier: 'pro', amount: 100 });\n * }\n * }\n *\n * @Injectable()\n * class CheckoutDashboardService {\n * constructor(private readonly store: StateStore) {}\n *\n * async tierOf(runId: string) {\n * const run = await this.store.getRun(runId);\n * const attrs = attributesOf(CheckoutWorkflow, run ?? {}); // OrderAttrs\n * return attrs.tier;\n * }\n * }\n * ```\n */\nexport function attributesOf<C extends WorkflowClass>(\n workflow: C,\n run: { searchAttributes?: SearchAttributes | null | undefined },\n): WorkflowAttributesOf<C> {\n // biome-ignore lint/complexity/noBannedTypes: getWorkflowMeta reads reflect-metadata off the class target, same contract as workflowName/bindWorkflowClass\n const meta = getWorkflowMeta(workflow as unknown as Function);\n if (!meta) {\n throw new Error(\n `attributesOf: ${workflow.name} is not a @Workflow class — is it decorated with @Workflow({ name, searchAttributes })?`,\n );\n }\n if (!meta.searchAttributes) {\n throw new Error(\n `attributesOf: workflow '${meta.name}' declares no searchAttributes schema — reading attributes by class requires the workflow to declare its schema: @Workflow({ name: '${meta.name}', searchAttributes: mySchema }).`,\n );\n }\n return readSearchAttributes(meta.searchAttributes, run) as WorkflowAttributesOf<C>;\n}\n","import {\n DURABLE_STEP_CONFIG,\n DURABLE_STEP_NAME,\n type SearchAttributesSchema,\n type SingletonConfig,\n type StepConfig,\n type StepError,\n WORKFLOW_NAME_KEY,\n type WorkflowRef,\n} from '@dudousxd/nestjs-durable-core';\nimport 'reflect-metadata';\nimport type { z } from 'zod';\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?: (new (...args: any[]) => object) | undefined;\n /** Custom input validator (throws on invalid). See `WorkflowOptions`. */\n validateInput?: ((input: unknown) => void | Promise<void>) | undefined;\n /** Typed/validated `searchAttributes` shape. See `WorkflowOptions`. */\n searchAttributes?: SearchAttributesSchema | 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 /** Capabilities a live worker must advertise to run this workflow's turns (handshake §7.5). See\n * `WorkflowOptions`. */\n requires?: string[] | 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 * Capabilities a live worker MUST advertise to run this workflow's turns (handshake design §7.5).\n * Only meaningful for a group-served / remote workflow (an in-app worker or a cross-runtime body):\n * the control-plane dispatches a turn only to a capability-capable + protocol-compatible worker, and\n * if descriptors are published on its group but none qualifies the run parks `blocked` until the\n * recovery poll finds one. Absent/empty = \"runs anywhere\" (a legacy fleet skips the guard, §7.7).\n */\n requires?: string[];\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 * Validate this workflow's {@link WorkflowCtx.upsertSearchAttributes} writes against a **Standard\n * Schema** (https://standardschema.dev — zod 3.24+, valibot, arktype, …). `ctx.upsertSearchAttributes`\n * validates the MERGED result (existing attributes shallow-merged with the patch) on every call —\n * an invalid merge throws, naming this workflow, the offending key(s), and the schema's issues. The\n * schema's inferred output must be search-attribute-shaped (flat `string`/`number`/`boolean` values\n * only) — a schema whose output has a nested object/array is a compile-time error here. Omit for\n * the prior unvalidated behavior.\n *\n * ```ts\n * import { z } from 'zod';\n *\n * const orderAttrs = z.object({\n * tier: z.enum(['free', 'pro']),\n * amount: z.number(),\n * });\n *\n * @Workflow({ name: 'checkout', searchAttributes: orderAttrs })\n * class CheckoutWorkflow {\n * async run(ctx: WorkflowCtx<InferSearchAttributes<typeof orderAttrs>>, input: CheckoutInput) {\n * await ctx.upsertSearchAttributes({ tier: input.tier, amount: input.total });\n * }\n * }\n * ```\n */\n searchAttributes?: SearchAttributesSchema;\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 `@OnDurableEvent(...)` 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 searchAttributes: options.searchAttributes,\n onEvent: options.onEvent,\n debounce: options.debounce,\n batch: options.batch,\n requires: options.requires,\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 /** The resolved routing name — derived (`Class.method`) or explicit, always present. */\n name: string;\n /** Opt-in runtime input schema, validated when the handler is served (see `scanSteps`). Absent on\n * a bare `@Step()` — compile-time types from the method signature only, no runtime check. */\n input?: z.ZodType | undefined;\n /** Opt-in runtime output schema, validated before the handler's result is handed back. */\n output?: z.ZodType | undefined;\n}\n\n/**\n * `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,\n * timeoutMs? })` — the object call form. See {@link Step}.\n */\nexport interface StepOptions {\n /** Explicit routing name, overriding the derived `Class.method`. */\n name?: string;\n /** Runtime input schema, validated at the serve boundary (opt-in — a bare `@Step()` skips it). */\n input?: z.ZodType;\n /** Runtime output schema, validated before the result is returned (opt-in). */\n output?: z.ZodType;\n /**\n * Def-level durable-dispatch policy stamped under `DURABLE_STEP_CONFIG` and read by `ctx.step` at\n * the dispatch boundary (see `stepConfigOf`) — a per-call `ctx.step(ref, input, opts)` overrides\n * these field-by-field. Max attempts before the step (and run) fails.\n */\n retries?: number;\n /** How the delay between retries grows: `fixed` (constant) or `exp` (doubles each attempt). */\n backoff?: 'fixed' | 'exp';\n /** Base delay in ms between retries. Omit (or 0) to retry with no delay. */\n backoffMs?: number;\n /** Upper bound on the (exponential) backoff delay. */\n backoffMaxMs?: number;\n /** Add random jitter (50–100% of the computed delay) to avoid thundering-herd retries. */\n jitter?: boolean;\n /**\n * Liveness window for a dispatched step: no result/heartbeat within this many ms presumes the\n * worker dead and fails the dispatch with a `RemoteStepTimeout` (retryable — re-dispatches per\n * `retries`). Omit to wait indefinitely.\n */\n timeoutMs?: number;\n /**\n * Capabilities a live worker MUST advertise to run this step (handshake design §7.5). The\n * control-plane routes the step only to workers whose descriptor advertises every name here; if\n * descriptors are published on the step's group but none is capability-capable + protocol-compatible,\n * the run parks `blocked` (never a silent hang) until the recovery poll finds one. Absent/empty =\n * \"runs anywhere\" (the default — a legacy fleet publishing no descriptors skips the guard, §7.7). A\n * per-call `ctx.step(ref, input, { requires })` overrides this.\n */\n requires?: string[];\n}\n\n/** Build the {@link StepConfig} to stamp under `DURABLE_STEP_CONFIG` from `@Step` options — omitting\n * every unset field, so a bare `@Step()` (or one with only `name`/`input`/`output`) stamps `undefined`\n * and leaves `ctx.step` reading nothing but a per-call `opts` override. */\nfunction stepConfigFrom(options: StepOptions): StepConfig | undefined {\n const config: StepConfig = {\n retries: options.retries,\n backoff: options.backoff,\n backoffMs: options.backoffMs,\n backoffMaxMs: options.backoffMaxMs,\n jitter: options.jitter,\n timeoutMs: options.timeoutMs,\n requires: options.requires,\n };\n const hasAnyField = Object.values(config).some((value) => value !== undefined);\n return hasAnyField ? config : undefined;\n}\n\n/**\n * Marks a provider method as a durable step handler. An in-process transport (e.g. the\n * event-emitter transport) or a co-located/thin worker runtime routes a dispatched task to it BY\n * NAME — `ctx.step(this.svc.method, input)` reads that same name off the method reference (stamped\n * via the shared, cross-package `DURABLE_STEP_NAME` symbol from `@dudousxd/nestjs-durable-core`), so\n * there's no separately-declared def linking a call site to this handler. The method's single\n * argument is the step input (plus an optional `StepLogger` second arg); its return value is the\n * step output.\n *\n * Three call forms:\n * - `@Step()` — bare: the routing name is DERIVED from the method as `` `${ClassName}.${method}` ``\n * (e.g. `ExtractionService.runExtractionPage`) — refactor-safe, no magic string.\n * - `@Step('custom:name')` — explicit name override (stable across refactors, or a cross-runtime\n * contract with a non-JS worker that has no `@Step` of its own).\n * - `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,\n * timeoutMs? })` — optional name override, opt-in RUNTIME zod schemas, and a def-level\n * durable-retry/liveness-timeout policy. `input` validates before the method runs; `output`\n * validates its return value before it's handed back (see `scanSteps`). `retries`/`backoff`/\n * `backoffMs`/`backoffMaxMs`/`jitter`/`timeoutMs` are the policy `ctx.step` reads off this method\n * reference (via `stepConfigOf`) to build the dispatched `StepDef`'s durable retry/backoff and\n * remote-liveness timeout — a per-call `ctx.step(ref, input, opts)` overrides them field-by-field.\n * A bare `@Step()` carries none of this and skips validation/retry/timeout — compile-time types\n * from the method signature are the only check, and the step dispatches with no retry/timeout.\n */\nexport function Step(nameOrOptions?: string | StepOptions): MethodDecorator {\n return (target, propertyKey, descriptor: PropertyDescriptor) => {\n const options =\n typeof nameOrOptions === 'string' ? { name: nameOrOptions } : (nameOrOptions ?? {});\n const derivedName = options.name ?? `${target.constructor.name}.${String(propertyKey)}`;\n const meta: DurableStepMeta = {\n name: derivedName,\n input: options.input,\n output: options.output,\n };\n Reflect.defineMetadata(DURABLE_STEP_METADATA, meta, descriptor.value as object);\n // Stamp the SAME resolved name core's `ctx.step` reads off a method reference (`stepNameOf`) —\n // the cross-package contract letting `ctx.step(this.svc.method, input)` route without a\n // separately-declared def. `DURABLE_STEP_NAME` is core's `Symbol.for`-keyed constant, so a\n // duplicate copy of core still reads back the same key (see `step-name-symbol.ts`).\n (descriptor.value as { [DURABLE_STEP_NAME]?: string })[DURABLE_STEP_NAME] = derivedName;\n // Stamp the def-level dispatch policy (retries/backoff/timeoutMs) core's `ctx.step` reads via\n // `stepConfigOf`, under the same `Symbol.for`-keyed contract as `DURABLE_STEP_NAME`. Omitted\n // entirely when no policy field is set, so a bare `@Step()` stamps nothing extra.\n const config = stepConfigFrom(options);\n if (config !== undefined) {\n (descriptor.value as { [DURABLE_STEP_CONFIG]?: StepConfig })[DURABLE_STEP_CONFIG] = config;\n }\n return descriptor;\n };\n}\n\n/**\n * @deprecated Use `@Step` instead. `@DurableStep` is a back-compat alias of {@link Step} and\n * writes the same `DURABLE_STEP_METADATA`, so discovery/registrars treat them identically.\n */\nexport const DurableStep = Step;\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\n * `@OnDurableEvent(...)` decorators and the option are all merged.\n *\n * Named \"durable\" on purpose: `@nestjs/event-emitter` exports an `@OnEvent` decorator, and in an\n * app using both libs an auto-import picking the wrong one fails silently in either direction.\n */\nexport function OnDurableEvent(...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/** @deprecated Renamed to `OnDurableEvent` — this alias clashes with `@nestjs/event-emitter`'s `@OnEvent` and will be removed in the next minor. */\nexport const OnEvent = OnDurableEvent;\n\n/** All events a workflow class subscribes to — the union of `@Workflow({ onEvent })` and `@OnDurableEvent`. */\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 { type StartRunDeps, startRun } from '@dudousxd/durable-worker';\nimport {\n type RunResult,\n type StartOptions,\n type WorkflowClass,\n type WorkflowInputOf,\n workflowName,\n} from '@dudousxd/nestjs-durable-core';\nimport { Injectable } from '@nestjs/common';\nimport type { DurableModuleOptions } from './durable.module';\n\n/**\n * The **store-less `engine.start` facade** for a tenant worker. Provided under the `WorkflowEngine`\n * DI token by {@link import('./durable.module').DurableModule} (`forRoot({ connection })`, no\n * `store`), so tenant code calls `engine.start(...)` UNCHANGED — it has no idea it is a tenant.\n * Instead of touching a DB, `start` publishes a `StartRunMessage` on the SHARED `durable-start-run`\n * queue (Option B: tenant rides as message DATA, never as wire segmentation). The operator (control\n * plane, `namespace: undefined`) consumes it, stamps the run's namespace from `tenant`, and routes\n * the run's task to `<workflow>@<tenant>` — the partition THIS tenant's worker serves.\n *\n * `cancel`/`deleteRun` need the store/driver a tenant does not have; they throw. No wire message\n * exists for them (the operator owns cancellation/retention).\n */\n@Injectable()\nexport class DurableStartClient {\n private readonly tenant: string;\n\n constructor(\n private readonly options: DurableModuleOptions,\n private readonly deps?: StartRunDeps,\n ) {\n this.tenant = options.partition ?? 'default';\n }\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 async start(\n workflow: string,\n input: unknown,\n runId: string = globalThis.crypto.randomUUID(),\n opts?: StartOptions,\n ): Promise<RunResult> {\n const name = workflowName(workflow);\n await startRun(this.options.connection, {\n tenant: this.tenant,\n workflow: name,\n input,\n runId,\n // Option B: DO NOT pass `namespace` — the start-run queue stays the shared\n // `durable-start-run` the operator consumes; tenant rides only as message data.\n ...(this.options.prefix !== undefined ? { prefix: this.options.prefix } : {}),\n ...(opts?.tags !== undefined ? { tags: opts.tags } : {}),\n ...(opts?.searchAttributes !== undefined ? { searchAttributes: opts.searchAttributes } : {}),\n ...(this.deps !== undefined ? { deps: this.deps } : {}),\n });\n return { runId, status: 'pending' };\n }\n\n cancel(_runId: string): Promise<void> {\n return tenantUnsupported('cancel');\n }\n\n deleteRun(_runId: string): Promise<void> {\n return tenantUnsupported('deleteRun');\n }\n\n // — everything below rejects: a tenant worker holds no store/driver, only the start channel. —\n\n // The remaining WorkflowEngine surface WorkflowService delegates to (resume/waitForRun/signal/\n // signalWithStart/publishEvent) all need the store or driver a tenant does not have. A tenant only\n // ever calls `start`; these exist so a mistaken call fails with a CLEAR, named tenant error instead\n // of a cryptic `this.engine.X is not a function` — the facade is honest about what it cannot do.\n // Params mirror the WorkflowEngine surface being faced; every method rejects without reading them.\n resume(_runId: string): Promise<void> {\n return tenantUnsupported('resume');\n }\n\n waitForRun(_runId: string, _opts?: { timeoutMs?: number }): Promise<void> {\n return tenantUnsupported('waitForRun');\n }\n\n signal(_token: string, _payload: unknown): Promise<void> {\n return tenantUnsupported('signal');\n }\n\n signalWithStart(\n _workflow: string,\n _input: unknown,\n _runId: string,\n _signal: { token: string; payload?: unknown },\n _opts?: StartOptions,\n ): Promise<void> {\n return tenantUnsupported('signalWithStart');\n }\n\n publishEvent(\n _name: string,\n _payload: unknown,\n _opts?: { id?: string; buffer?: boolean },\n ): Promise<void> {\n return tenantUnsupported('publishEvent');\n }\n}\n\n/** Reject with a clear, named tenant error for an operation that needs a store/driver. */\nfunction tenantUnsupported(method: string): Promise<void> {\n return Promise.reject(\n new Error(\n `${method}() is not available on a tenant worker (no store). Use the control plane for it.`,\n ),\n );\n}\n","import {\n DurableWorkerRuntime,\n type RunRedisWorkerOptions,\n type RunningWorker,\n runRedisWorker as defaultRunRedisWorker,\n} from '@dudousxd/durable-worker';\nimport {\n DURABLE_OPTIONS_CANONICAL,\n type DurableTopology,\n type EngineEvent,\n type GroupHealth,\n type RunDetail,\n type RunGateway,\n type RunListItem,\n type RunQuery,\n type RunResult,\n type RunWaiting,\n} from '@dudousxd/nestjs-durable-core';\nimport {\n Inject,\n Injectable,\n type OnApplicationBootstrap,\n type OnApplicationShutdown,\n type OnModuleInit,\n type Provider,\n} from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport { scanSteps, scanWorkflows } from './discovery-helpers';\nimport type { DurableModuleOptions } from './durable.module';\n\n/**\n * The `runRedisWorker` function the module uses to start each partition's BullMQ consumer.\n * Defaults to the real one from `@dudousxd/durable-worker`; tests `overrideProvider` it with a\n * fake so no real Redis is needed.\n */\nexport const RUN_REDIS_WORKER = Symbol('nestjs-durable:run-redis-worker');\n\n/** The list of started {@link RunningWorker} handles (the single handle {@link ThinWorkerBootstrap}\n * starts), closed on shutdown. */\nexport const DURABLE_WORKER_RUNNERS = Symbol('nestjs-durable:worker-runners');\n\n/** The signature of `runRedisWorker` — injected behind {@link RUN_REDIS_WORKER}. */\nexport type RunRedisWorkerFn = (opts: RunRedisWorkerOptions) => Promise<RunningWorker>;\n\n/** True for the **pure thin-worker** role — `connection` set, `store` unset. `DurableModule`\n * keeps {@link ThinWorkflowRegistrar}/{@link ThinStepRegistrar}/{@link ThinWorkerBootstrap} inert\n * (registered but no-op) outside this role — an operator (`store` set) drives its own bodies\n * inline or, with `connection` also set, through the separate in-app-worker mechanics instead. */\nfunction isPureThinWorker(options: DurableModuleOptions): boolean {\n return options.store === undefined && options.connection !== undefined;\n}\n\n/**\n * Discovers every provider carrying `@Workflow` metadata and registers its `run(ctx, input)` on the\n * thin {@link DurableWorkerRuntime}. Mirrors the engine-side `WorkflowRegistrar`, but registers on\n * the runner-core runtime instead of a `WorkflowEngine` — the runtime drives `run` with the thin\n * `WorkflowContext` (which `implements WorkflowCtx`), so the body runs unchanged. NO store/engine.\n * Inert (skips registration) outside the pure thin-worker role — see {@link isPureThinWorker}.\n */\n@Injectable()\nexport class ThinWorkflowRegistrar implements OnModuleInit {\n constructor(\n private readonly discovery: DiscoveryService,\n private readonly runtime: DurableWorkerRuntime,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n onModuleInit(): void {\n if (!isPureThinWorker(this.options)) return;\n scanWorkflows(this.discovery, (meta, instance) =>\n this.runtime.registerWorkflow(meta.name, (ctx, input) => instance.run(ctx, input)),\n );\n }\n}\n\n/**\n * Discovers every `@Step` method and registers it as a step handler on the thin\n * {@link DurableWorkerRuntime}. Mirrors the engine-side `DurableStepRegistrar`, but always registers\n * on the runtime (a thin worker IS the consumer — there is no in-process-vs-queue branch). Inert\n * outside the pure thin-worker role — see {@link isPureThinWorker}.\n */\n@Injectable()\nexport class ThinStepRegistrar implements OnModuleInit {\n constructor(\n private readonly discovery: DiscoveryService,\n private readonly metadataScanner: MetadataScanner,\n private readonly runtime: DurableWorkerRuntime,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n onModuleInit(): void {\n if (!isPureThinWorker(this.options)) return;\n scanSteps(this.discovery, this.metadataScanner, (meta, handler) =>\n this.runtime.registerStep(meta.name, handler),\n );\n }\n}\n\n/**\n * Starts ONE `runRedisWorker` call on bootstrap — after both registrars have run, so every handler\n * is registered before any task is consumed — and closes it on shutdown. The runner (`@dudousxd/\n * durable-worker`) derives its subscription from `runtime.registeredNames()` (one queue per\n * registered `@Workflow`/`@Step` name), not a hand-declared group list. Inert outside the pure\n * thin-worker role — see {@link isPureThinWorker}: an operator (`store` set) never starts this\n * consumer, and `store + connection` starts its OWN co-located consumer instead (`in-app-worker.ts`).\n *\n * For the shutdown close to fire, enable Nest's shutdown hooks: `app.enableShutdownHooks()`.\n */\n@Injectable()\nexport class ThinWorkerBootstrap implements OnApplicationBootstrap, OnApplicationShutdown {\n private readonly runners: RunningWorker[] = [];\n\n constructor(\n private readonly runtime: DurableWorkerRuntime,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n @Inject(RUN_REDIS_WORKER) private readonly runRedisWorker: RunRedisWorkerFn,\n @Inject(DURABLE_WORKER_RUNNERS) private readonly runnersSink: RunningWorker[],\n ) {}\n\n async onApplicationBootstrap(): Promise<void> {\n const options = this.options;\n if (!isPureThinWorker(options) || options.connection === undefined) return;\n // ONE call: the runner subscribes one queue per name registered on `this.runtime` — populated by\n // `ThinWorkflowRegistrar`/`ThinStepRegistrar`'s `onModuleInit`, which Nest's lifecycle guarantees\n // run (across the whole app) before any `onApplicationBootstrap` hook.\n const handle = await this.runRedisWorker({\n runtime: this.runtime,\n connection: options.connection,\n ...(options.partition !== undefined ? { partition: options.partition } : {}),\n ...(options.prefix !== undefined ? { prefix: options.prefix } : {}),\n ...(options.instanceId !== undefined ? { instanceId: options.instanceId } : {}),\n ...(options.concurrency !== undefined ? { concurrency: options.concurrency } : {}),\n });\n this.runners.push(handle);\n this.runnersSink.push(handle);\n }\n\n async onApplicationShutdown(): Promise<void> {\n await Promise.allSettled(this.runners.map((h) => h.close()));\n }\n}\n\n/**\n * The thin-worker slice of {@link import('./durable.module').DurableModule}'s unified provider set —\n * active for the pure thin-worker role (`connection` set, `store` unset): registers discovered\n * `@Workflow`/`@Step` on a store-less {@link DurableWorkerRuntime} and starts ONE `runRedisWorker`\n * consumer, one queue PER REGISTERED NAME. Always present in the provider list (so a single\n * `DurableModule.forRootAsync` can serve any role once its options resolve); every class/factory here\n * is inert (see {@link isPureThinWorker}) outside that role.\n */\nexport function thinWorkerProviders(): Provider[] {\n return [\n {\n provide: DurableWorkerRuntime,\n // The runtime's `WorkflowWorker` falls back to its own `group` ctor param as the WORKFLOW's\n // `workflowPartition` for any `ctx.step` call (see `workflow-context.ts`'s `resolveCallGroup`)\n // — that fallback MUST equal this module's own `partition`, or a dispatched step's decision\n // carries a mismatched token and the engine dispatches it to a queue nothing here subscribes\n // to. Explicit `''` (never\n // `undefined`) so it does NOT fall through to `WorkflowWorker`'s unrelated `'workflows'`\n // default parameter.\n useFactory: (options: DurableModuleOptions) =>\n new DurableWorkerRuntime({ workflowGroup: options.partition ?? '' }),\n inject: [DURABLE_OPTIONS_CANONICAL],\n },\n { provide: RUN_REDIS_WORKER, useValue: defaultRunRedisWorker },\n { provide: DURABLE_WORKER_RUNNERS, useValue: [] as RunningWorker[] },\n ThinWorkflowRegistrar,\n ThinStepRegistrar,\n ThinWorkerBootstrap,\n ];\n}\n\n/** Reject with a clear, named tenant error for a `RunGateway` call made without a `transport`. The\n * call site pins `T` from its own declared return type (e.g. `Promise<RunDetail | null>`); this\n * helper only ever rejects, so it never actually produces a `T` value. */\nfunction tenantGatewayUnavailable<T>(method: string): Promise<T> {\n return Promise.reject(\n new Error(`RunGateway.${method}() is not available — pass \\`transport\\` to use RunGateway.`),\n );\n}\n\n/**\n * The no-transport fallback bound to `RUN_GATEWAY` for a thin worker (`connection` set, `store`\n * unset) that didn't also pass a `transport` — mirrors `DurableStartClient`'s tenant-error idiom\n * (`tenantUnsupported`): every method rejects with a clear, named error instead of a cryptic\n * `this.gateway.X is not a function`. `subscribe` is synchronous on the `RunGateway` port, so it\n * throws synchronously (same message) rather than returning a rejected promise.\n */\nexport function unavailableRunGateway(): RunGateway {\n return {\n // Topology is local metadata, always answerable — a thin worker with no transport is still a tenant.\n topology(): DurableTopology {\n return { role: 'tenant' };\n },\n getRunDetail(_runId: string): Promise<RunDetail | null> {\n return tenantGatewayUnavailable<RunDetail | null>('getRunDetail');\n },\n listRuns(_query: RunQuery): Promise<RunListItem[]> {\n return tenantGatewayUnavailable<RunListItem[]>('listRuns');\n },\n waitingFor(_runIds: string[]): Promise<Record<string, RunWaiting>> {\n return tenantGatewayUnavailable<Record<string, RunWaiting>>('waitingFor');\n },\n workerHealth(): Promise<GroupHealth[]> {\n return tenantGatewayUnavailable<GroupHealth[]>('workerHealth');\n },\n cancel(_runId: string): Promise<RunResult | null> {\n return tenantGatewayUnavailable<RunResult | null>('cancel');\n },\n retry(_runId: string): Promise<RunResult | null> {\n return tenantGatewayUnavailable<RunResult | null>('retry');\n },\n continue(_runId: string): Promise<RunResult | null> {\n return tenantGatewayUnavailable<RunResult | null>('continue');\n },\n retryWithInput(_runId: string, _input: unknown): Promise<{ runId: string } | null> {\n return tenantGatewayUnavailable<{ runId: string } | null>('retryWithInput');\n },\n redispatchPending(_runId: string): Promise<(RunResult & { redispatched: number }) | null> {\n return tenantGatewayUnavailable<(RunResult & { redispatched: number }) | null>(\n 'redispatchPending',\n );\n },\n subscribe(_runId: string, _onEvent: (event: EngineEvent) => void): () => void {\n throw new Error(\n 'RunGateway.subscribe() is not available — pass `transport` to use RunGateway.',\n );\n },\n };\n}\n","import type { StepLogger, WorkflowCtx } from '@dudousxd/nestjs-durable-core';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport { getDurableStepMeta, getWorkflowMeta } from './decorators';\nimport type { DurableStepMeta, WorkflowMeta } from './decorators';\n\nexport interface WorkflowInstance {\n run(ctx: WorkflowCtx, input: unknown): Promise<unknown>;\n}\n\n/**\n * Walks every provider that carries `@Workflow` metadata and invokes `register(meta, instance)`.\n *\n * Guards applied (same as both registrars):\n * - null / non-object provider instances are skipped\n * - providers without `@Workflow` metadata are skipped\n * - a `@Workflow` provider that has no `run` method throws at boot\n */\nexport function scanWorkflows(\n discovery: DiscoveryService,\n register: (meta: WorkflowMeta, instance: WorkflowInstance) => void,\n): void {\n for (const wrapper of discovery.getProviders()) {\n const { instance } = wrapper;\n if (!instance || typeof instance !== 'object') continue;\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 register(meta, workflow);\n }\n}\n\n/**\n * Walks every provider method that carries `@Step` metadata and invokes\n * `register(meta, boundHandler)`.\n *\n * Guards applied (same as both registrars):\n * - null / non-object provider instances are skipped\n * - non-function methods are skipped\n * - methods without `@Step` metadata are skipped\n *\n * The handler passed to `register` is already bound to its instance, and is the ONE place a `@Step`\n * method actually runs with real input/output in this package — every registrar\n * (`DurableStepRegistrar`, `InAppWorkerBootstrap`, `ThinStepRegistrar`) funnels through here — so\n * this is where `@Step`'s optional `input`/`output` zod schemas are enforced: `input` validates\n * before the method runs, `output` validates its return value before it's handed back. A bare\n * `@Step()` (no schemas attached) skips both checks — `meta.input`/`meta.output` are `undefined`.\n */\nexport function scanSteps(\n discovery: DiscoveryService,\n scanner: MetadataScanner,\n register: (\n meta: DurableStepMeta,\n handler: (input: unknown, log: StepLogger) => Promise<unknown>,\n ) => void,\n): void {\n for (const wrapper of discovery.getProviders()) {\n const { instance } = wrapper;\n if (!instance || typeof instance !== 'object') continue;\n const prototype = Object.getPrototypeOf(instance);\n for (const methodName of scanner.getAllMethodNames(prototype)) {\n const method = (instance as Record<string, unknown>)[methodName];\n if (typeof method !== 'function') continue;\n const meta = getDurableStepMeta(method);\n if (!meta) continue;\n const boundMethod = method as (input: unknown, log: StepLogger) => unknown;\n const handler = async (input: unknown, log: StepLogger): Promise<unknown> => {\n const validInput = meta.input ? meta.input.parse(input) : input;\n const output = await boundMethod.call(instance, validInput, log);\n return meta.output ? meta.output.parse(output) : output;\n };\n register(meta, handler);\n }\n }\n}\n","import type { ConcurrencyOption } from '@dudousxd/durable-worker';\nimport {\n type AdmissionBackend,\n type ControlPlane,\n DURABLE_OPTIONS,\n DURABLE_OPTIONS_CANONICAL,\n type NamedTransport,\n type QueueConfig,\n type RetentionPolicy,\n RunGateway,\n STATE_STORE,\n STATE_STORE_CANONICAL,\n type ScheduledWorkflow,\n type StateStore,\n TRANSPORT,\n TRANSPORT_CANONICAL,\n type Transport,\n WorkflowEngine,\n type WorkflowRef,\n} from '@dudousxd/nestjs-durable-core';\nimport {\n type DynamicModule,\n Inject,\n Injectable,\n type InjectionToken,\n Module,\n type OnApplicationBootstrap,\n type OnModuleDestroy,\n type Provider,\n} from '@nestjs/common';\nimport { DiscoveryModule, ModuleRef } from '@nestjs/core';\nimport type { ContextAccessor } from './context-accessor';\nimport { DurableStartClient } from './durable-start-client';\nimport { DurableStepRegistrar } from './durable-step.registrar';\nimport { thinWorkerProviders, unavailableRunGateway } from './durable-worker.module';\nimport { EntityService } from './entity';\nimport { inAppWorkerProviders } from './in-app-worker';\nimport { ProxyRunGateway } from './proxy-run-gateway';\nimport { RetentionPoller } from './retention-poller';\nimport { isDrivingOperator } from './role';\nimport { RunRequestResponder, type RunRequestTransport } from './run-request-responder';\nimport { StoreRunGateway } from './store-run-gateway';\nimport { TenantEventRepublisher } from './tenant-event-republisher';\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\n/** A store that can derive a tenant-namespace-scoped view of itself (the MikroORM adapter). */\ninterface ScopeableStore {\n /** Derive a store confined to `scope.namespace`, sharing the same backing connection. */\n withScope(scope: { namespace?: string }): StateStore;\n}\n\n/**\n * True when `store` can produce a namespace-scoped view of itself. Structural check (same idiom as\n * {@link isControlPlane}) so the store-agnostic module never imports a concrete adapter — the\n * pre-built `store` is only re-scoped when it actually carries the capability.\n */\nfunction isScopeableStore(store: StateStore): store is StateStore & ScopeableStore {\n return typeof (store as StateStore & Partial<ScopeableStore>).withScope === 'function';\n}\n\n/**\n * Apply opt-in tenant read scoping to a pre-built store. The module receives `store` already\n * instantiated (it is store-agnostic — no MikroORM import), so it cannot reconstruct it; instead it\n * asks the store for a scoped view via the optional {@link ScopeableStore.withScope} capability. When\n * `scopeReads` is off, `namespace` is unset, or the store can't scope itself, the original store is\n * returned unchanged (the operator view) — the caller can always pass an already-scoped store.\n */\nfunction scopedStore(store: StateStore, options: DurableModuleOptions): StateStore {\n if (options.scopeReads !== true || options.namespace === undefined) return store;\n if (!isScopeableStore(store)) return store;\n return store.withScope({ namespace: options.namespace });\n}\n\n/**\n * Retention config for the {@link RetentionPoller}. One or more {@link RetentionPolicy policies}\n * (status sets must be disjoint — validated at boot), swept together on a shared interval.\n *\n * ```ts\n * retention: {\n * sweepInterval: '1m',\n * batchSize: 1_000,\n * policies: [\n * { statuses: ['completed', 'cancelled'], maxAge: '14d', maxCount: 200 },\n * { statuses: ['failed'], maxAge: '90d' }, // keep failures longer for debugging\n * ],\n * }\n * ```\n */\nexport interface DurableRetentionOptions {\n /** The retention rules, one per (disjoint) status group. */\n policies: RetentionPolicy[];\n /**\n * How often to run the prune sweep. A number is milliseconds; a string is an `ms`-style duration\n * (`'1m'`, `'5m'` — `'m'` is minutes). `0` runs it once on boot only. Defaults to 60000 (1 minute).\n */\n sweepInterval?: number | string;\n /** Max runs hard-deleted per batch (per policy, looped until drained). Defaults to 1000. */\n batchSize?: number;\n}\n\n/**\n * Options for `DurableModule.forRoot`/`forRootAsync`. The **role is inferred** from which of `store`/\n * `connection` are set:\n *\n * - `{ store, transport }` — **operator**: a real `WorkflowEngine` + `StoreRunGateway` + drivers/\n * timer/retention/registrars, executing registered bodies INLINE. Driven by {@link drive} (default\n * `true`).\n * - `{ connection }` (no `store`) — **thin worker**: `WorkflowEngine` resolves to a store-less\n * `DurableStartClient`; `RUN_GATEWAY` is a `ProxyRunGateway` when `transport` is also given, else\n * every method rejects with a clear error. No store/timer/retention/entity — just discovered\n * `@Workflow`/`@Step` handlers served over ONE `runRedisWorker` consumer.\n * - `{ store, transport, connection }` — an **operator that also runs a co-located worker**: every\n * `@Workflow` is registered GROUP-SERVED (dispatched over the transport, PER WORKFLOW NAME, instead\n * of inline) and a co-located consumer (one `runRedisWorker` call) replays the same bodies.\n *\n * `forRoot` throws when neither `store` nor `connection` is set, and when `store` is set without a\n * `transport`.\n *\n * Set {@link DurableModuleOptions.topology} to name the role explicitly instead of relying on this\n * inference — it additionally VALIDATES the axes above (`namespace` vs `partition`) that this prose\n * is otherwise the only source of truth for. Omitting it changes nothing.\n */\nexport interface DurableModuleOptions {\n /** State store — set this to play the **operator** role (see the interface doc for the full role\n * matrix). Omit for a store-less **thin worker** (`connection` only). */\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.step(handler, 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. Operator only. */\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 * Operator only.\n */\n autoSchema?: boolean;\n /**\n * Worker-pool namespace for this instance (forwarded to the engine). The poll paths\n * (`runPending`/`recoverIncomplete`/`resumeDueTimers`/`sweepTimeouts`) only act on runs in this\n * namespace. Set distinct values to safely share ONE state store across non-interchangeable\n * pools — e.g. a developer's local instance vs the deployed cluster. **Omit it to make this\n * instance an OPERATOR** — an unset namespace drives/recovers/resumes runs of EVERY namespace and\n * leaves the transport on its bare prefix. See `WorkflowEngineDeps.namespace`. Not to be confused\n * with {@link partition} (the co-located/thin-worker QUEUE routing suffix).\n */\n namespace?: string;\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. Operator only.\n */\n leaseMs?: number;\n /** Unique id for this instance (for leases, and the co-located/thin-worker consumer's heartbeats). */\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. Operator only.\n */\n maxRecoveryAttempts?: number;\n /**\n * Opt-in liveness deadline (ms) for a remote (polyglot) workflow `advance`. If the worker neither\n * returns a decision nor sends a run-scoped heartbeat within this window, the engine presumes it dead\n * and lets recovery re-drive; each heartbeat rearms the window so a slow-but-alive worker is never\n * re-driven. Pair with a worker SDK that emits run-scoped heartbeats (`@dudousxd/durable-worker` ≥ the\n * release that ships them, and the Python `durable-worker`). Omit for the prior unbounded await.\n * Operator only.\n */\n remoteAdvanceSilenceMs?: 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). Operator only.\n */\n deadLetterWorkflow?: WorkflowRef;\n /**\n * Whether an **operator** instance actively DRIVES runs — polls pending, recovers crashed\n * (`recoverIncomplete`), resumes due timers, sweeps timeouts, prunes retention, and consumes local\n * steps — as opposed to a read-only/dashboard replica. Defaults to `true`. Set `false` for a\n * **dashboard/dispatch-only** instance (e.g. an API pod) that mounts the store/dashboard but must\n * not process or recover workflows — leave that to another driving instance. `drive: false` also\n * installs the engine's no-op run dispatcher, so a freshly `start()`ed run stays enqueue-only until\n * a driving instance's poll picks it up. Ignored (irrelevant) for a thin worker (no `store`).\n */\n drive?: boolean;\n /** Max ms to wait for in-flight runs on shutdown before exiting. Defaults to 10000. Operator only. */\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 **driving** 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. Operator only.\n */\n schedules?: ScheduledWorkflow[];\n /**\n * Hard-prune terminal run history on an interval so `durable_workflow_runs` (and its child tables)\n * stays bounded — without it, completed/failed/cancelled runs accumulate forever and the timer\n * poller's per-tick status scans get linearly slower. Driving instances only. Omit to keep all\n * history (the default). Requires a store adapter that implements `pruneTerminalRuns` (the\n * MikroORM adapter does); other adapters no-op with a warning. See {@link DurableRetentionOptions}.\n * Operator only.\n */\n retention?: DurableRetentionOptions;\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. Operator only.\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.step(handler, input, { queue: name })` to cap its concurrency / admission rate.\n * Operator only.\n */\n queues?: QueueConfig[];\n /**\n * Admission backend for the flow-control `queues`. Defaults to in-process (per-instance) caps. Pass\n * a `RedisAdmissionBackend` (from `@dudousxd/nestjs-durable-admission-redis`) to make concurrency /\n * rate-limit / priority ordering GLOBAL across every engine replica. Operator only.\n */\n admission?: AdmissionBackend;\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`. Operator only.\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. Operator only.\n */\n context?: () => Record<string, unknown> | undefined;\n /** Attempts for each saga compensation when a run fails. Default 1 (no retry). Idempotent undos.\n * Operator only. */\n compensationRetries?: number;\n /**\n * Opt into tenant read scoping: when `true` AND {@link namespace} is set, the module confines the\n * store's reads to that namespace (a tenant-boundary view) instead of the operator view that sees\n * all namespaces. Default `false` — the control plane (e.g. flip's `/ctrl` operator screens) stays\n * unscoped. Requires a store that exposes the `withScope` capability (the MikroORM adapter does);\n * a pre-built store without it is used as-is (construct it already-scoped instead). Operator only.\n */\n scopeReads?: boolean;\n /**\n * ioredis connection (string or options) for a **thin worker** (`connection` only) or the\n * **co-located worker** consumer (`store` + `connection`). Set this to play a worker role (see the\n * interface doc for the full role matrix). Omit to keep every `@Workflow` on the operator's inline\n * fast path with zero dispatch round-trips.\n */\n connection?: string | Record<string, unknown>;\n /**\n * The isolation partition a worker role serves — a thin worker's or co-located worker's queue\n * subscription, AND (for a co-located worker) the suffix each `@Workflow`'s dispatch token carries.\n * Each handler's queue token is `tenantGroup(sanitizeQueueToken(name), partition)`\n * (`@dudousxd/nestjs-durable-core`), so `undefined`, `''`, or `'default'` stays byte-identical to\n * the bare (sanitized) name (single-tenant deployment unchanged), and any other partition serves\n * `<name>@<partition>` — matching the queue name an operator's convention dispatch routes that\n * tenant's runs to (`tenantGroup(run.workflow, run.namespace)` on the engine side). Not to be\n * confused with {@link namespace} (the operator's own poll-scoping axis). Ignored for a plain\n * operator (no `connection`).\n */\n partition?: string;\n /** Key prefix namespacing the durable queues for a worker role's consumer. Defaults to `durable`\n * (matches the transport). Ignored for a plain operator (no `connection`). */\n prefix?: string;\n /**\n * How many tasks a worker role's co-located/thin consumer runs concurrently PER SUBSCRIBED QUEUE\n * (BullMQ Worker concurrency — the same limit is applied to every per-name queue the single\n * `runRedisWorker` call starts). Defaults to 1. Raise it so a fanned-out batch (e.g. the N remote\n * steps of a `gather`) runs in parallel instead of serially.\n *\n * Pass `'adaptive'` (or `{ mode:'adaptive', ... }`) to let the consumer self-tune its concurrency\n * (latency gradient + RAM brake + backpressure) and publish a live status on its heartbeat. Ignored\n * for a plain operator (no `connection`).\n */\n concurrency?: ConcurrencyOption;\n /**\n * Per-handler concurrency override, keyed by workflow/step NAME. Falls back to {@link concurrency}\n * (then 1). NOT YET WIRED THROUGH: `runRedisWorker` (`@dudousxd/durable-worker`) currently applies\n * one {@link concurrency} limit uniformly across every per-name queue it subscribes to from a\n * single call — reserved here for when per-name concurrency lands there.\n */\n concurrencyByHandler?: Record<string, ConcurrencyOption>;\n /** Timeout for a thin worker's `RunGateway` round-trip over `transport` before it rejects. Defaults\n * to 10_000ms inside `ProxyRunGateway`. Ignored for an operator (bound to `StoreRunGateway`). */\n runGatewayTimeoutMs?: number;\n /**\n * An explicit preset that NAMES the deployment role instead of leaving it to `store`/`connection`\n * inference, and VALIDATES the axes that are otherwise only prose (see the interface doc's role\n * matrix, and {@link namespace} vs {@link partition}). Omit to keep the existing inference —\n * `topology` is entirely additive and changes nothing when unset.\n *\n * **`namespace` vs `partition` — the two axes this preset locks down:**\n * - `namespace` is the OPERATOR's poll-scoping axis: which runs a control-plane instance\n * drives/recovers/resumes (`runPending`/`recoverIncomplete`/`resumeDueTimers`/`sweepTimeouts`).\n * Unset makes the instance see (drive) EVERY namespace.\n * `{ role: 'control-plane' }` is the only preset that allows it.\n * - `partition` is the WORKER's queue-routing suffix: which queue a thin/co-located worker's\n * consumer subscribes to (`<name>@<partition>`), matching the queue an operator's convention\n * dispatch routes a run's `namespace` to. `{ role: 'tenant' }` sets this FOR you from `tenant` —\n * you never set `partition` directly under `topology`.\n *\n * **One `tenant` word, two axes — the preset maps it to the right one per role:** on a\n * `control-plane` an optional `tenant` scopes the OPERATOR to its own runs (maps to `namespace` —\n * a self-contained local stack sharing a broker with a deployed cluster names itself so neither\n * drives the other's runs; leave it unset on a deployed operator to drive every tenant). On a\n * `tenant` role it is required and maps to `partition` (the worker's queue suffix).\n *\n * ```ts\n * // Control plane: owns the store, dispatches over the transport, prunes old runs. The optional\n * // `tenant` (e.g. from an env var) scopes this operator to its own runs — undefined on a\n * // deployed operator (drives everything), set on a local stack sharing the broker.\n * DurableModule.forRoot({\n * topology: { role: 'control-plane', tenant: process.env.DURABLE_TENANT },\n * store,\n * transport,\n * retention: { policies: [{ statuses: ['completed', 'cancelled'], maxAge: '14d' }] },\n * });\n *\n * // Tenant: store-less worker scoped to its own partition — `tenant` maps to `partition` for you.\n * DurableModule.forRoot({\n * topology: { role: 'tenant', tenant: 'acme-corp' },\n * connection: process.env.REDIS_URL,\n * });\n * ```\n *\n * Validated at `forRoot`/`forRootAsync` resolution time — see {@link DurableModule} for the exact\n * error messages, which double as the axis primer above.\n */\n topology?: { role: 'control-plane'; tenant?: string } | { role: 'tenant'; tenant: string };\n}\n\nexport interface DurableModuleAsyncOptions {\n // `never[]` (not `object[]`) is intentional: a contravariant bottom-type rest param is what lets a\n // consumer supply a factory with concrete injected args, e.g. `(config: ConfigService) => …`.\n useFactory: (...args: never[]) => DurableModuleOptions | Promise<DurableModuleOptions>;\n inject?: InjectionToken[];\n}\n\n/** Throws the exact role-inference contract errors documented on {@link DurableModuleOptions}. */\nfunction assertValidRole(options: DurableModuleOptions): void {\n const hasStore = options.store !== undefined;\n const hasConnection = options.connection !== undefined;\n if (!hasStore && !hasConnection) {\n throw new Error(\n 'a durable module needs either a `store` (operator) or a `connection` (worker)',\n );\n }\n if (hasStore && options.transport === undefined && options.transports === undefined) {\n throw new Error('an operator (`store`) needs a `transport` (or `transports`)');\n }\n}\n\n/**\n * Validates the {@link DurableModuleOptions.topology} preset (a no-op when it's unset — existing\n * inference is untouched). Each thrown message NAMES the role, the offending option, and a one-line\n * explanation of the axis it protects — these messages are the feature: they teach `namespace` (the\n * operator's poll-scoping axis) apart from `partition`/`tenant` (the worker's queue-routing axis),\n * which today are only reconciled in consumer-app comments.\n */\nfunction assertValidTopology(options: DurableModuleOptions): void {\n const topology = options.topology;\n if (topology === undefined) return;\n\n if (topology.role === 'control-plane') {\n if (\n options.store === undefined ||\n (options.transport === undefined && options.transports === undefined)\n ) {\n throw new Error(\n \"topology: { role: 'control-plane' } needs `store` AND (`transport` or `transports`).\\n\" +\n 'A control-plane node is the durable operator: it owns the state store and dispatches ' +\n 'runs to workers over a transport — without both, there is nothing for it to operate.',\n );\n }\n if (options.partition !== undefined) {\n throw new Error(\n \"topology: { role: 'control-plane' } forbids `partition`.\\n\" +\n 'partition is the WORKER queue-routing suffix — on a control-plane node, runs are ' +\n \"dispatched to tenant partitions via each run's namespace, not via this option. Set \" +\n \"`partition` (or `topology: { role: 'tenant', tenant }`) on the WORKER node instead.\",\n );\n }\n if (\n topology.tenant !== undefined &&\n options.namespace !== undefined &&\n options.namespace !== topology.tenant\n ) {\n throw new Error(\n `topology: { role: 'control-plane', tenant: '${topology.tenant}' } conflicts with \\`namespace: '${options.namespace}'\\`.\\ntenant maps 1:1 onto namespace (the operator's poll-scoping axis) — set only \\`tenant\\`, or set \\`namespace\\` to the same value.`,\n );\n }\n return;\n }\n\n if (options.connection === undefined) {\n throw new Error(\n `topology: { role: 'tenant', tenant: '${topology.tenant}' } needs \\`connection\\`.\\nA tenant is a store-less worker: it connects to the broker directly to pull its own partition of tasks instead of polling a store it does not have.`,\n );\n }\n if (options.store !== undefined) {\n throw new Error(\n \"topology: { role: 'tenant' } forbids `store`.\\n\" +\n 'A tenant is store-less BY DEFINITION — a `store` present means you actually wanted ' +\n \"`topology: { role: 'control-plane' }` (the store-owning role).\",\n );\n }\n if (options.namespace !== undefined) {\n throw new Error(\n \"topology: { role: 'tenant' } forbids `namespace`.\\n\" +\n \"namespace is the OPERATOR's poll-scoping axis (which runs a control-plane instance \" +\n 'drives/recovers/resumes) — a tenant worker has no poll loop for it to scope. Use this ' +\n \"preset's `tenant` field for the worker's own routing axis instead.\",\n );\n }\n if (options.partition !== undefined && options.partition !== topology.tenant) {\n throw new Error(\n `topology: { role: 'tenant', tenant: '${topology.tenant}' } conflicts with \\`partition: '${options.partition}'\\`.\\ntenant maps 1:1 onto partition (the worker queue-routing suffix) — set only \\`tenant\\`, or set \\`partition\\` to the same value.`,\n );\n }\n}\n\n/**\n * Applies the {@link DurableModuleOptions.topology} preset's `tenant` mapping — per role: onto\n * `partition` (worker queue-routing) for `{ role: 'tenant' }`, onto `namespace` (operator\n * poll-scoping) for `{ role: 'control-plane' }`. Validated already by {@link assertValidTopology}\n * (an equal explicit value is a no-op; a mismatched one throws before this runs). A no-op for an\n * unset `topology` (existing behavior, zero change).\n */\nfunction resolveTopology(options: DurableModuleOptions): DurableModuleOptions {\n return servingPartition(resolveTopologyPreset(options));\n}\n\n/**\n * Fill the WORKER-side `partition` from this node's own `namespace` when it wasn't declared. The two\n * are one axis on the wire: the engine dispatches a run's steps to `<name>@<run.namespace>` (core's\n * `stepGroup`), so a node that stamps its runs with a namespace must SERVE the matching token or it\n * enqueues into queues nobody consumes. This is what keeps the documented local-dev recipe\n * (`docs/namespaces.md` — a namespaced engine on a private Redis) working with no extra wiring.\n *\n * An explicit `partition` always wins, so a node deliberately serving a different pool stays put.\n */\nfunction servingPartition(options: DurableModuleOptions): DurableModuleOptions {\n if (options.partition !== undefined || options.namespace === undefined) return options;\n return { ...options, partition: options.namespace };\n}\n\nfunction resolveTopologyPreset(options: DurableModuleOptions): DurableModuleOptions {\n const topology = options.topology;\n if (topology === undefined) return options;\n if (topology.role === 'control-plane') {\n // An optional tenant scopes the operator to its own runs — maps onto `namespace` (undefined =\n // global operator, drives every tenant; validated against an explicit mismatched `namespace`).\n // The namespace stays a STORE-side axis: the engine stamps runs with it and routes their work\n // via `@<tenant>` GROUP suffixes on the transport's own prefix (the cross-SDK convention every\n // tenant worker speaks natively) — it never re-scopes the transport keyspace.\n if (topology.tenant === undefined) return options;\n return {\n ...options,\n // An explicit `namespace` is already validated equal to `tenant` (assertValidTopology), so\n // keeping it is a no-op rather than a conflict.\n namespace: options.namespace ?? topology.tenant,\n // The tenant is ALSO this node's worker-routing suffix: its engine now dispatches a run's steps\n // to `<name>@<tenant>` (core's `stepGroup`), so its own co-located worker must SUBSCRIBE those\n // same tokens. Without this the node would dispatch into queues it is not itself listening on.\n // `partition` is user-forbidden on this role (assertValidTopology), so we own it here.\n partition: topology.tenant,\n };\n }\n if (options.partition !== undefined) return options;\n return { ...options, partition: topology.tenant };\n}\n\n/**\n * Boot-time wiring for the tenant run gateway's OPERATOR side, gated by {@link isDrivingOperator} (an\n * operator instance drives by default; `drive: false` — or a thin worker with no `store` at all —\n * does not). Two independent capabilities, each wired only when the transport carries it:\n *\n * 1. **`RunRequestResponder`** — answers a tenant's {@link RunRequest} against the bound\n * {@link RUN_GATEWAY}, tenant-scoped. Wired when the transport implements both\n * `onRunRequest`/`publishRunReply` (only broker transports carry the protocol).\n * 2. **Tenant-event re-publisher** ({@link TenantEventRepublisher}) — mirrors every engine\n * lifecycle event onto its run's per-tenant channel via `publishTenantEvent`, so a store-less\n * tenant worker can live-tail its OWN runs. See that class for the namespace-resolution and\n * cache-eviction details.\n */\n@Injectable()\nclass RunGatewayBootstrap implements OnApplicationBootstrap, OnModuleDestroy {\n private unsubscribe?: () => void;\n\n constructor(\n private readonly engine: WorkflowEngine,\n @Inject(TRANSPORT_CANONICAL) private readonly transport: Transport | null,\n private readonly gateway: RunGateway,\n @Inject(STATE_STORE_CANONICAL) private readonly store: StateStore | null,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n onApplicationBootstrap(): void {\n if (!isDrivingOperator(this.options) || !this.transport) return;\n const store = this.store;\n if (!store) return; // unreachable when isDrivingOperator is true — keeps types honest\n\n const { onRunRequest, publishRunReply, publishTenantEvent } = this.transport;\n if (typeof onRunRequest === 'function' && typeof publishRunReply === 'function') {\n // Bind to the transport: these are real methods that touch the transport's private fields\n // (e.g. BullMQTransport's `this.#runRequestName()`), so they must keep their receiver — passing\n // them destructured/unbound makes `this` the literal below and throws \"Receiver must be an\n // instance of class\" on first use. (Mirrors the `publishTenantEvent.bind` just below.)\n const runRequestTransport: RunRequestTransport = {\n onRunRequest: onRunRequest.bind(this.transport),\n publishRunReply: publishRunReply.bind(this.transport),\n };\n new RunRequestResponder(runRequestTransport, this.gateway).start();\n }\n\n if (typeof publishTenantEvent === 'function') {\n const republisher = new TenantEventRepublisher(\n store,\n publishTenantEvent.bind(this.transport),\n );\n this.unsubscribe = this.engine.subscribe((event) => {\n void republisher.handle(event);\n });\n }\n }\n\n onModuleDestroy(): void {\n this.unsubscribe?.();\n }\n}\n\n@Module({})\nexport class DurableModule {\n static forRoot(options: DurableModuleOptions): DynamicModule {\n DurableModule.assertValid(options);\n return DurableModule.build({\n provide: DURABLE_OPTIONS_CANONICAL,\n useValue: resolveTopology(options),\n });\n }\n\n static forRootAsync(options: DurableModuleAsyncOptions): DynamicModule {\n return DurableModule.build({\n provide: DURABLE_OPTIONS_CANONICAL,\n useFactory: async (...args: Parameters<DurableModuleAsyncOptions['useFactory']>) => {\n const resolved = await options.useFactory(...args);\n DurableModule.assertValid(resolved);\n return resolveTopology(resolved);\n },\n inject: options.inject ?? [],\n });\n }\n\n /**\n * Validates role/axis constraints, in resolution order. When {@link DurableModuleOptions.topology}\n * is set, it OWNS validation — {@link assertValidTopology}'s own store/transport/connection checks\n * are strictly stronger than {@link assertValidRole}'s, so the topology-specific, axis-teaching\n * message is what a `topology`-opted-in consumer sees (not the older generic one). Falls back to\n * {@link assertValidRole} unchanged when `topology` is absent — zero behavior change.\n */\n private static assertValid(options: DurableModuleOptions): void {\n if (options.topology !== undefined) {\n assertValidTopology(options);\n return;\n }\n assertValidRole(options);\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_CANONICAL,\n useFactory: (options: DurableModuleOptions): StateStore | null =>\n options.store !== undefined ? scopedStore(options.store, options) : null,\n inject: [DURABLE_OPTIONS_CANONICAL],\n },\n {\n provide: TRANSPORT_CANONICAL,\n // With a POOL (`transports`) and no singular `transport`, the canonical transport is the\n // pool's PRIMARY — the same one the engine dispatches on first. Leaving it null starved\n // the step registrar / in-app worker of a transport, so a pool-configured operator\n // registered NO step handlers and its own steps parked in `wait` with no consumer.\n useFactory: (options: DurableModuleOptions) =>\n options.transport ?? options.transports?.[0]?.transport ?? null,\n inject: [DURABLE_OPTIONS_CANONICAL],\n },\n // Legacy back-compat aliases (deprecated tokens still resolve to the same instances):\n { provide: STATE_STORE, useExisting: STATE_STORE_CANONICAL },\n { provide: TRANSPORT, useExisting: TRANSPORT_CANONICAL },\n { provide: DURABLE_OPTIONS, useExisting: DURABLE_OPTIONS_CANONICAL },\n {\n // Shared token, bound EXACTLY once: a real engine for the operator role, or a store-less\n // `DurableStartClient` facade for a thin worker (no `store`) — either way, `WorkflowService`\n // and app code call `engine.start(...)` unchanged.\n provide: WorkflowEngine,\n useFactory: async (\n options: DurableModuleOptions,\n store: StateStore | null,\n transport: Transport | null,\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 if (options.store === undefined) {\n return new DurableStartClient(options);\n }\n if (!store) {\n throw new Error(\n 'unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set',\n );\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 ?? options.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 options.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 `@Step` 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: options.transports,\n controlPlane: options.controlPlane ?? (isControlPlane(primary) ? primary : undefined),\n leaseMs: options.leaseMs,\n admission: options.admission,\n maxRecoveryAttempts: options.maxRecoveryAttempts,\n remoteAdvanceSilenceMs: options.remoteAdvanceSilenceMs,\n instanceId: options.instanceId,\n namespace: options.namespace,\n webhookUrl: options.webhookUrl,\n traceparent: options.traceparent,\n context,\n rehydrate: rehydrate || undefined,\n compensationRetries: options.compensationRetries,\n // A non-driving (dashboard/API) operator must not run workflows: enqueue-only, leaving\n // each `pending` run in the store for a DRIVING instance's poll. A driving operator gets\n // the engine's default in-process dispatcher: for a body registered inline, it runs\n // locally; for one registered GROUP-SERVED (co-located worker) or left unregistered, the\n // SAME default dispatcher routes it out remotely (group-served executor, or convention\n // dispatch — always on) instead.\n runDispatcher: options.drive === false ? { dispatch: () => {} } : undefined,\n });\n for (const queue of options.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: [\n DURABLE_OPTIONS_CANONICAL,\n STATE_STORE_CANONICAL,\n TRANSPORT_CANONICAL,\n ModuleRef,\n ],\n },\n WorkflowService,\n EntityService,\n WorkflowRegistrar,\n DurableStepRegistrar,\n TimerPoller,\n RetentionPoller,\n // Tenant run gateway: bound EXACTLY once — the store-backed `StoreRunGateway` for an operator,\n // or (for a thin worker, no `store`) a `ProxyRunGateway` over `transport` when given, else a\n // gateway whose every method rejects with a clear tenant error.\n {\n provide: RunGateway,\n useFactory: (\n options: DurableModuleOptions,\n store: StateStore | null,\n engine: WorkflowEngine,\n ): RunGateway => {\n if (options.store !== undefined) {\n if (!store) {\n throw new Error(\n 'unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set',\n );\n }\n return new StoreRunGateway(store, engine);\n }\n return options.transport\n ? new ProxyRunGateway(\n options.transport,\n options.partition ?? 'default',\n options.runGatewayTimeoutMs,\n )\n : unavailableRunGateway();\n },\n inject: [DURABLE_OPTIONS_CANONICAL, STATE_STORE_CANONICAL, WorkflowEngine],\n },\n RunGatewayBootstrap,\n // Co-located in-app worker (uniform dispatch): inert unless BOTH `store` and `connection` are\n // set — see `in-app-worker.ts`.\n ...inAppWorkerProviders(),\n // Pure thin-worker consumer: inert unless `connection` is set WITHOUT `store` — see\n // `durable-worker.module.ts`.\n ...thinWorkerProviders(),\n ],\n exports: [\n WorkflowService,\n EntityService,\n WorkflowEngine,\n STATE_STORE,\n STATE_STORE_CANONICAL,\n TRANSPORT,\n TRANSPORT_CANONICAL,\n DURABLE_OPTIONS_CANONICAL,\n RunGateway,\n ],\n };\n }\n}\n","import {\n DURABLE_OPTIONS_CANONICAL,\n type StepLogger,\n TRANSPORT_CANONICAL,\n type Transport,\n} from '@dudousxd/nestjs-durable-core';\nimport { Inject, Injectable, type OnModuleInit } from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport { scanSteps } from './discovery-helpers';\nimport type { DurableModuleOptions } from './durable.module';\nimport { isDrivingOperator } from './role';\n\n/** A transport that can run step handlers in-process (e.g. the event-emitter transport). */\ninterface LocalTaskHandling {\n handle(\n name: string,\n fn: (input: unknown, log: StepLogger) => Promise<unknown> | unknown,\n /** Isolation partition to SERVE — the queue token is `tenantGroup(sanitizeQueueToken(name),\n * partition)`. Omitted (or undefined) keeps the transport's own constructor partition. */\n partition?: string | undefined,\n ): void;\n}\n\nfunction supportsHandle(transport: unknown): transport is LocalTaskHandling {\n return typeof (transport as LocalTaskHandling | null)?.handle === 'function';\n}\n\n/**\n * Discovers `@Step` methods and registers them — under their RESOLVED name (derived `Class.method`\n * for a bare `@Step()`, or the explicit override) — as step handlers on the configured transport,\n * when that transport runs handlers in-process. With a queue/remote transport the handlers live in\n * 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 `@Step` 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_CANONICAL) private readonly transport: Transport | null,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n onModuleInit(): void {\n // A non-driving operator (`drive: false`) or a thin worker (no `store`) must not consume the\n // queue — receiving and running step tasks locally is exactly the driving-operator role we're\n // switching off here (a thin worker registers its steps via `ThinStepRegistrar` instead).\n if (!isDrivingOperator(this.options)) return;\n if (!supportsHandle(this.transport)) return;\n const transport = this.transport;\n\n // Forward the step logger as a second arg; methods that only declare `(input)` ignore it.\n // The partition is the node's own tenant axis (`topology: { role: 'control-plane', tenant }`\n // resolves it, see `resolveTopology`): this engine DISPATCHES a tenant run's steps to\n // `<name>@<tenant>`, so its in-process handlers must SERVE that same token. Undefined on a global\n // operator — the bare token, byte-identical to before.\n scanSteps(this.discovery, this.metadataScanner, (meta, handler) =>\n transport.handle(meta.name, handler, this.options.partition),\n );\n }\n}\n","import type { DurableModuleOptions } from './durable.module';\n\n/**\n * True for the **operator** role — `store` is set (with or without a co-located `connection`). See\n * {@link DurableModuleOptions} for the full role matrix. Kept in its own module (rather than\n * `durable.module.ts`) so the poller/registrar classes can import it as a plain VALUE without a\n * circular value dependency on the module that also imports them as providers.\n */\nexport function isOperatorRole(options: DurableModuleOptions): boolean {\n return options.store !== undefined;\n}\n\n/**\n * True when an operator instance actively DRIVES runs — polls pending, recovers crashed, resumes due\n * timers, sweeps timeouts, prunes retention, consumes local steps — as opposed to a read-only/\n * dashboard replica (`drive: false`) that mounts the store/dashboard but leaves driving to another\n * instance. Meaningless (`false`) outside the operator role. Defaults to `true` for an operator.\n */\nexport function isDrivingOperator(options: DurableModuleOptions): boolean {\n return isOperatorRole(options) && options.drive !== false;\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 {\n DurableWorkerRuntime,\n type RunningWorker,\n runRedisWorker as defaultRunRedisWorker,\n} from '@dudousxd/durable-worker';\nimport {\n DURABLE_OPTIONS_CANONICAL,\n TRANSPORT_CANONICAL,\n type Transport,\n} from '@dudousxd/nestjs-durable-core';\nimport {\n Inject,\n Injectable,\n type OnApplicationBootstrap,\n type OnApplicationShutdown,\n type OnModuleInit,\n type Provider,\n} from '@nestjs/common';\nimport { DiscoveryService, MetadataScanner } from '@nestjs/core';\nimport { scanSteps, scanWorkflows } from './discovery-helpers';\nimport type { RunRedisWorkerFn } from './durable-worker.module';\nimport type { DurableModuleOptions } from './durable.module';\n\n/**\n * The **co-located in-app worker** (uniform dispatch): active whenever an app supplies BOTH `store`\n * AND `connection` (`DurableModule.forRoot({ store, transport, connection, partition? })`) — the same\n * process runs the engine AND serves its own discovered `@Workflow`/`@Step`. The engine registers each\n * `@Workflow` GROUP-SERVED — its turns are dispatched, PER WORKFLOW NAME, to\n * `tenantGroup(sanitizeQueueToken(name), partition)` over the transport via a per-workflow\n * `RemoteWorkflowExecutor` instead of run inline — and a co-located {@link DurableWorkerRuntime}\n * subscribes one queue per discovered name (via `runRedisWorker`) and replays the very same TS bodies.\n * This is the uniform-dispatch \"one app, both roles\" shape — every turn pays a transport round-trip\n * even though the worker is the same process. Requires a transport that carries workflow tasks\n * (BullMQ); an in-process-only transport cannot dispatch a `WorkflowExecutor`.\n *\n * Distinct from the PURE thin-worker role (`connection` set, `store` unset — see\n * `durable-worker.module.ts`'s `ThinWorkflowRegistrar`/`ThinStepRegistrar`/`ThinWorkerBootstrap`),\n * which has no engine/store of its own at all.\n */\nexport const IN_APP_WORKER_BINDING = Symbol('nestjs-durable:in-app-worker-binding');\n\n/** The co-located worker's {@link DurableWorkerRuntime} (the consumer half). */\nexport const IN_APP_WORKER_RUNTIME = Symbol('nestjs-durable:in-app-worker-runtime');\n\n/** `runRedisWorker`, injected so tests can substitute a fake (no real Redis). Defaults to the real one. */\nexport const IN_APP_RUN_REDIS_WORKER = Symbol('nestjs-durable:in-app-run-redis-worker');\n\n/** Started {@link RunningWorker} handles for the in-app worker, closed on shutdown. */\nexport const IN_APP_WORKER_RUNNERS = Symbol('nestjs-durable:in-app-worker-runners');\n\n/** The group-served binding shape resolved behind {@link IN_APP_WORKER_BINDING}. */\nexport interface InAppWorkerBinding {\n transport: Transport;\n partition?: string;\n}\n\n/** True for the **co-located** role — both `store` AND `connection` set. */\nfunction isCoLocatedWorker(options: DurableModuleOptions): boolean {\n return options.store !== undefined && options.connection !== undefined;\n}\n\n/**\n * Builds the {@link IN_APP_WORKER_BINDING}: when this app is co-located (`store` + `connection`), the\n * engine's transport plus the configured partition — the registrar builds a PER-WORKFLOW\n * `RemoteWorkflowExecutor` from these, one per discovered name; otherwise `null` (inline default).\n * Fails fast if co-located but the transport can't carry workflow tasks — a group-served run would\n * otherwise dead-end at dispatch.\n */\nfunction inAppWorkerBinding(\n transport: Transport | null,\n options: DurableModuleOptions,\n): InAppWorkerBinding | null {\n if (!isCoLocatedWorker(options)) return null;\n if (!transport?.dispatchWorkflowTask || !transport.onDecision) {\n throw new Error(\n 'a co-located worker (store + connection) requires a transport that carries workflow tasks (dispatchWorkflowTask + onDecision), e.g. BullMQTransport. An in-process transport cannot serve a group-served workflow.',\n );\n }\n return {\n transport,\n ...(options.partition !== undefined ? { partition: options.partition } : {}),\n };\n}\n\n/**\n * The consumer half of the in-app worker: on init it registers every discovered `@Workflow`/`@Step`\n * on a {@link DurableWorkerRuntime} (the SAME bodies the engine registered group-served), and on\n * bootstrap it starts one `runRedisWorker` call that subscribes one queue per discovered name,\n * suffixed by the configured partition, closing it on shutdown. A no-op outside the co-located role\n * (see {@link isCoLocatedWorker}). Mirrors the thin {@link\n * import('./durable-worker.module').ThinWorkerBootstrap}, but co-located with a full engine.\n */\n@Injectable()\nexport class InAppWorkerBootstrap\n implements OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown\n{\n private readonly runners: RunningWorker[] = [];\n\n constructor(\n private readonly discovery: DiscoveryService,\n private readonly metadataScanner: MetadataScanner,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n @Inject(IN_APP_WORKER_RUNTIME) private readonly runtime: DurableWorkerRuntime,\n @Inject(IN_APP_RUN_REDIS_WORKER) private readonly runRedisWorker: RunRedisWorkerFn,\n @Inject(IN_APP_WORKER_RUNNERS) private readonly runnersSink: RunningWorker[],\n ) {}\n\n onModuleInit(): void {\n if (!isCoLocatedWorker(this.options)) return;\n // Register the same TS bodies the engine serves group-served, so the consumer can replay them.\n scanWorkflows(this.discovery, (meta, instance) =>\n this.runtime.registerWorkflow(meta.name, (ctx, input) => instance.run(ctx, input)),\n );\n scanSteps(this.discovery, this.metadataScanner, (meta, handler) =>\n this.runtime.registerStep(meta.name, handler),\n );\n }\n\n async onApplicationBootstrap(): Promise<void> {\n const options = this.options;\n if (!isCoLocatedWorker(options) || options.connection === undefined) return;\n const handle = await this.runRedisWorker({\n runtime: this.runtime,\n connection: options.connection,\n ...(options.partition !== undefined ? { partition: options.partition } : {}),\n ...(options.prefix !== undefined ? { prefix: options.prefix } : {}),\n ...(options.instanceId !== undefined ? { instanceId: options.instanceId } : {}),\n ...(options.concurrency !== undefined ? { concurrency: options.concurrency } : {}),\n });\n this.runners.push(handle);\n this.runnersSink.push(handle);\n }\n\n async onApplicationShutdown(): Promise<void> {\n await Promise.allSettled(this.runners.map((handle) => handle.close()));\n }\n}\n\n/**\n * The providers that stand up the co-located in-app worker, added to {@link\n * import('./durable.module').DurableModule}'s unified provider set. All are inert outside the\n * co-located role (`store` + `connection` both set — see {@link isCoLocatedWorker}), so a plain\n * operator (`store` only) or pure thin worker (`connection` only) is unaffected.\n */\nexport function inAppWorkerProviders(): Provider[] {\n return [\n {\n provide: IN_APP_WORKER_BINDING,\n useFactory: (transport: Transport | null, options: DurableModuleOptions) =>\n inAppWorkerBinding(transport, options),\n inject: [TRANSPORT_CANONICAL, DURABLE_OPTIONS_CANONICAL],\n },\n {\n provide: IN_APP_WORKER_RUNTIME,\n // The runtime's `WorkflowWorker` uses its `group` ctor param as the WORKFLOW's\n // `workflowPartition` for any `ctx.step` call (see `workflow-context.ts`'s `resolveCallGroup`)\n // — that fallback MUST equal this app's own `partition`, or a dispatched step's decision\n // carries a mismatched token and the engine dispatches it to a queue nothing here subscribes\n // to. Explicit `''` (never\n // `undefined`) so it does NOT fall through to `WorkflowWorker`'s unrelated `'workflows'`\n // default parameter.\n useFactory: (options: DurableModuleOptions) =>\n new DurableWorkerRuntime({ workflowGroup: options.partition ?? '' }),\n inject: [DURABLE_OPTIONS_CANONICAL],\n },\n { provide: IN_APP_RUN_REDIS_WORKER, useValue: defaultRunRedisWorker },\n { provide: IN_APP_WORKER_RUNNERS, useValue: [] as RunningWorker[] },\n InAppWorkerBootstrap,\n ];\n}\n","import type {\n DurableTopology,\n EngineEvent,\n GroupHealth,\n RunDetail,\n RunGateway,\n RunListItem,\n RunQuery,\n RunReply,\n RunRequestKind,\n RunResult,\n RunWaiting,\n Transport,\n} from '@dudousxd/nestjs-durable-core';\nimport { Injectable } from '@nestjs/common';\n\n/**\n * Method-shorthand signatures (not arrow-function-typed properties) are deliberate: TypeScript\n * checks method parameters bivariantly, so a `Promise<T>`'s own `resolve`/`reject` (typed for that\n * call's concrete `T`) can be stored here erased to `unknown` with no cast — the one place the\n * generic `T` from {@link ProxyRunGateway.request} crosses into the untyped bookkeeping map.\n */\ninterface PendingRequest {\n resolve(data: unknown): void;\n reject(err: Error): void;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Tenant-side `RunGateway` — round-trips every verb as a `RunRequest`/`RunReply` pair over the\n * transport, correlated by a minted `requestId`, and bridges `subscribe` onto the transport's\n * per-tenant event stream. Bound under `RUN_GATEWAY` by `DurableModule`'s thin-worker role\n * (`connection` set, no `store`) when the app supplies a `transport` (see `unavailableRunGateway`\n * for the no-transport fallback). The counterpart to the operator-side `StoreRunGateway`: a tenant\n * worker never touches a store/driver directly, only this proxy.\n */\n@Injectable()\nexport class ProxyRunGateway implements RunGateway {\n private readonly pending = new Map<string, PendingRequest>();\n\n constructor(\n private readonly transport: Transport,\n private readonly tenant: string,\n private readonly timeoutMs = 10_000,\n ) {\n this.transport.onRunReply?.((reply: RunReply) => this.handleReply(reply));\n }\n\n private handleReply(reply: RunReply): void {\n const pending = this.pending.get(reply.requestId);\n if (!pending) return;\n clearTimeout(pending.timer);\n this.pending.delete(reply.requestId);\n if (reply.result.ok) {\n pending.resolve(reply.result.data);\n } else {\n pending.reject(new Error(reply.result.error.message));\n }\n }\n\n private request<T>(body: RunRequestKind): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const requestId = globalThis.crypto.randomUUID();\n const timer = setTimeout(() => {\n this.pending.delete(requestId);\n reject(\n new Error(`control plane did not respond to ${body.kind} within ${this.timeoutMs}ms`),\n );\n }, this.timeoutMs);\n this.pending.set(requestId, { resolve, reject, timer });\n this.transport\n .dispatchRunRequest?.({ requestId, tenant: this.tenant, body })\n .catch((error: unknown) => {\n const stillPending = this.pending.get(requestId);\n if (!stillPending) return;\n clearTimeout(stillPending.timer);\n this.pending.delete(requestId);\n stillPending.reject(error instanceof Error ? error : new Error(String(error)));\n });\n });\n }\n\n topology(): DurableTopology {\n return { role: 'tenant', tenant: this.tenant };\n }\n\n getRunDetail(runId: string): Promise<RunDetail | null> {\n return this.request<RunDetail | null>({ kind: 'getRunDetail', runId });\n }\n\n /** The control plane's `StoreRunGateway` resolves each run's `waiting` descriptor; the reply carries\n * it through as plain JSON, so a tenant's list rows name the wait too. */\n listRuns(query: RunQuery): Promise<RunListItem[]> {\n return this.request<RunListItem[]>({ kind: 'listRuns', query });\n }\n\n /** Round-trips to the operator, which scopes the result to this tenant's own `@<tenant>` groups. */\n workerHealth(): Promise<GroupHealth[]> {\n return this.request<GroupHealth[]>({ kind: 'workerHealth' });\n }\n\n /** Bulk, one request for the whole id list (like `listRuns`, not one request per id). The operator\n * filters the reply to runs this tenant actually owns (see `RunRequestResponder`). */\n waitingFor(runIds: string[]): Promise<Record<string, RunWaiting>> {\n return this.request<Record<string, RunWaiting>>({ kind: 'waitingFor', runIds });\n }\n\n cancel(runId: string, opts?: { compensate?: boolean }): Promise<RunResult | null> {\n return this.request<RunResult | null>(\n opts === undefined ? { kind: 'cancel', runId } : { kind: 'cancel', runId, opts },\n );\n }\n\n retry(runId: string): Promise<RunResult | null> {\n return this.request<RunResult | null>({ kind: 'retry', runId });\n }\n\n continue(runId: string): Promise<RunResult | null> {\n return this.request<RunResult | null>({ kind: 'continue', runId });\n }\n\n retryWithInput(runId: string, input: unknown): Promise<{ runId: string } | null> {\n return this.request<{ runId: string } | null>({ kind: 'retryWithInput', runId, input });\n }\n\n redispatchPending(runId: string): Promise<(RunResult & { redispatched: number }) | null> {\n return this.request<(RunResult & { redispatched: number }) | null>({\n kind: 'redispatch',\n runId,\n });\n }\n\n subscribe(runId: string, onEvent: (event: EngineEvent) => void): () => void {\n const unsubscribe = this.transport.onTenantEvent?.(this.tenant, (evt) => {\n if (evt.event.runId === runId) onEvent(evt.event);\n });\n return unsubscribe ?? (() => {});\n }\n}\n","import {\n DURABLE_OPTIONS_CANONICAL,\n STATE_STORE_CANONICAL,\n type StateStore,\n TERMINAL_RUN_STATUSES,\n parseDuration,\n} from '@dudousxd/nestjs-durable-core';\nimport {\n Inject,\n Injectable,\n type OnApplicationBootstrap,\n type OnModuleDestroy,\n} from '@nestjs/common';\nimport type { DurableModuleOptions, DurableRetentionOptions } from './durable.module';\nimport { isDrivingOperator } from './role';\n\nconst DEFAULT_SWEEP_INTERVAL_MS = 60_000;\nconst DEFAULT_BATCH_SIZE = 1_000;\n// Backstop so a single sweep can never loop unbounded. At the default batch size this still drains up\n// to 100k runs per policy per sweep before yielding the event loop to the next tick.\nconst MAX_BATCHES_PER_POLICY = 100;\n\n/**\n * Reject a retention config that would silently misbehave: every policy must set at least one bound,\n * list only terminal statuses (pruning a live status would race the engine), and the status sets must\n * be disjoint (so \"most recent N\" is unambiguous per status group). Throws on the first violation.\n */\nexport function validateRetention(retention: DurableRetentionOptions): void {\n const seen = new Set<string>();\n for (const policy of retention.policies) {\n if (policy.statuses.length === 0) {\n throw new Error('durable retention: each policy must list at least one status');\n }\n if (policy.maxAge == null && policy.maxCount == null) {\n throw new Error('durable retention: each policy must set maxAge and/or maxCount');\n }\n // Fail fast on a typo'd duration string ('7days', '1 month') at boot rather than silently never pruning.\n if (policy.maxAge != null) parseDuration(policy.maxAge);\n for (const status of policy.statuses) {\n if (!TERMINAL_RUN_STATUSES.includes(status)) {\n throw new Error(\n `durable retention: status \"${status}\" is not terminal; only ${TERMINAL_RUN_STATUSES.join(\n ', ',\n )} can be pruned`,\n );\n }\n if (seen.has(status)) {\n throw new Error(\n `durable retention: status \"${status}\" appears in more than one policy; status sets must be disjoint`,\n );\n }\n seen.add(status);\n }\n }\n}\n\n/**\n * Hard-prunes terminal run history on an interval per the configured `retention` policies, keeping the\n * `durable_workflow_runs` table (and its children) bounded so the poller's per-tick scans stay cheap.\n *\n * Worker-only (a dashboard-only instance never prunes), separate from the 1s timer poll (defaults to a\n * 60s sweep), and self-draining: each policy is swept in `batchSize` chunks until a batch comes back\n * short. No-ops with a warning if the store adapter doesn't implement `pruneTerminalRuns`.\n */\n@Injectable()\nexport class RetentionPoller implements OnApplicationBootstrap, OnModuleDestroy {\n private timer?: ReturnType<typeof setInterval>;\n private sweeping = false;\n\n constructor(\n @Inject(STATE_STORE_CANONICAL) private readonly store: StateStore | null,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n /** Non-null once past the `isDrivingOperator` guard (which requires `options.store` to be set). */\n private requireStore(): StateStore {\n if (!this.store) {\n throw new Error(\n 'unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set',\n );\n }\n return this.store;\n }\n\n async onApplicationBootstrap(): Promise<void> {\n // Only a DRIVING operator prunes — a dashboard/dispatch-only instance, or a thin worker with no\n // `store` at all, must not delete history.\n if (!isDrivingOperator(this.options)) return;\n const retention = this.options.retention;\n if (!retention || retention.policies.length === 0) return;\n validateRetention(retention);\n if (typeof this.requireStore().pruneTerminalRuns !== 'function') {\n console.warn(\n '[nestjs-durable] `retention` is configured but the store adapter does not implement pruneTerminalRuns; retention is disabled.',\n );\n return;\n }\n await this.sweep();\n const intervalMs =\n retention.sweepInterval != null\n ? parseDuration(retention.sweepInterval)\n : DEFAULT_SWEEP_INTERVAL_MS;\n if (intervalMs > 0) {\n this.timer = setInterval(() => void this.sweep(), intervalMs);\n this.timer.unref?.();\n }\n }\n\n onModuleDestroy(): void {\n if (this.timer) clearInterval(this.timer);\n }\n\n private async sweep(): Promise<void> {\n if (this.sweeping) return; // never overlap two sweeps on this instance\n const retention = this.options.retention;\n const store = this.requireStore();\n const prune = store.pruneTerminalRuns;\n if (!retention || typeof prune !== 'function') return;\n this.sweeping = true;\n try {\n const batchSize = retention.batchSize ?? DEFAULT_BATCH_SIZE;\n const now = Date.now();\n for (const policy of retention.policies) {\n for (let batch = 0; batch < MAX_BATCHES_PER_POLICY; batch++) {\n const deleted = await prune.call(store, policy, now, batchSize);\n if (deleted < batchSize) break; // backlog drained for this policy\n }\n }\n } finally {\n this.sweeping = false;\n }\n }\n}\n","import type { RunGateway, RunReply, RunRequest, RunWaiting } from '@dudousxd/nestjs-durable-core';\n\n/** The narrow slice of `Transport` the responder needs — a tenant's read/control request in, a\n * correlated reply out. Both are OPTIONAL on the full `Transport` interface (only broker\n * transports carry the run-request/reply protocol); the caller capability-checks before wiring\n * this up (see `durable.module.ts`). */\nexport interface RunRequestTransport {\n onRunRequest(handler: (msg: RunRequest) => Promise<void>): void;\n publishRunReply(reply: RunReply): Promise<void>;\n}\n\n/**\n * Operator-side consumer of a tenant's {@link RunRequest}s: answers each one against a\n * `RunGateway`, enforcing the tenant boundary before touching the run. For every runId-bearing\n * verb it loads the run via `getRunDetail` FIRST and compares `run.namespace` to the requesting\n * `msg.tenant` — a mismatch short-circuits into a `cross-tenant` error reply WITHOUT calling the\n * verb, so a tenant can never read or act on another tenant's run. `listRuns` is scoped by\n * overwriting the query's `namespace` with the requester's tenant, ignoring whatever the client\n * sent. This is the security boundary of the tenant run gateway — do not weaken it.\n */\nexport class RunRequestResponder {\n constructor(\n private readonly transport: RunRequestTransport,\n private readonly gateway: RunGateway,\n ) {}\n\n /** Register the consumer on the transport. Each request is answered independently; a handler\n * failure never throws back into the transport (errors are captured into an error reply). */\n start(): void {\n this.transport.onRunRequest(async (msg) => {\n const reply = await this.handle(msg);\n await this.transport.publishRunReply(reply);\n });\n }\n\n private async handle(msg: RunRequest): Promise<RunReply> {\n const { body } = msg;\n if (body.kind === 'listRuns') {\n // Force the namespace to the requester's tenant — the client-supplied value is discarded,\n // never merely validated, so a tenant can't widen its own query into another's namespace.\n const data = await this.gateway.listRuns({ ...body.query, namespace: msg.tenant });\n return { requestId: msg.requestId, result: { ok: true, data } };\n }\n\n if (body.kind === 'workerHealth') {\n // Not runId-bearing, so it can't ride the getRunDetail namespace check below. Scope by the\n // group-name convention instead: a tenant's queues are suffixed `<name>@<tenant>`, so keep only\n // groups ending in the requester's `@<tenant>` — the operator's own bare groups and every other\n // tenant's are dropped, so a tenant's Workers panel only ever sees ITS OWN queues.\n const all = await this.gateway.workerHealth();\n const data = all.filter((h) => h.group.endsWith(`@${msg.tenant}`));\n return { requestId: msg.requestId, result: { ok: true, data } };\n }\n\n if (body.kind === 'waitingFor') {\n // Bulk, like `listRuns` — but unlike `listRuns` (which forces the query's namespace and so\n // scopes itself) the caller supplies arbitrary ids, which could probe another tenant's runs. The\n // gateway's own `waitingFor` has no namespace to filter by, so verify ownership per MATCHED entry\n // (bounded by how many of the requested ids are actually waiting, never by `runIds.length`) the\n // same way every other runId-bearing verb does below: `getRunDetail` + a `namespace` check.\n const all = await this.gateway.waitingFor(body.runIds);\n const owned = await Promise.all(\n Object.entries(all).map(async ([runId, waiting]) => {\n const runDetail = await this.gateway.getRunDetail(runId);\n return runDetail && runDetail.run.namespace === msg.tenant\n ? ([runId, waiting] as const)\n : undefined;\n }),\n );\n const data: Record<string, RunWaiting> = {};\n for (const entry of owned) {\n if (entry) data[entry[0]] = entry[1];\n }\n return { requestId: msg.requestId, result: { ok: true, data } };\n }\n\n // Every remaining verb is runId-bearing. Load the run FIRST — before calling the verb — so a\n // cross-tenant request never reaches the gateway's mutating methods (cancel/retry/continue/redispatch).\n const detail = await this.gateway.getRunDetail(body.runId);\n if (detail && detail.run.namespace !== msg.tenant) {\n return {\n requestId: msg.requestId,\n result: {\n ok: false,\n error: { message: 'run belongs to another tenant', code: 'cross-tenant' },\n },\n };\n }\n\n if (body.kind === 'getRunDetail') {\n return { requestId: msg.requestId, result: { ok: true, data: detail } };\n }\n\n try {\n const data = await this.callVerb(body);\n return { requestId: msg.requestId, result: { ok: true, data } };\n } catch (err) {\n return {\n requestId: msg.requestId,\n result: { ok: false, error: { message: err instanceof Error ? err.message : String(err) } },\n };\n }\n }\n\n private callVerb(\n body: Exclude<\n RunRequest['body'],\n | { kind: 'listRuns' }\n | { kind: 'getRunDetail' }\n | { kind: 'workerHealth' }\n | { kind: 'waitingFor' }\n >,\n ): Promise<unknown> {\n switch (body.kind) {\n case 'cancel':\n return this.gateway.cancel(body.runId, body.opts);\n case 'retry':\n return this.gateway.retry(body.runId);\n case 'continue':\n return this.gateway.continue(body.runId);\n case 'retryWithInput':\n return this.gateway.retryWithInput(body.runId, body.input);\n case 'redispatch':\n return this.gateway.redispatchPending(body.runId);\n }\n }\n}\n","import {\n type DurableTopology,\n type EngineEvent,\n type GroupHealth,\n type RunDetail,\n type RunGateway,\n type RunListItem,\n type RunQuery,\n type RunResult,\n type RunWaiting,\n STATE_STORE_CANONICAL,\n type StateStore,\n WorkflowEngine,\n indexWaitersByRun,\n resolveRunWaiting,\n} from '@dudousxd/nestjs-durable-core';\nimport { Inject, Injectable } from '@nestjs/common';\n\n/**\n * Store-backed `RunGateway` — the operator-side implementation, bound to {@link RUN_GATEWAY} on\n * worker/drive instances. Reuses `DashboardService`'s six read/control method bodies verbatim\n * (`dashboard.service.ts:76-172`), so a consumer that only needs the bounded `RunGateway` surface\n * (the `RunRequestResponder`, a thin controller) doesn't have to depend on the dashboard package.\n */\n@Injectable()\nexport class StoreRunGateway implements RunGateway {\n constructor(\n @Inject(STATE_STORE_CANONICAL) private readonly store: StateStore,\n private readonly engine: WorkflowEngine,\n ) {}\n\n topology(): DurableTopology {\n return { role: 'control-plane' };\n }\n\n async getRunDetail(runId: string): Promise<RunDetail | null> {\n const run = await this.store.getRun(runId);\n if (!run) return null;\n const [timeline, children] = await Promise.all([\n this.store.listCheckpoints(runId),\n this.engine.getRunChildren(runId), // canonical parent→children edge (shared with cancel cascade)\n ]);\n return { run, timeline, children };\n }\n\n async listRuns(query: RunQuery): Promise<RunListItem[]> {\n const runs = await this.store.listRuns(query);\n // ONE bulk scan of the signal-waiter table (indexed by runId) resolves what each suspended run is\n // parked on — signal / webhook / child — with no per-run timeline fetch. A timer wait falls back\n // to the run's own `wakeAt`. Non-suspended (and remote-step-in-flight) runs carry no `waiting`.\n const waiterByRun = indexWaitersByRun(await this.store.listSignalWaiters(''));\n return runs.map((run) => {\n const waiting = resolveRunWaiting(run, waiterByRun);\n return waiting ? { ...run, waiting } : run;\n });\n }\n\n /**\n * Bulk-resolve what each of `runIds` is currently parked on — for a consumer with its own filtered/\n * paginated run listing (e.g. \"which of MY suspended runs are stuck at a breakpoint\") without\n * re-deriving `listRuns`' waiter scan or querying `durable_step_checkpoints` directly. Mirrors\n * `listRuns`' waiting computation (the SAME bulk signal-waiter scan + `resolveRunWaiting`), but ALSO\n * bulk-fetches the currently-suspended runs to check real status: `engine.cancel` (the non-compensate\n * path) doesn't clear a run's signal waiter row, so a cancelled run can leave an ORPHANED waiter\n * behind — trusting waiter presence alone would wrongly report a terminal run as still waiting.\n * Two bulk scans total (never one query per requested id), same as `listRuns`.\n */\n async waitingFor(runIds: string[]): Promise<Record<string, RunWaiting>> {\n if (runIds.length === 0) return {};\n const idSet = new Set(runIds);\n const [suspended, waiters] = await Promise.all([\n this.store.listRuns({ statuses: ['suspended'] }),\n this.store.listSignalWaiters(''),\n ]);\n const waiterByRun = indexWaitersByRun(waiters);\n const result: Record<string, RunWaiting> = {};\n for (const run of suspended) {\n if (!idSet.has(run.id)) continue;\n const waiting = resolveRunWaiting(run, waiterByRun);\n if (waiting) result[run.id] = waiting;\n }\n return result;\n }\n\n /** Every group the engine knows about — unscoped. A tenant proxy's request is scoped by the\n * `RunRequestResponder` (to the requester's `@<tenant>` groups); the operator's own UI sees all. */\n workerHealth(): Promise<GroupHealth[]> {\n return this.engine.workerHealth();\n }\n\n cancel(runId: string, opts?: { compensate?: boolean }): Promise<RunResult | null> {\n return this.engine.cancel(runId, opts);\n }\n\n /** Re-enqueue (dispatch model) instead of resuming inline — a worker picks the run up and replays it. */\n retry(runId: string): Promise<RunResult | null> {\n return this.engine.requeue(runId);\n }\n\n continue(runId: string): Promise<RunResult | null> {\n return this.engine.continue(runId);\n }\n\n retryWithInput(runId: string, input: unknown): Promise<{ runId: string } | null> {\n return this.engine.retryWithInput(runId, input);\n }\n\n redispatchPending(runId: string): Promise<(RunResult & { redispatched: number }) | null> {\n return this.engine.redispatchPending(runId);\n }\n\n subscribe(runId: string, onEvent: (event: EngineEvent) => void): () => void {\n return this.engine.subscribe((event) => {\n if (event.runId === runId) onEvent(event);\n });\n }\n}\n","import type { EngineEvent, StateStore, TenantEvent } from '@dudousxd/nestjs-durable-core';\n\n/** The narrow slice of `StateStore` the republisher needs — the `step.*`-event namespace fallback\n * (see {@link TenantEventRepublisher.namespaceFor}). Mirrors the `RunRequestTransport` narrowing\n * convention (`run-request-responder.ts`): depend on the smallest surface, not the whole store. */\nexport type RunLookupStore = Pick<StateStore, 'getRun'>;\n\n/** `EngineEvent` types that end a run's lifecycle — no further event for that `runId` will ever\n * follow one of these, so it is the safe point to drop the run from `TenantEventRepublisher`'s\n * namespace cache. Matches the terminal members of `EngineEventType` (`packages/core/src/interfaces.ts`)\n * exactly: `run.suspended` is NOT terminal (a suspended run resumes and emits more events), and\n * there is no separate `run.cancelled`/`run.dead` event type — both cancellation and dead-lettering\n * are published as `run.failed`. */\nfunction isTerminalRunEvent(event: EngineEvent): boolean {\n return event.type === 'run.completed' || event.type === 'run.failed';\n}\n\n/**\n * Re-publishes engine lifecycle events onto a run's per-tenant channel via `publish` (bound from\n * `Transport.publishTenantEvent`), so a store-less tenant worker can live-tail its OWN runs.\n *\n * The run's namespace is read straight off `event.namespace` (stamped by `engine.ts`'s `emit()` on\n * every `run.*` lifecycle event, where the run is already in hand); a `step.*` event doesn't carry\n * it, so those fall back to `store.getRun`. EVERY run's resolved namespace is memoized in\n * `runNamespaces` — including a bare/`default` run — so the store is read at most ONCE per run id\n * regardless of how many `step.*` events that run emits; a `null` cache entry means \"resolved, not\n * a tenant\" (distinct from \"not yet resolved\", i.e. absent from the map). A run's entry is deleted\n * the moment its terminal event is handled, so the cache stays bounded to in-flight runs (tenant and\n * default alike) — the same order of magnitude it was already paying for tenant runs alone, in\n * exchange for turning a per-event store read into a per-run one.\n */\nexport class TenantEventRepublisher {\n private readonly runNamespaces = new Map<string, string | null>();\n\n constructor(\n private readonly store: RunLookupStore,\n private readonly publish: (event: TenantEvent) => Promise<void>,\n ) {}\n\n async handle(event: EngineEvent): Promise<void> {\n const namespace = await this.namespaceFor(event);\n // A terminal run emits no further events — drop its cache entry now regardless of whether this\n // event was itself re-published, so the slot is reclaimed even for a bare/default run.\n if (isTerminalRunEvent(event)) this.runNamespaces.delete(event.runId);\n // Skip bare/default runs — no real tenant to re-publish to, and it would just be noise on an\n // unpartitioned operator.\n if (!namespace || namespace === 'default') return;\n await this.publish({ tenant: namespace, event }).catch(() => undefined);\n }\n\n private async namespaceFor(event: EngineEvent): Promise<string | undefined> {\n if (this.runNamespaces.has(event.runId)) {\n const cached = this.runNamespaces.get(event.runId);\n return cached === null ? undefined : cached;\n }\n if (event.namespace !== undefined) {\n this.runNamespaces.set(event.runId, event.namespace === 'default' ? null : event.namespace);\n return event.namespace;\n }\n const run = await this.store.getRun(event.runId);\n const tenantNamespace =\n run?.namespace !== undefined && run.namespace !== 'default' ? run.namespace : null;\n this.runNamespaces.set(event.runId, tenantNamespace);\n return tenantNamespace === null ? undefined : tenantNamespace;\n }\n}\n","import {\n DURABLE_OPTIONS_CANONICAL,\n WorkflowEngine,\n runSchedules,\n} 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';\nimport { isDrivingOperator } from './role';\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_CANONICAL) private readonly options: DurableModuleOptions,\n ) {}\n\n async onApplicationBootstrap(): Promise<void> {\n // Drive suspended runs forward when this operator instance is DRIVING (defaults to true) — it\n // may execute locally or dispatch remotely (group-served / convention), either way it drives. A\n // non-driving (dashboard-only) operator, or a thin worker with no `store` at all, must not\n // resume timers — leave that to a driving instance.\n if (!isDrivingOperator(this.options)) 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","import { RunGateway } from '@dudousxd/nestjs-durable-core';\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\n/**\n * @deprecated Inject the `RunGateway` abstract class directly (it is its own DI token now):\n * `constructor(private readonly gateway: RunGateway)`, provider `{ provide: RunGateway, useClass }`.\n * This symbol is kept as a back-compat alias — it points at the `RunGateway` class, so existing\n * `@Inject(RUN_GATEWAY)` sites resolve the very same token — and will be removed in a future major.\n *\n * `RunGateway` is owned by `@dudousxd/nestjs-durable-core` (a required peer dep of both this package\n * and the dashboard), so the abstract class is a single shared token across packages without the\n * previous `Symbol.for` value-sharing hack.\n */\nexport const RUN_GATEWAY = RunGateway;\n","import {\n DURABLE_OPTIONS_CANONICAL,\n RemoteWorkflowExecutor,\n STATE_STORE_CANONICAL,\n type StateStore,\n type WorkflowCtx,\n WorkflowEngine,\n type WorkflowRun,\n bindWorkflowClass,\n parseDuration,\n sanitizeQueueToken,\n tenantGroup,\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, isDeadLetterHandler } from './decorators';\nimport { scanWorkflows } from './discovery-helpers';\nimport type { DurableModuleOptions } from './durable.module';\nimport { entityConfigFor, getEntityMeta } from './entity';\nimport { IN_APP_WORKER_BINDING, type InAppWorkerBinding } from './in-app-worker';\nimport { classValidatorInput } from './input-validation';\nimport { isDrivingOperator, isOperatorRole } from './role';\nimport { type DurableStepInterceptor, isStepInterceptor } from './step-interceptor';\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_CANONICAL) private readonly store: StateStore | null,\n @Inject(DURABLE_OPTIONS_CANONICAL) private readonly options: DurableModuleOptions,\n // Group-served binding when the app opted into an in-app worker (uniform dispatch); null = inline.\n @Inject(IN_APP_WORKER_BINDING) private readonly inAppWorker: InAppWorkerBinding | null,\n ) {}\n\n async onApplicationBootstrap(): Promise<void> {\n // Recover runs left incomplete by a crash/deploy when this operator instance is DRIVING\n // (defaults to true). A non-driving (dashboard-only) operator, or a thin worker with no `store`\n // at all, must not pick up and re-run workflows — leave that to a driving instance.\n if (!isDrivingOperator(this.options)) 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 * Operator only — a thin worker (no `store`) holds no engine to drain. */\n async onApplicationShutdown(): Promise<void> {\n if (!isOperatorRole(this.options)) return;\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 // Operator only — a thin worker (no `store`) registers its bodies on a `DurableWorkerRuntime`\n // instead (see `durable-worker.module.ts`'s `ThinWorkflowRegistrar`), never on this engine.\n if (!isOperatorRole(this.options)) return;\n const store = this.store;\n if (!store) {\n throw new Error(\n 'unreachable: STATE_STORE_CANONICAL must resolve a store when options.store is set',\n );\n }\n if (this.options.autoSchema !== false) {\n await 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 // Pre-pass: wire interceptors and register @Entity providers (engine-only concerns).\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 }\n }\n\n // Workflow registration pass — shared scan, engine-specific registration callback.\n scanWorkflows(this.discovery, (meta, workflow) => {\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 // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const workflowCtor: object = (workflow as any).constructor as object;\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 requires: meta.requires,\n validateInput,\n searchAttributesSchema: meta.searchAttributes,\n onEvent: getOnEvents(meta, workflowCtor),\n eventBatch,\n // Uniform dispatch (opt-in): when an in-app worker is configured, register the body GROUP-SERVED\n // so the engine dispatches its turns over the transport instead of running it inline; the\n // co-located worker consumer (Task 5: one queue PER REGISTERED NAME) replays the same body.\n // The routing token — and the executor that dispatches under it — MUST be keyed by THIS\n // workflow's own name (`tenantGroup(sanitizeQueueToken(meta.name), partition)`), not a single\n // group shared across every discovered `@Workflow`: a fixed shared token would dispatch every\n // workflow's turns to one queue while the co-located worker subscribes one queue per name,\n // so a turn dispatched under the wrong token would never be consumed. Absent → the inline fast\n // path (unchanged).\n ...(this.inAppWorker\n ? {\n group: tenantGroup(sanitizeQueueToken(meta.name), this.inAppWorker.partition),\n executor: new RemoteWorkflowExecutor(\n this.inAppWorker.transport,\n meta.name,\n this.inAppWorker.partition,\n ),\n }\n : {}),\n });\n\n // Class-first statics (`MyWorkflow.start()` / `.execute()`): bind the class to THIS engine,\n // so the statics resolve the engine that registered it — no global engine singleton.\n bindWorkflowClass(workflowCtor as new () => object, {\n start: (name, input, runId, opts) => this.engine.start(name, input, runId, opts),\n waitForRun: (runId, opts) => this.engine.waitForRun(runId, opts),\n });\n\n const inline = this.findDeadLetterHandler(workflow as unknown as object);\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 // Route the dead-letter handler to the dead run's OWN tenant so an operator dispatches it to\n // that tenant's worker group (`target@<tenant>`), not the bare group.\n { namespace: run.namespace },\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 })` / `@OnDurableEvent` (the\n * payload becomes its input). Pass `opts.id` to dedupe redeliveries. Returns how many runs it\n * touched (resumed + started).\n *\n * Reliable by default: a publish that touches NOBODY (no live waiter, no subscriber) buffers ONE\n * copy so a LATER `waitForEvent(name, { match })` still consumes it instead of it being dropped —\n * see {@link WorkflowEngine.publishEvent}'s full semantics doc. Pass `opts.buffer: false` to opt out.\n */\n publishEvent(\n name: string,\n payload: unknown,\n opts?: { id?: string; buffer?: boolean },\n ): Promise<number> {\n return this.engine.publishEvent(name, payload, opts);\n }\n}\n"],"mappings":"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","names":["import_nestjs_durable_core","WORKFLOW_METADATA","Symbol","Workflow","options","target","meta","name","version","deadLetterWorkflow","tags","singleton","executionTimeout","inputSchema","validateInput","searchAttributes","onEvent","debounce","batch","requires","Reflect","defineMetadata","Object","defineProperty","WORKFLOW_NAME_KEY","value","configurable","getWorkflowMeta","getMetadata","DURABLE_STEP_METADATA","stepConfigFrom","config","retries","backoff","backoffMs","backoffMaxMs","jitter","timeoutMs","hasAnyField","values","some","undefined","Step","nameOrOptions","propertyKey","descriptor","derivedName","String","input","output","DURABLE_STEP_NAME","DURABLE_STEP_CONFIG","DurableStep","getDurableStepMeta","method","DEAD_LETTER_METADATA","DeadLetter","_target","_propertyKey","isDeadLetterHandler","ON_EVENT_METADATA","OnDurableEvent","events","existing","OnEvent","getOnEvents","fromDecorator","Set","attributesOf","workflow","run","meta","getWorkflowMeta","Error","name","searchAttributes","readSearchAttributes","import_nestjs_durable_core","DurableStartClient","tenant","options","deps","partition","start","workflow","input","runId","globalThis","crypto","randomUUID","opts","name","workflowName","startRun","connection","prefix","undefined","tags","searchAttributes","status","cancel","_runId","tenantUnsupported","deleteRun","resume","waitForRun","_opts","signal","_token","_payload","signalWithStart","_workflow","_input","_signal","publishEvent","_name","method","Promise","reject","Error","import_durable_worker","import_nestjs_durable_core","import_common","scanWorkflows","discovery","register","wrapper","getProviders","instance","meta","getWorkflowMeta","workflow","run","Error","name","scanSteps","scanner","prototype","Object","getPrototypeOf","methodName","getAllMethodNames","method","getDurableStepMeta","boundMethod","handler","input","log","validInput","parse","output","call","RUN_REDIS_WORKER","Symbol","DURABLE_WORKER_RUNNERS","isPureThinWorker","options","store","undefined","connection","ThinWorkflowRegistrar","discovery","runtime","onModuleInit","scanWorkflows","meta","instance","registerWorkflow","name","ctx","input","run","ThinStepRegistrar","metadataScanner","scanSteps","handler","registerStep","ThinWorkerBootstrap","runners","runRedisWorker","runnersSink","onApplicationBootstrap","handle","partition","prefix","instanceId","concurrency","push","onApplicationShutdown","Promise","allSettled","map","h","close","thinWorkerProviders","provide","DurableWorkerRuntime","useFactory","workflowGroup","inject","DURABLE_OPTIONS_CANONICAL","useValue","defaultRunRedisWorker","tenantGatewayUnavailable","method","reject","Error","unavailableRunGateway","topology","role","getRunDetail","_runId","listRuns","_query","waitingFor","_runIds","workerHealth","cancel","retry","continue","retryWithInput","_input","redispatchPending","subscribe","_onEvent","import_nestjs_durable_core","import_common","import_core","import_nestjs_durable_core","import_common","import_core","isOperatorRole","options","store","undefined","isDrivingOperator","drive","supportsHandle","transport","handle","DurableStepRegistrar","discovery","metadataScanner","options","onModuleInit","isDrivingOperator","scanSteps","meta","handler","name","partition","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_durable_worker","import_nestjs_durable_core","import_common","import_core","IN_APP_WORKER_BINDING","Symbol","IN_APP_WORKER_RUNTIME","IN_APP_RUN_REDIS_WORKER","IN_APP_WORKER_RUNNERS","isCoLocatedWorker","options","store","undefined","connection","inAppWorkerBinding","transport","dispatchWorkflowTask","onDecision","Error","partition","InAppWorkerBootstrap","runners","discovery","metadataScanner","runtime","runRedisWorker","runnersSink","onModuleInit","scanWorkflows","meta","instance","registerWorkflow","name","ctx","input","run","scanSteps","handler","registerStep","onApplicationBootstrap","handle","prefix","instanceId","concurrency","push","onApplicationShutdown","Promise","allSettled","map","close","inAppWorkerProviders","provide","useFactory","inject","TRANSPORT_CANONICAL","DURABLE_OPTIONS_CANONICAL","DurableWorkerRuntime","workflowGroup","useValue","defaultRunRedisWorker","import_common","ProxyRunGateway","pending","Map","transport","tenant","timeoutMs","onRunReply","reply","handleReply","get","requestId","clearTimeout","timer","delete","result","ok","resolve","data","reject","Error","error","message","request","body","Promise","globalThis","crypto","randomUUID","setTimeout","kind","set","dispatchRunRequest","catch","stillPending","String","topology","role","getRunDetail","runId","listRuns","query","workerHealth","waitingFor","runIds","cancel","opts","undefined","retry","continue","retryWithInput","input","redispatchPending","subscribe","onEvent","unsubscribe","onTenantEvent","evt","event","import_nestjs_durable_core","import_common","DEFAULT_SWEEP_INTERVAL_MS","DEFAULT_BATCH_SIZE","MAX_BATCHES_PER_POLICY","validateRetention","retention","seen","Set","policy","policies","statuses","length","Error","maxAge","maxCount","parseDuration","status","TERMINAL_RUN_STATUSES","includes","join","has","add","RetentionPoller","timer","sweeping","store","options","requireStore","onApplicationBootstrap","isDrivingOperator","pruneTerminalRuns","console","warn","sweep","intervalMs","sweepInterval","setInterval","unref","onModuleDestroy","clearInterval","prune","batchSize","now","Date","batch","deleted","call","RunRequestResponder","transport","gateway","start","onRunRequest","msg","reply","handle","publishRunReply","body","kind","data","listRuns","query","namespace","tenant","requestId","result","ok","all","workerHealth","filter","h","group","endsWith","waitingFor","runIds","owned","Promise","Object","entries","map","runId","waiting","runDetail","getRunDetail","run","undefined","entry","detail","error","message","code","callVerb","err","Error","String","cancel","opts","retry","continue","retryWithInput","input","redispatchPending","import_nestjs_durable_core","import_common","StoreRunGateway","store","engine","topology","role","getRunDetail","runId","run","getRun","timeline","children","Promise","all","listCheckpoints","getRunChildren","listRuns","query","runs","waiterByRun","indexWaitersByRun","listSignalWaiters","map","waiting","resolveRunWaiting","waitingFor","runIds","length","idSet","Set","suspended","waiters","statuses","result","has","id","workerHealth","cancel","opts","retry","requeue","continue","retryWithInput","input","redispatchPending","subscribe","onEvent","event","isTerminalRunEvent","event","type","TenantEventRepublisher","runNamespaces","Map","store","publish","handle","namespace","namespaceFor","delete","runId","tenant","catch","undefined","has","cached","get","set","run","getRun","tenantNamespace","import_nestjs_durable_core","import_common","TimerPoller","timer","polling","unsubscribeEnqueued","engine","options","onApplicationBootstrap","isDrivingOperator","onEnqueued","runId","runOne","poll","intervalMs","timerPollMs","setInterval","unref","onModuleDestroy","clearInterval","runPending","recoverIncomplete","resumeDueTimers","sweepTimeouts","schedules","length","runSchedules","Date","now","import_nestjs_durable_core","CONTEXT_ACCESSOR","Symbol","for","RUN_GATEWAY","RunGateway","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","inAppWorker","onApplicationBootstrap","isDrivingOperator","recoverIncomplete","onApplicationShutdown","isOperatorRole","drain","shutdownTimeoutMs","transports","transport","map","t","Promise","allSettled","close","onModuleInit","Error","autoSchema","ensureSchema","deadLetterByWorkflow","Map","wrapper","getProviders","instance","isStepInterceptor","interceptor","use","invocation","next","intercept","entityMeta","getEntityMeta","registerEntity","name","entityConfigFor","scanWorkflows","meta","workflow","validateInput","inputSchema","classValidatorInput","undefined","eventBatch","debounce","mode","windowMs","parseDuration","batch","maxSize","within","workflowCtor","register","version","ctx","input","run","tags","singleton","executionTimeout","requires","searchAttributesSchema","searchAttributes","onEvent","getOnEvents","group","tenantGroup","sanitizeQueueToken","partition","executor","RemoteWorkflowExecutor","bindWorkflowClass","start","runId","opts","waitForRun","inline","findDeadLetterHandler","deadLetterWorkflow","dlqName","set","workflowName","installDeadLetterRouting","prototype","Object","getPrototypeOf","methodName","getAllMethodNames","method","isDeadLetterHandler","call","byWorkflow","fallback","size","onDead","target","get","deadRunId","id","error","namespace","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","isScopeableStore","store","withScope","scopedStore","options","scopeReads","namespace","assertValidRole","hasStore","hasConnection","connection","Error","transport","transports","assertValidTopology","topology","role","partition","tenant","resolveTopology","servingPartition","resolveTopologyPreset","RunGatewayBootstrap","unsubscribe","engine","gateway","onApplicationBootstrap","isDrivingOperator","onRunRequest","publishRunReply","publishTenantEvent","runRequestTransport","bind","RunRequestResponder","start","republisher","TenantEventRepublisher","subscribe","event","handle","onModuleDestroy","DurableModule","forRoot","assertValid","build","provide","DURABLE_OPTIONS_CANONICAL","useValue","forRootAsync","useFactory","args","resolved","inject","optionsProvider","module","global","imports","DiscoveryModule","providers","STATE_STORE_CANONICAL","TRANSPORT_CANONICAL","STATE_STORE","useExisting","TRANSPORT","DURABLE_OPTIONS","WorkflowEngine","DurableStartClient","primary","context","runtime","rehydrate","fn","Object","keys","length","controlPlane","leaseMs","admission","maxRecoveryAttempts","remoteAdvanceSilenceMs","instanceId","webhookUrl","traceparent","compensationRetries","runDispatcher","drive","dispatch","queue","queues","registerQueue","ModuleRef","WorkflowService","EntityService","WorkflowRegistrar","DurableStepRegistrar","TimerPoller","RetentionPoller","RunGateway","StoreRunGateway","ProxyRunGateway","runGatewayTimeoutMs","unavailableRunGateway","inAppWorkerProviders","thinWorkerProviders","exports","import_nestjs_durable_core"]}