Mid-rewrite

This commit is contained in:
2026-02-06 13:14:24 +00:00
parent 601c3db9d0
commit eb1bf9020e
12 changed files with 1300 additions and 103 deletions

106
src/services/storage.ts Normal file
View File

@@ -0,0 +1,106 @@
import Database from 'better-sqlite3';
import { decodeExtendedJsonObject, encodeExtendedJsonObject } from '../utils/ext-json.js';
export class Storage {
static async create(dbPath: string): Promise<Storage> {
// Create the database
const database = new Database(dbPath);
// Create the storage table if it doesn't exist
database.prepare('CREATE TABLE IF NOT EXISTS storage (key TEXT PRIMARY KEY, value TEXT)').run();
return new Storage(database, '');
}
constructor(
private readonly database: Database.Database,
private readonly basePath: string,
) {}
/**
* Get the full key with basePath prefix
*/
private getFullKey(key: string): string {
return this.basePath ? `${this.basePath}.${key}` : key;
}
/**
* Strip the basePath prefix from a key
*/
private stripBasePath(fullKey: string): string {
if (!this.basePath) return fullKey;
const prefix = `${this.basePath}.`;
return fullKey.startsWith(prefix) ? fullKey.slice(prefix.length) : fullKey;
}
async set(key: string, value: any): Promise<void> {
// Encode the extended json object
const encodedValue = encodeExtendedJsonObject(value);
// Insert or replace the value into the database with full key (including basePath)
const fullKey = this.getFullKey(key);
this.database.prepare('INSERT OR REPLACE INTO storage (key, value) VALUES (?, ?)').run(fullKey, encodedValue);
}
/**
* Get all key-value pairs from this storage namespace (shallow only - no nested children)
*/
async all(): Promise<{ key: string; value: any }[]> {
let query = 'SELECT key, value FROM storage';
const params: any[] = [];
if (this.basePath) {
// Filter by basePath prefix
query += ' WHERE key LIKE ?';
params.push(`${this.basePath}.%`);
}
// Get all the rows from the database
const rows = await this.database.prepare(query).all(...params) as { key: string; value: any }[];
// Filter for shallow results (only direct children)
const filteredRows = rows.filter(row => {
const strippedKey = this.stripBasePath(row.key);
// Only include keys that don't have additional dots (no deeper nesting)
return !strippedKey.includes('.');
});
// Decode the extended json objects and strip basePath from keys
return filteredRows.map(row => ({
key: this.stripBasePath(row.key),
value: decodeExtendedJsonObject(row.value)
}));
}
async get(key: string): Promise<any> {
// Get the row from the database using full key
const fullKey = this.getFullKey(key);
const row = await this.database.prepare('SELECT value FROM storage WHERE key = ?').get(fullKey) as { value: any };
// Return null if not found
if (!row) return null;
// Decode the extended json object
return decodeExtendedJsonObject(row.value);
}
async remove(key: string): Promise<void> {
// Delete using full key
const fullKey = this.getFullKey(key);
this.database.prepare('DELETE FROM storage WHERE key = ?').run(fullKey);
}
async clear(): Promise<void> {
if (this.basePath) {
// Clear only items under this namespace
this.database.prepare('DELETE FROM storage WHERE key LIKE ?').run(`${this.basePath}.%`);
} else {
// Clear everything
this.database.prepare('DELETE FROM storage').run();
}
}
child(key: string): Storage {
return new Storage(this.database, this.getFullKey(key));
}
}