Switch search runtime to vendored sqlite3 WASM

This commit is contained in:
Jordan Wages 2025-09-18 19:38:10 -05:00
commit 04c8ce7005
6 changed files with 13059 additions and 10 deletions

View file

@ -1,3 +1,7 @@
# mp3-com-meta-browser
A self contained static website for browsing the collection of salvaged mp3.com files.
A self contained static website for browsing the collection of salvaged mp3.com files.
## Runtime Notes
- Uses the official `sqlite3` WASM build (vendored under `vendor/sqlite3/`) so that FTS5 is available client-side.

View file

@ -365,8 +365,8 @@
<!-- Third-party libs loaded from CDNs (no trackers). Pinned versions. -->
<!-- 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>
<!-- sql.js (WASM SQLite) loader; WASM resolved via locateFile in script.js. -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.2/sql-wasm.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- SQLite WASM build (oo1 API) shipped locally to ensure FTS5 support. -->
<script src="./vendor/sqlite3/sqlite3.js"></script>
<!-- App logic -->
<script src="./script.js"></script>

109
script.js
View file

@ -2,13 +2,13 @@
Application bootstrap for the static, client-side metadata browser.
- 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
- Loads sql.js (WASM) and opens the database from the cached bytes
- Loads the official sqlite3 WASM build (oo1 API) and opens the database from the cached bytes
- Swaps interchangeable UX elements (views) in a viewport, with fade transitions
Assumptions:
- 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 `sql.js` from a CDN; the WASM is located via `locateFile` (see initSql()).
- We ship the sqlite3 WASM runtime locally to guarantee FTS5 support.
- We store raw DB bytes in IndexedDB (localStorage is too small for large DBs).
*/
@ -297,15 +297,109 @@
return dbBytes;
}
// --- SQL.js init ---
// --- SQLite WASM init ---
async function initSql() {
if (typeof initSqlJs !== 'function') throw new Error('sql.js not loaded');
if (typeof sqlite3InitModule !== 'function') throw new Error('sqlite3.js not loaded');
loader.setStep('Initializing SQLite…');
loader.setDetail('Loading WASM');
const SQL = await initSqlJs({
locateFile: (file) => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.2/${file}`,
const sqlite3 = await sqlite3InitModule({
locateFile: (file) => `./vendor/sqlite3/${file}`,
});
return SQL;
return createSqlCompat(sqlite3);
}
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;
}
}
class Statement {
constructor(stmt) {
this._stmt = stmt;
this._columnNames = null;
}
bind(values) {
this._stmt.bind(values);
return true;
}
step() {
return !!this._stmt.step();
}
getAsObject() {
if (!this._columnNames) this._columnNames = this._stmt.getColumnNames([]);
const row = Object.create(null);
const count = this._columnNames.length;
for (let i = 0; i < count; i += 1) {
row[this._columnNames[i]] = this._stmt.get(i);
}
return row;
}
free() {
this._stmt.finalize();
}
}
class Database {
constructor(bytes) {
this._db = new sqlite3.oo1.DB();
if (bytes) loadDatabaseBytes(this._db, bytes);
}
prepare(sql) {
return new Statement(this._db.prepare(sql));
}
exec(sql) {
return this._db.exec(sql);
}
close() {
this._db.close();
}
get pointer() {
return this._db.pointer;
}
}
function ensureFts5Available(dbHandle) {
let stmt;
try {
stmt = dbHandle.prepare('CREATE VIRTUAL TABLE temp.__fts5_check USING fts5(content TEXT)');
stmt.step();
} catch (err) {
throw new Error('SQLite build missing FTS5 support; search requires an fts5-enabled wasm');
} finally {
try { if (stmt) stmt.free(); } catch (_) {}
try { dbHandle.exec('DROP TABLE IF EXISTS temp.__fts5_check'); } catch (_) {}
}
}
return { Database, ensureFts5Available };
}
// --- Templates & Views ---
@ -2380,6 +2474,7 @@
const SQL = await initSql();
loader.setDetail('Opening database');
const db = new SQL.Database(dbBytes);
SQL.ensureFts5Available(db);
activeDb = db;
window.__db = db;

46
vendor/sqlite3/README.txt vendored Normal file
View file

@ -0,0 +1,46 @@
This is the README for the sqlite3 WASM/JS distribution.
Main project page: https://sqlite.org
Documentation: https://sqlite.org/wasm
This archive contains the following deliverables for the WASM/JS
build:
- jswasm/sqlite3.js is the canonical "vanilla JS" version.
- jswasm/sqlite3.mjs is the same but in ES6 module form
- jswasm/*-bundler-friendly.js and .mjs are variants which are
intended to be compatible with "bundler" tools commonly seen in
node.js-based projects. Projects using such tools should use those
variants, where available, instead of files without the
"-bundler-friendly" suffix. Some files do not have separate
variants.
- jswasm/sqlite3.wasm is the binary WASM file imported by all of the
above-listed JS files.
- The jswasm directory additionally contains a number of supplemental
JS files which cannot be bundled directly with the main JS files
but are necessary for certain usages.
- The top-level directory contains various demonstration and test
applications for sqlite3.js and sqlite3.mjs.
sqlite3-bundler-friendly.mjs requires client-side build tools to make
use of and is not demonstrated here.
Browsers will not serve WASM files from file:// URLs, so the test and
demonstration apps require a web server and that server must include
the following headers in its response when serving the files:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
The core library will function without those headers but certain
features, most notably OPFS storage, will not be available.
One simple way to get the demo apps up and running on Unix-style
systems is to install althttpd (https://sqlite.org/althttpd) and run:
althttpd --enable-sab --page index.html

12904
vendor/sqlite3/sqlite3.js vendored Normal file

File diff suppressed because it is too large Load diff

BIN
vendor/sqlite3/sqlite3.wasm vendored Normal file

Binary file not shown.