Resolve "feat(SQLite): Create SQLite Storage Provider"

This commit is contained in:
2025-08-19 01:19:58 +00:00
committed by Jim Hamill
parent 4f11147182
commit aea2c03e33
3 changed files with 163 additions and 2 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
"types": "dist/index.types.d.ts",
"exports": {
".": {
"types": "./dist/index.browser.d.ts",
"types": "./dist/index.types.d.ts",
"browser": {
"types": "./dist/index.browser.d.ts",
"default": "./dist/index.browser.js"
+1 -1
View File
@@ -3,4 +3,4 @@ export * from './index.js';
// These exports will only work in a NodeJS (backend) environment.
// TODO: FIx me!
// export * from './storage/store-sqlite.js';
export * from './storage/store-sqlite.js';
+161
View File
@@ -0,0 +1,161 @@
import { type StorageOpts, BaseStorage, BaseStore } from './base-store.js';
import sqlite3, { type Database } from 'better-sqlite3';
import { pack, unpack } from 'msgpackr';
import { StoreInMemorySynced } from './store-inmemory-synced.js';
export interface SQLiteOptions {
wal: boolean;
}
/**
* Storage Adapter that uses SQLite as the storage backend. Everything is stored as a BLOB, to avoid type issues.
*
* @remarks This implementation is single threaded. It WILL block execution of the main thread.
*/
export class StorageSQLite extends BaseStorage {
/**
* Create or open a SQLite database.
* @param filepath - The path to the SQLite database file.
* @param options - The options for the SQLite database.
* @returns A new instance of StorageSQLite.
*/
static async createOrOpen(
filepath: string,
options: Partial<SQLiteOptions> = {},
) {
const db = sqlite3(filepath);
const opts: SQLiteOptions = {
wal: true,
...options,
};
if (opts.wal) {
db.pragma('journal_mode = WAL');
}
return new StorageSQLite(db);
}
constructor(public db: Database) {
super();
}
async listStores() {
const result = this.db
.prepare(`SELECT * FROM sqlite_master WHERE type='table'`)
.all() as { name: string }[];
return result.map((row) => row.name);
}
async createStore(storeName: string, opts: StorageOpts = {}) {
// Create table with proper schema for key-value storage
this.db
.prepare(
`
CREATE TABLE IF NOT EXISTS "${storeName}" (
key TEXT PRIMARY KEY,
value BLOB
)
`,
)
.run();
const store = new StoreSQLite(this.db, storeName, opts);
return opts.syncInMemory ? await StoreInMemorySynced.create(store) : store;
}
async getStore(storeName: string, opts: StorageOpts) {
// Check if table exists
const tableExists = this.db
.prepare(
`
SELECT name FROM sqlite_master
WHERE type='table' AND name=?
`,
)
.get(storeName);
if (!tableExists) {
return null;
}
const store = new StoreSQLite(this.db, storeName, opts);
return opts.syncInMemory ? await StoreInMemorySynced.create(store) : store;
}
async createOrGetStore(storeName: string, opts: StorageOpts) {
return await this.createStore(storeName, opts);
}
async deleteStore(storeName: string) {
this.db.prepare(`DROP TABLE IF EXISTS "${storeName}"`).run();
}
async deleteDatabase() {
this.db.close();
}
}
export class StoreSQLite extends BaseStore {
constructor(
protected db: Database,
protected storeName: string,
protected opts: StorageOpts = {},
) {
super();
}
async keys() {
const result = this.db
.prepare(`SELECT key FROM "${this.storeName}"`)
.all() as { key: string }[];
return result.map((row) => row.key);
}
async get<T>(key: string): Promise<T | undefined> {
const result = this.db
.prepare(`SELECT value FROM "${this.storeName}" WHERE key = ?`)
.get(key) as { value: Buffer } | undefined;
if (!result) {
return undefined;
}
// Handle decryption if encryption is enabled
const decryptedValue = this.opts.encryption
? await this.opts.encryption.decrypt(new Uint8Array(result.value))
: new Uint8Array(result.value);
// Deserialize using msgpackr for consistency with other implementations
return unpack(decryptedValue) as T;
}
async set<T>(key: string, value: T): Promise<void> {
// Serialize using msgpackr for consistency with other implementations
const serializedValue = pack(value);
// Handle encryption if encryption is enabled
const finalValue = this.opts.encryption
? await this.opts.encryption.encrypt(serializedValue)
: serializedValue;
this.db
.prepare(
`INSERT OR REPLACE INTO "${this.storeName}" (key, value) VALUES (?, ?)`,
)
.run(key, Buffer.from(finalValue));
this.emit('set', { key, value });
}
async delete(key: string): Promise<void> {
this.db.prepare(`DELETE FROM "${this.storeName}" WHERE key = ?`).run(key);
this.emit('delete', { key });
}
async clear(): Promise<void> {
this.db.prepare(`DELETE FROM "${this.storeName}"`).run();
this.emit('clear', undefined);
}
}