0.0.1 • Published 5 months ago
next-machine v0.0.1
My Machine Library
A type-safe state machine library for managing complex workflows in TypeScript, React, and Next.js.
Features
- Type-safe events and transitions: Ensures correctness at compile time.
- Immutable context updates: Uses
immer
for safe and efficient state management. - Error handling: Built-in error handling with custom error handlers.
- Entry/Exit Actions: Support for state entry and exit actions.
- Guards: Conditional transitions based on context state.
- Testing utilities:
createMockMachine
for simplified testing. - Hierarchical States: Support for nested state configurations.
- History States: Track and return to previous states.
- Delayed Transitions: Time-based state transitions.
- Persistence: Built-in adapters for event sourcing and state persistence
- Concurrency Control: Optimistic locking for handling concurrent updates
Installation
npm install my-machine-lib
Basic Usage
import { StateMachine, createState } from 'my-machine-lib';
// Define your context
interface MyContext {
id: string;
count: number;
}
// Define your events
type MyEventMap = {
INCREMENT: void;
DECREMENT: void;
};
const config = {
states: {
idle: createState<MyContext, MyEventMap>('idle')
.on('INCREMENT', 'active', [(ctx) => ({ count: ctx.count + 1 })])
.build(),
active: createState<MyContext, MyEventMap>('active')
.on('DECREMENT', 'idle', [(ctx) => ({ count: ctx.count - 1 })])
.build()
},
initial: 'idle',
context: { id: 'counter-1', count: 0 }
};
const machine = new StateMachine(config);
Persistence
Use the built-in MemoryAdapter or create your own:
import { MemoryAdapter } from 'my-machine-lib';
const persistenceAdapter = new MemoryAdapter<MyContext, MyEventMap>({
maxEventsBeforeSnapshot: 100
});
const machine = new StateMachine({
...config,
persistence: persistenceAdapter
});
Concurrency Control
Handle concurrent updates with optimistic locking:
// Send event with version check
try {
await machine.send(
{ type: 'INCREMENT', data: undefined },
{ expectedVersion: currentVersion }
);
} catch (error) {
if (error instanceof ConcurrencyError) {
// Handle concurrent modification
console.error('Concurrent modification detected');
}
}
// Get current version
const version = machine.getVersion();
Testing
Use the testing utilities for simplified testing:
import { createMockMachine } from 'my-machine-lib';
const { machine, sendEvent, getContext } = createMockMachine({
...config,
persistence: new MemoryAdapter()
});
// Test with version control
await sendEvent(
{ type: 'INCREMENT', data: undefined },
{ expectedVersion: 0 }
);
expect(getContext().count).toBe(1);
expect(machine.getVersion()).toBe(1);
/**
- How to Create a State Machine
- Define event types with data payloads:
- type Events = {
- 'EVENT.NAME': { data: string }; // With payload
- 'EVENT.OTHER': void; // No payload
- };
- Define context interface for state data:
- interface Context {
- someData: string;
- otherData: number;
- }
- Create machine with StateMachine.create<Context, Events>({
- initial: 'startState',
- context: {...}, // Initial context values
- persistence: { // Optional local storage
prefix: 'unique-id'
- },
- states: {
stateName: {
id: 'stateName',
on: {
'EVENT.NAME': {
target: 'nextState',
actions: [(ctx, event, send) => {
// Modify context
Object.assign(ctx, {...});
// Optionally send new events
send({type: 'EVENT.OTHER'});
}]
}
}
}
- }
- });
- Key Rules:
- Context is modified via Object.assign(ctx, {...})
- Use send() to trigger new events from actions
- Each state needs unique id matching its key
- Events can transition between states via target */
License
This library is open-sourced under the MIT License - see the LICENSE file for details.
0.0.1
5 months ago