Browse Source

add more to the inital migration, and refactor the locations of types

sql-absurd-sql
Trent Larson 2 weeks ago
parent
commit
83771caee1
  1. 71
      src/db-sql/migration.ts
  2. 14
      src/interfaces/database.ts
  3. 2
      src/libs/util.ts
  4. 27
      src/services/database.d.ts
  5. 81
      src/services/database.js
  6. 97
      src/services/database.ts

71
src/db-sql/migration.ts

@ -1,9 +1,10 @@
import migrationService from '../services/migrationService'; import migrationService from '../services/migrationService';
import type { QueryExecResult } from '../services/migrationService'; import type { QueryExecResult } from '../services/migrationService';
// Each migration can include multiple SQL statements (with semicolons)
const MIGRATIONS = [ const MIGRATIONS = [
{ {
name: '001_create_accounts_table', name: '001_initial',
// see ../db/tables files for explanations // see ../db/tables files for explanations
sql: ` sql: `
CREATE TABLE IF NOT EXISTS accounts ( 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_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
);
`
} }
]; ];

14
src/interfaces/database.ts

@ -0,0 +1,14 @@
export type SqlValue = string | number | null | Uint8Array;
export interface QueryExecResult {
columns: Array<string>;
values: Array<Array<SqlValue>>;
}
export interface DatabaseService {
initialize(): Promise<void>;
query(sql: string, params?: any[]): Promise<QueryExecResult[]>;
run(sql: string, params?: any[]): Promise<{ changes: number; lastId?: number }>;
get(sql: string, params?: any[]): Promise<any[] | undefined>;
all(sql: string, params?: any[]): Promise<any[][]>;
}

2
src/libs/util.ts

@ -549,7 +549,7 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
mnemonic: mnemonic, mnemonic: mnemonic,
publicKeyHex: newId.keys[0].publicKeyHex, publicKeyHex: newId.keys[0].publicKeyHex,
}); });
await updateDefaultSettings({ activeDid: newId.did }); await updateDefaultSettings({ activeDid: newId.did });
} catch (error) { } catch (error) {
console.error("Failed to update default settings:", error); console.error("Failed to update default settings:", error);

27
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<SQL>;
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;

81
src/services/database.js

@ -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;

97
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<QueryExecResult[]>;
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<void> {
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<QueryExecResult[]> {
this.ensureInitialized();
return this.db!.exec(sql, params);
}
async get(sql: string, params: any[] = []): Promise<any[] | undefined> {
this.ensureInitialized();
const result = await this.db!.exec(sql, params);
return result[0]?.values[0];
}
async all(sql: string, params: any[] = []): Promise<any[][]> {
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;
Loading…
Cancel
Save