import * as fs from 'fs/promises';
import * as path from 'path';
import { Conversation } from '../types/conversation.js';
import { Store } from './Store.js';
interface FSError extends Error {
code?: string;
message: string;
}
function isFSError(error: unknown): error is FSError {
return error instanceof Error && ('code' in error || 'message' in error);
}
export class FileSystemStore implements Store {
private dataPath: string;
private initialized: boolean = false;
constructor(dataPath: string) {
this.dataPath = dataPath;
}
async initialize(): Promise<void> {
if (this.initialized) {
return;
}
try {
await fs.mkdir(this.dataPath, { recursive: true });
this.initialized = true;
} catch (error) {
throw new Error(`Failed to initialize store: ${error instanceof Error ? error.message : String(error)}`);
}
}
private getConversationPath(id: string): string {
return path.join(this.dataPath, `${id}.json`);
}
async saveConversation(conversation: Conversation): Promise<void> {
const filePath = this.getConversationPath(conversation.id);
try {
await fs.writeFile(filePath, JSON.stringify(conversation, null, 2));
} catch (error) {
throw new Error(`Failed to save conversation: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getConversation(id: string): Promise<Conversation | null> {
const filePath = this.getConversationPath(id);
try {
const data = await fs.readFile(filePath, 'utf-8');
return JSON.parse(data) as Conversation;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
return null;
}
throw new Error(`Failed to read conversation: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listConversations(): Promise<Conversation[]> {
try {
const files = await fs.readdir(this.dataPath);
const conversations: Conversation[] = [];
for (const file of files) {
if (path.extname(file) === '.json') {
try {
const data = await fs.readFile(path.join(this.dataPath, file), 'utf-8');
conversations.push(JSON.parse(data) as Conversation);
} catch (error) {
console.error(`Failed to read conversation file ${file}:`, error);
}
}
}
return conversations;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
return [];
}
throw new Error(`Failed to list conversations: ${error instanceof Error ? error.message : String(error)}`);
}
}
async deleteConversation(id: string): Promise<void> {
const filePath = this.getConversationPath(id);
try {
await fs.unlink(filePath);
} catch (error) {
if (isFSError(error)) {
if (error.code !== 'ENOENT') {
throw new Error(`Failed to delete conversation ${id}: ${error.message}`);
}
} else {
throw new Error(`Failed to delete conversation ${id}: Unknown error`);
}
}
}
}