import type { EngineJob } from "@nanobpm/urban/runtime"; /** * A resolved worker outcome — a discriminated union with exactly one variant per * engine completion method (`completeJob` / `failJob` / `throwError`). Shared with * the child-process mock slice so both apply outcomes through the one * {@link applyOutcome} implementation. */ export type MockOutcome = /** Complete the job with these variables → `completeJob(jobKey, JSON.stringify(variables))`. */ { readonly kind: "complete"; readonly variables: Record; } /** Fail the job → `failJob(jobKey, retries, message)`. `retries: 0` raises an incident. */ | { readonly kind: "fail"; readonly retries: number; readonly message: string; } /** Raise a BPMN error → `throwError(jobKey, errorCode, message)` (drives the error boundary). */ | { readonly kind: "throwError"; readonly errorCode: string; readonly message: string; }; /** * The minimal engine surface {@link applyOutcome} needs — the three completion * methods a mocked job resolves through. `@nanobpm/engine-wasm`'s `TestEngine` * satisfies this structurally, so no cast is needed at the call site. */ export interface OutcomeEngine { completeJob(jobKey: string, variablesJson: string): unknown; failJob(jobKey: string, retries: number, message: string): unknown; throwError(jobKey: string, errorCode: string, errorMessage: string): unknown; } /** * Apply a resolved {@link MockOutcome} against the engine for `jobKey` — the single * canonical mapping from an outcome variant to its engine completion call. Mirrors * the real completion path in `WasmEngineClient.#runJob` exactly (synchronous, no * wall-clock), so a mocked job leaves the engine in the same shape a real handler * would and the deterministic drain still reaches a fixpoint. * * The `switch` is exhaustive over `MockOutcome["kind"]`: a new engine completion * method (hence a new outcome variant) makes this fail to compile until it is * handled here — the derivation seam the S5 completeness guard relies on. */ export declare function applyOutcome(engine: OutcomeEngine, jobKey: string, outcome: MockOutcome): void; /** A pure, synchronous predicate over an {@link EngineJob} used by {@link MockWorkerBuilder.when}. */ export type JobPredicate = (job: EngineJob) => boolean; /** * A fluent builder describing how a mocked `taskType` (or, for the child-process * slice, a called process) should resolve. Obtain one from `app.mockWorker(type)`. * * ## Conditions and ordering * A builder holds an ordered list of clauses. `when(predicate)` arms a guard for the * NEXT outcome; an outcome method with no preceding `when` is an unconditional * default. On each dispatch the clauses are evaluated in **registration order, * first match wins**: the first clause whose predicate is absent or returns `true` * for the job supplies the outcome. If **no** clause matches, {@link resolve} * returns `undefined` and the dispatch falls through to the next mock rule or, if * none, to the real handler. Because an unconditional clause matches every job, any * clause registered after it is unreachable. * * All outcome methods and `when` return `this`, so a guard chains before its * outcome, e.g. `mock.when(j => j.variables.vip === true).completeWith({ fast: true })`. */ export declare class MockWorkerBuilder { #private; /** @param remove deregisters this builder from its owning registry (used by {@link reset}). */ constructor(remove: () => void); /** * Arm a predicate for the NEXT outcome added to this builder. Predicates must be * pure and synchronous over the {@link EngineJob} (its `jobType`, `variables`, * `elementId`, keys) — they are evaluated on every matching dispatch under the * virtual clock, so a side-effecting or async predicate would break determinism. * Evaluated in registration order, first match wins (see class docs). Throws if a * predicate is already armed (two consecutive `when(...)` calls with no intervening * outcome) so silently dropping the first guard fails fast instead. */ when(predicate: JobPredicate): this; /** Complete the job with `vars` (mirrors the engine's `completeJob`). */ completeWith(vars: Record): this; /** * Fail the job. Defaults to `retries: 0`, which raises an incident (matching the * `failWith({ retries: 0 })` sketch); pass a positive `retries` to fail with * redelivery budget instead. Maps to the engine's `failJob`. */ failWith(opts?: { retries?: number; message?: string; }): this; /** * Throw a BPMN error, driving the modelled error-boundary flow — maps to the * engine's `throwError`. `message` defaults to `code`. */ throwBpmnError(code: string, message?: string): this; /** * Raise an incident visible in the engine snapshot (`snapshot().incidents[]`). * The engine has no dedicated "raise incident" call; it raises one when a job * fails with zero retries left, so this is a zero-retry `failJob` carrying the * given message — the most direct path on the `TestEngine` surface. */ raiseIncident(opts?: { message?: string; }): this; /** * Resolve the outcome for `job`, or `undefined` when no clause matches (the * dispatch then falls through to the real handler). First-match-wins over the * clauses in registration order. */ resolve(job: EngineJob): MockOutcome | undefined; /** True once at least one clause has been added — a bare, unused builder is inert. */ get hasClauses(): boolean; /** * Remove this mock entirely: drop every clause and deregister from the owning * engine so the mocked type resumes running its real handler. Idempotent — a second * call on an already-removed builder is a no-op. After reset the builder is * **tombstoned**: any further mutation ({@link when} or an outcome method) throws, * because a clause added to a deregistered builder never affects dispatch. Create a * fresh mock via `app.mockWorker(type)` instead of re-arming a removed builder. */ reset(): void; }