@rustyrush/dispatch
v0.0.7
Published
A lightweight TypeScript state machine library inspired by XState. Provides predictable state management with event-driven transitions and validation.
Downloads
195
Maintainers
Readme
Dispatch - Type-Safe State Machines
A lightweight, type-safe state machine library for TypeScript. Get autocomplete for events, runtime validation, and optional schema validation.
✨ Features
- 🔒 Fully Type-Safe: Event names, state, and payloads are all type-checked
- 🎯 IDE Autocomplete: Your editor suggests valid event names
- ✅ Runtime Validation: Catches invalid transitions and event references
- 🔄 Immutable Updates: Powered by Immer for clean state updates
- 🪝 Simple API: Easy to learn, powerful to use
- 🎨 Flexible Patterns: Support for both return-style and draft-style updates
📦 Installation
npm install dispatch
# or
bun add dispatch🚀 Quick Start
import { createDispatch } from "dispatch";
const counter = createDispatch({
initialState: { count: 0 },
events: {
increment: (state: { count: number }) => ({ count: state.count + 1 }),
decrement: (state: { count: number }) => ({ count: state.count - 1 }),
reset: () => ({ count: 0 }),
},
validNextEvents: {
increment: ["increment", "decrement", "reset"],
decrement: ["increment", "decrement", "reset"],
reset: ["increment"],
},
});
// Subscribe to state changes
counter.subscribe((state) => {
console.log("Count:", state.count);
});
// Dispatch events
counter.dispatch("increment"); // ✅ count = 1
counter.dispatch("increment"); // ✅ count = 2
counter.dispatch("decrement"); // ✅ count = 1📚 API Reference
createDispatch(config)
Create a type-safe state machine.
const machine = createDispatch({
initialState: Data,
events: {
[eventName]: (state: Data, payload?: any) => Partial<Data> | void
},
validNextEvents: {
[eventName]: string[]
},
schema?: z.ZodSchema<Data> // Optional Zod schema
});dispatch(eventName, payload?)
Trigger a state transition.
machine.dispatch("increment");
machine.dispatch("setValue", { value: 42 });subscribe(listener)
Listen to state changes. Returns unsubscribe function.
const unsubscribe = machine.subscribe((state) => {
console.log(state);
});
unsubscribe(); // Stop listeninggetState(): State
Get the current state (returns a copy).
const state = dispatch.getState();
console.log(state.count);getCurrentEvent(): string | null
Get the name of the last event that was dispatched.
dispatch.send("increment");
console.log(dispatch.getCurrentEvent()); // "increment"getValidNextEvents(): string[]
Get the list of valid events that can be dispatched next.
dispatch.send("increment");
console.log(dispatch.getValidNextEvents()); // ["increment", "decrement"]resetState(): void
Reset the state back to the initial state and clear the current event.
dispatch.resetState();🛡️ Zod Schema Validation
Validate your state shape at runtime:
import { z } from "zod";
import { createValidatedDispatch } from "dispatch";
const UserSchema = z.object({
name: z.string().min(1),
age: z.number().min(0).max(150),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>;
const user = createValidatedDispatch({
schema: UserSchema,
initialState: {
name: "John",
age: 30,
email: "[email protected]",
} as User,
events: {
updateName: (state: User, name: string) => ({ name }),
updateAge: (state: User, age: number) => ({ age }),
},
validNextEvents: {
updateName: ["updateAge"],
updateAge: ["updateName"],
},
});Draft-Style Updates (Immer)
Mutate drafts directly for complex updates:
const todos = createDispatch({
initialState: {
items: [] as Array<{ id: number; text: string; done: boolean }>,
},
events: {
addTodo: (draft, text: string) => {
draft.items.push({ id: Date.now(), text, done: false });
},
toggleTodo: (draft, id: number) => {
const todo = draft.items.find((t) => t.id === id);
if (todo) todo.done = !todo.done;
},
},
validNextEvents: {
addTodo: ["addTodo", "toggleTodo"],
toggleTodo: ["addTodo", "toggleTodo"],
},
});💡 Patterns
React Integration
Dispatch integrates seamlessly with React:
import { createDispatch, useDispatch, useSelector, useMachine } from "dispatch";
const counter = createDispatch({
initialState: { count: 0 },
events: {
increment: (state) => ({ count: state.count + 1 }),
decrement: (state) => ({ count: state.count - 1 }),
},
validNextEvents: {
increment: ["increment", "decrement"],
decrement: ["increment", "decrement"],
},
});
// Option 1: Subscribe to full state
function Counter() {
const state = useDispatch(counter);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => counter.dispatch("increment")}>+</button>
<button onClick={() => counter.dispatch("decrement")}>-</button>
</div>
);
}
// Option 2: Select derived values
function DoubleCounter() {
const double = useSelector(counter, (state) => state.count * 2);
return <p>Double: {double}</p>;
}
// Option 3: Full machine API
function FullCounter() {
const [state, dispatch, machine] = useMachine(counter);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch("increment")}>+</button>
<p>Valid: {machine.getValidNextEvents().join(", ")}</p>
</div>
);
}React Hooks API
useDispatch(machine)- Subscribe to full stateuseSelector(machine, selector)- Subscribe to derived valueuseCurrentEvent(machine)- Get current event nameuseValidNextEvents(machine)- Get valid next eventsuseMachine(machine)- Returns[state, dispatch, machine]tuple
⚠️ Error Handling
Invalid Event
try {
machine.dispatch("nonexistent");
} catch (error) {
// ❌ Error: Event "nonexistent" does not exist
}🎯 Type Safety in Action
Event Name Autocomplete
Event names are fully type-safe with IDE autocomplete:
counter.dispatch("increment"); // ✅ Autocomplete suggests: "increment" | "decrement" | "reset"
counter.dispatch("invalid"); // ❌ TypeScript error: Argument of type '"invalid"' is not assignableValidation in validNextEvents
validNextEvents: {
increment: ["dec"], // ❌ TypeScript error!
// ^^^^^
// Type '"dec"' is not assignable to type '"increment" | "decrement" | "reset"'
}Runtime Safety
try {
counter.dispatch("invalidEvent"); // ❌ Runtime error
} catch (error) {
// Error: Event "invalidEvent" does not exist
}🆚 Why Not XState?
| Feature | Dispatch | XState | | ------------------- | ----------- | ---------- | | Learning Curve | Simple | Steeper | | Type Safety | ✅ Built-in | ⚠️ Complex | | Object Schema | ✅ | ❌ | | State Machines | ✅ | ✅ | | Hierarchical | ❌ | ✅ | | Parallel States | ❌ | ✅ | | Actors | ❌ | ✅ |
Choose Dispatch for simple, type-safe state machines with simple DX.
Choose XState for complex hierarchical/parallel state charts.
📖 Learn More
- Examples - Real-world usage examples
License
MIT
