Drizzle Storage Plugin
Multi-database storage for agent state checkpoints using Drizzle ORM with support for SQLite, MySQL, and PostgreSQL.
Overview
The @tokenring-ai/drizzle-storage package provides a production-ready, multi-database storage solution for managing agent state checkpoints in the Token Ring AI system. It implements the AgentCheckpointProvider interface with support for SQLite (Bun), MySQL, and PostgreSQL databases using Drizzle ORM for type-safe operations and automatic schema migrations.
Key Features
- Multi-Database Support: SQLite (Bun), MySQL, PostgreSQL
- Type-Safe Operations: Full TypeScript type safety with Drizzle ORM
- Automatic Migrations: Runtime migration application using codebase-first approach
- Connection Pooling: Built-in pooling for MySQL and PostgreSQL
- Unified Interface: Same API across all database types
- Production Ready: Comprehensive testing with Docker containers
Core Components
Storage Factory Functions
The package provides separate factory functions for each database type, all implementing the AgentCheckpointProvider interface.
Factory Functions:
createSQLiteStorage(config: { type: "sqlite", databasePath: string })
createMySQLStorage(config: { type: "mysql", connectionString: string })
createPostgresStorage(config: { type: "postgres", connectionString: string })
Key Methods:
-
storeCheckpoint(checkpoint: NamedAgentCheckpoint): Promise<string>- Stores or updates a named checkpoint
- Parameters:
{ agentId, name, state, createdAt } - Returns: ID of stored checkpoint
-
retrieveCheckpoint(id: string): Promise<StoredAgentCheckpoint | null>- Retrieves a checkpoint by ID
- Parses JSON state from database
- Returns checkpoint with parsed state, or null if not found
-
listCheckpoints(): Promise<AgentCheckpointListItem[]>- Lists all checkpoints ordered by creation time (descending)
- Returns array with ID, name, agentId, createdAt (excludes state)
Database Schema
All databases use the same logical schema:
-- SQLite
CREATE TABLE agent_state (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT NOT NULL,
name TEXT NOT NULL,
state TEXT NOT NULL,
created_at INTEGER NOT NULL
);
-- MySQL
CREATE TABLE agent_state (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
agent_id TEXT NOT NULL,
name TEXT NOT NULL,
state TEXT NOT NULL,
created_at BIGINT NOT NULL
);
-- PostgreSQL
CREATE TABLE agent_state (
id BIGSERIAL PRIMARY KEY,
agent_id TEXT NOT NULL,
name TEXT NOT NULL,
state TEXT NOT NULL,
created_at BIGINT NOT NULL
);
Migration Strategy
Follows Drizzle's codebase-first approach:
- Schema Definition: TypeScript schema in
db/schema.tsas source of truth - Migration Generation:
bun run db:generatecreates SQL files - Runtime Application: Migrations auto-apply on storage initialization
- Tracking: Drizzle maintains migration history in database
Usage Examples
SQLite
import { createSQLiteStorage } from '@tokenring-ai/drizzle-storage/sqlite/createSQLiteStorage';
const storage = createSQLiteStorage({
type: "sqlite",
databasePath: "./agent_state.db"
});
const checkpoint = {
agentId: "agent-123",
name: "session-1",
state: { messages: ["Hello"], count: 1 },
createdAt: Date.now()
};
const id = await storage.storeCheckpoint(checkpoint);
const retrieved = await storage.retrieveCheckpoint(id);
MySQL
import { createMySQLStorage } from '@tokenring-ai/drizzle-storage/mysql/createMySQLStorage';
const storage = createMySQLStorage({
type: "mysql",
connectionString: "mysql://user:pass@localhost:3306/dbname"
});
// Same API as SQLite
const id = await storage.storeCheckpoint(checkpoint);
PostgreSQL
import { createPostgresStorage } from '@tokenring-ai/drizzle-storage/postgres/createPostgresStorage';
const storage = createPostgresStorage({
type: "postgres",
connectionString: "postgres://user:pass@localhost:5432/dbname"
});
// Same API as SQLite
const id = await storage.storeCheckpoint(checkpoint);
Token Ring Configuration
// .tokenring/coder-config.mjs
export default {
checkpoint: {
providers: {
main: {
type: "postgres",
connectionString: process.env.DATABASE_URL
}
}
}
};
Full Workflow
import { createPostgresStorage } from '@tokenring-ai/drizzle-storage/postgres/createPostgresStorage';
async function agentWorkflow() {
const storage = createPostgresStorage({
type: "postgres",
connectionString: process.env.DATABASE_URL
});
// Store initial state
const id = await storage.storeCheckpoint({
agentId: 'my-agent',
name: 'initial',
state: { step: 0 },
createdAt: Date.now()
});
// Retrieve and update
const current = await storage.retrieveCheckpoint(id);
if (current) {
current.state.step += 1;
await storage.storeCheckpoint({ ...current, createdAt: Date.now() });
}
// List all checkpoints
const checkpoints = await storage.listCheckpoints();
console.log('All checkpoints:', checkpoints);
}
Configuration Options
SQLite Configuration
- type:
"sqlite"(required) - databasePath:
string(required) – Path to SQLite file
MySQL Configuration
- type:
"mysql"(required) - connectionString:
string(required) – MySQL connection string- Format:
mysql://user:password@host:port/database - Supports connection pooling automatically
- Format:
PostgreSQL Configuration
- type:
"postgres"(required) - connectionString:
string(required) – PostgreSQL connection string- Format:
postgres://user:password@host:port/database - Supports connection pooling automatically
- Format:
API Reference
Factory Functions
createSQLiteStorage(config: { type: "sqlite", databasePath: string }): AgentCheckpointProvidercreateMySQLStorage(config: { type: "mysql", connectionString: string }): AgentCheckpointProvidercreatePostgresStorage(config: { type: "postgres", connectionString: string }): AgentCheckpointProvider
AgentCheckpointProvider Interface
storeCheckpoint(checkpoint: NamedAgentCheckpoint): Promise<string>retrieveCheckpoint(id: string): Promise<StoredAgentCheckpoint | null>listCheckpoints(): Promise<AgentCheckpointListItem[]>
Types
NamedAgentCheckpoint:{ agentId: string; name: string; state: any; createdAt: number }StoredAgentCheckpoint:{ id: string; name: string; agentId: string; state: any; createdAt: number }AgentCheckpointListItem:{ id: string; name: string; agentId: string; createdAt: number }
Dependencies
Runtime:
drizzle-orm: Type-safe ORMmysql2: MySQL driverpostgres: PostgreSQL driver@tokenring-ai/ai-client: Token Ring integrationbun:sqlite: SQLite runtime (Bun)
Development:
drizzle-kit: Migration generatorvitest: Testing frameworktestcontainers: Docker container management for tests
Testing
Run comprehensive tests with automatic Docker container provisioning:
bun run test
Test coverage:
- SQLite with local file database
- MySQL with Docker container (mysql:8.0)
- PostgreSQL with Docker container (postgres:16)
- CRUD operations for all database types
- Edge cases (non-existent records, etc.)
Migration Management
Generate Migrations
After modifying the schema in db/schema.ts:
bun run db:generate
This generates SQL migration files for all three database types:
db/migrations/– SQLite migrationsdb/migrations-mysql/– MySQL migrationsdb/migrations-postgres/– PostgreSQL migrations
Migration Application
Migrations are automatically applied at runtime when the storage is initialized. Drizzle tracks applied migrations in the database to prevent duplicate execution.
Advantages over SQLite Storage
- ✅ Multi-database support (production-ready MySQL/PostgreSQL)
- ✅ Type-safe queries with Drizzle ORM
- ✅ Automatic migration management
- ✅ Better scalability for production workloads
- ✅ Connection pooling for MySQL/PostgreSQL
- ✅ Comprehensive test coverage with real databases
- ✅ Same interface as sqlite-storage (drop-in replacement)
Notes
- State must be JSON-serializable
- Migrations auto-apply on initialization
- Connection pooling enabled for MySQL/PostgreSQL
- SQLite requires Bun runtime
- Suitable for production workloads with MySQL/PostgreSQL
- Drop-in replacement for
@tokenring-ai/sqlite-storage