Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/sharp-wasms-opfs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@mandel59/mojidata-api-sqlite-wasm": minor
"@mandel59/mojidata-api-runtime": patch
---

Add the initial SQLite wasm OPFS backend package for `mojidata-api`, including
an OO1 `SqlExecutor` adapter, OPFS SAH pool materialization helpers, and a
browser worker entrypoint.
29 changes: 29 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [Mojidata API compatibility facade](packages/mojidata-api)
- [Mojidata API runtime](packages/mojidata-api-runtime)
- [Mojidata API sql.js backend](packages/mojidata-api-sqljs)
- [Mojidata API SQLite wasm OPFS backend](packages/mojidata-api-sqlite-wasm)
- [Mojidata API better-sqlite3 backend](packages/mojidata-api-better-sqlite3)
- [Mojidata API node:sqlite backend](packages/mojidata-api-node-sqlite)
- [Mojidata API D1 backend](packages/mojidata-api-d1)
Expand Down
1 change: 1 addition & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ For the split `mojidata-api` packages, use this bootstrap flow for:

- `@mandel59/mojidata-api-core`
- `@mandel59/mojidata-api-sqljs`
- `@mandel59/mojidata-api-sqlite-wasm`
- `@mandel59/mojidata-api-hono`
- `@mandel59/mojidata-api-runtime`
- `@mandel59/mojidata-api-better-sqlite3`
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"changeset": "changeset",
"build:idsdb-stack": "(cd packages/mojidata && yarn prepare) && (cd packages/idsdb-utils && yarn prepare) && (cd packages/idsdb && yarn prepare)",
"build:mojidata-api-split": "(cd packages/mojidata-api-core && yarn prepare) && (cd packages/mojidata-api-hono && yarn prepare) && (cd packages/mojidata-api-runtime && yarn prepare) && (cd packages/mojidata-api-sqljs && yarn prepare) && (cd packages/mojidata-api-better-sqlite3 && yarn prepare) && (cd packages/mojidata-api-node-sqlite && yarn prepare) && (cd packages/mojidata-api-d1 && yarn prepare)",
"build:mojidata-api-split": "(cd packages/mojidata-api-core && yarn prepare) && (cd packages/mojidata-api-hono && yarn prepare) && (cd packages/mojidata-api-runtime && yarn prepare) && (cd packages/mojidata-api-sqljs && yarn prepare) && (cd packages/mojidata-api-sqlite-wasm && yarn prepare) && (cd packages/mojidata-api-better-sqlite3 && yarn prepare) && (cd packages/mojidata-api-node-sqlite && yarn prepare) && (cd packages/mojidata-api-d1 && yarn prepare)",
"ci:build:affected": "node ./scripts/run-affected-workspaces.mjs build",
"ci:build": "yarn build",
"ci:pack:affected": "node ./scripts/run-affected-workspaces.mjs pack",
Expand All @@ -26,7 +26,7 @@
"mojidata-api:bench:remote": "yarn workspace @mandel59/mojidata-api-bench bench:remote",
"mojidata-api:bench:prepare": "yarn workspace @mandel59/mojidata-api-bench bench:prepare",
"release": "yarn ci:build && changeset publish",
"test": "yarn build:idsdb-stack && yarn build:mojidata-api-split && (cd packages/mojidata && yarn test) && (cd packages/mojidata-api && yarn test) && (cd packages/mojidata-api-sqljs && yarn test) && (cd packages/mojidata-api-better-sqlite3 && yarn test) && (cd packages/mojidata-api-node-sqlite && yarn test) && (cd packages/mojidata-api-d1 && yarn test) && (cd packages/react-mojidata-api && yarn test:unit) && (cd packages/idstool && yarn test) && (cd packages/mojidata-cli && yarn test)",
"test": "yarn build:idsdb-stack && yarn build:mojidata-api-split && (cd packages/mojidata && yarn test) && (cd packages/mojidata-api && yarn test) && (cd packages/mojidata-api-sqljs && yarn test) && (cd packages/mojidata-api-sqlite-wasm && yarn test) && (cd packages/mojidata-api-better-sqlite3 && yarn test) && (cd packages/mojidata-api-node-sqlite && yarn test) && (cd packages/mojidata-api-d1 && yarn test) && (cd packages/react-mojidata-api && yarn test:unit) && (cd packages/idstool && yarn test) && (cd packages/mojidata-cli && yarn test)",
"version-packages": "changeset version",
"build": "yarn build:idsdb-stack && yarn build:mojidata-api-split && (cd packages/mojidata-api && yarn prepare) && (cd packages/react-mojidata-api && yarn prepare) && (cd packages/idstool && yarn prepare) && (cd packages/mojidata-cli && yarn prepare)"
},
Expand Down
15 changes: 15 additions & 0 deletions packages/mojidata-api-runtime/lib/worker-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ export type WorkerInit = {
sqlWasmUrl: string
mojidataDbUrl: string
idsfindDbUrl: string
sqliteWasm?: {
wasmUrl?: string
wasmBinary?: ArrayBuffer | Uint8Array
opfsName?: string
opfsDirectory?: string
initialCapacity?: number
clearOnInit?: boolean
manifestDirectory?: string
mojidataDbName?: string
idsfindDbName?: string
mojidataDbVersion?: string
idsfindDbVersion?: string
mojidataDbByteLength?: number
idsfindDbByteLength?: number
}
}

export type WorkerCall = {
Expand Down
1 change: 1 addition & 0 deletions packages/mojidata-api-sqlite-wasm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
16 changes: 16 additions & 0 deletions packages/mojidata-api-sqlite-wasm/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Change Log

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [1.8.0] - 2026-05-03

### Added

- Publish the initial SQLite wasm OPFS backend package for `mojidata-api`.
- Add a `SqlExecutor` adapter for the SQLite wasm OO1 API.
- Add `opfs-sahpool` initialization, asset import, manifest, and worker entrypoint helpers.

[1.8.0]: https://github.com/mandel59/mojidata/releases/tag/v1.8.0
19 changes: 19 additions & 0 deletions packages/mojidata-api-sqlite-wasm/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2026 Ryusei Yamaguchi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
62 changes: 62 additions & 0 deletions packages/mojidata-api-sqlite-wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# mojidata-api-sqlite-wasm

`@mandel59/mojidata-api-sqlite-wasm` provides a browser-oriented SQLite wasm
backend for `mojidata-api`.

The package targets `@sqlite.org/sqlite-wasm` and its `opfs-sahpool` VFS. It
keeps the default `@mandel59/mojidata-api` and `@mandel59/mojidata-api-sqljs`
paths unchanged, so applications can opt in to the larger SQLite wasm backend
only when they need persistent OPFS storage.

It includes:

- `createSqliteWasmExecutor()`: an OO1 API adapter for `SqlExecutor`
- `installMojidataSqliteWasmFunctions()`: registers mojidata UDFs on SQLite wasm
- `installOpfsSAHPool()`: initializes `opfs-sahpool` in a Worker context
- `tryInstallOpfsSAHPool()`: reports pool initialization failures for fallback handling
- `ensureOpfsSAHPoolDatabase()`: imports an asset DB into OPFS with a manifest
- `createSqliteWasmDbFromOpfsSAHPool()`: wires OPFS DB handles to `createSqlApiDb()`
- `@mandel59/mojidata-api-sqlite-wasm/browser-worker`: worker entrypoint using the shared mojidata-api worker protocol

Example worker client:

```ts
import { createMojidataApiWorkerClient } from "@mandel59/mojidata-api-runtime"

const worker = new Worker(
new URL("@mandel59/mojidata-api-sqlite-wasm/browser-worker", import.meta.url),
{ type: "module" },
)

const db = createMojidataApiWorkerClient(worker, {
sqlWasmUrl: "/assets/sqlite3.wasm",
mojidataDbUrl: "/assets/moji.db",
idsfindDbUrl: "/assets/idsfind.db",
sqliteWasm: {
mojidataDbVersion: "moji-db-v1",
idsfindDbVersion: "idsfind-db-v1",
mojidataDbByteLength: 123456,
idsfindDbByteLength: 654321,
},
})

await db.ready
```

`sqlWasmUrl` is passed to SQLite wasm as the `sqlite3.wasm` URL. Applications
can also pass `sqliteWasm.wasmUrl` to override it, or `sqliteWasm.wasmBinary`
when the app already fetched the wasm bytes through its own asset pipeline.
Custom workers can call `getSqliteWasm({ wasmUrl, wasmBinary, locateFile })`
directly when they need lower-level control.

The browser worker initializes SQLite and the OPFS pool during `ready`, but DB
assets are imported lazily. `moji.db` is downloaded/imported on the first
mojidata query, and `idsfind.db` is downloaded/imported on the first IDS query.

`opfs-sahpool` is only available from Worker contexts with OPFS APIs. In
unsupported contexts, use `tryEnsureOpfsSAHPoolDatabase()` or catch
`SqliteWasmOpfsError` and fall back to the existing SQL.js backend.

The package does not copy or serve `moji.db`, `idsfind.db`, or `sqlite3.wasm`
assets. Applications should keep asset URL/versioning, Cache Storage prefetch,
and framework middleware in the application layer.
1 change: 1 addition & 0 deletions packages/mojidata-api-sqlite-wasm/browser-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./lib/browser-worker.js"
33 changes: 33 additions & 0 deletions packages/mojidata-api-sqlite-wasm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export {
createSqliteWasmDb,
createSqliteWasmDbFromOpfsSAHPool,
createSqliteWasmExecutorProvider,
createSqliteWasmMojidataDbProvider,
openOpfsSAHPoolDatabase,
type CreateSqliteWasmDbFromOpfsSAHPoolOptions,
} from "./lib/sqlite-wasm-runtime.js"
export {
createSqliteWasmExecutor,
type SqliteWasmDatabaseLike,
} from "./lib/sqlite-wasm-executor.js"
export { installMojidataSqliteWasmFunctions } from "./lib/sqlite-wasm-functions.js"
export {
DEFAULT_OPFS_MANIFEST_DIRECTORY,
SqliteWasmOpfsError,
ensureOpfsSAHPoolDatabase,
getSqliteWasm,
installOpfsSAHPool,
isOpfsSAHPoolSupported,
readOpfsSAHPoolManifest,
tryInstallOpfsSAHPool,
tryEnsureOpfsSAHPoolDatabase,
writeOpfsSAHPoolManifest,
type EnsureOpfsSAHPoolDatabaseResult,
type OpfsSAHPoolInstallOptions,
type OpfsSAHPoolManifest,
type OpfsSAHPoolMaterializeOptions,
type SqliteWasmOpfsFailure,
type SqliteWasmOpfsFailureReason,
type SqliteWasmInitOptions,
type SqliteWasmSAHPoolUtil,
} from "./lib/opfs-sahpool.js"
76 changes: 76 additions & 0 deletions packages/mojidata-api-sqlite-wasm/lib/browser-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { MojidataApiDb } from "@mandel59/mojidata-api-core"
import type {
WorkerRequest,
WorkerResponse,
} from "@mandel59/mojidata-api-runtime/lib/worker-protocol"
import type { WorkerInit } from "@mandel59/mojidata-api-runtime/lib/worker-protocol"

import {
createSqliteWasmDbFromOpfsSAHPool,
getSqliteWasm,
installOpfsSAHPool,
} from "../index.js"

let api: MojidataApiDb | undefined

function serializeError(error: unknown) {
if (error instanceof Error) {
return { message: error.message, stack: error.stack }
}
return { message: String(error) }
}

async function initWorker(init: WorkerInit) {
const wasmUrl = init.sqliteWasm?.wasmUrl ?? init.sqlWasmUrl
const sqlite3 = await getSqliteWasm({
wasmUrl: wasmUrl || undefined,
wasmBinary: init.sqliteWasm?.wasmBinary,
})
const poolUtil = await installOpfsSAHPool(sqlite3, {
name: init.sqliteWasm?.opfsName,
directory: init.sqliteWasm?.opfsDirectory,
initialCapacity: init.sqliteWasm?.initialCapacity ?? 6,
clearOnInit: init.sqliteWasm?.clearOnInit,
})
const manifestDirectory = init.sqliteWasm?.manifestDirectory
api = await createSqliteWasmDbFromOpfsSAHPool({
poolUtil,
mojidata: {
name: init.sqliteWasm?.mojidataDbName ?? "/mojidata/moji.db",
assetUrl: init.mojidataDbUrl,
assetVersion: init.sqliteWasm?.mojidataDbVersion ?? init.mojidataDbUrl,
byteLength: init.sqliteWasm?.mojidataDbByteLength,
manifestDirectory,
},
idsfind: {
name: init.sqliteWasm?.idsfindDbName ?? "/mojidata/idsfind.db",
assetUrl: init.idsfindDbUrl,
assetVersion: init.sqliteWasm?.idsfindDbVersion ?? init.idsfindDbUrl,
byteLength: init.sqliteWasm?.idsfindDbByteLength,
manifestDirectory,
},
})
}

globalThis.addEventListener("message", async (ev: MessageEvent) => {
const req = ev.data as WorkerRequest
const post = (res: WorkerResponse) => (globalThis as unknown as Worker).postMessage(res)

try {
if (req.type === "init") {
await initWorker(req.init)
post({ id: req.id, ok: true, result: null })
return
}

if (!api) {
throw new Error("Worker is not initialized; call init first.")
}

const { method, args } = req.call
const result = await (api as any)[method](...args)
post({ id: req.id, ok: true, result })
} catch (error) {
post({ id: req.id, ok: false, error: serializeError(error) })
}
})
Loading
Loading