UNPKG

nucypher-experimental-taco-storage

Version:

TypeScript SDK for encrypted data storage with TACo (Threshold Access Control), supporting multiple storage providers including IPFS and SQLite

266 lines 9.77 kB
/** * SQLite storage adapter implementation */ import { Database } from 'sqlite3'; import { promisify } from 'util'; import { BaseStorageAdapter } from './base'; import { TacoStorageError, TacoStorageErrorType } from '../types'; /** * SQLite storage adapter for local/centralized data storage */ export class SQLiteAdapter extends BaseStorageAdapter { constructor(config = {}) { super(config); this.isClosed = false; const sqliteConfig = config; this.dbPath = sqliteConfig.databasePath || ':memory:'; this.timeout = sqliteConfig.timeout || 30000; try { // Only create database connection - schema setup happens in initialize() this.db = new Database(this.dbPath); // Promisify database methods this.dbRun = promisify(this.db.run.bind(this.db)); this.dbGet = promisify(this.db.get.bind(this.db)); this.dbAll = promisify(this.db.all.bind(this.db)); } catch (error) { throw new TacoStorageError(TacoStorageErrorType.ADAPTER_ERROR, 'Failed to create SQLite database connection', error); } } /** * Initialize the SQLite adapter by setting up database schema and testing connectivity */ async initialize() { if (this.isClosed) { throw new TacoStorageError(TacoStorageErrorType.ADAPTER_ERROR, 'Database is closed'); } try { // Configure database settings asynchronously const sqliteConfig = this.config; await this.setupDatabase(sqliteConfig.enableWAL || false); // Test database connectivity with a simple query await this.dbGet('SELECT 1 as test'); } catch (error) { throw new TacoStorageError(TacoStorageErrorType.ADAPTER_ERROR, 'Failed to initialize SQLite database', error); } } generateReference(id) { return `sqlite://${this.dbPath}#${id}`; } /** * Setup database schema and configuration asynchronously */ async setupDatabase(enableWAL) { try { // Enable WAL mode if requested if (enableWAL) { await this.dbRun('PRAGMA journal_mode = WAL'); } // Enable foreign key constraints await this.dbRun('PRAGMA foreign_keys = ON'); // Set other performance optimizations await this.dbRun('PRAGMA synchronous = NORMAL'); await this.dbRun('PRAGMA temp_store = MEMORY'); await this.dbRun(`PRAGMA busy_timeout = ${this.timeout}`); // Create the main storage table await this.dbRun(` CREATE TABLE IF NOT EXISTS items ( key TEXT PRIMARY KEY, content_type TEXT NOT NULL, size INTEGER NOT NULL, created_at TEXT NOT NULL, message_kit BLOB NOT NULL, conditions TEXT NOT NULL, metadata TEXT ) `); // Create the data table to store encrypted content await this.dbRun(` CREATE TABLE IF NOT EXISTS item_data ( key TEXT PRIMARY KEY, encrypted_data BLOB NOT NULL, FOREIGN KEY (key) REFERENCES items (key) ON DELETE CASCADE ) `); // Create index for faster lookups await this.dbRun('CREATE INDEX IF NOT EXISTS idx_created_at ON items(created_at)'); } catch (error) { throw new TacoStorageError(TacoStorageErrorType.ADAPTER_ERROR, 'Failed to setup database schema', error); } } /** * Store encrypted data and metadata in SQLite */ async store(encryptedData, metadata) { this.validateData(encryptedData); try { const sql = ` INSERT OR REPLACE INTO items ( key, content_type, size, created_at, message_kit, conditions, metadata ) VALUES (?, ?, ?, ?, ?, ?, ?) `; const params = [ metadata.id, metadata.contentType, metadata.size, metadata.createdAt.toISOString(), Buffer.from(metadata.encryptionMetadata.messageKit), JSON.stringify(metadata.encryptionMetadata.conditions), metadata.metadata ? JSON.stringify(metadata.metadata) : null, ]; // Store the metadata await this.dbRun(sql, params); // Store the encrypted data separately const dataSql = ` INSERT OR REPLACE INTO item_data (key, encrypted_data) VALUES (?, ?) `; await this.dbRun(dataSql, [metadata.id, Buffer.from(encryptedData)]); return { id: metadata.id, reference: this.generateReference(metadata.id), metadata, }; } catch (error) { throw new TacoStorageError(TacoStorageErrorType.STORAGE_ERROR, `Failed to store data in SQLite: ${error.message}`, error); } } /** * Retrieve encrypted data and metadata from SQLite */ async retrieve(id) { this.validateId(id); try { const sql = ` SELECT i.key, i.content_type, i.size, i.created_at, i.message_kit, i.conditions, i.metadata, d.encrypted_data FROM items i LEFT JOIN item_data d ON i.key = d.key WHERE i.key = ? `; const row = await this.dbGet(sql, [id]); if (!row) { throw new TacoStorageError(TacoStorageErrorType.NOT_FOUND, `Data not found for ID: ${id}`); } const encryptedData = row.encrypted_data ? new Uint8Array(row.encrypted_data) : new Uint8Array(); const metadata = { id: row.key, contentType: row.content_type, size: row.size, createdAt: new Date(row.created_at), encryptionMetadata: { messageKit: Array.from(new Uint8Array(row.message_kit)), conditions: JSON.parse(row.conditions), }, metadata: row.metadata ? JSON.parse(row.metadata) : undefined, }; return { encryptedData, metadata }; } catch (error) { if (error instanceof TacoStorageError) { throw error; } throw new TacoStorageError(TacoStorageErrorType.RETRIEVAL_ERROR, `Failed to retrieve data from SQLite: ${error.message}`, error); } } /** * Delete data from SQLite */ async delete(id) { this.validateId(id); try { // First check if the record exists const existsBefore = await this.exists(id); if (!existsBefore) { return false; } // Delete from items table - CASCADE will handle item_data await this.dbRun('DELETE FROM items WHERE key = ?', [id]); // Check if the record still exists after deletion const existsAfter = await this.exists(id); return !existsAfter; } catch (error) { throw new TacoStorageError(TacoStorageErrorType.STORAGE_ERROR, `Failed to delete data from SQLite: ${error.message}`, error); } } /** * Check if data exists in SQLite */ async exists(id) { this.validateId(id); try { const result = await this.dbGet('SELECT 1 FROM items WHERE key = ?', [id]); return !!result; } catch (error) { throw new TacoStorageError(TacoStorageErrorType.STORAGE_ERROR, `Failed to check existence in SQLite: ${error.message}`, error); } } /** * List stored data IDs with pagination */ async list(limit = 100, offset = 0) { try { const sql = ` SELECT key FROM items ORDER BY created_at DESC LIMIT ? OFFSET ? `; const rows = await this.dbAll(sql, [limit, offset]); return rows.map(row => row.key); } catch (error) { throw new TacoStorageError(TacoStorageErrorType.RETRIEVAL_ERROR, `Failed to list data from SQLite: ${error.message}`, error); } } /** * Get SQLite database health status */ async getHealth() { try { // Simple health check: try to query the database const result = await this.dbGet('SELECT COUNT(*) as count FROM items'); const pragma = await this.dbGet('PRAGMA integrity_check'); return { healthy: pragma.integrity_check === 'ok', details: { databasePath: this.dbPath, recordCount: result.count, integrityCheck: pragma.integrity_check, }, }; } catch (error) { return { healthy: false, details: { databasePath: this.dbPath, error: error.message, }, }; } } /** * Clean up SQLite database connection */ async cleanup() { if (this.isClosed) { return; // Already closed, nothing to do } return new Promise((resolve, reject) => { this.db.close((error) => { this.isClosed = true; if (error) { reject(new TacoStorageError(TacoStorageErrorType.ADAPTER_ERROR, 'Failed to close SQLite database', error)); } else { resolve(); } }); }); } } //# sourceMappingURL=sqlite.js.map