Make frontend.worker.db-worker and its dependencies run in both browser and Node.js. Add a Node.js daemon that can be started from the command line and exposes HTTP APIs to the same worker capabilities.
src/main/frontend/worker/db_worker.cljs.src/main/frontend/worker/**, src/main/frontend/worker_common/**, and any browser-only utilities used by those namespaces.src/main/frontend/persist_db/browser.cljs, src/main/frontend/handler/worker.cljs, and any callers that assume a WebWorker or Comlink transport.frontend.worker.db-core) that contains thread-api functions and business logic.js/self, js/location, js/navigator, importScripts, BroadcastChannel, and navigator.locks out of core.frontend.worker.platform interface: storage, kv-store, broadcast, websocket, crypto, timers, and env flags.frontend.worker.platform.browser using OPFS, IDB, BroadcastChannel, navigator.locks, WebSocket, and globalThis.frontend.worker.platform.node using fs/promises, path, crypto, and ws.better-sqlite3 (no OPFS, no sqlite-wasm).importScripts bootstrap with an explicit init entrypoint.
:web-worker, but entrypoint should call init! with a browser platform adapter.init! with a Node adapter.frontend.worker.rtc.crypt uses IDB/OPFS and should switch to the platform kv-store and file API.js/navigator or OPFS should be routed through the platform adapter.js/WebSocket usage with a platform websocket factory.
js/WebSocket.ws client with the same interface shape.frontend.persist-db.node or frontend.persist-db.remote) that talks to the HTTP daemon.frontend.persist-db.browser using WebWorker + Comlink.shadow-cljs.edn for db-worker (e.g. :db-worker-node).:node-script or :node-library with the correct externs.better-sqlite3 dependency and ensure Node target treats it as a native external.Node runtime must not use OPFS or sqlite-wasm. Instead, use better-sqlite3 as the direct file-backed sqlite engine.
src/main/frontend/worker/db_core.cljs (init-sqlite-module!, <get-opfs-pool, get-dbs, <export-db-file, <import-db, close-db-aux!, :thread-api/init)
src/main/frontend/worker/platform.cljs (validate-platform!)
:sqlite section (or expand :storage) defining: open-db, close-db, exec, transaction, export-db, import-db.src/main/frontend/worker/platform/node.cljs (node-platform, install-opfs-pool, export-file, import-db, remove-vfs!)
better-sqlite3 adapter: open db files, exec, transactions, close.src/main/frontend/worker/platform/browser.cljs (browser-platform, install-opfs-pool)
:sqlite interface used by db-core.src/main/frontend/worker/state.cljs (*opfs-pools, get-opfs-pool)
src/main/frontend/worker/db_worker_node.cljs (main, <init-worker!)
better-sqlite3 ready.src/main/frontend/persist_db/node.cljs (start!, <invoke)
shadow-cljs.edn (:db-worker-node)
better-sqlite3 stays external; no bundling of sqlite-wasm artifacts for Node target.package.json
better-sqlite3 dependency; keep sqlite-wasm for browser path only.better-sqlite3 backing: list-db, create-or-open-db, q, transact.DONE 3. Extract db-worker core logic into a platform-agnostic module (e.g. frontend.worker.db-core).
Core worker module has zero direct references to js/self, js/location, js/navigator, importScripts, BroadcastChannel, or navigator.locks.
frontend.worker.platform exists with required sections and validation; platform adapter passes validation at init time.
Browser worker entry initializes via init!/init-core! with a platform adapter.
bb dev:lint-and-test passes.
frontend.worker.platform.browser.DONE 7. Replace direct js/WebSocket usage with platform websocket factory.
Browser platform adapter encapsulates OPFS/IDB storage, kv-store, and WebSocket factory; worker submodules no longer import OPFS/IDB directly.
RTC WebSocket creation uses the platform adapter.
Browser db-worker entry injects the platform adapter and serves Comlink RPC.
bb dev:lint-and-test passes.
frontend.worker.platform.node in single-client mode (no locks or BroadcastChannel).shadow-cljs.edn for db-worker.DONE 12a. Switch Node sqlite implementation to better-sqlite3 (no OPFS, no sqlite-wasm).
Node platform adapter provides storage/kv/broadcast/websocket/crypto/timers and validates via frontend.worker.platform.
Node sqlite adapter uses better-sqlite3 and opens file-backed dbs in data-dir.
Node build target compiles db-worker core without browser-only APIs.
Node daemon starts via CLI and reports readiness; GET /healthz and GET /readyz return 200 OK.
POST /v1/invoke handles list-db, create-or-open-db, q, transact in a smoke test:
tmp_scripts/db-worker-smoke-test.cljLATER Node client can invoke at least one RPC and receive one event (SSE).
bb dev:lint-and-test passes.
DONE 13. Add tests: adapter unit tests + daemon integration smoke test.
Adapter unit tests cover browser and node implementations for storage/kv/broadcast/websocket factories.
Daemon integration smoke test starts the node process and exercises /v1/invoke with at least one method.
bb dev:lint-and-test passes.
The db-worker should be runnable as a standalone process for Node.js environments.
bin/logseq-db-worker or node dist/db-worker-node.js).--data-dir (path for sqlite files, required or default to ~/.logseq/db-worker)--repo (optional: auto-open a repo on boot)--rtc-ws-url (optional)--log-level (default info)--auth-token (optional; bearer token for HTTP)Use HTTP for RPC and event delivery. Prefer a single generic RPC entrypoint to avoid one endpoint per method.
Required endpoints:
GET /healthz -> 200 OK when process is alive.GET /readyz -> 200 OK only after sqlite init completes.POST /v1/invoke
method: string, e.g. "thread-api/create-or-open-db"directPass: booleanargsTransit: string (transit-encoded args) OR args: array for direct passok: booleanresultTransit: string (transit-encoded result) when directPass=falseresult: any (when directPass=true)error: error object if failedEvent delivery options:
GET /v1/events using SSE for worker -> client events
postMessage payloads in frontend.handler.worker.type and payload.WS /v1/events with the same payload format.--auth-token is provided, require Authorization: Bearer <token> for all endpoints except healthz and readyz.BroadcastChannel and navigator.locks are browser-only; Node should use a simpler single-client mode.Comlink is browser-optimized; the Node daemon should use HTTP, not Comlink.better-sqlite3 directly.list-dbcreate-or-open-dbqtransact