Skip to content

Instantly share code, notes, and snippets.

@gerzhan
Last active December 8, 2023 05:31
Show Gist options
  • Save gerzhan/71561c16f0b53ee4c3479df67931231c to your computer and use it in GitHub Desktop.
Save gerzhan/71561c16f0b53ee4c3479df67931231c to your computer and use it in GitHub Desktop.
SQLite WebAssembly (WASM) for TypeScript Application

SQLite WASM

  • создать директорию для бибилиотеки
$mkdir -p src/lib/sqlite-wasm/worker
  • скачать архив sqlite.org/2023/sqlite-wasm-3440200.zip
  • распаковать и перенести директорию jswasm в src/lib/sqlite-wasm/jswasm
  • создать файлы для кода запуска worker и инициализации БД
$touch src/lib/sqlite-wasm/loader-worker-sqlite.ts
$touch src/lib/sqlite-wasm/worker/worker-sqlite.ts
$touch src/lib/sqlite-wasm/worker/init-db.ts
$touch src/lib/sqlite-wasm/worker/sqlite3oo1.d.ts
  • установить и подключить пакеты для vite сборщика
$npm i -D vite-plugin-cross-origin-isolation
$npm i -D vite-plugin-mkcert
+ import mkcert from 'vite-plugin-mkcert';
+ import crossOriginIsolation from 'vite-plugin-cross-origin-isolation';

/** @type {import('vite').UserConfig} */
const config = {
-  plugins: [sveltekit()],
+  plugins: [sveltekit(), mkcert(), crossOriginIsolation()],
  test: {
    include: ['src/**/*.{test,spec}.{js,ts}'],
  },
  server: {
+    https: true,
+    proxy: {},
    port: 5175,
  },
};

export default config;

Первоисточники

// import { WorkerMessageTypes, type WorkerMessage } from './types';
import { WorkerMessageTypes, type WorkerMessage } from './worker/worker.ts';
const workerImp = await import('./worker/worker.ts?worker');
export default function initWorker() {
const worker = new workerImp.default();
const msg: WorkerMessage = { type: WorkerMessageTypes.INIT_DB };
console.log(`Sending message to worker:`, msg);
worker.postMessage(msg);
worker.addEventListener('message', async ({ data }: { data: WorkerMessage }) => {
console.log('Received message from worker:', data.type);
});
}
// NOTE: файл инициализации подключения к SQLite
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { DB } from 'sqlite3oo1';
const DB_NAME = 'file:///offline-db.sqlite';
export let db: DB;
declare global {
function sqlite3InitModule(options: { print: object; printErr: object }): Promise<void>;
}
type InitDbReturn = {
ok: boolean;
error?: string;
};
export async function initDb(): Promise<InitDbReturn> {
return new Promise((resolve) => {
try {
self
.sqlite3InitModule({ print: console.log, printErr: console.error })
.then((sqlite3: any) => {
try {
console.log('Initialized sqlite3 module.', sqlite3);
const oo = sqlite3?.oo1 as any;
//const opfs = sqlite3?.opfs as any;
const capi = sqlite3.capi as any;
const opfsFound = capi.sqlite3_vfs_find('opfs');
console.log(
'sqlite3 version',
capi.sqlite3_libversion(),
capi.sqlite3_sourceid(),
`OPFS? ==> ${opfsFound}`
);
if (opfsFound) {
db = new oo.OpfsDb(DB_NAME) as DB;
console.log('The OPFS is available.');
} else {
db = new oo.DB(DB_NAME, 'ct') as DB;
console.log('The OPFS is not available.');
}
console.log('transient db =', (db as any).filename);
// optimize for speed (with safety): https://cj.rs/blog/sqlite-pragma-cheatsheet-for-performance-and-consistency/
db.exec(['PRAGMA journal_mode = wal;', 'PRAGMA synchronous = normal;']);
resolve({ ok: true });
} catch (e: any) {
console.error(`Could not initialize database: ${e.message}`);
resolve({ ok: false, error: e.message });
}
});
} catch (e: any) {
console.error(`Could not initialize database: ${e.message}`);
resolve({ ok: false, error: e.message });
}
});
}
// NOTE: файл описания типов для реализации подключения sqlite wasm
// NOTE: @see https://www.youtube.com/watch?v=Uvnzwp72Ze8
/* eslint-disable @typescript-eslint/no-explicit-any */
declare module 'sqlite3oo1' {
export type bind = any[] | { [key: string]: any };
export type execOptions = {
sql?: string | string[];
bind?: bind;
saveSql?: any[];
returnValue?: 'this' | 'resultRows' | 'saveSql';
callback?: (arr: any[]) => void;
columnNames?: string[];
resultRows?: any[];
};
export type execOptionsWOsql = Omit<execOptions, 'sql'>;
type boundable = number | string | undefined | null | Uint8Array | Int8Array | ArrayBuffer;
export class Statement {
bind(values: boundable | any[] | object): Statement;
bind(idx: string | number, value: boundable): Statement;
clearBindings(): Statement;
step(): boolean;
stepReset(): Statement;
finalize(): void;
}
export class DB {
constructor(filename: string, mode: string);
exec(sql: string, optionsObject?: execOptionsWOsql): DB | any[];
exec(sql: string[], optionsObject?: execOptionsWOsql): DB | any[];
exec(optionsObject: execOptions): DB | any[];
selectObject(sql: string, bind?: bind): { [key: string]: any };
selectObjects(sql: string, bind?: bind): { [key: string]: any }[];
transaction(callback: () => void): void;
prepare(sql: string): Statement;
}
}
// NOTE: файл worker.ts для обработки сообщений по инициализации локально БД SQLite WASM
/* eslint-disable no-case-declarations */
// import { WorkerMessageTypes, type WorkerMessage } from '../types';
export enum WorkerMessageTypes {
INIT_DB,
INIT_DB_RESPONSE
}
export type WorkerMessage = {
type: WorkerMessageTypes;
};
import { initDb } from './initDb';
console.log('worker loaded');
(async function () {
addEventListener('message', async function ({ data }: { data: WorkerMessage }) {
console.log('worker received message:', data.type);
let res: WorkerMessage;
switch (data.type) {
case WorkerMessageTypes.INIT_DB:
await import('../jswasm/sqlite3.mjs');
const initRes = await initDb();
console.log('worker initDb result:', initRes);
res = { type: WorkerMessageTypes.INIT_DB_RESPONSE };
console.log('worker sending message back to main:', res);
this.postMessage(res);
break;
default:
throw new Error(`Unknown message type: ${data.type}`);
}
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment