6 changed files with 207 additions and 85 deletions
@ -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[][]>; |
|||
} |
@ -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; |
@ -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; |
@ -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…
Reference in new issue