Skip to content

Storage API

Storage adapters persist conversation state between interactions.

Available Adapters

AdapterPersistenceBest For
MemoryStorageNoneDevelopment, testing
FileStorageFile-basedSingle server, prototypes
RedisStorageRedisProduction, distributed
SQLiteStorageSQLiteSingle server production

MemoryStorage

In-memory storage. Data is lost when the process ends.

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

const storage = new MemoryStorage();

WARNING

Only for development. Data is lost on restart.


FileStorage

Persists to JSON files.

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

const storage = new FileStorage({
  dir: "./data/conversations",
  pretty: true,
});

Configuration

typescript
interface FileStorageOptions {
  dir: string;          // Path to storage directory
  extension?: string;   // File extension (default: ".json")
  pretty?: boolean;     // Pretty print JSON (default: false)
}

RedisStorage

Persists to Redis. Ideal for production and distributed systems.

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

const client = new Redis(process.env.REDIS_URL ?? "redis://localhost:6379");

const storage = new RedisStorage({
  client,
  prefix: "flowkit:",
  ttl: 3600, // 1 hour expiry
});

Configuration

typescript
interface RedisStorageOptions {
  client: RedisClient; // ioredis client instance
  prefix?: string;     // Key prefix (default: "flowkit:")
  ttl?: number;        // TTL in seconds (default: 86400)
}

SQLiteStorage

Persists to SQLite database.

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

const db = new Database("./data/conversations.db");

const storage = new SQLiteStorage({
  db,
  table: "conversations",
});

Configuration

typescript
interface SQLiteStorageOptions {
  db: SQLiteDatabase;  // better-sqlite3 instance
  table?: string;      // Table name (default: "conversations")
}

StorageAdapter Interface

All adapters implement:

typescript
interface StorageAdapter {
  /** Load conversation state */
  load(conversationId: string): Promise<ConversationState | null>;
  
  /** Save conversation state */
  save(state: ConversationState): Promise<void>;
  
  /** Delete conversation state */
  delete(conversationId: string): Promise<void>;
}

ConversationState Type

typescript
interface ConversationState {
  conversationId: string;
  currentStep: string;
  slots: Record<string, JsonValue>;
  history: Array<{ role: "user" | "assistant"; content: string }>;
  ended: boolean;
  createdAt: number;
  updatedAt: number;
}

Custom Storage Adapter

Create your own adapter:

typescript
import { StorageAdapter, ConversationState } from "@andresaya/flowkit";

class PostgresStorage implements StorageAdapter {
  constructor(private pool: Pool) {}

  async load(conversationId: string): Promise<ConversationState | null> {
    const result = await this.pool.query(
      "SELECT state FROM conversations WHERE id = $1",
      [conversationId]
    );
    return result.rows[0]?.state ?? null;
  }

  async save(state: ConversationState): Promise<void> {
    await this.pool.query(
      `INSERT INTO conversations (id, state, updated_at)
       VALUES ($1, $2, NOW())
       ON CONFLICT (id) DO UPDATE SET state = $2, updated_at = NOW()`,
      [state.conversationId, state]
    );
  }

  async delete(conversationId: string): Promise<void> {
    await this.pool.query(
      "DELETE FROM conversations WHERE id = $1",
      [conversationId]
    );
  }
}

Usage in FlowEngine

typescript
const engine = new FlowEngine(flow, {
  llm: adapter,
  storage: new RedisStorage({
    host: process.env.REDIS_HOST,
    ttl: 3600
  })
});

Accessing State

Through the engine:

typescript
// Get all slots
const slots = await engine.getSlots(sessionId);

// Get specific slot
const name = await engine.getSlot(sessionId, "user_name");

// Set a slot
await engine.setSlot(sessionId, "verified", true);

// Get full state
const state = await engine.getState(sessionId);

// Delete conversation
await engine.delete(sessionId);

Best Practices

Choose the Right Storage

  • Development: MemoryStorage
  • Single server: FileStorage or SQLiteStorage
  • Production: RedisStorage

Set TTL

For Redis, always set a TTL to prevent memory issues:

typescript
new RedisStorage({ ttl: 3600 }) // 1 hour

Session IDs

Use meaningful session IDs:

typescript
const sessionId = `user-${userId}-${Date.now()}`;

Released under the MIT License.