add DB setup with migrations
This commit is contained in:
@@ -333,7 +333,6 @@ export default class ImageMethodDialog extends Vue {
|
||||
* @throws {Error} When settings retrieval fails
|
||||
*/
|
||||
async mounted() {
|
||||
logger.log("ImageMethodDialog mounted");
|
||||
try {
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
this.activeDid = settings.activeDid || "";
|
||||
|
||||
38
src/db-sql/migration.ts
Normal file
38
src/db-sql/migration.ts
Normal file
@@ -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);
|
||||
}
|
||||
@@ -1,33 +1,6 @@
|
||||
import initSqlJs from '@jlongster/sql.js';
|
||||
import { SQLiteFS } from 'absurd-sql';
|
||||
import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend';
|
||||
import databaseService from './services/database';
|
||||
|
||||
async function run() {
|
||||
console.log("----- initSqlJs");
|
||||
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);
|
||||
await databaseService.initialize();
|
||||
}
|
||||
run();
|
||||
|
||||
81
src/services/database.js
Normal file
81
src/services/database.js
Normal file
@@ -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
Normal file
63
src/services/migrationService.ts
Normal file
@@ -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();
|
||||
@@ -49,5 +49,5 @@ export default defineConfig({
|
||||
}
|
||||
}
|
||||
},
|
||||
assetsInclude: ['**/*.wasm']
|
||||
assetsInclude: ['**/*.wasm', '**/*.sql']
|
||||
});
|
||||
Reference in New Issue
Block a user