Guides
Workers & crash recovery
On the web the core is a wasm module. It can run off the main thread, and when a panic traps it, it can come back from its last snapshot.
Worker mode#
mode: "wasm-worker" runs the core in a Web Worker, so a heavy call never blocks the UI. The core calls some ports synchronously (Clock, Rng, Log and any #[undra::port(sync)]) and cannot wait for the main thread, so those are answered in the worker. Clock, Rng and Log need nothing from you. Ports with asynchronous methods (Http, Kv, SecureStore, Fs and your own async ports) run on the main thread, exactly as in wasm-main.
Your own synchronous ports#
Put them in a small module the worker imports before the core starts, and pass its URL. The same module overrides Clock, Rng or the core's timers in this mode:
// main.ts
const core = await UndraCore.load({
mode: "wasm-worker", wasm, expectedSchemaHash: UndraIds.schemaHash,
worker: { ports: new URL("./worker-ports.js", import.meta.url) },
});
// worker-ports.ts: runs inside the worker
export default {
[UndraIds.Ports.Locale.portId]: localePortImpl({ hello: () => "Bonjour" }),
};
export const adapters = { timer: myTimer }; // optional: clock, rng, timerA synchronous port registered on the main thread in this mode is refused when the core loads (UndraError("options"), naming the port), not at its first call. With a bundler, make the module a worker entry.
Recovering from a crash#
A native core contains a panic and keeps going. A wasm core cannot: the panic traps the instance. With recovery: crashRecovery() (imported from @undra/runtime, so an app without it ships none of this code), the runtime keeps a snapshot of your stores (at most once a second while they change, in an idle moment, in the worker in worker mode) and restarts from it:
import { UndraCore, crashRecovery } from "@undra/runtime";
const core = await UndraCore.load({
mode: "wasm-main", wasm, expectedSchemaHash: UndraIds.schemaHash,
recovery: crashRecovery(), // or crashRecovery({ snapshotEveryMs, maxSnapshotBytes, maxRestarts, perMs })
onPanic: (report) => crashReporter.send(report),
onCoreRestarted: (e) => configureRemote(core, settings), // re-apply core state that is not in a store
});| On a trap | What your app sees |
|---|---|
| Calls and streams in flight | fail with UndraCallError.Unavailable, reason restarted; they may or may not have run and are never retried for you |
| Stores | keep their handles and show the snapshot's values |
| Query handles | keep their handle and their wrapper (the core re-issues them from the snapshot, and builds each one again when the recovery observes it), then fetch again; one created after the last snapshot goes stale |
| Other objects | go stale: calls on them are refused |
| Store writes after the last snapshot, running tasks and timers, configuration a call set | lost; re-apply configuration in onCoreRestarted |
Persisted queries and the offline queue live in Kv and are not affected. Past maxRestarts within perMs (default 3 a minute) the core stays down and onClose reports the crash, so a panic that recurs on every restart cannot loop. Recovery is off by default: it costs a snapshot up to once a second (about 0.04 ms for 100 KB of state; a restart takes about 1.6 ms).
Randomness#
A wasm core needs WebCrypto. Without crypto.getRandomValues, load rejects with UndraTransportError("unsupported") rather than run with predictable idempotency keys.