nestflow-js
v0.1.15
Published
Workflow and state machine for NestJS
Maintainers
Keywords
Readme
Introduction
Business state mangement with zero dependencies, no vendor lock-in, no infrastructure constraints — just your entities and their transitions.
Features
- 🎯 State Machine Engine: Define workflows with states, transitions, and events
- ⚡ Serverless Optimized: Built for AWS Lambda with durable execution support
- 🔄 Durable Execution: Checkpoint and replay workflows across Lambda invocations using AWS Durable Functions
- 📦 Tree-Shakable: Subpath exports ensure minimal bundle sizes
- 🛡️ Type-Safe: Full TypeScript support with comprehensive type definitions
- 🔁 Retry Logic: Built-in retry mechanisms with exponential backoff
- 🎨 Decorator-Based API: Clean, declarative workflow definitions
Installation
# Using bun
bun add nestflow-js @nestjs/common @nestjs/core reflect-metadata rxjs
# Using npm
npm install nestflow-js @nestjs/common @nestjs/core reflect-metadata rxjs
# Using yarn
yarn add nestflow-js @nestjs/common @nestjs/core reflect-metadata rxjsQuick Start
1. Define Your Entity
export enum OrderStatus {
Pending = 'pending',
Processing = 'processing',
Completed = 'completed',
Failed = 'failed',
}
export class Order {
id: string;
status: OrderStatus;
// ... other properties
}2. Create a Workflow
import { Workflow, OnEvent, Entity, Payload } from 'nestflow-js/core';
@Workflow({
name: 'OrderWorkflow',
states: {
finals: [OrderStatus.Completed, OrderStatus.Failed],
idles: [OrderStatus.Pending],
failed: OrderStatus.Failed,
},
transitions: [
{
from: [OrderStatus.Pending],
to: OrderStatus.Processing,
event: 'order.submit',
},
{
from: [OrderStatus.Processing],
to: OrderStatus.Completed,
event: 'order.complete',
},
],
entityService: 'entity.order',
})
export class OrderWorkflow {
@OnEvent('order.submit')
async onSubmit(@Entity() entity: Order, @Payload() data: any) {
console.log('Order submitted:', entity.id);
return entity;
}
@OnEvent('order.complete')
async onComplete(@Entity() entity: Order) {
console.log('Order completed:', entity.id);
return entity;
}
}3. Implement Entity Service
import { Injectable } from '@nestjs/common';
import { IWorkflowEntity } from 'nestflow-js/core';
@Injectable()
export class OrderEntityService implements IWorkflowEntity<Order, OrderStatus> {
async create(): Promise<Order> {
// Create new order
}
async load(urn: string): Promise<Order | null> {
// Load order from database
}
async update(entity: Order, status: OrderStatus): Promise<Order> {
// Update order status
}
status(entity: Order): OrderStatus {
return entity.status;
}
urn(entity: Order): string {
return entity.id;
}
}4. Register the Module
import { Module } from '@nestjs/common';
import { WorkflowModule } from 'nestflow-js/core';
import { OrderWorkflow } from './order.workflow';
import { OrderEntityService } from './order-entity.service';
@Module({
imports: [
WorkflowModule.register({
entities: [{ provide: 'entity.order', useClass: OrderEntityService }],
workflows: [OrderWorkflow],
}),
],
})
export class OrderModule {}Documentation
Package Structure
The library is organized into tree-shakable subpath exports:
nestflow-js/
├── core # Core workflow engine (decorators, services, types, IWorkflowEvent)
├── adapter # BaseWorkflowAdapter + Durable Lambda adapter for checkpoint/replay execution
└── exception # Custom exception typesImport Only What You Need
// Core workflow engine
import { WorkflowModule, IWorkflowEvent } from 'nestflow-js/core';
// Adapter — base class + built-in durable Lambda adapter
import { BaseWorkflowAdapter, DurableLambdaEventHandler } from 'nestflow-js/adapter';
// Exceptions
import { UnretriableException } from 'nestflow-js/exception';This ensures minimal bundle sizes and faster cold starts in serverless environments.
Transit Result
The transit() method on the orchestrator returns a TransitResult, which adapters use to decide what to do next:
type TransitResult =
| { status: 'final'; state: string | number }
| { status: 'idle'; state: string | number; timeout?: Duration }
| { status: 'continued'; nextEvent: IWorkflowEvent }
| { status: 'no_transition'; state: string | number; timeout?: Duration };final-- the workflow has reached a terminal state.idle-- the workflow is waiting for an external event.continued-- the workflow auto-transitioned and provides the next event to process.no_transition-- no matching transition was found from the current state.
Custom Adapters
Extend BaseWorkflowAdapter to plug the orchestrator into any runtime. The base class owns the orchestration loop and dispatches each TransitResult variant to a handler method you override:
import { BaseWorkflowAdapter } from 'nestflow-js/adapter';
import { OrchestratorService } from 'nestflow-js/core';
import type { IWorkflowEvent, TransitResult } from 'nestflow-js/core';
class MyAdapter extends BaseWorkflowAdapter<MyContext, MyResult> {
constructor(orchestrator: OrchestratorService) {
super(orchestrator);
}
protected async executeTransit(event: IWorkflowEvent): Promise<TransitResult> {
return this.orchestrator.transit(event);
}
protected onFinal(result: Extract<TransitResult, { status: 'final' }>): MyResult {
return { status: 'completed', state: result.state };
}
protected async onIdle(result: Extract<TransitResult, { status: 'idle' }>): Promise<IWorkflowEvent> {
// Wait for callback, poll a queue, etc.
}
protected async onContinued(result: Extract<TransitResult, { status: 'continued' }>): Promise<IWorkflowEvent> {
return result.nextEvent;
}
protected async onNoTransition(result: Extract<TransitResult, { status: 'no_transition' }>): Promise<IWorkflowEvent> {
// Wait for an explicit external event
}
}The built-in DurableLambdaEventHandler extends this base with AWS checkpointing and waitForCallback() support. See the Custom Adapter recipe for full examples.
Examples
Check out the examples directory for complete working examples:
- Lambda Order State Machine: Complete AWS Lambda example with DynamoDB and durable execution
Key Concepts
States
States represent the different stages your entity can be in:
- Finals: Terminal states where the workflow ends
- Idles: States where the workflow waits for external events
- Failed: The failure state to transition to on errors
Transitions
Transitions define how entities move from one state to another, triggered by events:
{
from: [OrderStatus.Pending],
to: OrderStatus.Processing,
event: 'order.submit',
conditions: [
(entity: Order, payload: any) => entity.items.length > 0,
],
}Events
Events trigger state transitions. The IWorkflowEvent interface (from nestflow-js/core) defines the shape of workflow events:
interface IWorkflowEvent<T = any> {
event: string; // event name that triggers a transition
urn: string | number; // unique identifier of the entity
payload?: T; // optional data passed to the handler
attempt: number; // retry attempt counter
}Define event handlers using the @OnEvent decorator:
@OnEvent('order.submit')
async onSubmit(@Entity() entity: Order, @Payload() data: any) {
// Handle the event
}AWS Lambda Integration
The library includes a Durable Lambda adapter (DurableLambdaEventHandler) that leverages AWS Lambda Durable Functions for checkpoint and replay. Each workflow instance runs as a single durable execution spanning multiple Lambda invocations:
- Checkpointed steps -- completed transitions are persisted; on replay they return stored results.
- Idle state pausing -- idle states pause via
waitForCallback()until an external system resumes the workflow. - Final state completion -- reaching a final state ends the durable execution.
import { withDurableExecution } from '@aws/durable-execution-sdk-js';
import { NestFactory } from '@nestjs/core';
import { DurableLambdaEventHandler } from 'nestflow-js/adapter';
import { OrderModule } from './order/order.module';
const app = await NestFactory.createApplicationContext(OrderModule);
await app.init();
export const handler = DurableLambdaEventHandler(app, withDurableExecution);Requirements
- Node.js >= 20.0.0 or Bun >= 1.3.4
- NestJS >= 11.0.0
- TypeScript >= 5.0.0
Contributing
Contributions are welcome! Please read our Contributing Guide for details on:
- Code style and conventions
- Development setup
- Testing guidelines
- Pull request process
License
This project is licensed under the MIT License - see the LICENSE file for details.
Author
Thomas Do (tung-dnt)
- GitHub: @tung-dnt
- Repository: nestflow-js
Support
Related Projects
- NestJS - A progressive Node.js framework
- AWS Lambda - Serverless compute service
- AWS Lambda Durable Functions - Durable execution for Lambda
