/** * Durable workflows: authoring surface + the per-slice executor. * * A workflow is a TS function that composes `step` / `sleep` / * `waitForEvent` calls. The Rust WorkflowEngine owns the durable state * (instance, step results, wake timers — persisted in SQLite); this * module executes ONE slice at a time by REPLAY: on every advance the * whole workflow function re-runs, already-completed steps return their * recorded outputs without executing, and the first not-yet-done * primitive either executes (a `step` at the current index) or pauses * the run (`sleep` / `waitForEvent`). * * Determinism contract for authors: the sequence of step/sleep/ * waitForEvent calls must be identical on every replay for the same * input + recorded outputs. Branch on `input` and on step OUTPUTS all * you like — never on wall-clock time, randomness, or external state * read outside a step. * * Files live in the app's `workflows/` directory (sibling of * `functions/`), one default-exported `workflow(...)` per file. The * runtime reports them in its ready handshake; the host registers them * and drives execution back through the function pool as an internal * `__pylon_workflow_run` action call — so step code runs with a full * ActionCtx (ctx.db, ctx.llm, ctx.scheduler, the idle timeout, and * cancellation) rather than in a bespoke side-channel process. */ import type { ActionCtx } from "./types"; /** Wire shape of one recorded step — mirrors Rust `StepResult` exactly. */ export interface WorkflowStepResult { step_id: string; name: string; status: "pending" | "running" | "completed" | "failed" | "skipped"; output?: unknown; error?: string | null; started_at?: string | null; completed_at?: string | null; duration_ms?: number | null; retry_count?: number; } /** The advance request the Rust engine sends for one slice. */ export interface WorkflowRunRequest { workflow_id: string; workflow_name: string; input: unknown; current_step: number; completed_steps: WorkflowStepResult[]; } /** The verdict of one slice — mirrors Rust `apply_response`'s actions. */ export type WorkflowRunnerResponse = { action: "step_complete"; step_name: string; output: unknown; duration_ms: number; } | { action: "sleep"; duration: string; } | { action: "wait_event"; event: string; } | { action: "complete"; output: unknown; } | { action: "fail"; error: string; step_name?: string; }; /** What a workflow function receives, besides the per-slice ActionCtx. */ export interface WorkflowRun { /** The workflow instance id (stable across slices + restarts). */ id: string; /** The input `start()` was called with. */ input: TInput; /** * Run a named step exactly once. On replay a completed step returns * its recorded output without executing. Step names must be unique * within one workflow run — the replay cache is name-keyed. */ step(name: string, fn: () => Promise | T): Promise; /** Pause the workflow for a duration ("30s", "5m", "24h", "7d"). */ sleep(duration: string): Promise; /** * Pause until `POST /api/workflows//event` delivers this event. * Resolves with the event's data payload. */ waitForEvent(eventName: string): Promise; } export interface WorkflowDefinition { readonly __pylonWorkflow: true; name: string; description?: string; /** Max retries per step before the workflow fails (engine default: 3). */ maxRetries?: number; fn: (wf: WorkflowRun, ctx: ActionCtx) => Promise; } /** * Declare a workflow. Default-export the result from a file in the * app's `workflows/` directory: * * ```ts * // workflows/onboarding.ts * import { workflow } from "@pylonsync/functions"; * * export default workflow("onboarding", async (wf, ctx) => { * const user = await wf.step("load-user", () => * ctx.runQuery("getUser", { id: wf.input.userId }), * ); * await wf.step("send-welcome", () => * ctx.email.send({ to: user.email, subject: "Welcome!", text: "..." }), * ); * await wf.sleep("24h"); * const confirmed = await wf.waitForEvent("email_confirmed"); * return { done: true, confirmed }; * }); * ``` */ export declare function workflow(name: string, fn: (wf: WorkflowRun, ctx: ActionCtx) => Promise, opts?: { description?: string; maxRetries?: number; }): WorkflowDefinition; /** Runtime shape check for a `workflows/` file's default export. */ export declare function isWorkflowDefinition(v: unknown): v is WorkflowDefinition; /** * Execute one slice of `def` for `request`, returning the engine verdict. * Never throws for handler errors — a step failure becomes * `{action: "fail"}` so the engine's retry accounting runs. */ export declare function executeWorkflowSlice(def: WorkflowDefinition, request: WorkflowRunRequest, ctx: ActionCtx): Promise;