UNPKG

@aituber-onair/kizuna

Version:

A sophisticated bond system (絆 - Kizuna) for managing relationships between users and AI characters in AITuber OnAir.

180 lines 6 kB
/** * ExternalStorageProvider - Storage provider with dependency injection * * Allows users to provide their own file system implementation * for Node.js, Deno, or any other environment */ import { StorageProvider, StorageError, StorageErrorCode, } from "./StorageProvider"; const DEFAULT_DATA_DIR = "./kizuna-data"; /** * ExternalStorageProvider * Data persistence using external file system adapter */ export class ExternalStorageProvider extends StorageProvider { constructor(adapter, config = {}) { super(); this.adapter = adapter; this.config = { dataDir: DEFAULT_DATA_DIR, encoding: "utf8", prettyJson: true, autoCreateDir: true, ...config, }; } /** * Save data */ async save(key, data) { try { // Check and create directory if (this.config.autoCreateDir) { await this.ensureDirectoryExists(); } const filePath = this.getFilePath(key); const jsonData = this.config.prettyJson ? JSON.stringify(data, null, 2) : JSON.stringify(data); await this.adapter.writeFile(filePath, jsonData); } catch (error) { throw new StorageError(`Failed to save data to file: ${error}`, StorageErrorCode.SAVE_ERROR, error); } } /** * Load data */ async load(key) { try { const filePath = this.getFilePath(key); if (!(await this.adapter.exists(filePath))) { return null; } const jsonData = await this.adapter.readFile(filePath); return JSON.parse(jsonData); } catch (error) { throw new StorageError(`Failed to load data from file: ${error}`, StorageErrorCode.LOAD_ERROR, error); } } /** * Remove data */ async remove(key) { try { const filePath = this.getFilePath(key); if (await this.adapter.exists(filePath)) { await this.adapter.deleteFile(filePath); } } catch (error) { throw new StorageError(`Failed to remove data file: ${error}`, StorageErrorCode.REMOVE_ERROR, error); } } /** * Clear storage */ async clear() { try { if (!(await this.adapter.exists(this.config.dataDir))) { return; } const files = await this.adapter.listFiles(this.config.dataDir); const jsonFiles = files.filter((file) => file.endsWith(".json")); await Promise.all(jsonFiles.map((file) => this.adapter.deleteFile(this.adapter.joinPath(this.config.dataDir, file)))); } catch (error) { throw new StorageError(`Failed to clear storage: ${error}`, StorageErrorCode.CLEAR_ERROR, error); } } /** * Get all keys */ async getAllKeys() { try { if (!(await this.adapter.exists(this.config.dataDir))) { return []; } const files = await this.adapter.listFiles(this.config.dataDir); return files .filter((file) => file.endsWith(".json")) .map((file) => file.slice(0, -5)); // Remove .json extension } catch (error) { throw new StorageError(`Failed to get keys: ${error}`, StorageErrorCode.LOAD_ERROR, error); } } /** * Check if storage is available */ isAvailable() { return this.adapter !== null && typeof this.adapter === "object"; } /** * Check if data can be stored */ async canStore(data) { try { JSON.stringify(data); return true; } catch { return false; } } /** * Get storage information */ async getStorageInfo() { try { let totalSize = 0; let itemCount = 0; if (await this.adapter.exists(this.config.dataDir)) { const files = await this.adapter.listFiles(this.config.dataDir); const jsonFiles = files.filter((file) => file.endsWith(".json")); if (this.adapter.getFileStats) { for (const file of jsonFiles) { const filePath = this.adapter.joinPath(this.config.dataDir, file); const stats = await this.adapter.getFileStats(filePath); totalSize += stats.size; itemCount++; } } else { // Fallback: estimate size from file contents for (const file of jsonFiles) { const filePath = this.adapter.joinPath(this.config.dataDir, file); const content = await this.adapter.readFile(filePath); totalSize += new Blob([content]).size; itemCount++; } } } return { used: totalSize, keyCount: itemCount, lastUpdated: new Date(), }; } catch (error) { throw new StorageError(`Failed to get storage info: ${error}`, StorageErrorCode.INFO_ERROR, error); } } /** * Get file path */ getFilePath(key) { // Convert key to safe filename format const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, "_"); return this.adapter.joinPath(this.config.dataDir, `${safeKey}.json`); } /** * Ensure directory exists and create if necessary */ async ensureDirectoryExists() { if (!(await this.adapter.exists(this.config.dataDir))) { await this.adapter.ensureDir(this.config.dataDir); } } } //# sourceMappingURL=ExternalStorageProvider.js.map