@aituber-onair/kizuna
Version:
A sophisticated bond system (絆 - Kizuna) for managing relationships between users and AI characters in AITuber OnAir.
180 lines • 6 kB
JavaScript
/**
* 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