Skip to content

Storage

Storage adapters manage conversation state persistence. FlowKit includes several storage options out of the box.

Built-in Storage Adapters

MemoryStorage

In-memory storage for development and single-server deployments.

typescript
import { MemoryStorage } from "@andresaya/flowkit";

const storage = new MemoryStorage();
const engine = new FlowEngine(flow, { llm: adapter, storage });

Characteristics:

  • ✅ No setup required
  • ✅ Fast (in-memory)
  • ❌ Data lost on restart
  • ❌ Not shared across servers

FileStorage

JSON file-based storage for simple persistence.

typescript
import { FileStorage } from "@andresaya/flowkit";

const storage = new FileStorage({
    directory: "./data/sessions",  // Where to store files
    pretty: true,                  // Pretty-print JSON (optional)
});

const engine = new FlowEngine(flow, { llm: adapter, storage });

Characteristics:

  • ✅ Persists across restarts
  • ✅ No external dependencies
  • ✅ Easy to inspect/debug
  • ❌ Not suitable for high concurrency
  • ❌ Not shared across servers

API:

typescript
// List all stored conversations
const conversations = await storage.list();
// Returns: ["session-1", "session-2", ...]

// Clean up old conversations (older than 24 hours)
await storage.cleanup(24 * 60 * 60 * 1000);

RedisStorage

Distributed storage using Redis. Perfect for production multi-server deployments.

typescript
import { RedisStorage } from "@andresaya/flowkit";
import Redis from "ioredis";  // npm install ioredis

// Create Redis client
const redisClient = new Redis({
    host: "localhost",
    port: 6379,
    password: "optional-password",
});

// Create storage with client
const storage = new RedisStorage({
    client: redisClient,          // Redis client instance (required)
    prefix: "flowkit:",           // Key prefix (default: "flowkit:")
    ttl: 86400,                   // TTL in seconds (default: 24 hours)
});

const engine = new FlowEngine(flow, { llm: adapter, storage });

Characteristics:

  • ✅ Distributed/shared state
  • ✅ Automatic expiration (TTL)
  • ✅ High performance
  • ⚠️ Requires Redis server
  • ⚠️ Requires ioredis package: npm install ioredis

SQLiteStorage

Local SQL database storage. Great for single-server production deployments.

typescript
import { SQLiteStorage } from "@andresaya/flowkit";
import Database from "better-sqlite3";  // npm install better-sqlite3

// Create database instance
const db = new Database("./data/flowkit.db");

// Create storage with database
const storage = new SQLiteStorage({
    db: db,                       // SQLite database instance (required)
    table: "conversations",       // Table name (default: "conversations")
});

const engine = new FlowEngine(flow, { llm: adapter, storage });

// Additional methods available:
storage.list();           // Get all conversation IDs
storage.clear();          // Delete all conversations
storage.count();          // Get number of conversations

Characteristics:

  • ✅ Persistent storage
  • ✅ SQL query capabilities
  • ✅ Single file, easy backups
  • ⚠️ Single server only
  • ⚠️ Requires better-sqlite3 package: npm install better-sqlite3

StorageAdapter Interface

Create custom storage for other databases or services.

typescript
interface StorageAdapter {
  load(conversationId: string): Promise<ConversationState | null>;
  save(state: ConversationState): Promise<void>;
  delete(conversationId: string): Promise<void>;
}

ConversationState Structure

typescript
interface ConversationState {
  conversationId: string;                 // Conversation/session ID
  currentStep: string;                    // Current step ID
  slots: Record<string, JsonValue>;       // Collected data
  history: Array<{ role: "user" | "assistant"; content: string }>;  // Chat history
  ended: boolean;                         // Flow completed?
  createdAt: number;                      // Creation timestamp
  updatedAt: number;                      // Last update timestamp
}

Custom Storage Examples

MongoDB Storage

typescript
import { MongoClient, Collection } from "mongodb";
import type { StorageAdapter, ConversationState } from "@andresaya/flowkit";

class MongoStorage implements StorageAdapter {
  private collection: Collection;
  
  constructor(uri: string, dbName: string) {
    const client = new MongoClient(uri);
    this.collection = client.db(dbName).collection("sessions");
    this.collection.createIndex({ sessionId: 1 }, { unique: true });
    this.collection.createIndex(
      { updatedAt: 1 }, 
      { expireAfterSeconds: 86400 }  // Auto-expire after 24h
    );
  }
  
  async get(sessionId: string): Promise<ConversationState | null> {
    const doc = await this.collection.findOne({ sessionId });
    return doc?.state || null;
  }
  
  async set(sessionId: string, state: ConversationState): Promise<void> {
    await this.collection.updateOne(
      { sessionId },
      { $set: { state, updatedAt: new Date() } },
      { upsert: true }
    );
  }
  
  async delete(sessionId: string): Promise<void> {
    await this.collection.deleteOne({ sessionId });
  }
}

File-based Storage

typescript
import { readFile, writeFile, unlink, mkdir } from "fs/promises";
import { join } from "path";
import { StorageAdapter, ConversationState } from "@andresaya/flowkit";

class FileStorage implements StorageAdapter {
  private dir: string;
  
  constructor(directory: string = "./sessions") {
    this.dir = directory;
    mkdir(this.dir, { recursive: true });
  }
  
  private path(sessionId: string): string {
    // Sanitize sessionId to prevent directory traversal
    const safe = sessionId.replace(/[^a-zA-Z0-9-_]/g, "_");
    return join(this.dir, `${safe}.json`);
  }
  
  async get(sessionId: string): Promise<ConversationState | null> {
    try {
      const data = await readFile(this.path(sessionId), "utf-8");
      return JSON.parse(data);
    } catch {
      return null;
    }
  }
  
  async set(sessionId: string, state: ConversationState): Promise<void> {
    await writeFile(this.path(sessionId), JSON.stringify(state, null, 2));
  }
  
  async delete(sessionId: string): Promise<void> {
    try {
      await unlink(this.path(sessionId));
    } catch {
      // Ignore if file doesn't exist
    }
  }
}

Accessing Stored Data

Use Engine methods to read/write state:

typescript
// Get all collected data
const slots = engine.getSlots(sessionId);
// { name: "John", email: "john@example.com" }

// Get specific value
const name = engine.getSlot(sessionId, "name");
// "John"

// Update a value
await engine.setSlot(sessionId, "verified", "true");

// Get conversation history
const history = engine.getHistory(sessionId);

// Check if flow ended
const ended = engine.isEnded(sessionId);

// Clear session
await engine.delete(sessionId);

Tips

  1. Use TTL/expiration - Don't let sessions live forever

  2. Index session IDs - For faster lookups in databases

  3. Handle errors gracefully - Storage can fail

  4. Consider serialization - JSON.stringify/parse for objects

  5. Security - Sanitize session IDs, encrypt sensitive data

Released under the MIT License.