/** * Ending a durable wait with a value somebody delivered. * * A wait has two durable halves. The request says what is awaited and is * published the moment the document asks; the **answer** says what ended it and * is published only when one really did. Both are ordinary journal events, so a * resumed execution that already received its answer replays it rather than * asking for a second one — which is what makes an answered wait a step in a * procedure rather than a question repeated at every resume. * * ## Why the answer is its own durable operation * * The alternative — enter the wait, then append an event afterwards — has no * position in the journal, so replay could not tell an answered wait from an * unanswered one. As a durable operation it has a position of its own, directly * behind the request, and replay restores its recorded value without reaching * the live controller at all. * * ## Publication is the provider's, inside the provider's transaction * * Retained delivery state and the answer event have to move together: an answer * consumed without an event would be lost, and an event published without the * consumption could be delivered twice. Neither is something this module can * arrange, because only the host that keeps the run has its transaction. So the * live path hands the publication to whoever owns that state and returns only * what came back through it — the same shape the Workspace effect uses, without * the Workspace. * * The route to that owner is a private registration rather than a contextual * value anybody can read. A name is selection, and a publication capability * reachable by name would let document code publish an answer to its own wait. */ import { type Operation } from "effection"; import { type DurableEffect, type Json, type JournalProvenance } from "@executablemd/durable-streams"; import { WorkflowSuspensionRequestError, type WorkflowSuspensionRequest } from "./api.js"; /** The effect type one delivered answer is retained under. */ export declare const SUSPENSION_ANSWER = "suspension_answer"; /** What a live answer publication is given, and what it may do with it. */ export interface SuspensionAnswerPublication { /** The wait being answered, as this run derives it. */ readonly suspensionId: string; /** What that wait asked for, and what shape an answer takes. */ readonly request: WorkflowSuspensionRequest; /** The journal this effect publishes through, for the owner to recognize. */ readonly journalProvenance: JournalProvenance | undefined; /** * Publish the durable answer Yield carrying this value. * * Called inside the owner's own transaction, so the event and whatever * retained state it consumes commit together or not at all. */ publish(value: Json): Operation; } /** Whoever keeps a run's retained delivery state. */ export interface SuspensionAnswerProvider { /** * The value that ends this wait, already published, or nothing when none is * retained. * * Returning a value without having published it is a defect rather than an * answer, and is refused: what makes a wait over is the event, not the * return. */ claim(publication: SuspensionAnswerPublication): Operation; } /** A live answer publication that did not happen the way its contract says. */ export declare class SuspensionAnswerPublicationError extends WorkflowSuspensionRequestError { name: string; } /** * Install the owner of this run's retained delivery state for the current scope. * * `{ at: "min" }` for the reason every provider here uses it: middleware at the * default position runs outermost, so an outer scope's registration would be * selected ahead of the one installed nearer the run. */ export declare function useSuspensionAnswerProvider(provider: SuspensionAnswerProvider): Operation; /** * The wait's answer: replayed when one was published, claimed when one is * retained, and otherwise the wait itself. * * `enter` is the public composable route into the wait. It is reached only when * no answer is retained, and nothing it returns is an answer — a value arriving * from it means some handler stood between this operation and the controller, * so the operation does what an unentered wait is and stops. */ export declare function suspensionAnswerEffect(id: string, request: WorkflowSuspensionRequest, enter: () => Operation): DurableEffect;