@dynoxide/wasm-engine
v0.11.3-preview
Published
The dynoxide DynamoDB-compatible engine compiled to WebAssembly, with a Web Worker client for running real DynamoDB operations in the browser. Preview.
Downloads
816
Maintainers
Readme
@dynoxide/wasm-engine
The dynoxide DynamoDB-compatible engine, compiled to WebAssembly and packaged to run in the browser. It carries the engine .wasm, the official @sqlite.org/sqlite-wasm SQLite build, the bundled Web Worker, and an EngineClient that drives them, so you can run real DynamoDB operations client-side against a SQLite-backed store persisted to OPFS.
It's a preview. The wasm build is not run against the conformance suite that backs dynoxide's native build, so treat its behaviour as illustrative rather than authoritative.
Install
npm install @dynoxide/wasm-engineThis is a preview build: the version carries -preview and the wasm path is not run against the conformance suite. npm install @dynoxide/wasm-engine gets the current preview; pin the exact version (0.11.1-preview) to lock to one.
Quick start
import { EngineClient } from "@dynoxide/wasm-engine";
const client = new EngineClient();
await client.ready();
await client.execute("CreateTable", {
TableName: "Music",
KeySchema: [
{ AttributeName: "artist", KeyType: "HASH" },
{ AttributeName: "song", KeyType: "RANGE" },
],
AttributeDefinitions: [
{ AttributeName: "artist", AttributeType: "S" },
{ AttributeName: "song", AttributeType: "S" },
],
BillingMode: "PAY_PER_REQUEST",
});
await client.execute("PutItem", {
TableName: "Music",
Item: { artist: { S: "Pixies" }, song: { S: "Debaser" } },
});
const { Items } = await client.execute("Query", {
TableName: "Music",
KeyConditionExpression: "artist = :a",
ExpressionAttributeValues: { ":a": { S: "Pixies" } },
});execute takes the operation name and a plain DynamoDB-JSON request, resolves with the response, and rejects with a typed EngineError. The client runs the engine in a Web Worker (OPFS's synchronous file handles are Worker-only) and owns the message round-trip, so you deal in objects rather than postMessage envelopes.
API
new EngineClient(opts?)- boots the engine eagerly. Calls queue behind boot, so you can issue work beforeready()resolves.await client.ready()- resolves with the boot descriptor ({ contractVersion, capabilities, persistenceMode }).client.execute(op, request)- run one operation.client.supports(op)- whether the engine implements an operation, for capability-gating your UI.client.persistent-truewhen the session persists across reloads (OPFS),falsein the in-memory fallback.client.terminate()- tear the Worker down and reject any in-flight calls.
The package also exports EngineError (the engine's __type is on .type) and CONTRACT_VERSION. TypeScript declarations ship with it, so the client and its boot descriptor are typed out of the box.
Where the assets live
The Worker and the two .wasm travel inside the package. new EngineClient() with no arguments resolves the Worker next to this module, and the Worker resolves its .wasm next to itself, so a bundler that copies the package's files - or a plain static deploy of them - needs no configuration.
Serving the assets from a CDN or a different origin? Two options:
assetBase- the directory the assets sit in, e.g.new EngineClient({ assetBase: "https://cdn.example.com/dynoxide/" }).workerUrl- the exact Worker URL, if it doesn't sit beside its.wasmunder a shared base.
If you'd rather construct the Worker yourself - to let a bundler handle it, say - the package exposes it at the ./worker subpath. Build it and hand it back through createWorker:
new EngineClient({
createWorker: () =>
new Worker(new URL("@dynoxide/wasm-engine/worker", import.meta.url), { type: "module" }),
});Two clients on one page are fine: each gets its own storage pool, keyed on name (default dynoxide.db), so give them distinct names if both should persist.
Hosting
The engine needs a secure context (HTTPS, or localhost for development) for OPFS, but no COOP/COEP cross-origin isolation. A Content-Security-Policy must allow 'wasm-unsafe-eval', or the engine won't instantiate. Serve the .wasm as application/wasm, and serve the assets gzip- or brotli-encoded - the wasm and the Worker both more than halve over the wire.
Versioning
CONTRACT_VERSION stamps the message-envelope shape, not the engine version. Adding an operation leaves it alone; changing a request, response, or error envelope bumps it. The client validates it against the engine on boot and fails loudly on a mismatch, so a pinned consumer fails with a clear error rather than mis-reading a newer engine. The shipped engine and contract versions sit in manifest.json. manifest.engineVersion is the dynoxide crate version (e.g. 0.11.1); the npm package version layers a -preview suffix on top (e.g. 0.11.1-preview), since the package is a preview distribution of that crate version.
Persistence
State persists across reloads via OPFS. Where OPFS synchronous access handles are unavailable (Firefox private windows, older Safari), the client falls back to an in-memory session and reports it through persistent / persistenceMode rather than failing.
Licence
MIT OR Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.
