@contextgraph/storage

Storage abstraction layer with SQLite and in-memory implementations.

Installation

pnpm add @contextgraph/storage

Storage Interface

All storage backends implement the StorageInterface:

interface StorageInterface {
  // Basic operations
  get<T>(key: string): Promise<Result<T | null>>;
  set<T>(key: string, value: T): Promise<Result<void>>;
  delete(key: string): Promise<Result<void>>;
  has(key: string): Promise<Result<boolean>>;

  // Batch operations
  getMany<T>(keys: string[]): Promise<Result<Map<string, T>>>;
  setMany<T>(entries: Map<string, T>): Promise<Result<void>>;
  deleteMany(keys: string[]): Promise<Result<void>>;

  // List and query
  list<T>(prefix: string, options?: ListOptions): Promise<Result<T[]>>;
  query<T>(query: Query): Promise<Result<T[]>>;
  count(prefix: string): Promise<Result<number>>;

  // Lifecycle
  initialize(): Promise<Result<void>>;
  close(): Promise<Result<void>>;
  clear(): Promise<Result<void>>;
}

In-Memory Storage

Fast, ephemeral storage ideal for testing and development:

import { createInMemoryStorage } from '@contextgraph/storage';

const storage = createInMemoryStorage();
await storage.initialize();

// Use storage
await storage.set('key', { name: 'value' });
const result = await storage.get('key');

Features

  • Zero dependencies
  • Fast operations
  • Perfect for testing
  • Data lost on restart

SQLite Storage

Persistent storage for production use:

import { createSQLiteStorage } from '@contextgraph/storage';

const storage = createSQLiteStorage({
  path: './contextgraph.db',
  // Optional configuration
  wal: true,
  busyTimeout: 5000,
});

await storage.initialize();

Features

  • ACID compliance
  • Persistent storage
  • WAL mode support
  • Full-text search (planned)

Configuration

interface SQLiteStorageConfig {
  path: string;
  wal?: boolean;           // Enable WAL mode (default: true)
  busyTimeout?: number;    // Busy timeout in ms (default: 5000)
  pageSize?: number;       // Page size (default: 4096)
}

Queries

List with Prefix

// List all entities
const entities = await storage.list('entity:');

// List with options
const recent = await storage.list('entity:', {
  limit: 10,
  offset: 0,
  sortBy: 'createdAt',
  sortOrder: 'desc',
});

Query with Filters

const results = await storage.query({
  prefix: 'entity:',
  filters: [
    { field: 'type', operator: 'eq', value: 'person' },
    { field: 'createdAt', operator: 'gt', value: timestamp },
  ],
  limit: 50,
});

Query Operators

OperatorDescription
eqEquals
neNot equals
gtGreater than
gteGreater than or equal
ltLess than
lteLess than or equal
inIn array
ninNot in array
containsContains substring
startsWithStarts with
endsWithEnds with

Batch Operations

// Get multiple keys
const entries = await storage.getMany(['key1', 'key2', 'key3']);

// Set multiple entries
await storage.setMany(new Map([
  ['key1', value1],
  ['key2', value2],
]));

// Delete multiple keys
await storage.deleteMany(['key1', 'key2']);

Indexing

The storage layer supports indexes for optimized queries:

// Create index (SQLite)
await storage.createIndex('entity:type', {
  fields: ['type', 'createdAt'],
  unique: false,
});

Query Optimization

For frequent query patterns, use the query optimizer:

import { QueryOptimizer } from '@contextgraph/storage';

const optimizer = new QueryOptimizer(storage);

// Optimize query
const optimizedQuery = optimizer.optimize({
  prefix: 'entity:',
  filters: [...],
  limit: 100,
});

Custom Storage Backends

Implement StorageInterface for custom backends:

import type { StorageInterface, Result } from '@contextgraph/storage';

class RedisStorage implements StorageInterface {
  constructor(private client: RedisClient) {}

  async get<T>(key: string): Promise<Result<T | null>> {
    const value = await this.client.get(key);
    return ok(value ? JSON.parse(value) : null);
  }

  async set<T>(key: string, value: T): Promise<Result<void>> {
    await this.client.set(key, JSON.stringify(value));
    return ok(undefined);
  }

  // Implement remaining methods...
}

Storage Factory

Use the factory for flexible configuration:

import { createStorage } from '@contextgraph/storage';

// Memory
const memStorage = createStorage({ type: 'memory' });

// SQLite
const sqliteStorage = createStorage({
  type: 'sqlite',
  path: './data.db',
});