@evolu/web
Version:
Evolu for web
119 lines (101 loc) • 2.92 kB
text/typescript
import {
bytesToHex,
createPreparedStatementsCache,
CreateSqliteDriver,
SqliteDriver,
SqliteRow,
} from "@evolu/common";
import sqlite3InitModule, {
Database,
PreparedStatement,
} from "@evolu/sqlite-wasm";
// @ts-expect-error Missing types.
globalThis.sqlite3ApiConfig = {
warn: (arg: unknown) => {
// Ignore irrelevant warning.
// https://github.com/sqlite/sqlite-wasm/issues/62
if (
typeof arg === "string" &&
arg.startsWith("Ignoring inability to install OPFS sqlite3_vfs")
)
return;
// eslint-disable-next-line no-console
console.warn(arg);
},
};
// Init ASAP.
const sqlite3Promise = sqlite3InitModule();
export const createWasmSqliteDriver: CreateSqliteDriver = async (
name,
options,
) => {
const sqlite3 = await sqlite3Promise;
// This is used to make OPFS default vfs for multipleciphers
// @ts-expect-error Missing types (update @evolu/sqlite-wasm types)
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
sqlite3.capi.sqlite3mc_vfs_create("opfs", 1);
let db: Database;
if (options?.memory) {
db = new sqlite3.oo1.DB(":memory:");
} else if (options?.encryptionKey) {
const pool = await sqlite3.installOpfsSAHPoolVfs({ directory: `.${name}` });
db = new pool.OpfsSAHPoolDb(
"file:evolu1.db?vfs=multipleciphers-opfs-sahpool",
);
db.exec(`
PRAGMA cipher = 'sqlcipher';
PRAGMA legacy = 4;
PRAGMA key = "x'${bytesToHex(options.encryptionKey)}'";
`);
} else {
const pool = await sqlite3.installOpfsSAHPoolVfs({ name });
db = new pool.OpfsSAHPoolDb("file:evolu1.db");
}
let isDisposed = false;
const cache = createPreparedStatementsCache<PreparedStatement>(
(sql) => db.prepare(sql),
(statement) => {
statement.finalize();
},
);
const driver: SqliteDriver = {
exec: (query, isMutation) => {
const prepared = cache.get(query);
if (prepared) {
prepared.bind(query.parameters);
if (isMutation) {
prepared.stepReset();
return {
rows: [] as ReadonlyArray<SqliteRow>,
changes: db.changes(),
};
}
const rows = [];
while (prepared.step()) {
rows.push(prepared.get({}));
}
prepared.reset();
return {
rows: rows as ReadonlyArray<SqliteRow>,
changes: 0,
};
}
const rows = db.exec(query.sql, {
returnValue: "resultRows",
rowMode: "object",
bind: query.parameters,
}) as ReadonlyArray<SqliteRow>;
const changes = db.changes();
return { rows, changes };
},
export: () => sqlite3.capi.sqlite3_js_db_export(db),
[Symbol.dispose]: () => {
if (isDisposed) return;
isDisposed = true;
// poolUtil.unlink?
cache[Symbol.dispose]();
db.close();
},
};
return driver;
};