Skip to content

tvilela88/conversational-flow-engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

conversational-flow-engine

Type-safe, phase-based conversational state machine with lifecycle hooks and Redis persistence.

TypeScript License: MIT Node.js

Features

  • Phase-based state machine — define conversation flows as numbered or named phases
  • Type-safe genericsConversationState<TPhase, TData> parameterized to your domain
  • Lifecycle hooksonEnter, onMessage, onAction, onExit per phase
  • Automatic transitions — chain phases together with auto-transition support
  • Redis persistence — conversation state survives server restarts
  • Validation framework — required fields, custom validators, merge helpers
  • Injectable logger — bring your own pino/winston or use the console default
  • Handler registry — dynamic handler registration and lookup

Architecture

graph TD
    A[User Message/Action] --> B[ConversationOrchestrator]
    B --> C[StateManager.getState]
    C --> D{Active?}
    D -->|No| E[Throw Error]
    D -->|Yes| F[HandlerRegistry.getHandler]
    F --> G[Handler.onMessage / onAction]
    G --> H{nextPhase?}
    H -->|No| I[Return Response]
    H -->|Yes| J[Validate Transition]
    J --> K[Handler.onExit]
    K --> L[StateManager.transitionPhase]
    L --> M[NewHandler.onEnter]
    M --> N{Chained Transition?}
    N -->|Yes| J
    N -->|No| I

    subgraph Redis
        O[(State)] --- P[(Transition Log)]
    end
    C -.-> O
    L -.-> P
Loading

Quick Start

Installation

npm install

Define Your Phases

import type { PhaseConfig } from "conversational-flow-engine";

enum OrderPhase {
  WELCOME = 1,
  SELECT_ITEM = 2,
  CHECKOUT = 3,
  CONFIRMATION = 4,
}

interface OrderData {
  item?: string;
  quantity?: number;
  email?: string;
}

const transitions = new Map<OrderPhase, OrderPhase[]>([
  [OrderPhase.WELCOME, [OrderPhase.SELECT_ITEM]],
  [OrderPhase.SELECT_ITEM, [OrderPhase.CHECKOUT]],
  [OrderPhase.CHECKOUT, [OrderPhase.CONFIRMATION, OrderPhase.SELECT_ITEM]],
  [OrderPhase.CONFIRMATION, []],
]);

Implement Phase Handlers

import { BasePhaseHandler } from "conversational-flow-engine";

class WelcomeHandler extends BasePhaseHandler<OrderPhase, OrderData> {
  constructor() {
    super({
      config: {
        phase: OrderPhase.WELCOME,
        name: "Welcome",
        description: "Greet the user",
        required: true,
        canSkip: false,
        canGoBack: false,
        autoTransition: false,
      },
      validTransitions: [OrderPhase.SELECT_ITEM],
      totalPhases: 4,
    });
  }

  async onEnter(state) {
    return this.createResponse("Welcome! What would you like to order?", {
      nextPhase: OrderPhase.SELECT_ITEM,
    });
  }

  async onMessage(state, message) {
    return this.createResponse("Let me help you find something.", {
      nextPhase: OrderPhase.SELECT_ITEM,
    });
  }

  async onAction(state, action, data) {
    return this.createResponse("Let's get started!");
  }
}

Wire It Up

import Redis from "ioredis";
import {
  ConversationOrchestrator,
  PhaseHandlerRegistry,
} from "conversational-flow-engine";

const redis = new Redis();
const registry = new PhaseHandlerRegistry<OrderPhase, OrderData>();
registry.register(new WelcomeHandler());
// ... register other handlers

const orchestrator = new ConversationOrchestrator<OrderPhase, OrderData>({
  redis,
  handlerRegistry: registry,
  initialPhase: OrderPhase.WELCOME,
  validTransitions: transitions,
});

// Start a conversation
const result = await orchestrator.startConversation("session-123");
console.log(result.response.message);

// Handle user messages
const msgResult = await orchestrator.handleMessage({
  sessionId: "session-123",
  message: "I want a large coffee",
});

// Handle button actions
const actionResult = await orchestrator.handleAction({
  sessionId: "session-123",
  action: "confirm_order",
});

Project Structure

src/
├── index.ts              # Barrel exports
├── types.ts              # Generic types (ConversationState, PhaseHandler, etc.)
├── logger.ts             # Injectable logger interface + console default
├── orchestrator.ts       # Main orchestrator (message routing, transitions)
├── state-manager.ts      # Redis-only state persistence
├── base-handler.ts       # Abstract base class with validation helpers
└── handler-registry.ts   # Dynamic handler registration

examples/
└── pizza-ordering/       # Complete 5-phase example
    ├── index.ts
    ├── phases.ts
    └── handlers/
        ├── welcome.handler.ts
        ├── size.handler.ts
        └── confirmation.handler.ts

Key Concepts

ConversationState

The core state object, parameterized by your phase enum and data type:

interface ConversationState<TPhase, TData> {
  conversationId: string;
  sessionId: string;
  currentPhase: TPhase;
  phaseData: Record<string, any>; // Temporary, reset on transition
  collectedData: TData; // Persistent across phases
  status: ConversationStatus;
  // ... timestamps, metadata
}

Phase Lifecycle

Each phase handler implements four lifecycle methods:

Method When Called Purpose
onEnter Transitioning into this phase Show initial UI/message
onMessage User sends text in this phase Process conversational input
onAction User clicks a button/action Handle discrete interactions
onExit Transitioning out of this phase Cleanup, logging

Transition Validation

Transitions are validated against a Map<TPhase, TPhase[]>:

// Only these transitions are allowed
const transitions = new Map([
  [Phase.A, [Phase.B, Phase.C]], // A can go to B or C
  [Phase.B, [Phase.C]],          // B can only go to C
  [Phase.C, []],                  // C is terminal
]);

State Persistence

All state is stored in Redis with configurable TTL:

  • State key: {prefix}:state:{sessionId}
  • Transitions key: {prefix}:transitions:{conversationId}
  • Default TTL: 2 hours

License

MIT

About

Type-safe phase-based conversational state machine with lifecycle hooks and Redis persistence

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages