npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@flowcreate/core

v0.1.1

Published

Core library for distributed CRDT-backed finite state machines

Readme

@flowcreate/core

Core library for distributed CRDT-backed finite state machines.

Installation

npm install @flowcreate/core

Features

  • Type-Safe FSM Engine - Define and validate state machines with TypeScript
  • CRDT Operation Layer - Automatic conflict resolution using last-write-wins
  • Storage Abstraction - Pluggable storage backends (in-memory, Supabase)
  • HTTP Middleware - Express and Fastify middleware included
  • Comprehensive Testing - Unit tests and property-based tests

Quick Start

Basic Example

import express from 'express';
import { createExpressMiddleware, InMemoryStorage } from '@flowcreate/core';

const app = express();
app.use(express.json());

// Define your state machine
const orderFlow = {
  initial: 'pending',
  states: {
    pending: { events: { pay: 'paid', cancel: 'cancelled' } },
    paid: { events: { ship: 'shipped', refund: 'refunded' } },
    shipped: { events: { deliver: 'delivered' } },
    delivered: { events: {} },
    cancelled: { events: {} },
    refunded: { events: {} }
  }
};

// Create middleware
app.use('/flows', createExpressMiddleware({
  flows: { orders: orderFlow },
  storage: new InMemoryStorage()
}));

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

API Usage

# Apply a transition
curl -X POST http://localhost:3000/flows/orders/order-123/events/pay \
  -H "Content-Type: application/json" \
  -d '{"actorId":"user-1"}'

# Response:
# {
#   "resourceId": "order-123",
#   "state": "paid",
#   "event": "pay"
# }

# Get current state
curl http://localhost:3000/flows/orders/order-123/state

# Response:
# {
#   "resourceId": "order-123",
#   "state": "paid"
# }

API Reference

State Machine Definition

Define your state machine using the StateMachineDefinition type:

import type { StateMachineDefinition } from '@flowcreate/core';

const myFlow: StateMachineDefinition = {
  initial: 'start',  // Initial state
  states: {
    start: {
      events: {
        next: 'middle',    // Event 'next' transitions to 'middle'
        skip: 'end'        // Event 'skip' transitions to 'end'
      }
    },
    middle: {
      events: {
        next: 'end',
        back: 'start'
      }
    },
    end: {
      events: {}  // Terminal state (no outgoing transitions)
    }
  }
};

FSM Engine

The FSM engine validates and executes state transitions:

import { FSMEngine } from '@flowcreate/core';

const engine = new FSMEngine(myFlow);

// Check if a transition is valid
const canTransition = engine.canTransition('start', 'next');  // true

// Execute a transition
const nextState = engine.transition('start', 'next');  // 'middle'

// Get initial state
const initial = engine.getInitialState();  // 'start'

CRDT Engine

The CRDT engine handles distributed state with conflict resolution:

import { CRDTEngine, FSMEngine } from '@flowcreate/core';
import { InMemoryStorage } from '@flowcreate/core';

const fsmEngine = new FSMEngine(myFlow);
const storage = new InMemoryStorage();
const crdtEngine = new CRDTEngine(fsmEngine, storage);

// Apply a transition
const newState = await crdtEngine.applyTransition(
  'resource-123',  // Resource ID
  'next',          // Event
  'user-1',        // Actor ID
  Date.now()       // Timestamp (optional, defaults to Date.now())
);

// Get current state
const currentState = await crdtEngine.getCurrentState('resource-123');

// Get operation history
const operations = await crdtEngine.getOperations('resource-123');

Storage Adapters

In-Memory Storage

import { InMemoryStorage } from '@flowcreate/core';

const storage = new InMemoryStorage();

Supabase Storage

import { SupabaseStorage } from '@flowcreate/core';
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_KEY!
);

const storage = new SupabaseStorage(supabase, 'flowcreate_operations');

Custom Storage Adapter

Implement the StorageAdapter interface:

import type { StorageAdapter, TransitionOperation } from '@flowcreate/core';

class MyStorage implements StorageAdapter {
  async getState(resourceId: string): Promise<string | null> {
    // Return current state or null if resource doesn't exist
  }
  
  async saveOp(operation: TransitionOperation): Promise<void> {
    // Persist the operation
  }
  
  async listOps(resourceId: string): Promise<TransitionOperation[]> {
    // Return all operations for the resource, sorted by timestamp
  }
}

Express Middleware

import { createExpressMiddleware } from '@flowcreate/core';
import type { Request } from 'express';

const middleware = createExpressMiddleware({
  flows: {
    orders: orderFlow,
    tickets: ticketFlow,
    // ... more flows
  },
  storage: new InMemoryStorage(),
  
  // Optional: Custom actor ID extraction
  actorIdExtractor: (req: Request) => {
    return req.user?.id || req.headers['x-user-id'] || 'anonymous';
  }
});

app.use('/flows', middleware);

Endpoints:

  • POST /flows/:resource/:id/events/:event - Apply a transition
  • GET /flows/:resource/:id/state - Get current state

Fastify Middleware

import Fastify from 'fastify';
import { createFastifyMiddleware } from '@flowcreate/core';

const fastify = Fastify();

await fastify.register(createFastifyMiddleware({
  flows: { orders: orderFlow },
  storage: new InMemoryStorage(),
  actorIdExtractor: (req) => req.headers['x-user-id'] || 'anonymous'
}), { prefix: '/flows' });

await fastify.listen({ port: 3000 });

Error Handling

FlowCreate provides specific error types for different failure scenarios:

import {
  ValidationError,
  TransitionError,
  StorageError,
  ResourceNotFoundError
} from '@flowcreate/core';

try {
  await crdtEngine.applyTransition('order-123', 'invalid-event', 'user-1');
} catch (error) {
  if (error instanceof TransitionError) {
    console.log('Invalid transition:', error.message);
    console.log('Resource:', error.resourceId);
    console.log('Current state:', error.currentState);
    console.log('Attempted event:', error.event);
  }
}

Error Types

  • ValidationError - State machine definition is invalid
  • TransitionError - Attempted transition is not allowed
  • StorageError - Storage operation failed
  • ResourceNotFoundError - Requested resource doesn't exist

CRDT Conflict Resolution

FlowCreate uses a last-write-wins (LWW) strategy for conflict resolution:

  1. Each transition is stored as an operation with a timestamp
  2. Operations are sorted by timestamp (ascending)
  3. Ties are broken using actor ID (lexicographic order)
  4. Current state is computed by replaying operations in order
  5. Invalid operations (that violate FSM rules) are skipped during replay

This ensures:

  • Deterministic - Same operations always produce same state
  • Commutative - Order of arrival doesn't matter
  • Eventually Consistent - All nodes converge to the same state

Resource Auto-Initialization

Resources are automatically initialized when first accessed:

// First transition on a new resource
await crdtEngine.applyTransition('new-order', 'pay', 'user-1');
// Resource is created in initial state ('pending') and transition is applied

TypeScript Support

FlowCreate is written in TypeScript and provides full type definitions:

import type {
  StateMachineDefinition,
  TransitionOperation,
  StorageAdapter,
  MiddlewareConfig
} from '@flowcreate/core';

// Generic types for custom state and event names
type OrderState = 'pending' | 'paid' | 'shipped' | 'delivered';
type OrderEvent = 'pay' | 'ship' | 'deliver';

const orderFlow: StateMachineDefinition<OrderState, OrderEvent> = {
  initial: 'pending',
  states: {
    pending: { events: { pay: 'paid' } },
    paid: { events: { ship: 'shipped' } },
    shipped: { events: { deliver: 'delivered' } },
    delivered: { events: {} }
  }
};

Testing

FlowCreate includes comprehensive tests:

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run specific test file
npm test -- fsm.test.ts

License

MIT © FlowCreate Contributors