Removes the last piece of bespoke browser wiring in @actual-app/api so
consumers call api.init({...}) with no worker construction of their own.
Under the hood, the api package now:
- Spawns its Web Worker itself via `new Worker(new URL('./worker.js',
import.meta.url), { type: 'module' })`. Consumer bundlers resolve the
sibling asset at their own build time.
- Speaks loot-core's existing {id, name, args} / {type:'reply', id,
result} backend protocol, including the {type:'connect'} handshake
— same protocol desktop-client's browser-preload.js already feeds.
- Delegates sqlite bootstrap to loot-core's public init(config) via a
worker-registered `api-browser/init` handler; server-side dispatch is
handled by the existing packages/loot-core/src/platform/server/
connection layer, so no custom {op, payload} shape remains.
The absurd-sql main-thread plumbing (initSQLBackend + __absurd:* filter)
is now a single function in loot-core:
`packages/loot-core/src/platform/client/backend-worker/createBackendWorker`,
consumed by both desktop-client's browser-preload.js and the api's
browser rpc.ts.
Test split moves accordingly: browser-facade.test.ts swaps in a
Worker mock that speaks the new protocol (id, name, args / reply handshake)
and confirms init forwards config via api-browser/init.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
absurd-sql uses Atomics.wait for sync sqlite access, which only works
inside a Web Worker. Rather than forcing every consumer to wire up
their own worker + RPC glue, ship two artifacts:
- dist/browser.js: tiny main-thread facade (~10 KB). Reuses
packages/api/methods.ts verbatim by aliasing
@actual-app/core/server/main to browser/lib-stub.ts at build time;
every lib.send call posts to the worker.
- dist/worker.js: the full loot-core + sql.js + absurd-sql stack
(~3.6 MB) running in a Web Worker.
Consumer wiring:
const worker = new Worker(
new URL('@actual-app/api/dist/worker.js', import.meta.url),
{ type: 'module' },
);
await api.init({ worker, dataDir: '/documents', serverURL, password });
await api.getAccounts();
Same named imports as Node/Electron — the worker is the only
browser-specific wiring. Keeping the URL construction in consumer
code lets their bundler (Vite, Webpack, ...) handle worker.js as an
asset without forcing us onto a single bundler convention.
Tests split accordingly: Node runs the full CRUD roundtrip against
real loot-core; jsdom runs a facade test that verifies init
validation, postMessage payload shapes, and error propagation via
a mock Worker.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>