import { WorkflowCtx, SearchAttributes, WorkflowClass, StepError, WorkflowRef, SingletonConfig, SearchAttributesSchema, StateStore, Transport, NamedTransport, ControlPlane, ScheduledWorkflow, RetentionPolicy, QueueConfig, AdmissionBackend, WorkflowInputOf, StartOptions, RunResult, RunGateway, WorkflowEngine, EntityHandler, DurableTopology, RunDetail, RunQuery, RunListItem, GroupHealth, RunWaiting, EngineEvent, RunRequest, RunReply, StepInvocation, TenantEvent } from '@dudousxd/nestjs-durable-core';
export { AttributeFilter, EngineEvent, InferSearchAttributes, RunDetail, RunGateway, RunListItem, RunQuery, RunStatus, RunWaiting, SearchAttributes, SearchAttributesSchema, StepCheckpoint, StepEvent, StepLogger, WorkflowCtx, WorkflowEngine, WorkflowHandler, WorkflowRun, readSearchAttributes } from '@dudousxd/nestjs-durable-core';
import { z } from 'zod';
import { ConcurrencyOption, StartRunDeps, RunRedisWorkerOptions, RunningWorker, DurableWorkerRuntime } from '@dudousxd/durable-worker';
import { DynamicModule, InjectionToken, OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown, Provider } from '@nestjs/common';
import { DiscoveryService, MetadataScanner } from '@nestjs/core';

/**
 * The search-attributes shape a `@Workflow` class declares — extracted from its `run` method's
 * `ctx: WorkflowCtx<A>` annotation, mirroring the `WorkflowInputOf`/`WorkflowOutputOf` structural-
 * typing idiom in core's `workflow-ref.ts` (which extract `run`'s `input`/return type the same way).
 * A class whose `run` leaves `ctx` untyped (or types it as the bare `WorkflowCtx`) resolves to the
 * untyped `SearchAttributes` default — matching `WorkflowCtx` itself.
 */
type WorkflowAttributesOf<C> = C extends abstract new (...args: never[]) => {
    run(ctx: WorkflowCtx<infer A>, input: never): unknown;
} ? A : SearchAttributes;
/**
 * Read a run's search attributes **by workflow class**, with the schema resolved from that class's
 * `@Workflow({ searchAttributes })` decorator metadata — the same single-source-of-truth idiom as
 * triggering a workflow by class (`ctx.child(ShippingWorkflow, input)`, `engine.start(CheckoutWorkflow,
 * input)`): the decorator is the one place the schema lives, and every reader references the
 * WORKFLOW, never re-imports or re-declares the schema itself. The return type is inferred
 * structurally from the class's `run(ctx: WorkflowCtx<A>, …)` annotation (see
 * {@link WorkflowAttributesOf}), so a valid read is typed to `A` with no explicit type argument.
 *
 * Delegates to core's `readSearchAttributes(schema, run)` for the actual read, so the same lenient
 * safe-parse semantics apply: a run whose stored `searchAttributes` predate the schema, or fail it,
 * reads back as `{}` rather than throwing (see `readSearchAttributes`'s doc comment).
 *
 * @throws if `workflow` isn't a `@Workflow`-decorated class (no metadata at all — nothing to resolve
 * the schema from).
 * @throws if `workflow` is a `@Workflow` class that never declared a `searchAttributes` schema —
 * reading attributes by class needs one to resolve against.
 *
 * @example
 * ```ts
 * import { z } from 'zod';
 * import { Injectable } from '@nestjs/common';
 * import {
 *   attributesOf,
 *   InferSearchAttributes,
 *   Workflow,
 *   WorkflowCtx,
 *   WorkflowHandler,
 * } from '@dudousxd/nestjs-durable';
 *
 * const orderAttrs = z.object({ tier: z.enum(['free', 'pro']), amount: z.number() });
 * type OrderAttrs = InferSearchAttributes<typeof orderAttrs>;
 *
 * @Workflow({ name: 'checkout', searchAttributes: orderAttrs })
 * class CheckoutWorkflow implements WorkflowHandler<{ orderId: string }, void, OrderAttrs> {
 *   async run(ctx: WorkflowCtx<OrderAttrs>, input: { orderId: string }): Promise<void> {
 *     await ctx.upsertSearchAttributes({ tier: 'pro', amount: 100 });
 *   }
 * }
 *
 * @Injectable()
 * class CheckoutDashboardService {
 *   constructor(private readonly store: StateStore) {}
 *
 *   async tierOf(runId: string) {
 *     const run = await this.store.getRun(runId);
 *     const attrs = attributesOf(CheckoutWorkflow, run ?? {}); // OrderAttrs
 *     return attrs.tier;
 *   }
 * }
 * ```
 */
declare function attributesOf<C extends WorkflowClass>(workflow: C, run: {
    searchAttributes?: SearchAttributes | null | undefined;
}): WorkflowAttributesOf<C>;

/**
 * Local, structural mirror of `@dudousxd/nestjs-context`'s public accessor
 * (`packages/core/src/accessor.ts`).
 *
 * We deliberately do NOT import nestjs-context (it is an OPTIONAL peer). Instead
 * we declare the same shape here and inject it via the shared
 * {@link CONTEXT_ACCESSOR} token with `@Optional()`. Any object that structurally
 * satisfies this interface — including nestjs-context's real accessor — works.
 *
 * Kept byte-aligned with nestjs-context's `ContextAccessor` (and the identical
 * mirror in `@dudousxd/nestjs-authz`): `traceId()` / `tenantId()` are REQUIRED
 * (the real accessor always provides them) and `get()` is included, so the
 * structural match stays exact when DurableModule auto-feeds the carrier.
 */
interface UserRef {
    type: string;
    id: string | number;
}
/** Opaque shape of the context store. durable never reads it; mirrors the upstream surface. */
type ContextStore = Record<string, unknown>;
interface ContextAccessor {
    /** Trace id for the current request, or `undefined` when unavailable. */
    traceId(): string | undefined;
    /** Current tenant id, or `undefined` when no multi-tenant context is populated. */
    tenantId(): string | undefined;
    /** Reference to the current user, or `undefined` when unauthenticated. */
    userRef(): UserRef | undefined;
    /** The raw context store for the current request, or `undefined`. */
    get(): ContextStore | undefined;
}

declare const WORKFLOW_METADATA: unique symbol;
interface WorkflowMeta {
    name: string;
    version: string;
    /** The workflow this workflow's dead runs route to (a name or a class). See `WorkflowOptions`. */
    deadLetterWorkflow?: WorkflowRef | undefined;
    /** Static searchable labels stamped on every run of this workflow. See `WorkflowOptions`. */
    tags?: string[] | undefined;
    /** Per-key serialization (a durable mutex). See `WorkflowOptions`. */
    singleton?: SingletonConfig | undefined;
    /** Max wall-clock lifetime before a run is cancelled (e.g. `'2h'`). See `WorkflowOptions`. */
    executionTimeout?: string | number | undefined;
    /** class-validator DTO validated at start. See `WorkflowOptions`. */
    inputSchema?: (new (...args: any[]) => object) | undefined;
    /** Custom input validator (throws on invalid). See `WorkflowOptions`. */
    validateInput?: ((input: unknown) => void | Promise<void>) | undefined;
    /** Typed/validated `searchAttributes` shape. See `WorkflowOptions`. */
    searchAttributes?: SearchAttributesSchema | undefined;
    /** Event names that start a fresh run of this workflow. See `WorkflowOptions`. */
    onEvent?: string[] | undefined;
    /** Debounce `onEvent` triggers — fire once it's quiet for this long. See `WorkflowOptions`. */
    debounce?: string | number | undefined;
    /** Batch `onEvent` triggers — fire on size or window. See `WorkflowOptions`. */
    batch?: {
        maxSize: number;
        within: string | number;
    } | undefined;
    /** Capabilities a live worker must advertise to run this workflow's turns (handshake §7.5). See
     *  `WorkflowOptions`. */
    requires?: string[] | undefined;
}
interface WorkflowOptions {
    name: string;
    version?: string;
    /**
     * Route this workflow's dead-lettered runs to another **registered** workflow — by class
     * (`deadLetterWorkflow: CheckoutDlqWorkflow`, refactor-safe) or by name for a cross-runtime
     * handler. For a handler co-located on the same class, prefer an inline `@DeadLetter()` method
     * instead — it takes precedence, and declaring both is a boot-time error. The handler receives a
     * {@link DeadLetter} payload, idempotent by a `dlq:<runId>` id.
     */
    deadLetterWorkflow?: WorkflowRef;
    /**
     * Static labels stamped on **every run** of this workflow (e.g. `tags: ['etl', 'critical']`) and
     * merged with any per-run tags passed to `start`. Searchable/filterable in the dashboard.
     */
    tags?: string[];
    /**
     * Serialize runs of this workflow that share a key — a durable FIFO mutex. e.g.
     * `singleton: { key: (input) => `base:${input.baseId}` }` runs at most one pipeline per base at a
     * time; same-key runs queue (suspended) and admit in creation order as slots free. `limit` (default
     * 1) raises the concurrency.
     */
    singleton?: SingletonConfig;
    /**
     * Max wall-clock lifetime for a run of this workflow (e.g. `'2h'`, `'7 days'`, or ms). A run that
     * outlives it is moved to `cancelled` (`execution_timeout`) by the timer poller — a backstop for
     * runs that get stuck or loop forever. Omit for no limit.
     */
    executionTimeout?: string | number;
    /**
     * Capabilities a live worker MUST advertise to run this workflow's turns (handshake design §7.5).
     * Only meaningful for a group-served / remote workflow (an in-app worker or a cross-runtime body):
     * the control-plane dispatches a turn only to a capability-capable + protocol-compatible worker, and
     * if descriptors are published on its group but none qualifies the run parks `blocked` until the
     * recovery poll finds one. Absent/empty = "runs anywhere" (a legacy fleet skips the guard, §7.7).
     */
    requires?: string[];
    /**
     * Validate the workflow input at `start` against a **class-validator DTO** (the same
     * `plainToInstance` + `validate` NestJS runs in controllers) — invalid input is rejected before any
     * run is created. Needs the optional peers `class-validator` + `class-transformer`.
     * `@Workflow({ inputSchema: CheckoutInput })`.
     */
    inputSchema?: new (...args: any[]) => object;
    /**
     * Custom input validator (throws on invalid) — an escape hatch for zod/yup/etc. instead of
     * `inputSchema`. Takes precedence over `inputSchema` if both are set.
     */
    validateInput?: (input: unknown) => void | Promise<void>;
    /**
     * Validate this workflow's {@link WorkflowCtx.upsertSearchAttributes} writes against a **Standard
     * Schema** (https://standardschema.dev — zod 3.24+, valibot, arktype, …). `ctx.upsertSearchAttributes`
     * validates the MERGED result (existing attributes shallow-merged with the patch) on every call —
     * an invalid merge throws, naming this workflow, the offending key(s), and the schema's issues. The
     * schema's inferred output must be search-attribute-shaped (flat `string`/`number`/`boolean` values
     * only) — a schema whose output has a nested object/array is a compile-time error here. Omit for
     * the prior unvalidated behavior.
     *
     * ```ts
     * import { z } from 'zod';
     *
     * const orderAttrs = z.object({
     *   tier: z.enum(['free', 'pro']),
     *   amount: z.number(),
     * });
     *
     * @Workflow({ name: 'checkout', searchAttributes: orderAttrs })
     * class CheckoutWorkflow {
     *   async run(ctx: WorkflowCtx<InferSearchAttributes<typeof orderAttrs>>, input: CheckoutInput) {
     *     await ctx.upsertSearchAttributes({ tier: input.tier, amount: input.total });
     *   }
     * }
     * ```
     */
    searchAttributes?: SearchAttributesSchema;
    /**
     * Start a fresh run of this workflow whenever any of these events is published via
     * `publishEvent(name, payload)` — the payload becomes the run's input. e.g.
     * `onEvent: ['user.registered', 'user.invited']`. The same subscription can also be declared with
     * the `@OnDurableEvent(...)` class decorator; the two are merged.
     */
    onEvent?: string[];
    /**
     * Coalesce `onEvent` triggers by **debouncing** — start one run only once events have been quiet
     * for this long (resets on each event), with the LAST payload. e.g. `debounce: '30s'`.
     */
    debounce?: string | number;
    /**
     * Coalesce `onEvent` triggers by **batching** — start one run with all payloads (`{ events: [...] }`)
     * once `maxSize` is reached or `within` elapses from the first event. e.g.
     * `batch: { maxSize: 100, within: '10s' }`.
     */
    batch?: {
        maxSize: number;
        within: string | number;
    };
}
/**
 * Marks a provider class as a durable workflow. Its `run(ctx, input)` method becomes the
 * workflow function the engine executes and replays.
 */
declare function Workflow(options: WorkflowOptions): ClassDecorator;
declare function getWorkflowMeta(target: Function): WorkflowMeta | undefined;
declare const DURABLE_STEP_METADATA: unique symbol;
interface DurableStepMeta {
    /** The resolved routing name — derived (`Class.method`) or explicit, always present. */
    name: string;
    /** Opt-in runtime input schema, validated when the handler is served (see `scanSteps`). Absent on
     *  a bare `@Step()` — compile-time types from the method signature only, no runtime check. */
    input?: z.ZodType | undefined;
    /** Opt-in runtime output schema, validated before the handler's result is handed back. */
    output?: z.ZodType | undefined;
}
/**
 * `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,
 * timeoutMs? })` — the object call form. See {@link Step}.
 */
interface StepOptions {
    /** Explicit routing name, overriding the derived `Class.method`. */
    name?: string;
    /** Runtime input schema, validated at the serve boundary (opt-in — a bare `@Step()` skips it). */
    input?: z.ZodType;
    /** Runtime output schema, validated before the result is returned (opt-in). */
    output?: z.ZodType;
    /**
     * Def-level durable-dispatch policy stamped under `DURABLE_STEP_CONFIG` and read by `ctx.step` at
     * the dispatch boundary (see `stepConfigOf`) — a per-call `ctx.step(ref, input, opts)` overrides
     * these field-by-field. Max attempts before the step (and run) fails.
     */
    retries?: number;
    /** How the delay between retries grows: `fixed` (constant) or `exp` (doubles each attempt). */
    backoff?: 'fixed' | 'exp';
    /** Base delay in ms between retries. Omit (or 0) to retry with no delay. */
    backoffMs?: number;
    /** Upper bound on the (exponential) backoff delay. */
    backoffMaxMs?: number;
    /** Add random jitter (50–100% of the computed delay) to avoid thundering-herd retries. */
    jitter?: boolean;
    /**
     * Liveness window for a dispatched step: no result/heartbeat within this many ms presumes the
     * worker dead and fails the dispatch with a `RemoteStepTimeout` (retryable — re-dispatches per
     * `retries`). Omit to wait indefinitely.
     */
    timeoutMs?: number;
    /**
     * Capabilities a live worker MUST advertise to run this step (handshake design §7.5). The
     * control-plane routes the step only to workers whose descriptor advertises every name here; if
     * descriptors are published on the step's group but none is capability-capable + protocol-compatible,
     * the run parks `blocked` (never a silent hang) until the recovery poll finds one. Absent/empty =
     * "runs anywhere" (the default — a legacy fleet publishing no descriptors skips the guard, §7.7). A
     * per-call `ctx.step(ref, input, { requires })` overrides this.
     */
    requires?: string[];
}
/**
 * Marks a provider method as a durable step handler. An in-process transport (e.g. the
 * event-emitter transport) or a co-located/thin worker runtime routes a dispatched task to it BY
 * NAME — `ctx.step(this.svc.method, input)` reads that same name off the method reference (stamped
 * via the shared, cross-package `DURABLE_STEP_NAME` symbol from `@dudousxd/nestjs-durable-core`), so
 * there's no separately-declared def linking a call site to this handler. The method's single
 * argument is the step input (plus an optional `StepLogger` second arg); its return value is the
 * step output.
 *
 * Three call forms:
 * - `@Step()` — bare: the routing name is DERIVED from the method as `` `${ClassName}.${method}` ``
 *   (e.g. `ExtractionService.runExtractionPage`) — refactor-safe, no magic string.
 * - `@Step('custom:name')` — explicit name override (stable across refactors, or a cross-runtime
 *   contract with a non-JS worker that has no `@Step` of its own).
 * - `@Step({ name?, input?, output?, retries?, backoff?, backoffMs?, backoffMaxMs?, jitter?,
 *   timeoutMs? })` — optional name override, opt-in RUNTIME zod schemas, and a def-level
 *   durable-retry/liveness-timeout policy. `input` validates before the method runs; `output`
 *   validates its return value before it's handed back (see `scanSteps`). `retries`/`backoff`/
 *   `backoffMs`/`backoffMaxMs`/`jitter`/`timeoutMs` are the policy `ctx.step` reads off this method
 *   reference (via `stepConfigOf`) to build the dispatched `StepDef`'s durable retry/backoff and
 *   remote-liveness timeout — a per-call `ctx.step(ref, input, opts)` overrides them field-by-field.
 *   A bare `@Step()` carries none of this and skips validation/retry/timeout — compile-time types
 *   from the method signature are the only check, and the step dispatches with no retry/timeout.
 */
declare function Step(nameOrOptions?: string | StepOptions): MethodDecorator;
/**
 * @deprecated Use `@Step` instead. `@DurableStep` is a back-compat alias of {@link Step} and
 * writes the same `DURABLE_STEP_METADATA`, so discovery/registrars treat them identically.
 */
declare const DurableStep: typeof Step;
declare function getDurableStepMeta(method: Function): DurableStepMeta | undefined;
declare const DEAD_LETTER_METADATA: unique symbol;
/**
 * The payload a dead-letter handler receives: the dead run's id, its workflow, the input it was
 * started with (typed via `TInput`), and the failure that killed it.
 */
interface DeadLetter<TInput = unknown> {
    /** Id of the run that was dead-lettered (inspectable + retriable in the dashboard). */
    deadRunId: string;
    /** Name of the workflow whose run died. */
    workflow: string;
    /** The original input the dead run was started with. */
    input: TInput;
    /** The structured error that moved the run to `dead`, when known. */
    error?: StepError;
}
/**
 * Marks a method on a `@Workflow` class as that workflow's **inline dead-letter handler**. When a
 * run of the workflow is moved to `dead` (exceeded `maxRecoveryAttempts`), this method runs — as a
 * durable workflow itself, auto-registered as `<workflow>.dlq` with a `dlq:<runId>` id — receiving a
 * {@link DeadLetter} payload. It shares the class's injected dependencies, so the handler lives in
 * the same file as the workflow it protects.
 *
 * Takes precedence over `@Workflow({ deadLetterWorkflow })` and the module-level `deadLetterWorkflow`
 * default. The method signature is `(ctx, dead)` — the same `ctx` a workflow `run` gets.
 */
declare function DeadLetter(): MethodDecorator;
declare function isDeadLetterHandler(method: Function): boolean;
declare const ON_EVENT_METADATA: unique symbol;
/**
 * Subscribe a `@Workflow` class to one or more events: when any of them is published via
 * `publishEvent(name, payload)`, a fresh run of the workflow starts with the payload as input.
 * Equivalent to `@Workflow({ onEvent })` — use whichever reads better; multiple
 * `@OnDurableEvent(...)` decorators and the option are all merged.
 *
 * Named "durable" on purpose: `@nestjs/event-emitter` exports an `@OnEvent` decorator, and in an
 * app using both libs an auto-import picking the wrong one fails silently in either direction.
 */
declare function OnDurableEvent(...events: string[]): ClassDecorator;
/** @deprecated Renamed to `OnDurableEvent` — this alias clashes with `@nestjs/event-emitter`'s `@OnEvent` and will be removed in the next minor. */
declare const OnEvent: typeof OnDurableEvent;
/** All events a workflow class subscribes to — the union of `@Workflow({ onEvent })` and `@OnDurableEvent`. */
declare function getOnEvents(meta: WorkflowMeta, target: object): string[];

/**
 * Retention config for the {@link RetentionPoller}. One or more {@link RetentionPolicy policies}
 * (status sets must be disjoint — validated at boot), swept together on a shared interval.
 *
 * ```ts
 * retention: {
 *   sweepInterval: '1m',
 *   batchSize: 1_000,
 *   policies: [
 *     { statuses: ['completed', 'cancelled'], maxAge: '14d', maxCount: 200 },
 *     { statuses: ['failed'], maxAge: '90d' }, // keep failures longer for debugging
 *   ],
 * }
 * ```
 */
interface DurableRetentionOptions {
    /** The retention rules, one per (disjoint) status group. */
    policies: RetentionPolicy[];
    /**
     * How often to run the prune sweep. A number is milliseconds; a string is an `ms`-style duration
     * (`'1m'`, `'5m'` — `'m'` is minutes). `0` runs it once on boot only. Defaults to 60000 (1 minute).
     */
    sweepInterval?: number | string;
    /** Max runs hard-deleted per batch (per policy, looped until drained). Defaults to 1000. */
    batchSize?: number;
}
/**
 * Options for `DurableModule.forRoot`/`forRootAsync`. The **role is inferred** from which of `store`/
 * `connection` are set:
 *
 * - `{ store, transport }` — **operator**: a real `WorkflowEngine` + `StoreRunGateway` + drivers/
 *   timer/retention/registrars, executing registered bodies INLINE. Driven by {@link drive} (default
 *   `true`).
 * - `{ connection }` (no `store`) — **thin worker**: `WorkflowEngine` resolves to a store-less
 *   `DurableStartClient`; `RUN_GATEWAY` is a `ProxyRunGateway` when `transport` is also given, else
 *   every method rejects with a clear error. No store/timer/retention/entity — just discovered
 *   `@Workflow`/`@Step` handlers served over ONE `runRedisWorker` consumer.
 * - `{ store, transport, connection }` — an **operator that also runs a co-located worker**: every
 *   `@Workflow` is registered GROUP-SERVED (dispatched over the transport, PER WORKFLOW NAME, instead
 *   of inline) and a co-located consumer (one `runRedisWorker` call) replays the same bodies.
 *
 * `forRoot` throws when neither `store` nor `connection` is set, and when `store` is set without a
 * `transport`.
 *
 * Set {@link DurableModuleOptions.topology} to name the role explicitly instead of relying on this
 * inference — it additionally VALIDATES the axes above (`namespace` vs `partition`) that this prose
 * is otherwise the only source of truth for. Omitting it changes nothing.
 */
interface DurableModuleOptions {
    /** State store — set this to play the **operator** role (see the interface doc for the full role
     *  matrix). Omit for a store-less **thin worker** (`connection` only). */
    store?: StateStore;
    transport?: Transport;
    /**
     * An ordered pool of named transports for failover / multi-broker setups. The engine dispatches on
     * the first and fails over to the next; a step pins one via `ctx.step(handler, input, { transport })`.
     * Use instead of `transport`.
     */
    transports?: NamedTransport[];
    /**
     * Cross-instance broadcast pub/sub (lifecycle events + cancellation). Defaults to the (first)
     * transport when it can broadcast (event-emitter, BullMQ); set explicitly to use a dedicated one.
     */
    controlPlane?: ControlPlane;
    /** Interval (ms) for the durable-timer poller. `0` disables it. Defaults to 1000. Operator only. */
    timerPollMs?: number;
    /**
     * Auto-create the durable tables on boot via `store.ensureSchema()`. Defaults to true. Turn
     * off in production and call the store adapter's `ensure*DurableSchema()` from a migration.
     * Operator only.
     */
    autoSchema?: boolean;
    /**
     * Worker-pool namespace for this instance (forwarded to the engine). The poll paths
     * (`runPending`/`recoverIncomplete`/`resumeDueTimers`/`sweepTimeouts`) only act on runs in this
     * namespace. Set distinct values to safely share ONE state store across non-interchangeable
     * pools — e.g. a developer's local instance vs the deployed cluster. **Omit it to make this
     * instance an OPERATOR** — an unset namespace drives/recovers/resumes runs of EVERY namespace and
     * leaves the transport on its bare prefix. See `WorkflowEngineDeps.namespace`. Not to be confused
     * with {@link partition} (the co-located/thin-worker QUEUE routing suffix).
     */
    namespace?: string;
    /**
     * Multi-instance recovery lease, in ms — how long an instance owns a run it picked up before
     * another may take over. Defaults to 30000. Set above your longest synchronous run. Operator only.
     */
    leaseMs?: number;
    /** Unique id for this instance (for leases, and the co-located/thin-worker consumer's heartbeats). */
    instanceId?: string;
    /**
     * Cap recovery attempts before a still-`running` run is moved to the `dead` dead-letter state
     * (a poison pill that crashes the process every boot). Omit for unlimited. Operator only.
     */
    maxRecoveryAttempts?: number;
    /**
     * Opt-in liveness deadline (ms) for a remote (polyglot) workflow `advance`. If the worker neither
     * returns a decision nor sends a run-scoped heartbeat within this window, the engine presumes it dead
     * and lets recovery re-drive; each heartbeat rearms the window so a slow-but-alive worker is never
     * re-driven. Pair with a worker SDK that emits run-scoped heartbeats (`@dudousxd/durable-worker` ≥ the
     * release that ships them, and the Python `durable-worker`). Omit for the prior unbounded await.
     * Operator only.
     */
    remoteAdvanceSilenceMs?: number;
    /**
     * The **default** workflow to route dead-lettered runs to, for workflows that don't declare their
     * own. When a run is moved to `dead` (exceeded `maxRecoveryAttempts`), the started handler gets a
     * `DeadLetter` payload `{ deadRunId, workflow, input, error }` (idempotent by a `dlq:<runId>` id) —
     * it can alert, compensate, or queue for review. Resolution per dead run: the workflow's inline
     * `@DeadLetter()` method → its `@Workflow({ deadLetterWorkflow })` reference → this default. Omit
     * everything to just leave dead runs parked (inspectable + retriable from the dashboard). Accepts a
     * workflow class (refactor-safe) or a name (cross-runtime). Operator only.
     */
    deadLetterWorkflow?: WorkflowRef;
    /**
     * Whether an **operator** instance actively DRIVES runs — polls pending, recovers crashed
     * (`recoverIncomplete`), resumes due timers, sweeps timeouts, prunes retention, and consumes local
     * steps — as opposed to a read-only/dashboard replica. Defaults to `true`. Set `false` for a
     * **dashboard/dispatch-only** instance (e.g. an API pod) that mounts the store/dashboard but must
     * not process or recover workflows — leave that to another driving instance. `drive: false` also
     * installs the engine's no-op run dispatcher, so a freshly `start()`ed run stays enqueue-only until
     * a driving instance's poll picks it up. Ignored (irrelevant) for a thin worker (no `store`).
     */
    drive?: boolean;
    /** Max ms to wait for in-flight runs on shutdown before exiting. Defaults to 10000. Operator only. */
    shutdownTimeoutMs?: number;
    /**
     * Recurring workflows to start on a schedule (fixed interval or cron). The timer poller fires
     * them each tick on **driving** instances only; `engine.start` is idempotent by the schedule's
     * time-bucket run id, so racing instances start each window exactly once. Cron schedules need the
     * optional `cron-parser` peer dependency. Operator only.
     */
    schedules?: ScheduledWorkflow[];
    /**
     * Hard-prune terminal run history on an interval so `durable_workflow_runs` (and its child tables)
     * stays bounded — without it, completed/failed/cancelled runs accumulate forever and the timer
     * poller's per-tick status scans get linearly slower. Driving instances only. Omit to keep all
     * history (the default). Requires a store adapter that implements `pruneTerminalRuns` (the
     * MikroORM adapter does); other adapters no-op with a warning. See {@link DurableRetentionOptions}.
     * Operator only.
     */
    retention?: DurableRetentionOptions;
    /**
     * Build the public callback URL for a `ctx.webhook()` token, e.g.
     * ``(t) => `https://api.example.com/durable/api/webhooks/${t}` ``. Populates `DurableWebhook.url`
     * so a step can hand the URL to a third party. The dashboard's `POST webhooks/:token` receives the
     * callback. Omit to build URLs yourself from the token. Operator only.
     */
    webhookUrl?: (token: string) => string;
    /**
     * Flow-control queues for remote steps, registered on the engine at startup. Reference one from a
     * workflow with `ctx.step(handler, input, { queue: name })` to cap its concurrency / admission rate.
     * Operator only.
     */
    queues?: QueueConfig[];
    /**
     * Admission backend for the flow-control `queues`. Defaults to in-process (per-instance) caps. Pass
     * a `RedisAdmissionBackend` (from `@dudousxd/nestjs-durable-admission-redis`) to make concurrency /
     * rate-limit / priority ordering GLOBAL across every engine replica. Operator only.
     */
    admission?: AdmissionBackend;
    /**
     * Provide the current W3C `traceparent` to stamp on dispatched remote tasks, so workers continue
     * the distributed trace. Pass `otelTraceparent` from `@dudousxd/nestjs-durable-otel`. Operator only.
     */
    traceparent?: () => string | undefined;
    /**
     * Provide an opaque context carrier (tenant / user / correlation ids) to stamp on dispatched remote
     * tasks, so workers re-expose it to the step handler alongside the `traceparent`. The engine never
     * inspects its shape.
     *
     * **Auto-feed**: if you omit this AND `@dudousxd/nestjs-context` is installed (its accessor is bound
     * to the shared `CONTEXT_ACCESSOR` token), DurableModule defaults this to a reader that builds
     * `{ traceId, tenantId, userRef }` from the accessor — so a workflow dispatched within a request
     * automatically carries the originating context across process boundaries. Pass your own reader to
     * override the auto-feed; with neither, the carrier is omitted (unchanged behavior).
     *
     * Re-evaluated at each (re)dispatch — including a retry or a crash/scale-down resume that the engine
     * drives OUTSIDE the originating request scope, where this reader may return empty or stale values.
     * Treat the carrier as best-effort correlation/propagation metadata only — do NOT treat it as an
     * authorization boundary. Operator only.
     */
    context?: () => Record<string, unknown> | undefined;
    /** Attempts for each saga compensation when a run fails. Default 1 (no retry). Idempotent undos.
     *  Operator only. */
    compensationRetries?: number;
    /**
     * Opt into tenant read scoping: when `true` AND {@link namespace} is set, the module confines the
     * store's reads to that namespace (a tenant-boundary view) instead of the operator view that sees
     * all namespaces. Default `false` — the control plane (e.g. flip's `/ctrl` operator screens) stays
     * unscoped. Requires a store that exposes the `withScope` capability (the MikroORM adapter does);
     * a pre-built store without it is used as-is (construct it already-scoped instead). Operator only.
     */
    scopeReads?: boolean;
    /**
     * ioredis connection (string or options) for a **thin worker** (`connection` only) or the
     * **co-located worker** consumer (`store` + `connection`). Set this to play a worker role (see the
     * interface doc for the full role matrix). Omit to keep every `@Workflow` on the operator's inline
     * fast path with zero dispatch round-trips.
     */
    connection?: string | Record<string, unknown>;
    /**
     * The isolation partition a worker role serves — a thin worker's or co-located worker's queue
     * subscription, AND (for a co-located worker) the suffix each `@Workflow`'s dispatch token carries.
     * Each handler's queue token is `tenantGroup(sanitizeQueueToken(name), partition)`
     * (`@dudousxd/nestjs-durable-core`), so `undefined`, `''`, or `'default'` stays byte-identical to
     * the bare (sanitized) name (single-tenant deployment unchanged), and any other partition serves
     * `<name>@<partition>` — matching the queue name an operator's convention dispatch routes that
     * tenant's runs to (`tenantGroup(run.workflow, run.namespace)` on the engine side). Not to be
     * confused with {@link namespace} (the operator's own poll-scoping axis). Ignored for a plain
     * operator (no `connection`).
     */
    partition?: string;
    /** Key prefix namespacing the durable queues for a worker role's consumer. Defaults to `durable`
     *  (matches the transport). Ignored for a plain operator (no `connection`). */
    prefix?: string;
    /**
     * How many tasks a worker role's co-located/thin consumer runs concurrently PER SUBSCRIBED QUEUE
     * (BullMQ Worker concurrency — the same limit is applied to every per-name queue the single
     * `runRedisWorker` call starts). Defaults to 1. Raise it so a fanned-out batch (e.g. the N remote
     * steps of a `gather`) runs in parallel instead of serially.
     *
     * Pass `'adaptive'` (or `{ mode:'adaptive', ... }`) to let the consumer self-tune its concurrency
     * (latency gradient + RAM brake + backpressure) and publish a live status on its heartbeat. Ignored
     * for a plain operator (no `connection`).
     */
    concurrency?: ConcurrencyOption;
    /**
     * Per-handler concurrency override, keyed by workflow/step NAME. Falls back to {@link concurrency}
     * (then 1). NOT YET WIRED THROUGH: `runRedisWorker` (`@dudousxd/durable-worker`) currently applies
     * one {@link concurrency} limit uniformly across every per-name queue it subscribes to from a
     * single call — reserved here for when per-name concurrency lands there.
     */
    concurrencyByHandler?: Record<string, ConcurrencyOption>;
    /** Timeout for a thin worker's `RunGateway` round-trip over `transport` before it rejects. Defaults
     *  to 10_000ms inside `ProxyRunGateway`. Ignored for an operator (bound to `StoreRunGateway`). */
    runGatewayTimeoutMs?: number;
    /**
     * An explicit preset that NAMES the deployment role instead of leaving it to `store`/`connection`
     * inference, and VALIDATES the axes that are otherwise only prose (see the interface doc's role
     * matrix, and {@link namespace} vs {@link partition}). Omit to keep the existing inference —
     * `topology` is entirely additive and changes nothing when unset.
     *
     * **`namespace` vs `partition` — the two axes this preset locks down:**
     * - `namespace` is the OPERATOR's poll-scoping axis: which runs a control-plane instance
     *   drives/recovers/resumes (`runPending`/`recoverIncomplete`/`resumeDueTimers`/`sweepTimeouts`).
     *   Unset makes the instance see (drive) EVERY namespace.
     *   `{ role: 'control-plane' }` is the only preset that allows it.
     * - `partition` is the WORKER's queue-routing suffix: which queue a thin/co-located worker's
     *   consumer subscribes to (`<name>@<partition>`), matching the queue an operator's convention
     *   dispatch routes a run's `namespace` to. `{ role: 'tenant' }` sets this FOR you from `tenant` —
     *   you never set `partition` directly under `topology`.
     *
     * **One `tenant` word, two axes — the preset maps it to the right one per role:** on a
     * `control-plane` an optional `tenant` scopes the OPERATOR to its own runs (maps to `namespace` —
     * a self-contained local stack sharing a broker with a deployed cluster names itself so neither
     * drives the other's runs; leave it unset on a deployed operator to drive every tenant). On a
     * `tenant` role it is required and maps to `partition` (the worker's queue suffix).
     *
     * ```ts
     * // Control plane: owns the store, dispatches over the transport, prunes old runs. The optional
     * // `tenant` (e.g. from an env var) scopes this operator to its own runs — undefined on a
     * // deployed operator (drives everything), set on a local stack sharing the broker.
     * DurableModule.forRoot({
     *   topology: { role: 'control-plane', tenant: process.env.DURABLE_TENANT },
     *   store,
     *   transport,
     *   retention: { policies: [{ statuses: ['completed', 'cancelled'], maxAge: '14d' }] },
     * });
     *
     * // Tenant: store-less worker scoped to its own partition — `tenant` maps to `partition` for you.
     * DurableModule.forRoot({
     *   topology: { role: 'tenant', tenant: 'acme-corp' },
     *   connection: process.env.REDIS_URL,
     * });
     * ```
     *
     * Validated at `forRoot`/`forRootAsync` resolution time — see {@link DurableModule} for the exact
     * error messages, which double as the axis primer above.
     */
    topology?: {
        role: 'control-plane';
        tenant?: string;
    } | {
        role: 'tenant';
        tenant: string;
    };
}
interface DurableModuleAsyncOptions {
    useFactory: (...args: never[]) => DurableModuleOptions | Promise<DurableModuleOptions>;
    inject?: InjectionToken[];
}
declare class DurableModule {
    static forRoot(options: DurableModuleOptions): DynamicModule;
    static forRootAsync(options: DurableModuleAsyncOptions): DynamicModule;
    /**
     * Validates role/axis constraints, in resolution order. When {@link DurableModuleOptions.topology}
     * is set, it OWNS validation — {@link assertValidTopology}'s own store/transport/connection checks
     * are strictly stronger than {@link assertValidRole}'s, so the topology-specific, axis-teaching
     * message is what a `topology`-opted-in consumer sees (not the older generic one). Falls back to
     * {@link assertValidRole} unchanged when `topology` is absent — zero behavior change.
     */
    private static assertValid;
    private static build;
}

/**
 * The **store-less `engine.start` facade** for a tenant worker. Provided under the `WorkflowEngine`
 * DI token by {@link import('./durable.module').DurableModule} (`forRoot({ connection })`, no
 * `store`), so tenant code calls `engine.start(...)` UNCHANGED — it has no idea it is a tenant.
 * Instead of touching a DB, `start` publishes a `StartRunMessage` on the SHARED `durable-start-run`
 * queue (Option B: tenant rides as message DATA, never as wire segmentation). The operator (control
 * plane, `namespace: undefined`) consumes it, stamps the run's namespace from `tenant`, and routes
 * the run's task to `<workflow>@<tenant>` — the partition THIS tenant's worker serves.
 *
 * `cancel`/`deleteRun` need the store/driver a tenant does not have; they throw. No wire message
 * exists for them (the operator owns cancellation/retention).
 */
declare class DurableStartClient {
    private readonly options;
    private readonly deps?;
    private readonly tenant;
    constructor(options: DurableModuleOptions, deps?: StartRunDeps | undefined);
    start<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, runId?: string, opts?: StartOptions): Promise<RunResult>;
    start(workflow: string, input: unknown, runId?: string, opts?: StartOptions): Promise<RunResult>;
    cancel(_runId: string): Promise<void>;
    deleteRun(_runId: string): Promise<void>;
    resume(_runId: string): Promise<void>;
    waitForRun(_runId: string, _opts?: {
        timeoutMs?: number;
    }): Promise<void>;
    signal(_token: string, _payload: unknown): Promise<void>;
    signalWithStart(_workflow: string, _input: unknown, _runId: string, _signal: {
        token: string;
        payload?: unknown;
    }, _opts?: StartOptions): Promise<void>;
    publishEvent(_name: string, _payload: unknown, _opts?: {
        id?: string;
        buffer?: boolean;
    }): Promise<void>;
}

/**
 * The `runRedisWorker` function the module uses to start each partition's BullMQ consumer.
 * Defaults to the real one from `@dudousxd/durable-worker`; tests `overrideProvider` it with a
 * fake so no real Redis is needed.
 */
declare const RUN_REDIS_WORKER: unique symbol;
/** The list of started {@link RunningWorker} handles (the single handle {@link ThinWorkerBootstrap}
 *  starts), closed on shutdown. */
declare const DURABLE_WORKER_RUNNERS: unique symbol;
/** The signature of `runRedisWorker` — injected behind {@link RUN_REDIS_WORKER}. */
type RunRedisWorkerFn = (opts: RunRedisWorkerOptions) => Promise<RunningWorker>;
/**
 * Discovers every provider carrying `@Workflow` metadata and registers its `run(ctx, input)` on the
 * thin {@link DurableWorkerRuntime}. Mirrors the engine-side `WorkflowRegistrar`, but registers on
 * the runner-core runtime instead of a `WorkflowEngine` — the runtime drives `run` with the thin
 * `WorkflowContext` (which `implements WorkflowCtx`), so the body runs unchanged. NO store/engine.
 * Inert (skips registration) outside the pure thin-worker role — see {@link isPureThinWorker}.
 */
declare class ThinWorkflowRegistrar implements OnModuleInit {
    private readonly discovery;
    private readonly runtime;
    private readonly options;
    constructor(discovery: DiscoveryService, runtime: DurableWorkerRuntime, options: DurableModuleOptions);
    onModuleInit(): void;
}
/**
 * Discovers every `@Step` method and registers it as a step handler on the thin
 * {@link DurableWorkerRuntime}. Mirrors the engine-side `DurableStepRegistrar`, but always registers
 * on the runtime (a thin worker IS the consumer — there is no in-process-vs-queue branch). Inert
 * outside the pure thin-worker role — see {@link isPureThinWorker}.
 */
declare class ThinStepRegistrar implements OnModuleInit {
    private readonly discovery;
    private readonly metadataScanner;
    private readonly runtime;
    private readonly options;
    constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, runtime: DurableWorkerRuntime, options: DurableModuleOptions);
    onModuleInit(): void;
}
/**
 * Starts ONE `runRedisWorker` call on bootstrap — after both registrars have run, so every handler
 * is registered before any task is consumed — and closes it on shutdown. The runner (`@dudousxd/
 * durable-worker`) derives its subscription from `runtime.registeredNames()` (one queue per
 * registered `@Workflow`/`@Step` name), not a hand-declared group list. Inert outside the pure
 * thin-worker role — see {@link isPureThinWorker}: an operator (`store` set) never starts this
 * consumer, and `store + connection` starts its OWN co-located consumer instead (`in-app-worker.ts`).
 *
 * For the shutdown close to fire, enable Nest's shutdown hooks: `app.enableShutdownHooks()`.
 */
declare class ThinWorkerBootstrap implements OnApplicationBootstrap, OnApplicationShutdown {
    private readonly runtime;
    private readonly options;
    private readonly runRedisWorker;
    private readonly runnersSink;
    private readonly runners;
    constructor(runtime: DurableWorkerRuntime, options: DurableModuleOptions, runRedisWorker: RunRedisWorkerFn, runnersSink: RunningWorker[]);
    onApplicationBootstrap(): Promise<void>;
    onApplicationShutdown(): Promise<void>;
}
/**
 * The thin-worker slice of {@link import('./durable.module').DurableModule}'s unified provider set —
 * active for the pure thin-worker role (`connection` set, `store` unset): registers discovered
 * `@Workflow`/`@Step` on a store-less {@link DurableWorkerRuntime} and starts ONE `runRedisWorker`
 * consumer, one queue PER REGISTERED NAME. Always present in the provider list (so a single
 * `DurableModule.forRootAsync` can serve any role once its options resolve); every class/factory here
 * is inert (see {@link isPureThinWorker}) outside that role.
 */
declare function thinWorkerProviders(): Provider[];
/**
 * The no-transport fallback bound to `RUN_GATEWAY` for a thin worker (`connection` set, `store`
 * unset) that didn't also pass a `transport` — mirrors `DurableStartClient`'s tenant-error idiom
 * (`tenantUnsupported`): every method rejects with a clear, named error instead of a cryptic
 * `this.gateway.X is not a function`. `subscribe` is synchronous on the `RunGateway` port, so it
 * throws synchronously (same message) rather than returning a rejected promise.
 */
declare function unavailableRunGateway(): RunGateway;

declare const ENTITY_METADATA: unique symbol;
declare const ENTITY_ON_METADATA: unique symbol;
/**
 * Marks an `@Injectable()` class as a **durable entity** (a virtual object): its `@On(op)` methods run
 * serialized per key over the instance's fields as durable state. e.g.
 *
 * ```ts
 * @Entity({ name: 'cart' }) @Injectable()
 * class Cart { items: Item[] = []; @On('add') add(i: Item) { this.items.push(i); } @On('list') list() { return this.items; } }
 * ```
 *
 * The class must be **constructible with no arguments** (a fresh instance is the initial state per key)
 * — keep entities pure state, no DI. Drive them with `EntityService` or `ctx.signalEntity`/`callEntity`.
 */
declare function Entity(options: {
    name: string;
}): ClassDecorator;
/** Marks an entity method as the handler for operation `op`. */
declare function On(op: string): MethodDecorator;
declare function getEntityMeta(target: Function): {
    name: string;
} | undefined;
/**
 * Build the engine `EntityConfig` for a discovered `@Entity` class: a fresh instance per key, and
 * handlers that rehydrate the class prototype onto the (serialized) state before dispatching the op,
 * so methods work after replay.
 */
declare function entityConfigFor(ctor: Function): {
    initialState: () => object;
    handlers: Record<string, EntityHandler>;
};
/** Inject this to drive durable entities from outside a workflow. */
declare class EntityService {
    private readonly engine;
    constructor(engine: WorkflowEngine);
    /** Send an operation to an entity (fire-and-forget; ordered + exactly-once per key). */
    signal(name: string, key: string, op: string, arg?: unknown): Promise<void>;
    /** Read an entity's current durable state (or undefined if it has none yet). */
    getState<S = unknown>(name: string, key: string): Promise<S | undefined>;
}

/**
 * The **co-located in-app worker** (uniform dispatch): active whenever an app supplies BOTH `store`
 * AND `connection` (`DurableModule.forRoot({ store, transport, connection, partition? })`) — the same
 * process runs the engine AND serves its own discovered `@Workflow`/`@Step`. The engine registers each
 * `@Workflow` GROUP-SERVED — its turns are dispatched, PER WORKFLOW NAME, to
 * `tenantGroup(sanitizeQueueToken(name), partition)` over the transport via a per-workflow
 * `RemoteWorkflowExecutor` instead of run inline — and a co-located {@link DurableWorkerRuntime}
 * subscribes one queue per discovered name (via `runRedisWorker`) and replays the very same TS bodies.
 * This is the uniform-dispatch "one app, both roles" shape — every turn pays a transport round-trip
 * even though the worker is the same process. Requires a transport that carries workflow tasks
 * (BullMQ); an in-process-only transport cannot dispatch a `WorkflowExecutor`.
 *
 * Distinct from the PURE thin-worker role (`connection` set, `store` unset — see
 * `durable-worker.module.ts`'s `ThinWorkflowRegistrar`/`ThinStepRegistrar`/`ThinWorkerBootstrap`),
 * which has no engine/store of its own at all.
 */
declare const IN_APP_WORKER_BINDING: unique symbol;
/** The co-located worker's {@link DurableWorkerRuntime} (the consumer half). */
declare const IN_APP_WORKER_RUNTIME: unique symbol;
/** `runRedisWorker`, injected so tests can substitute a fake (no real Redis). Defaults to the real one. */
declare const IN_APP_RUN_REDIS_WORKER: unique symbol;
/** Started {@link RunningWorker} handles for the in-app worker, closed on shutdown. */
declare const IN_APP_WORKER_RUNNERS: unique symbol;
/** The group-served binding shape resolved behind {@link IN_APP_WORKER_BINDING}. */
interface InAppWorkerBinding {
    transport: Transport;
    partition?: string;
}
/**
 * The consumer half of the in-app worker: on init it registers every discovered `@Workflow`/`@Step`
 * on a {@link DurableWorkerRuntime} (the SAME bodies the engine registered group-served), and on
 * bootstrap it starts one `runRedisWorker` call that subscribes one queue per discovered name,
 * suffixed by the configured partition, closing it on shutdown. A no-op outside the co-located role
 * (see {@link isCoLocatedWorker}). Mirrors the thin {@link
 * import('./durable-worker.module').ThinWorkerBootstrap}, but co-located with a full engine.
 */
declare class InAppWorkerBootstrap implements OnModuleInit, OnApplicationBootstrap, OnApplicationShutdown {
    private readonly discovery;
    private readonly metadataScanner;
    private readonly options;
    private readonly runtime;
    private readonly runRedisWorker;
    private readonly runnersSink;
    private readonly runners;
    constructor(discovery: DiscoveryService, metadataScanner: MetadataScanner, options: DurableModuleOptions, runtime: DurableWorkerRuntime, runRedisWorker: RunRedisWorkerFn, runnersSink: RunningWorker[]);
    onModuleInit(): void;
    onApplicationBootstrap(): Promise<void>;
    onApplicationShutdown(): Promise<void>;
}
/**
 * The providers that stand up the co-located in-app worker, added to {@link
 * import('./durable.module').DurableModule}'s unified provider set. All are inert outside the
 * co-located role (`store` + `connection` both set — see {@link isCoLocatedWorker}), so a plain
 * operator (`store` only) or pure thin worker (`connection` only) is unaffected.
 */
declare function inAppWorkerProviders(): Provider[];

/**
 * Tenant-side `RunGateway` — round-trips every verb as a `RunRequest`/`RunReply` pair over the
 * transport, correlated by a minted `requestId`, and bridges `subscribe` onto the transport's
 * per-tenant event stream. Bound under `RUN_GATEWAY` by `DurableModule`'s thin-worker role
 * (`connection` set, no `store`) when the app supplies a `transport` (see `unavailableRunGateway`
 * for the no-transport fallback). The counterpart to the operator-side `StoreRunGateway`: a tenant
 * worker never touches a store/driver directly, only this proxy.
 */
declare class ProxyRunGateway implements RunGateway {
    private readonly transport;
    private readonly tenant;
    private readonly timeoutMs;
    private readonly pending;
    constructor(transport: Transport, tenant: string, timeoutMs?: number);
    private handleReply;
    private request;
    topology(): DurableTopology;
    getRunDetail(runId: string): Promise<RunDetail | null>;
    /** The control plane's `StoreRunGateway` resolves each run's `waiting` descriptor; the reply carries
     *  it through as plain JSON, so a tenant's list rows name the wait too. */
    listRuns(query: RunQuery): Promise<RunListItem[]>;
    /** Round-trips to the operator, which scopes the result to this tenant's own `@<tenant>` groups. */
    workerHealth(): Promise<GroupHealth[]>;
    /** Bulk, one request for the whole id list (like `listRuns`, not one request per id). The operator
     *  filters the reply to runs this tenant actually owns (see `RunRequestResponder`). */
    waitingFor(runIds: string[]): Promise<Record<string, RunWaiting>>;
    cancel(runId: string, opts?: {
        compensate?: boolean;
    }): Promise<RunResult | null>;
    retry(runId: string): Promise<RunResult | null>;
    continue(runId: string): Promise<RunResult | null>;
    retryWithInput(runId: string, input: unknown): Promise<{
        runId: string;
    } | null>;
    redispatchPending(runId: string): Promise<(RunResult & {
        redispatched: number;
    }) | null>;
    subscribe(runId: string, onEvent: (event: EngineEvent) => void): () => void;
}

/**
 * True for the **operator** role — `store` is set (with or without a co-located `connection`). See
 * {@link DurableModuleOptions} for the full role matrix. Kept in its own module (rather than
 * `durable.module.ts`) so the poller/registrar classes can import it as a plain VALUE without a
 * circular value dependency on the module that also imports them as providers.
 */
declare function isOperatorRole(options: DurableModuleOptions): boolean;
/**
 * True when an operator instance actively DRIVES runs — polls pending, recovers crashed, resumes due
 * timers, sweeps timeouts, prunes retention, consumes local steps — as opposed to a read-only/
 * dashboard replica (`drive: false`) that mounts the store/dashboard but leaves driving to another
 * instance. Meaningless (`false`) outside the operator role. Defaults to `true` for an operator.
 */
declare function isDrivingOperator(options: DurableModuleOptions): boolean;

/** The narrow slice of `Transport` the responder needs — a tenant's read/control request in, a
 *  correlated reply out. Both are OPTIONAL on the full `Transport` interface (only broker
 *  transports carry the run-request/reply protocol); the caller capability-checks before wiring
 *  this up (see `durable.module.ts`). */
interface RunRequestTransport {
    onRunRequest(handler: (msg: RunRequest) => Promise<void>): void;
    publishRunReply(reply: RunReply): Promise<void>;
}
/**
 * Operator-side consumer of a tenant's {@link RunRequest}s: answers each one against a
 * `RunGateway`, enforcing the tenant boundary before touching the run. For every runId-bearing
 * verb it loads the run via `getRunDetail` FIRST and compares `run.namespace` to the requesting
 * `msg.tenant` — a mismatch short-circuits into a `cross-tenant` error reply WITHOUT calling the
 * verb, so a tenant can never read or act on another tenant's run. `listRuns` is scoped by
 * overwriting the query's `namespace` with the requester's tenant, ignoring whatever the client
 * sent. This is the security boundary of the tenant run gateway — do not weaken it.
 */
declare class RunRequestResponder {
    private readonly transport;
    private readonly gateway;
    constructor(transport: RunRequestTransport, gateway: RunGateway);
    /** Register the consumer on the transport. Each request is answered independently; a handler
     *  failure never throws back into the transport (errors are captured into an error reply). */
    start(): void;
    private handle;
    private callVerb;
}

declare const STEP_INTERCEPTOR_METADATA: unique symbol;
/**
 * The shape a `@StepInterceptor()` provider must implement: `intercept(invocation, next)` wraps the
 * real execution of every local `ctx.step` (call `next()` to run the step body / next interceptor,
 * and return — or transform — its result). The engine-level {@link StepInterceptor} primitive, with
 * NestJS dependency injection.
 */
interface DurableStepInterceptor {
    intercept(invocation: StepInvocation, next: () => Promise<unknown>): Promise<unknown>;
}
/**
 * Marks an `@Injectable()` class as a durable step interceptor. The module discovers it on boot and
 * registers its `intercept` method with the engine (so it can inject loggers/tracers/etc.). First
 * declared is outermost. Interceptors fire only when a step actually executes, never on replay.
 */
declare function StepInterceptor(): ClassDecorator;
declare function isStepInterceptor(target: Function): boolean;

/**
 * Store-backed `RunGateway` — the operator-side implementation, bound to {@link RUN_GATEWAY} on
 * worker/drive instances. Reuses `DashboardService`'s six read/control method bodies verbatim
 * (`dashboard.service.ts:76-172`), so a consumer that only needs the bounded `RunGateway` surface
 * (the `RunRequestResponder`, a thin controller) doesn't have to depend on the dashboard package.
 */
declare class StoreRunGateway implements RunGateway {
    private readonly store;
    private readonly engine;
    constructor(store: StateStore, engine: WorkflowEngine);
    topology(): DurableTopology;
    getRunDetail(runId: string): Promise<RunDetail | null>;
    listRuns(query: RunQuery): Promise<RunListItem[]>;
    /**
     * Bulk-resolve what each of `runIds` is currently parked on — for a consumer with its own filtered/
     * paginated run listing (e.g. "which of MY suspended runs are stuck at a breakpoint") without
     * re-deriving `listRuns`' waiter scan or querying `durable_step_checkpoints` directly. Mirrors
     * `listRuns`' waiting computation (the SAME bulk signal-waiter scan + `resolveRunWaiting`), but ALSO
     * bulk-fetches the currently-suspended runs to check real status: `engine.cancel` (the non-compensate
     * path) doesn't clear a run's signal waiter row, so a cancelled run can leave an ORPHANED waiter
     * behind — trusting waiter presence alone would wrongly report a terminal run as still waiting.
     * Two bulk scans total (never one query per requested id), same as `listRuns`.
     */
    waitingFor(runIds: string[]): Promise<Record<string, RunWaiting>>;
    /** Every group the engine knows about — unscoped. A tenant proxy's request is scoped by the
     *  `RunRequestResponder` (to the requester's `@<tenant>` groups); the operator's own UI sees all. */
    workerHealth(): Promise<GroupHealth[]>;
    cancel(runId: string, opts?: {
        compensate?: boolean;
    }): Promise<RunResult | null>;
    /** Re-enqueue (dispatch model) instead of resuming inline — a worker picks the run up and replays it. */
    retry(runId: string): Promise<RunResult | null>;
    continue(runId: string): Promise<RunResult | null>;
    retryWithInput(runId: string, input: unknown): Promise<{
        runId: string;
    } | null>;
    redispatchPending(runId: string): Promise<(RunResult & {
        redispatched: number;
    }) | null>;
    subscribe(runId: string, onEvent: (event: EngineEvent) => void): () => void;
}

/** The narrow slice of `StateStore` the republisher needs — the `step.*`-event namespace fallback
 *  (see {@link TenantEventRepublisher.namespaceFor}). Mirrors the `RunRequestTransport` narrowing
 *  convention (`run-request-responder.ts`): depend on the smallest surface, not the whole store. */
type RunLookupStore = Pick<StateStore, 'getRun'>;
/**
 * Re-publishes engine lifecycle events onto a run's per-tenant channel via `publish` (bound from
 * `Transport.publishTenantEvent`), so a store-less tenant worker can live-tail its OWN runs.
 *
 * The run's namespace is read straight off `event.namespace` (stamped by `engine.ts`'s `emit()` on
 * every `run.*` lifecycle event, where the run is already in hand); a `step.*` event doesn't carry
 * it, so those fall back to `store.getRun`. EVERY run's resolved namespace is memoized in
 * `runNamespaces` — including a bare/`default` run — so the store is read at most ONCE per run id
 * regardless of how many `step.*` events that run emits; a `null` cache entry means "resolved, not
 * a tenant" (distinct from "not yet resolved", i.e. absent from the map). A run's entry is deleted
 * the moment its terminal event is handled, so the cache stays bounded to in-flight runs (tenant and
 * default alike) — the same order of magnitude it was already paying for tenant runs alone, in
 * exchange for turning a per-event store read into a per-run one.
 */
declare class TenantEventRepublisher {
    private readonly store;
    private readonly publish;
    private readonly runNamespaces;
    constructor(store: RunLookupStore, publish: (event: TenantEvent) => Promise<void>);
    handle(event: EngineEvent): Promise<void>;
    private namespaceFor;
}

/**
 * Cross-lib injection token for the current-request context accessor, owned by
 * `@dudousxd/nestjs-context`. We do NOT import nestjs-context (it is an OPTIONAL
 * peer dependency) — instead we share its well-known token by value so DI
 * resolves the same provider when nestjs-context is installed and present.
 *
 * `Symbol.for(key)` uses the global symbol registry, so this resolves to the
 * SAME symbol instance as nestjs-context's `tokens.ts` (and the identical token
 * declared by `@dudousxd/nestjs-authz`) without any import. The key MUST stay
 * byte-identical with nestjs-context's export.
 */
declare const CONTEXT_ACCESSOR: unique symbol;
/**
 * @deprecated Inject the `RunGateway` abstract class directly (it is its own DI token now):
 * `constructor(private readonly gateway: RunGateway)`, provider `{ provide: RunGateway, useClass }`.
 * This symbol is kept as a back-compat alias — it points at the `RunGateway` class, so existing
 * `@Inject(RUN_GATEWAY)` sites resolve the very same token — and will be removed in a future major.
 *
 * `RunGateway` is owned by `@dudousxd/nestjs-durable-core` (a required peer dep of both this package
 * and the dashboard), so the abstract class is a single shared token across packages without the
 * previous `Symbol.for` value-sharing hack.
 */
declare const RUN_GATEWAY: typeof RunGateway;

/** Public entry point for starting and resuming workflow runs. */
declare class WorkflowService {
    private readonly engine;
    constructor(engine: WorkflowEngine);
    /**
     * Enqueue a workflow run — it creates the run (`pending`) and returns `{ runId, status: 'pending' }`
     * immediately; a worker executes the body (so the caller never blocks on workflow logic). Use
     * {@link waitForRun} when you need the outcome. Pass the workflow's **class**
     * (`start(CheckoutWorkflow, input)`) for a typed input + refactor-safety, or a **name** string for a
     * cross-runtime workflow. `runId` defaults to a random id; pass your own to make the start idempotent
     * (a redelivery returns the existing run). `opts.tags` are merged with the workflow's static
     * `@Workflow({ tags })`; `opts.searchAttributes` stamp typed, queryable run data.
     */
    start<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, runId?: string, opts?: StartOptions): Promise<RunResult>;
    start(workflow: string, input: unknown, runId?: string, opts?: StartOptions): Promise<RunResult>;
    resume(runId: string): Promise<RunResult>;
    /**
     * Resolve once a run settles — terminal (completed/failed/cancelled/dead) or suspended. `start`
     * only enqueues (a worker runs the body), so pair them when a request needs the outcome:
     * `const { runId } = await svc.start(...); const result = await svc.waitForRun(runId)`.
     */
    waitForRun(runId: string, opts?: {
        timeoutMs?: number;
    }): Promise<RunResult>;
    /** Deliver an external signal (e.g. from a webhook) to the run waiting on `token`. */
    signal(token: string, payload: unknown): Promise<RunResult | null>;
    /**
     * Ensure a run exists for `runId`, then deliver a signal to it — race-free (the signal is buffered
     * until the run reaches its `waitForSignal`). The durable-entity / accumulator pattern: one
     * long-lived run per key fed events by many calls. See {@link WorkflowEngine.signalWithStart}.
     */
    signalWithStart<C extends WorkflowClass>(workflow: C, input: WorkflowInputOf<C>, runId: string, signal: {
        token: string;
        payload?: unknown;
    }, opts?: StartOptions): Promise<{
        runId: string;
    }>;
    signalWithStart(workflow: string, input: unknown, runId: string, signal: {
        token: string;
        payload?: unknown;
    }, opts?: StartOptions): Promise<{
        runId: string;
    }>;
    /**
     * Publish a named event. Resumes runs waiting on it via `ctx.waitForEvent(name, { match })` and
     * starts a fresh run of every workflow subscribed via `@Workflow({ onEvent })` / `@OnDurableEvent` (the
     * payload becomes its input). Pass `opts.id` to dedupe redeliveries. Returns how many runs it
     * touched (resumed + started).
     *
     * Reliable by default: a publish that touches NOBODY (no live waiter, no subscriber) buffers ONE
     * copy so a LATER `waitForEvent(name, { match })` still consumes it instead of it being dropped —
     * see {@link WorkflowEngine.publishEvent}'s full semantics doc. Pass `opts.buffer: false` to opt out.
     */
    publishEvent(name: string, payload: unknown, opts?: {
        id?: string;
        buffer?: boolean;
    }): Promise<number>;
}

export { CONTEXT_ACCESSOR, type ContextAccessor, type ContextStore, DEAD_LETTER_METADATA, DURABLE_STEP_METADATA, DURABLE_WORKER_RUNNERS, DeadLetter, DurableModule, type DurableModuleAsyncOptions, type DurableModuleOptions, type DurableRetentionOptions, DurableStartClient, DurableStep, type DurableStepInterceptor, type DurableStepMeta, ENTITY_METADATA, ENTITY_ON_METADATA, Entity, EntityService, IN_APP_RUN_REDIS_WORKER, IN_APP_WORKER_BINDING, IN_APP_WORKER_RUNNERS, IN_APP_WORKER_RUNTIME, type InAppWorkerBinding, InAppWorkerBootstrap, ON_EVENT_METADATA, On, OnDurableEvent, OnEvent, ProxyRunGateway, RUN_GATEWAY, RUN_REDIS_WORKER, type RunLookupStore, type RunRedisWorkerFn, RunRequestResponder, type RunRequestTransport, STEP_INTERCEPTOR_METADATA, Step, StepInterceptor, type StepOptions, StoreRunGateway, TenantEventRepublisher, ThinStepRegistrar, ThinWorkerBootstrap, ThinWorkflowRegistrar, type UserRef, WORKFLOW_METADATA, Workflow, type WorkflowAttributesOf, type WorkflowMeta, type WorkflowOptions, WorkflowService, attributesOf, entityConfigFor, getDurableStepMeta, getEntityMeta, getOnEvents, getWorkflowMeta, inAppWorkerProviders, isDeadLetterHandler, isDrivingOperator, isOperatorRole, isStepInterceptor, thinWorkerProviders, unavailableRunGateway };
