Skip to content

Storage

Meloqui provides storage backends for persisting conversation history across sessions.

IStorage

Interface for conversation history storage implementations.

typescript
interface IStorage {
  /** Save messages for a conversation */
  save(conversationId: string, messages: Message[]): Promise<void>;

  /** Load messages for a conversation */
  load(conversationId: string): Promise<Message[]>;

  /** Delete a conversation and all its messages */
  delete(conversationId: string): Promise<void>;

  /** List all conversation IDs */
  list(): Promise<string[]>;
}

InMemoryStorage

In-memory storage using a Map. Messages are not persisted across process restarts.

typescript
import { ChatClient, InMemoryStorage } from 'meloqui';

const storage = new InMemoryStorage();

const client = new ChatClient({
  provider: 'openai',
  model: 'gpt-4o',
  storage,
  conversationId: 'my-conversation'
});

TIP

InMemoryStorage is the default if no storage is specified. Each ChatClient instance maintains its own isolated storage.

FileStorage

File-based storage using JSON files. Each conversation is stored in a separate file.

typescript
import { ChatClient, FileStorage } from 'meloqui';

const storage = new FileStorage('./conversations');

const client = new ChatClient({
  provider: 'openai',
  model: 'gpt-4o',
  storage,
  conversationId: 'my-conversation'
});

FileStorageOptions

typescript
interface FileStorageOptions {
  /** File format for storage (default: 'json') */
  format?: 'json';
}

Security

FileStorage includes security measures:

  • Path traversal protection: IDs with .. are rejected
  • Filename sanitization: Only alphanumeric, hyphens, underscores, and dots allowed
  • Hidden file prevention: IDs starting with . are rejected
  • Directory containment: Resolved paths must stay within the base directory
typescript
// These will throw InvalidConversationIdError
storage.save('../../../etc/passwd', []);   // Path traversal
storage.save('.hidden', []);                // Hidden file
storage.save('a/b/c', []);                  // Path separators sanitized to underscores

testStorageImplementation

Test helper to verify custom storage implementations.

typescript
import { testStorageImplementation, IStorage } from 'meloqui';

class MyCustomStorage implements IStorage {
  // ... implementation
}

// Run standard test suite
testStorageImplementation(
  'MyCustomStorage',
  () => new MyCustomStorage()
);

This runs a comprehensive test suite covering:

  • Save and load roundtrip
  • Empty conversation handling
  • Delete behavior
  • List functionality
  • Message isolation between conversations

Custom Storage Implementation

Implement IStorage to create custom backends (Redis, PostgreSQL, etc.):

typescript
import { IStorage, Message } from 'meloqui';

class RedisStorage implements IStorage {
  private redis: RedisClient;

  constructor(redisUrl: string) {
    this.redis = createClient({ url: redisUrl });
  }

  async save(conversationId: string, messages: Message[]): Promise<void> {
    await this.redis.set(
      `conv:${conversationId}`,
      JSON.stringify(messages)
    );
  }

  async load(conversationId: string): Promise<Message[]> {
    const data = await this.redis.get(`conv:${conversationId}`);
    return data ? JSON.parse(data) : [];
  }

  async delete(conversationId: string): Promise<void> {
    await this.redis.del(`conv:${conversationId}`);
  }

  async list(): Promise<string[]> {
    const keys = await this.redis.keys('conv:*');
    return keys.map(k => k.replace('conv:', ''));
  }
}

Released under the MIT License.