From 83771caee112f982604154c6aea44a3f416a5675 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Sun, 25 May 2025 17:55:04 -0600 Subject: [PATCH] add more to the inital migration, and refactor the locations of types --- src/db-sql/migration.ts | 71 ++++++++++++++++++++++++++-- src/interfaces/database.ts | 14 ++++++ src/libs/util.ts | 2 +- src/services/database.d.ts | 27 +++++++++++ src/services/database.js | 81 ------------------------------- src/services/database.ts | 97 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 207 insertions(+), 85 deletions(-) create mode 100644 src/interfaces/database.ts create mode 100644 src/services/database.d.ts delete mode 100644 src/services/database.js create mode 100644 src/services/database.ts diff --git a/src/db-sql/migration.ts b/src/db-sql/migration.ts index 8f4cc69c..b81a93d8 100644 --- a/src/db-sql/migration.ts +++ b/src/db-sql/migration.ts @@ -1,9 +1,10 @@ import migrationService from '../services/migrationService'; import type { QueryExecResult } from '../services/migrationService'; +// Each migration can include multiple SQL statements (with semicolons) const MIGRATIONS = [ { - name: '001_create_accounts_table', + name: '001_initial', // see ../db/tables files for explanations sql: ` CREATE TABLE IF NOT EXISTS accounts ( @@ -18,8 +19,72 @@ const MIGRATIONS = [ ); CREATE INDEX IF NOT EXISTS idx_accounts_did ON accounts(did); - CREATE INDEX IF NOT EXISTS idx_accounts_publicKeyHex ON accounts(publicKeyHex); - ` + + CREATE TABLE IF NOT EXISTS secret ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + secret TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS settings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + accountDid TEXT, + activeDid TEXT, + apiServer TEXT, + filterFeedByNearby BOOLEAN, + filterFeedByVisible BOOLEAN, + finishedOnboarding BOOLEAN, + firstName TEXT, + hideRegisterPromptOnNewContact BOOLEAN, + isRegistered BOOLEAN, + lastName TEXT, + lastAckedOfferToUserJwtId TEXT, + lastAckedOfferToUserProjectsJwtId TEXT, + lastNotifiedClaimId TEXT, + lastViewedClaimId TEXT, + notifyingNewActivityTime TEXT, + notifyingReminderMessage TEXT, + notifyingReminderTime TEXT, + partnerApiServer TEXT, + passkeyExpirationMinutes INTEGER, + profileImageUrl TEXT, + searchBoxes TEXT, -- Stored as JSON string + showContactGivesInline BOOLEAN, + showGeneralAdvanced BOOLEAN, + showShortcutBvc BOOLEAN, + vapid TEXT, + warnIfProdServer BOOLEAN, + warnIfTestServer BOOLEAN, + webPushServer TEXT + ); + + CREATE INDEX IF NOT EXISTS idx_settings_accountDid ON settings(accountDid); + + CREATE TABLE IF NOT EXISTS contacts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + did TEXT NOT NULL, + name TEXT, + contactMethods TEXT, -- Stored as JSON string + nextPubKeyHashB64 TEXT, + notes TEXT, + profileImageUrl TEXT, + publicKeyBase64 TEXT, + seesMe BOOLEAN, + registered BOOLEAN + ); + + CREATE INDEX IF NOT EXISTS idx_contacts_did ON contacts(did); + CREATE INDEX IF NOT EXISTS idx_contacts_name ON contacts(name); + + CREATE TABLE IF NOT EXISTS logs ( + date TEXT PRIMARY KEY, + message TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS temp ( + id TEXT PRIMARY KEY, + blobB64 TEXT + ); + ` } ]; diff --git a/src/interfaces/database.ts b/src/interfaces/database.ts new file mode 100644 index 00000000..ad61521e --- /dev/null +++ b/src/interfaces/database.ts @@ -0,0 +1,14 @@ +export type SqlValue = string | number | null | Uint8Array; + +export interface QueryExecResult { + columns: Array; + values: Array>; +} + +export interface DatabaseService { + initialize(): Promise; + query(sql: string, params?: any[]): Promise; + run(sql: string, params?: any[]): Promise<{ changes: number; lastId?: number }>; + get(sql: string, params?: any[]): Promise; + all(sql: string, params?: any[]): Promise; +} \ No newline at end of file diff --git a/src/libs/util.ts b/src/libs/util.ts index b98f747c..3c7c794d 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -549,7 +549,7 @@ export const generateSaveAndActivateIdentity = async (): Promise => { mnemonic: mnemonic, publicKeyHex: newId.keys[0].publicKeyHex, }); - + await updateDefaultSettings({ activeDid: newId.did }); } catch (error) { console.error("Failed to update default settings:", error); diff --git a/src/services/database.d.ts b/src/services/database.d.ts new file mode 100644 index 00000000..032cc419 --- /dev/null +++ b/src/services/database.d.ts @@ -0,0 +1,27 @@ +import { DatabaseService } from '../interfaces/database'; + +declare module '@jlongster/sql.js' { + interface SQL { + Database: any; + FS: any; + register_for_idb: (fs: any) => void; + } + + function initSqlJs(config: { locateFile: (file: string) => string }): Promise; + export default initSqlJs; +} + +declare module 'absurd-sql' { + export class SQLiteFS { + constructor(fs: any, backend: any); + } +} + +declare module 'absurd-sql/dist/indexeddb-backend' { + export default class IndexedDBBackend { + constructor(); + } +} + +declare const databaseService: DatabaseService; +export default databaseService; diff --git a/src/services/database.js b/src/services/database.js deleted file mode 100644 index ea872ae1..00000000 --- a/src/services/database.js +++ /dev/null @@ -1,81 +0,0 @@ -import initSqlJs from '@jlongster/sql.js'; -import { SQLiteFS } from 'absurd-sql'; -import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend'; -import { runMigrations } from '../db-sql/migration'; - -class DatabaseService { - constructor() { - this.db = null; - this.SQL = null; - this.initialized = false; - } - - async initialize() { - if (this.initialized) return; - - this.SQL = await initSqlJs({ - locateFile: file => { - return new URL(`/node_modules/@jlongster/sql.js/dist/${file}`, import.meta.url).href; - } - }); - - let sqlFS = new SQLiteFS(this.SQL.FS, new IndexedDBBackend()); - this.SQL.register_for_idb(sqlFS); - - this.SQL.FS.mkdir('/sql'); - this.SQL.FS.mount(sqlFS, {}, '/sql'); - - const path = '/sql/db.sqlite'; - if (typeof SharedArrayBuffer === 'undefined') { - let stream = this.SQL.FS.open(path, 'a+'); - await stream.node.contents.readIfFallback(); - this.SQL.FS.close(stream); - } - - this.db = new this.SQL.Database(path, { filename: true }); - this.db.exec(` - PRAGMA journal_mode=MEMORY; - `); - const sqlExec = this.db.exec.bind(this.db); - - // Run migrations - await runMigrations(sqlExec); - - this.initialized = true; - } - - async query(sql, params = []) { - if (!this.initialized) { - await this.initialize(); - } - return this.db.exec(sql, params); - } - - async run(sql, params = []) { - if (!this.initialized) { - await this.initialize(); - } - return this.db.run(sql, params); - } - - async get(sql, params = []) { - if (!this.initialized) { - await this.initialize(); - } - const result = await this.db.exec(sql, params); - return result[0]?.values[0]; - } - - async all(sql, params = []) { - if (!this.initialized) { - await this.initialize(); - } - const result = await this.db.exec(sql, params); - return result[0]?.values || []; - } -} - -// Create a singleton instance -const databaseService = new DatabaseService(); - -export default databaseService; \ No newline at end of file diff --git a/src/services/database.ts b/src/services/database.ts new file mode 100644 index 00000000..c87d1edf --- /dev/null +++ b/src/services/database.ts @@ -0,0 +1,97 @@ +// Add type declarations for external modules +declare module '@jlongster/sql.js'; +declare module 'absurd-sql'; +declare module 'absurd-sql/dist/indexeddb-backend'; + +import initSqlJs from '@jlongster/sql.js'; +import { SQLiteFS } from 'absurd-sql'; +import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend'; +import { runMigrations } from '../db-sql/migration'; +import { QueryExecResult } from './migrationService'; + +interface SQLDatabase { + exec: (sql: string, params?: any[]) => Promise; + run: (sql: string, params?: any[]) => Promise<{ changes: number; lastId?: number }>; +} + +class DatabaseService { + private db: SQLDatabase | null; + private initialized: boolean; + + constructor() { + this.db = null; + this.initialized = false; + } + + async initialize(): Promise { + if (this.initialized) return; + + const SQL = await initSqlJs({ + locateFile: (file: string) => { + return new URL(`/node_modules/@jlongster/sql.js/dist/${file}`, import.meta.url).href; + } + }); + + let sqlFS = new SQLiteFS(SQL.FS, new IndexedDBBackend()); + SQL.register_for_idb(sqlFS); + + SQL.FS.mkdir('/sql'); + SQL.FS.mount(sqlFS, {}, '/sql'); + + const path = '/sql/db.sqlite'; + if (typeof SharedArrayBuffer === 'undefined') { + let stream = SQL.FS.open(path, 'a+'); + await stream.node.contents.readIfFallback(); + SQL.FS.close(stream); + } + + this.db = new SQL.Database(path, { filename: true }); + if (!this.db) { + throw new Error('Failed to initialize database'); + } + + await this.db.exec(` + PRAGMA journal_mode=MEMORY; + `); + const sqlExec = this.db.exec.bind(this.db); + + // Run migrations + await runMigrations(sqlExec); + + this.initialized = true; + } + + private ensureInitialized(): void { + if (!this.initialized || !this.db) { + throw new Error('Database not initialized'); + } + } + + // Used for inserts, updates, and deletes + async run(sql: string, params: any[] = []): Promise<{ changes: number; lastId?: number }> { + this.ensureInitialized(); + return this.db!.run(sql, params); + } + + async query(sql: string, params: any[] = []): Promise { + this.ensureInitialized(); + return this.db!.exec(sql, params); + } + + async get(sql: string, params: any[] = []): Promise { + this.ensureInitialized(); + const result = await this.db!.exec(sql, params); + return result[0]?.values[0]; + } + + async all(sql: string, params: any[] = []): Promise { + this.ensureInitialized(); + const result = await this.db!.exec(sql, params); + return result[0]?.values || []; + } +} + +// Create a singleton instance +const databaseService = new DatabaseService(); + +export default databaseService; \ No newline at end of file