UNPKG

strata-storage

Version:

Zero-dependency universal storage plugin providing a unified API for all storage operations across web, Android, and iOS platforms

278 lines (277 loc) 8.12 kB
/** * SQLite Adapter - Native SQLite database storage * Available on iOS and Android */ import { BaseAdapter } from "../../core/BaseAdapter.js"; import { StrataStorage } from "../../plugin/index.js"; import { StorageError, TransactionError } from "../../utils/errors.js"; import { isCapacitor } from "../../utils/index.js"; /** * Native SQLite adapter using Capacitor plugin */ export class SqliteAdapter extends BaseAdapter { name = 'sqlite'; capabilities = { persistent: true, synchronous: false, observable: false, transactional: true, queryable: true, maxSize: -1, // Limited by device storage binary: true, encrypted: false, // Can be encrypted at OS level crossTab: true, }; database; table; constructor(config = {}) { super(); this.database = config.database || 'strata_storage'; this.table = config.table || 'storage'; } /** * Check if SQLite is available */ async isAvailable() { if (!isCapacitor()) return false; try { const result = await StrataStorage.isAvailable({ storage: 'sqlite' }); return result.available; } catch { return false; } } /** * Initialize the adapter */ async initialize(config) { if (config?.database) this.database = config.database; if (config?.table) this.table = config.table; this.startTTLCleanup(); } /** * Get a value from SQLite */ async get(key) { try { const result = await StrataStorage.get({ key, storage: 'sqlite', database: this.database, table: this.table, }); if (!result.value) return null; const value = result.value; // Check TTL if (this.isExpired(value)) { await this.remove(key); return null; } return value; } catch (error) { console.error(`Failed to get key ${key} from SQLite:`, error); return null; } } /** * Set a value in SQLite */ async set(key, value) { const oldValue = await this.get(key); try { await StrataStorage.set({ key, value, storage: 'sqlite', database: this.database, table: this.table, }); this.emitChange(key, oldValue?.value, value.value, 'local'); } catch (error) { throw new StorageError(`Failed to set key ${key} in SQLite: ${error}`); } } /** * Remove a value from SQLite */ async remove(key) { const oldValue = await this.get(key); try { await StrataStorage.remove({ key, storage: 'sqlite', database: this.database, table: this.table, }); if (oldValue) { this.emitChange(key, oldValue.value, undefined, 'local'); } } catch (error) { throw new StorageError(`Failed to remove key ${key} from SQLite: ${error}`); } } /** * Clear SQLite table */ async clear(options) { if (!options || (!options.pattern && !options.tags && !options.expiredOnly)) { try { await StrataStorage.clear({ storage: 'sqlite', database: this.database, table: this.table, }); this.emitChange('*', undefined, undefined, 'local'); return; } catch (error) { throw new StorageError(`Failed to clear SQLite: ${error}`); } } // Use base implementation for filtered clear await super.clear(options); } /** * Get all keys */ async keys(pattern) { try { const result = await StrataStorage.keys({ storage: 'sqlite', database: this.database, table: this.table, pattern: pattern instanceof RegExp ? pattern.source : pattern, }); const keys = result.keys; // Check for expired keys const validKeys = []; for (const key of keys) { const value = await this.get(key); if (value) { validKeys.push(key); } } return this.filterKeys(validKeys, pattern); } catch (error) { throw new StorageError(`Failed to get keys from SQLite: ${error}`); } } /** * Query SQLite with conditions */ async query(condition) { try { if (!StrataStorage.query) { throw new StorageError('Query not supported on this platform'); } const result = await StrataStorage.query({ storage: 'sqlite', database: this.database, table: this.table, condition, }); // Filter expired entries const validResults = []; for (const item of result.results) { const value = await this.get(item.key); if (value) { validResults.push({ key: item.key, value: value.value }); } } return validResults; } catch (error) { throw new StorageError(`Failed to query SQLite: ${error}`); } } /** * Get storage size */ async size(detailed) { try { const result = await StrataStorage.size({ storage: 'sqlite', database: this.database, table: this.table, detailed, }); return result; } catch (error) { throw new StorageError(`Failed to get size of SQLite: ${error}`); } } /** * Begin a transaction */ async transaction() { // Transactions are handled natively // For now, return a simple implementation return new SqliteTransaction(this); } } /** * SQLite transaction implementation */ class SqliteTransaction { adapter; operations = []; committed = false; aborted = false; constructor(adapter) { this.adapter = adapter; } async get(key) { if (this.aborted) throw new TransactionError('Transaction already aborted'); const value = await this.adapter.get(key); return value ? value.value : null; } async set(key, value) { if (this.aborted) throw new TransactionError('Transaction already aborted'); this.operations.push(async () => { const now = Date.now(); await this.adapter.set(key, { value, created: now, updated: now, }); }); } async remove(key) { if (this.aborted) throw new TransactionError('Transaction already aborted'); this.operations.push(async () => { await this.adapter.remove(key); }); } async commit() { if (this.aborted) throw new TransactionError('Cannot commit aborted transaction'); if (this.committed) throw new TransactionError('Transaction already committed'); this.committed = true; // Execute all operations for (const operation of this.operations) { await operation(); } } async rollback() { if (this.committed) throw new TransactionError('Cannot rollback committed transaction'); if (this.aborted) return; this.aborted = true; this.operations = []; } }