flow

js/ui/web/flow.ts

fino:ui/web/flow — workflow-backed server-driven pages.

flowPage() connects bounded multi-step workflows to the portable UI engine. A GET without ?run= starts the workflow and redirects to a URL containing the run id. Later GETs render the persisted WorkflowState as HTML or as a semantic JSON stream, and the signal the workflow is waiting on is delivered through an ordinary view action, so it inherits session ownership, origin and CSRF checks, replay protection, input validation, and the shared safe error envelope rather than reimplementing them.

The workflow store stays the only durable copy of run state. The view snapshot holds just the run id and the step the rendered form was addressed to; the run itself is a derived signal, reloaded from the store for every render.

webUI() must be installed, because the action and live-stream paths belong to it. Only a GET route is needed — the action posts back to the same path and is handled by the middleware.

import { flowPage } from 'fino:ui/web/flow';
import { webUI } from 'fino:ui/web';
import { h } from 'fino:ui';

const ui = app.layer(webUI({ store: views, secret: 'dev-secret' }));
ui.get('/checkout').handle(
  flowPage(checkout, {
    store,
    start: () => ({ cart: [] }),
    render: (ctx, state, advance) =>
      h('form', { action: advance }, h('button', null, state.waitingOn?.name ?? 'done')),
  }),
);

Types

type FlowAdvance = unknown

Action descriptor passed to a flow page's render function.

Use it as a form's action prop so the submit is wired to the flow action.

Interfaces

interface FlowPageOptions<In = unknown> {

Properties

store: Store

Durable workflow store containing runs for this page.

start: (ctx: HttpContext) => In | Promise<In>

Input factory used when a GET starts a new run.

render: (ctx: HttpContext, state: WorkflowState, advance: FlowAdvance) => VNode

Render the current workflow state.

advance is the action descriptor for delivering the awaited signal; give it to a form's action prop. The signal payload is read from the form field named after the awaited signal.

input?: JsonSchema

Optional schema validating the submitted signal payload object.

id?: string

View definition id. Defaults to fino:flow/<workflow id>.

Set this when one workflow backs more than one page, so each page gets its own view definition.

Functions

function flowPage<In, Out>(workflow: Workflow<In, Out>, opts: FlowPageOptions<In>): Handler

Create a route handler for a bounded workflow-backed page.