Switch SPA to bundled sql.js runtime
This commit is contained in:
parent
944dcc643d
commit
e83de10d93
3 changed files with 15 additions and 50 deletions
|
@ -4,4 +4,4 @@ A self contained static website for browsing the collection of salvaged mp3.com
|
||||||
|
|
||||||
## Runtime Notes
|
## Runtime Notes
|
||||||
|
|
||||||
- Uses the official `sqlite3` WASM build (vendored under `vendor/sqlite3/`) so that FTS5 is available client-side.
|
- Uses the `sql.js` WASM build (bundled as `sql-wasm.js`/`sql-wasm.wasm`) so that FTS5 is available client-side.
|
||||||
|
|
|
@ -365,8 +365,8 @@
|
||||||
<!-- Third-party libs loaded from CDNs (no trackers). Pinned versions. -->
|
<!-- Third-party libs loaded from CDNs (no trackers). Pinned versions. -->
|
||||||
<!-- fflate: tiny ZIP library used to unzip the downloaded DB archive client-side. -->
|
<!-- fflate: tiny ZIP library used to unzip the downloaded DB archive client-side. -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/fflate@0.8.2/umd/index.js" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/fflate@0.8.2/umd/index.js" crossorigin="anonymous"></script>
|
||||||
<!-- SQLite WASM build (oo1 API) shipped locally to ensure FTS5 support. -->
|
<!-- sql.js build (FTS5-enabled) shipped locally to ensure search support. -->
|
||||||
<script src="./vendor/sqlite3/sqlite3.js"></script>
|
<script src="./sql-wasm.js"></script>
|
||||||
|
|
||||||
<!-- App logic -->
|
<!-- App logic -->
|
||||||
<script src="./script.js"></script>
|
<script src="./script.js"></script>
|
||||||
|
|
59
script.js
59
script.js
|
@ -2,13 +2,13 @@
|
||||||
Application bootstrap for the static, client-side metadata browser.
|
Application bootstrap for the static, client-side metadata browser.
|
||||||
- Downloads a zipped SQLite DB (db.zip) with progress and unzips it in-memory
|
- Downloads a zipped SQLite DB (db.zip) with progress and unzips it in-memory
|
||||||
- Caches the uncompressed DB bytes in IndexedDB for reuse on next load
|
- Caches the uncompressed DB bytes in IndexedDB for reuse on next load
|
||||||
- Loads the official sqlite3 WASM build (oo1 API) and opens the database from the cached bytes
|
- Loads the locally bundled sql.js WASM build (FTS5-enabled) and opens the database from the cached bytes
|
||||||
- Swaps interchangeable UX elements (views) in a viewport, with fade transitions
|
- Swaps interchangeable UX elements (views) in a viewport, with fade transitions
|
||||||
|
|
||||||
Assumptions:
|
Assumptions:
|
||||||
- A ZIP archive is hosted at `/db.zip` containing a single `.sqlite` file.
|
- A ZIP archive is hosted at `/db.zip` containing a single `.sqlite` file.
|
||||||
- We use `fflate` (UMD) from a CDN to unzip after download (keeps implementation minimal).
|
- We use `fflate` (UMD) from a CDN to unzip after download (keeps implementation minimal).
|
||||||
- We ship the sqlite3 WASM runtime locally to guarantee FTS5 support.
|
- We ship the sql.js WASM runtime locally to guarantee FTS5 support.
|
||||||
- We store raw DB bytes in IndexedDB (localStorage is too small for large DBs).
|
- We store raw DB bytes in IndexedDB (localStorage is too small for large DBs).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -299,62 +299,30 @@
|
||||||
|
|
||||||
// --- SQLite WASM init ---
|
// --- SQLite WASM init ---
|
||||||
async function initSql() {
|
async function initSql() {
|
||||||
if (typeof sqlite3InitModule !== 'function') throw new Error('sqlite3.js not loaded');
|
if (typeof initSqlJs !== 'function') throw new Error('sql.js runtime not loaded');
|
||||||
loader.setStep('Initializing SQLite…');
|
loader.setStep('Initializing SQLite…');
|
||||||
loader.setDetail('Loading WASM');
|
loader.setDetail('Loading WASM');
|
||||||
const sqlite3 = await sqlite3InitModule({
|
const SQL = await initSqlJs({
|
||||||
locateFile: (file) => `./vendor/sqlite3/${file}`,
|
locateFile: (file) => `./${file}`,
|
||||||
});
|
});
|
||||||
return createSqlCompat(sqlite3);
|
return createSqlCompat(SQL);
|
||||||
}
|
|
||||||
|
|
||||||
function createSqlCompat(sqlite3) {
|
|
||||||
const { capi, wasm } = sqlite3;
|
|
||||||
const SQLITE_DESERIALIZE_FREEONCLOSE = 0x01;
|
|
||||||
const SQLITE_DESERIALIZE_READONLY = 0x04;
|
|
||||||
|
|
||||||
function loadDatabaseBytes(dbHandle, bytes) {
|
|
||||||
if (!bytes || !bytes.byteLength) return;
|
|
||||||
const byteLength = bytes.byteLength;
|
|
||||||
const pData = wasm.alloc(byteLength);
|
|
||||||
try {
|
|
||||||
wasm.heap8u().set(bytes, pData);
|
|
||||||
const [pSchema] = wasm.allocCString('main', true);
|
|
||||||
try {
|
|
||||||
const rc = capi.sqlite3_deserialize(
|
|
||||||
dbHandle.pointer,
|
|
||||||
pSchema,
|
|
||||||
pData,
|
|
||||||
byteLength,
|
|
||||||
byteLength,
|
|
||||||
SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_READONLY
|
|
||||||
);
|
|
||||||
if (rc !== capi.SQLITE_OK) {
|
|
||||||
throw new sqlite3.SQLite3Error(`sqlite3_deserialize failed with rc=${rc}`);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
wasm.dealloc(pSchema);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
wasm.dealloc(pData);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createSqlCompat(SQL) {
|
||||||
class Statement {
|
class Statement {
|
||||||
constructor(stmt) {
|
constructor(stmt) {
|
||||||
this._stmt = stmt;
|
this._stmt = stmt;
|
||||||
this._columnNames = null;
|
this._columnNames = null;
|
||||||
}
|
}
|
||||||
bind(values) {
|
bind(values) {
|
||||||
this._stmt.bind(values);
|
if (values !== undefined) this._stmt.bind(values);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
step() {
|
step() {
|
||||||
return !!this._stmt.step();
|
return !!this._stmt.step();
|
||||||
}
|
}
|
||||||
getAsObject() {
|
getAsObject() {
|
||||||
if (!this._columnNames) this._columnNames = this._stmt.getColumnNames([]);
|
if (!this._columnNames) this._columnNames = this._stmt.getColumnNames();
|
||||||
const row = Object.create(null);
|
const row = Object.create(null);
|
||||||
const count = this._columnNames.length;
|
const count = this._columnNames.length;
|
||||||
for (let i = 0; i < count; i += 1) {
|
for (let i = 0; i < count; i += 1) {
|
||||||
|
@ -363,14 +331,14 @@
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
free() {
|
free() {
|
||||||
this._stmt.finalize();
|
this._stmt.free();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Database {
|
class Database {
|
||||||
constructor(bytes) {
|
constructor(bytes) {
|
||||||
this._db = new sqlite3.oo1.DB();
|
const source = bytes instanceof Uint8Array ? bytes : bytes ? new Uint8Array(bytes) : undefined;
|
||||||
if (bytes) loadDatabaseBytes(this._db, bytes);
|
this._db = source ? new SQL.Database(source) : new SQL.Database();
|
||||||
}
|
}
|
||||||
prepare(sql) {
|
prepare(sql) {
|
||||||
return new Statement(this._db.prepare(sql));
|
return new Statement(this._db.prepare(sql));
|
||||||
|
@ -381,9 +349,6 @@
|
||||||
close() {
|
close() {
|
||||||
this._db.close();
|
this._db.close();
|
||||||
}
|
}
|
||||||
get pointer() {
|
|
||||||
return this._db.pointer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureFts5Available(dbHandle) {
|
function ensureFts5Available(dbHandle) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue