0.0.1 • Published 5 months ago

next-machine v0.0.1

Weekly downloads
-
License
-
Repository
-
Last release
5 months ago

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
    1. Define event types with data payloads:
  • type Events = {
  • 'EVENT.NAME': { data: string }; // With payload
  • 'EVENT.OTHER': void; // No payload
  • };
    1. Define context interface for state data:
  • interface Context {
  • someData: string;
  • otherData: number;
  • }
    1. 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.