@molecule/api-workflow
Core interface · workflow · API (Node) · v1.0.3 · Apache-2.0
Workflow/state machine core interface for molecule.dev — definitions, instances, transitions, and event history.
npm install @molecule/api-workflowHow it works
@molecule/api-workflow is the workflow core interface on the API (Node) side: the API your app calls, with no vendor inside.
Choose the implementation by bonding one of its 1 provider: @molecule/api-workflow-database.
import { setProvider, createWorkflow, startInstance, transition } from '@molecule/api-workflow'
import { provider as dbWorkflow } from '@molecule/api-workflow-database'
setProvider(dbWorkflow)
const workflow = await createWorkflow({
name: 'order-lifecycle',
initialState: 'pending',
states: {
pending: { transitions: { confirm: { target: 'confirmed' } } },
confirmed: { transitions: { ship: { target: 'shipped' } }, final: false },
shipped: { transitions: { deliver: { target: 'delivered' } } },
delivered: { final: true, transitions: {} },
},
})
const instance = await startInstance(workflow.id, { orderId: '123' })
await transition(instance.id, 'confirm')Providers (1): @molecule/api-workflow-database
Works with: @molecule/api-bond, @molecule/api-i18n
Reference
Workflow/state machine core interface for molecule.dev.
Defines the WorkflowProvider interface for managing workflow definitions,
instances, state transitions, and event history. Bond packages (database,
in-memory, etc.) implement this interface. Application code uses the
convenience functions which delegate to the bonded provider.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { setProvider, createWorkflow, startInstance, transition } from '@molecule/api-workflow'
import { provider as dbWorkflow } from '@molecule/api-workflow-database'
setProvider(dbWorkflow)
const workflow = await createWorkflow({
name: 'order-lifecycle',
initialState: 'pending',
states: {
pending: { transitions: { confirm: { target: 'confirmed' } } },
confirmed: { transitions: { ship: { target: 'shipped' } }, final: false },
shipped: { transitions: { deliver: { target: 'delivered' } } },
delivered: { final: true, transitions: {} },
},
})
const instance = await startInstance(workflow.id, { orderId: '123' })
await transition(instance.id, 'confirm')
Type
core
Installation
npm install @molecule/api-workflow @molecule/api-bond @molecule/api-i18n
API
Interfaces
WorkflowDefinition
A complete workflow definition describing states and their transitions.
interface WorkflowDefinition {
/** Human-readable name for this workflow. */
name: string
/** The state in which new instances start. */
initialState: string
/** Map of state names to their definitions. */
states: Record<string, StateDefinition>
}
StateDefinition
Definition of a single state within a workflow.
interface StateDefinition {
/** Map of action names to their transition definitions. */
transitions: Record<string, TransitionDefinition>
/** Optional hook identifier to execute when entering this state. */
onEnter?: string
/** Optional hook identifier to execute when exiting this state. */
onExit?: string
/** Whether this is a terminal state (no outgoing transitions expected). */
final?: boolean
}
TransitionDefinition
Definition of a transition from one state to another.
interface TransitionDefinition {
/** The target state this transition leads to. */
target: string
/** Optional guard identifier — transition is allowed only if the guard passes. */
guard?: string
/** Optional action identifier to execute during the transition. */
action?: string
}
Workflow
A persisted workflow definition with an assigned identifier.
interface Workflow {
/** Unique workflow identifier. */
id: string
/** Human-readable workflow name. */
name: string
/** The initial state for new instances. */
initialState: string
/** State definitions. */
states: Record<string, StateDefinition>
/** Creation timestamp. */
createdAt: string
/** Last modification timestamp. */
updatedAt: string
}
WorkflowInstance
A running instance of a workflow.
interface WorkflowInstance {
/** Unique instance identifier. */
id: string
/** The workflow definition this instance belongs to. */
workflowId: string
/** Current state of the instance. */
state: string
/** Arbitrary data attached to this instance. */
data: Record<string, unknown>
/** Creation timestamp. */
createdAt: string
/** Last modification timestamp. */
updatedAt: string
}
WorkflowEvent
A recorded event in a workflow instance's history.
interface WorkflowEvent {
/** Unique event identifier. */
id: string
/** The workflow instance this event belongs to. */
instanceId: string
/** The action that triggered this event. */
action: string
/** The state before the transition. */
fromState: string
/** The state after the transition. */
toState: string
/** Optional data snapshot at the time of the event. */
data?: Record<string, unknown>
/** Event timestamp. */
createdAt: string
}
WorkflowProvider
Workflow provider interface.
All workflow providers must implement this interface to supply workflow definition storage, instance management, and transition logic.
interface WorkflowProvider {
/** Provider name (e.g. 'database', 'memory'). */
readonly name: string
/**
* Persists a new workflow definition.
*
* @param definition - The workflow definition to create.
* @returns The persisted workflow with generated ID and timestamps.
*/
createWorkflow(definition: WorkflowDefinition): Promise<Workflow>
/**
* Retrieves a workflow definition by ID.
*
* @param workflowId - The workflow identifier.
* @returns The workflow, or `null` if not found.
*/
getWorkflow(workflowId: string): Promise<Workflow | null>
/**
* Lists all persisted workflow definitions.
*
* @returns An array of all workflows.
*/
listWorkflows(): Promise<Workflow[]>
/**
* Creates and starts a new instance of a workflow.
*
* @param workflowId - The workflow to instantiate.
* @param data - Optional initial data for the instance.
* @returns The newly created instance in the workflow's initial state.
*/
startInstance(workflowId: string, data?: Record<string, unknown>): Promise<WorkflowInstance>
/**
* Retrieves a workflow instance by ID.
*
* @param instanceId - The instance identifier.
* @returns The instance, or `null` if not found.
*/
getInstance(instanceId: string): Promise<WorkflowInstance | null>
/**
* Applies an action to transition an instance from its current state.
*
* @param instanceId - The instance to transition.
* @param action - The action name triggering the transition.
* @param data - Optional data to merge into the instance.
* @returns The updated instance in its new state.
* @throws {Error} If the action is not valid for the current state.
*/
transition(
instanceId: string,
action: string,
data?: Record<string, unknown>,
): Promise<WorkflowInstance>
/**
* Returns the current state name of an instance.
*
* @param instanceId - The instance identifier.
* @returns The current state name.
*/
getState(instanceId: string): Promise<string>
/**
* Returns the transition history for an instance.
*
* @param instanceId - The instance identifier.
* @returns An array of workflow events in chronological order.
*/
getHistory(instanceId: string): Promise<WorkflowEvent[]>
/**
* Returns the list of action names available from the instance's current state.
*
* @param instanceId - The instance identifier.
* @returns An array of available action names.
*/
getAvailableActions(instanceId: string): Promise<string[]>
}
WorkflowConfig
Configuration options for the workflow provider.
interface WorkflowConfig {
/** Optional table name prefix for database-backed providers. */
tablePrefix?: string
/** Additional provider-specific options. */
[key: string]: unknown
}
Functions
setProvider(provider)
Registers a workflow provider as the active singleton. Called by bond packages during application startup.
function setProvider(provider: WorkflowProvider): void
provider— The workflow provider implementation to bond.
getProvider()
Retrieves the bonded workflow provider, throwing if none is configured.
function getProvider(): WorkflowProvider
Returns: The bonded workflow provider.
hasProvider()
Checks whether a workflow provider is currently bonded.
function hasProvider(): boolean
Returns: true if a workflow provider is bonded.
createWorkflow(definition)
Persists a new workflow definition.
function createWorkflow(definition: WorkflowDefinition): Promise<Workflow>
definition— The workflow definition to create.
Returns: The persisted workflow with generated ID and timestamps.
getWorkflow(workflowId)
Retrieves a workflow definition by ID.
function getWorkflow(workflowId: string): Promise<Workflow | null>
workflowId— The workflow identifier.
Returns: The workflow, or null if not found.
listWorkflows()
Lists all persisted workflow definitions.
function listWorkflows(): Promise<Workflow[]>
Returns: An array of all workflows.
startInstance(workflowId, data)
Creates and starts a new instance of a workflow.
function startInstance(
workflowId: string,
data?: Record<string, unknown>,
): Promise<WorkflowInstance>
workflowId— The workflow to instantiate.data— Optional initial data for the instance.
Returns: The newly created instance in the workflow's initial state.
getInstance(instanceId)
Retrieves a workflow instance by ID.
function getInstance(instanceId: string): Promise<WorkflowInstance | null>
instanceId— The instance identifier.
Returns: The instance, or null if not found.
transition(instanceId, action, data)
Applies an action to transition an instance from its current state.
function transition(
instanceId: string,
action: string,
data?: Record<string, unknown>,
): Promise<WorkflowInstance>
instanceId— The instance to transition.action— The action name triggering the transition.data— Optional data to merge into the instance.
Returns: The updated instance in its new state.
getState(instanceId)
Returns the current state name of an instance.
function getState(instanceId: string): Promise<string>
instanceId— The instance identifier.
Returns: The current state name.
getHistory(instanceId)
Returns the transition history for an instance.
function getHistory(instanceId: string): Promise<WorkflowEvent[]>
instanceId— The instance identifier.
Returns: An array of workflow events in chronological order.
getAvailableActions(instanceId)
Returns the list of action names available from the instance's current state.
function getAvailableActions(instanceId: string): Promise<string[]>
instanceId— The instance identifier.
Returns: An array of available action names.
Available Providers
| Provider | Package |
|---|---|
| Database | @molecule/api-workflow-database |
Injection Notes
Requirements
Peer dependencies:
@molecule/api-bond^1.0.1@molecule/api-i18n^1.0.1
Runtime Dependencies
-
@molecule/api-bond -
@molecule/api-i18n -
guard/onEnter/onExit/actionidentifiers are DECLARATIVE ONLY. The bundled bonds do not evaluate guards or execute hooks —guard: 'isPaid'blocks nothing. Enforce preconditions and side-effects in YOUR handler aroundtransition(check, then transition, then act), or in a bond that documents hook execution. -
transition()THROWS when the action is not valid for the instance's current state — catch it and answer 4xx; build action buttons fromgetAvailableActionsso the UI only offers legal moves. -
The database bond requires its tables.
@molecule/api-workflow-databaseships__setup__/workflow.sql(workflows,workflow_instances,workflow_events); molecule scaffolds replay__setup__/*.sqlonmigrate, but adding the bond to an existing app means applying that DDL yourself before first use. -
Nothing is user-scoped and transitions are read-then-write: persist ownership yourself, authorize server-side before every transition/list, and serialize concurrent transitions per instance (transaction/lock) when double-fires would matter.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual workflow screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- Starting an instance from the UI puts it in the workflow's
initialState; each UI action drives onetransition()and the instance advances ONLY along a transition defined for its current state. Walk the whole path front to back — the step/status shown in the UI matchesgetState()at every stage andgetHistory()lists the fromState -> toState hops in the exact order they happened. - A step whose work must succeed before advancing actually gates the
next transition.
guard/actionidentifiers are DECLARATIVE ONLY — the bundled bonds never evaluate or run them — so the handler enforces it aroundtransition()(check, then transition, then act): a required approval or input holds the instance in its current state, the advancing action only appears ingetAvailableActions()once the precondition is met, and the UI cannot move on until the real work succeeded. - Branching routes correctly: an input that should take branch A takes
A, not B. The handler picks which action to apply from the instance data
and the instance lands in branch A's target state (confirm via
getState()/history), never the other branch's. - A failed step is handled per the definition — retry loops back, halt
lands in the error/terminal state, compensate runs the rollback
transition — never silently left in the pre-failure state as if it
succeeded and never wedged with no available actions.
transition()THROWS on an action illegal for the current state; the handler catches it and answers an error instead of pretending the step advanced. - State is durable, not in-memory: reload the page (or come back later /
restart the server) and re-fetch the instance — it is at the SAME step
with its
dataintact, proving state lives in the workflow bond's store. Requires theworkflows/workflow_instances/workflow_eventstables to be migrated first. - Integrity — a caller cannot POST an arbitrary action or target state
to jump ahead or skip a required approval.
transition()only honors actions defined for the instance's CURRENT state (throws otherwise), and the server authorizes ownership before every transition/list (nothing is user-scoped by default) so one user can neither advance nor read another's instance. Build the UI's buttons fromgetAvailableActions(), but enforce every transition server-side.