API Reference
API Reference
Complete API documentation for StateFlow functions, types, and patterns.
Core Functions
StateFlow's essential functions for state management:
defineState
Creates a state definition with typed variants using a builder pattern.
function defineState<TProps>(): StateBuilder<TProps, "", unknown, "">
Builder methods:
.name(string)
- Set unique state name.variant(name, isInitial?)
- Add state variant.signals(object)
- Define handled signals.parser(func)
- Custom property parsing.stringRepr(func)
- Custom string representation.build()
- Finalize definition
Example:
const taskState = defineState<{
id: string;
title: string;
assignee?: string;
priority: 'low' | 'medium' | 'high';
}>()
.name("task")
.signals(taskSignals)
.variant("draft", true)
.variant("assigned")
.variant("completed")
.stringRepr(s => `${s.title} (${s.priority})`)
.parser(obj => ({
id: obj.id || crypto.randomUUID(),
title: obj.title || "Untitled",
assignee: obj.assignee,
priority: obj.priority || 'medium'
}))
.build();
defineSignal
Creates a signal definition for triggering state transitions.
function defineSignal<TArgs>(name: string, stringRepr?: (args: TArgs) => string)
Parameters:
name
- Unique signal identifierstringRepr
- Optional custom string representation
Returns signal factory function.
Example:
// Parameterless signal
const refresh = defineSignal("refresh");
const signal1 = refresh(); // Creates signal instance
// Parameterized signal
const updatePriority = defineSignal<{
taskId: string;
priority: 'low' | 'medium' | 'high';
}>("updatePriority");
const signal2 = updatePriority({
taskId: "123",
priority: "high"
});
// Custom string representation
const complexSignal = defineSignal<{
action: string;
metadata: Record<string, unknown>;
}>("complexSignal", (args) => `${args.action}:${Object.keys(args.metadata).length} props`);
defineFlow
Defines signal handlers for a state variant.
function defineFlow(state: StateVariant, handlers: SignalHandlers): void
Handler signature: (state, signal, context) => StateResult
Example:
defineFlow(taskState.draft, {
assign: (state, signal) => {
if (!signal.userId) {
return Result.reject("User ID required for assignment");
}
return taskState.assigned({
...state,
assignee: signal.userId
});
},
updatePriority: (state, signal) => ({
...state,
priority: signal.priority
}),
delete: () => Result.reject("Cannot delete draft tasks")
});
applyFlow
Applies state definitions to an application object and sets up handlers.
function applyFlow(target: object, states: StateDefinition[],
initializer: (sm: StateManager) => void): void
StateManager methods:
addEnterHandler(state, handler)
- Called when entering stateaddExitHandler(state, handler)
- Called when leaving stateaddUpdateHandler(state, handler)
- Called when state data changesaddRollbackHandler(state, handler)
- Called on transition failures
Example:
const app = {
task: { id: "", title: "", priority: "medium" as const },
ui: { loading: false, error: null }
};
applyFlow(app, [taskState, uiState], (sm) => {
sm.addEnterHandler(taskState.assigned, async (state) => {
return Result.transition(async () => {
await notifyUser(state.assignee, state);
return Result.ok();
}, 3000);
});
sm.addExitHandler(uiState.error, (state) => {
clearErrorDisplay();
return Result.ok();
});
});
dispatch
Dispatches a signal to trigger state transitions.
function dispatch(target: object, signal: Signal, mute?: boolean): Result
Returns Result indicating the outcome. Use with .expect()
and .done()
.
Example:
// Basic dispatch with expectation
try {
await dispatch(app, signals.assign({ userId: "user123" }))
.expect(ResultKind.OK)
.done();
console.log("Task assigned successfully");
} catch (error) {
console.error(`Assignment failed: ${error}`);
}
// Asynchronous dispatch
await dispatch(app, signals.save()).done();
console.log('Save completed');
// Muted dispatch (no logging)
await dispatch(app, signals.ping(), true).done();
// Multiple expected results
await dispatch(app, signals.optionalAction())
.expect(ResultKind.OK, ResultKind.Ignored)
.done();
observe
Watches for changes in specific state variants.
function observe(target: object, stateVariants: StateVariant[],
handler: (state: StateInstance) => void,
compareFn?: (prev, curr) => boolean): Disposer
Returns disposable observer. Use [Symbol.dispose]()
for cleanup.
Example:
// Basic observation
const observer = observe(
app,
[taskState.assigned, taskState.completed],
(state) => updateTaskDisplay(state)
);
// With custom comparison
const priorityObserver = observe(
app,
[taskState.assigned],
(state) => highlightHighPriority(state),
(prev, curr) => prev.priority !== curr.priority
);
// Using the using keyword (ES2023)
function watchTasks() {
using observer = observe(app, [taskState.assigned], updateUI);
// Observer automatically disposed when scope exits
}
// Manual disposal
const sub = observe(app, [taskState.completed], logCompletion);
// Later...
sub[Symbol.dispose]();
sync
Waits for all pending async transitions to complete.
async function sync(target: object): Promise<void>
Essential before operations requiring stable state.
Example:
async function performComplexOperation() {
// Ensure no transitions are pending
await sync(app);
// Now safe to dispatch new signals
await dispatch(app, signals.startBatch()).done();
// Multiple async operations
const results = await Promise.all([
dispatch(app, signals.process({ id: "1" })).done(),
dispatch(app, signals.process({ id: "2" })).done(),
dispatch(app, signals.process({ id: "3" })).done()
]);
// Wait for all to complete
await sync(app);
// Continue with next phase
await dispatch(app, signals.finalizeBatch()).done();
}
Result API
Results provide explicit feedback for all operations with type-safe handling.
Result Types
enum ResultKind {
OK, Ignored, InTransition, Rejected, Error
}
class Result {
readonly kind: ResultKind;
readonly data: unknown;
readonly error: Error | null;
readonly message: string | null;
in(...kinds: ResultKind[]): boolean;
done(): Promise<Result>;
expect(...kinds: ResultKind[]): this;
}
Static Methods
Result.ok(data?) // Success
Result.ignore(message) // Signal not applicable
Result.reject(message) // Validation failed
Result.error(error) // Exception occurred
Result.transition(asyncFn, timeout?) // Async operation
Example:
// In flow handlers
defineFlow(orderState.pending, {
confirm: (state) => {
if (!state.paymentVerified) {
return Result.reject("Payment not verified");
}
if (state.items.length === 0) {
return Result.ignore("No items in order");
}
try {
validateOrder(state);
return orderState.confirmed(state);
} catch (error) {
return Result.error(error);
}
}
});
// In state handlers
sm.addEnterHandler(orderState.processing, (state) => {
return Result.transition(async () => {
try {
await processPayment(state);
await updateInventory(state);
return Result.ok();
} catch (error) {
return Result.error(error);
}
}, 10000); // 10 second timeout
});
Type Utilities
TypeScript utilities for extracting type information:
Infer
Extracts property types from states, variants, or signals.
type UserProps = Infer<typeof userState>; // State properties
type ActiveProps = Infer<typeof userState.active>; // Variant properties
type SignalArgs = Infer<typeof updateSignal>; // Signal arguments
Common utility types:
ExtractVariants<T>
- Get variant namesExtractSignals<T>
- Get signal typesExtractName<T>
- Get state nameArrayToRecord<T>
- Convert state array to record
Error Types
StateFlow uses custom errors for framework issues:
class StateFlowError extends Error {
name: "StateFlowError";
}
Common errors:
- Redefining flows for same state variant
- Dispatching signals during active transitions
- Invalid state configuration (missing name/variants)
The StateFlow API provides type-safe, comprehensive feedback for predictable state management.