Browse Source

add DB setup with migrations

sql-absurd-sql
Trent Larson 2 weeks ago
parent
commit
8c3920e108
  1. 1
      src/components/ImageMethodDialog.vue
  2. 38
      src/db-sql/migration.ts
  3. 31
      src/registerSQLWorker.js
  4. 81
      src/services/database.js
  5. 63
      src/services/migrationService.ts
  6. 2
      vite.config.ts

1
src/components/ImageMethodDialog.vue

@ -333,7 +333,6 @@ export default class ImageMethodDialog extends Vue {
* @throws {Error} When settings retrieval fails * @throws {Error} When settings retrieval fails
*/ */
async mounted() { async mounted() {
logger.log("ImageMethodDialog mounted");
try { try {
const settings = await retrieveSettingsForActiveAccount(); const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || ""; this.activeDid = settings.activeDid || "";

38
src/db-sql/migration.ts

@ -0,0 +1,38 @@
import migrationService from '../services/migrationService';
import type { QueryExecResult } from '../services/migrationService';
const MIGRATIONS = [
{
name: '001_create_accounts_table',
// see ../db/tables files for explanations
sql: `
CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dateCreated TEXT NOT NULL,
derivationPath TEXT,
did TEXT NOT NULL,
identity TEXT,
mnemonic TEXT,
passkeyCredIdHex TEXT,
publicKeyHex TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_accounts_did ON accounts(did);
CREATE INDEX IF NOT EXISTS idx_accounts_publicKeyHex ON accounts(publicKeyHex);
`
}
];
export async function registerMigrations(): Promise<void> {
// Register all migrations
for (const migration of MIGRATIONS) {
await migrationService.registerMigration(migration);
}
}
export async function runMigrations(
sqlExec: (sql: string, params?: any[]) => Promise<Array<QueryExecResult>>
): Promise<void> {
await registerMigrations();
await migrationService.runMigrations(sqlExec);
}

31
src/registerSQLWorker.js

@ -1,33 +1,6 @@
import initSqlJs from '@jlongster/sql.js'; import databaseService from './services/database';
import { SQLiteFS } from 'absurd-sql';
import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend';
async function run() { async function run() {
console.log("----- initSqlJs"); await databaseService.initialize();
let SQL = await initSqlJs({
locateFile: file => {
// In Vite, we need to use the full URL to the WASM file
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);
}
let db = new SQL.Database(path, { filename: true });
// You might want to try `PRAGMA page_size=8192;` too!
db.exec(`
PRAGMA journal_mode=MEMORY;
`);
console.log("----- db", db);
} }
run(); run();

81
src/services/database.js

@ -0,0 +1,81 @@
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;

63
src/services/migrationService.ts

@ -0,0 +1,63 @@
type SqlValue = string | number | null | Uint8Array;
export interface QueryExecResult {
columns: Array<string>;
values: Array<Array<SqlValue>>;
}
interface Migration {
name: string;
sql: string;
}
export class MigrationService {
private static instance: MigrationService;
private migrations: Migration[] = [];
private constructor() {}
static getInstance(): MigrationService {
if (!MigrationService.instance) {
MigrationService.instance = new MigrationService();
}
return MigrationService.instance;
}
async registerMigration(migration: Migration): Promise<void> {
this.migrations.push(migration);
}
async runMigrations(
sqlExec: (sql: string, params?: any[]) => Promise<Array<QueryExecResult>>
): Promise<void> {
// Create migrations table if it doesn't exist
await sqlExec(`
CREATE TABLE IF NOT EXISTS migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
// Get list of executed migrations
const result = await sqlExec('SELECT name FROM migrations');
const singleResult = result[0];
const executedMigrations = new Set(singleResult.values.map(row => row[0]));
// Run pending migrations in order
for (const migration of this.migrations) {
if (!executedMigrations.has(migration.name)) {
try {
await sqlExec(migration.sql);
await sqlExec('INSERT INTO migrations (name) VALUES (?)', [migration.name]);
console.log(`Migration ${migration.name} executed successfully`);
} catch (error) {
console.error(`Error executing migration ${migration.name}:`, error);
throw error;
}
}
}
}
}
export default MigrationService.getInstance();

2
vite.config.ts

@ -49,5 +49,5 @@ export default defineConfig({
} }
} }
}, },
assetsInclude: ['**/*.wasm'] assetsInclude: ['**/*.wasm', '**/*.sql']
}); });
Loading…
Cancel
Save