mirror of
https://github.com/logseq/logseq.git
synced 2026-02-01 22:47:36 +00:00
11 KiB
11 KiB
task--db-worker-nodejs-compatible
Goal
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.
Scope
- Primary target:
src/main/frontend/worker/db_worker.cljs. - All dependencies used by db-worker:
src/main/frontend/worker/**,src/main/frontend/worker_common/**, and any browser-only utilities used by those namespaces. - Callers:
src/main/frontend/persist_db/browser.cljs,src/main/frontend/handler/worker.cljs, and any callers that assume a WebWorker or Comlink transport.
Refactor Items (Concrete Work List)
- Split worker core logic from runtime-specific host APIs.
- Create a core module (e.g.
frontend.worker.db-core) that contains thread-api functions and business logic. - Move all direct uses of
js/self,js/location,js/navigator,importScripts,BroadcastChannel, andnavigator.locksout of core.
- Create a core module (e.g.
- Add a platform adapter layer with a consistent interface for browser and Node.js.
- Define
frontend.worker.platforminterface: storage, kv-store, broadcast, websocket, crypto, timers, and env flags. - Implement
frontend.worker.platform.browserusing OPFS, IDB, BroadcastChannel, navigator.locks, WebSocket, andglobalThis. - Implement
frontend.worker.platform.nodeusingfs/promises,path,crypto, andws.
- Define
- Abstract sqlite storage and VFS specifics.
- Browser: keep OPFS SAH pool implementation.
- Node: use file-backed sqlite storage via
better-sqlite3(no OPFS, no sqlite-wasm). - Route db path resolution through the platform adapter (data dir, per-repo paths).
- Replace
importScriptsbootstrap with an explicit init entrypoint.- Browser build still uses
:web-worker, but entrypoint should callinit!with a browser platform adapter. - Node build should call the same
init!with a Node adapter.
- Browser build still uses
- Normalize RPC and transport.
- Define a transport-agnostic RPC layer that accepts a method name and args (transit string or direct args).
- Keep Comlink for browser worker transport.
- Add HTTP transport for Node (see daemon section).
- Update shared-service for non-browser environments.
- Provide a "single-client" fallback for Node; no multi-client coordination is needed.
- Replace browser-only storage in RTC and crypto modules.
frontend.worker.rtc.cryptuses IDB/OPFS and should switch to the platform kv-store and file API.- Any other worker modules using
js/navigatoror OPFS should be routed through the platform adapter.
- Replace direct
js/WebSocketusage with a platform websocket factory.- Browser:
js/WebSocket. - Node:
wsclient with the same interface shape.
- Browser:
- Update caller-side initialization.
- Add a Node-specific db worker client (e.g.
frontend.persist-db.nodeorfrontend.persist-db.remote) that talks to the HTTP daemon. - Keep browser
frontend.persist-db.browserusing WebWorker + Comlink.
- Add a Node-specific db worker client (e.g.
- Build config changes.
- Add a Node build target in
shadow-cljs.ednfor db-worker (e.g.:db-worker-node). - Ensure shared code compiles for
:node-scriptor:node-librarywith the correct externs. - Add
better-sqlite3dependency and ensure Node target treats it as a native external.
- Add a Node build target in
- Tests and fixtures.
- Add unit tests for platform adapters and storage abstraction.
- Add a minimal integration test that starts the Node daemon and exercises a small RPC call.
Node.js sqlite Implementation (better-sqlite3)
Node runtime must not use OPFS or sqlite-wasm. Instead, use better-sqlite3 as the direct file-backed sqlite engine.
Concrete Refactor Items (File + Function + Summary)
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)- Remove sqlite-wasm initialization and OPFS pool usage on Node.
- Route all sqlite open/close/exec/transaction operations through a platform-provided sqlite interface.
- Replace OPFS export/import with file-based export/import in Node.
src/main/frontend/worker/platform.cljs(validate-platform!)- Extend platform contract to include a
:sqlitesection (or expand:storage) defining:open-db,close-db,exec,transaction,export-db,import-db.
- Extend platform contract to include a
src/main/frontend/worker/platform/node.cljs(node-platform,install-opfs-pool,export-file,import-db,remove-vfs!)- Remove OPFS pool and sqlite-wasm Node VFS behavior.
- Implement
better-sqlite3adapter: open db files, exec, transactions, close. - Resolve db paths under data-dir and ensure directories exist.
src/main/frontend/worker/platform/browser.cljs(browser-platform,install-opfs-pool)- Keep sqlite-wasm + OPFS behavior but conform to the same
:sqliteinterface used by db-core.
- Keep sqlite-wasm + OPFS behavior but conform to the same
src/main/frontend/worker/state.cljs(*opfs-pools,get-opfs-pool)- Ensure Node path does not write/read OPFS pools; keep OPFS state browser-only.
src/main/frontend/worker/db_worker_node.cljs(main,<init-worker!)- Initialize platform adapter before core init; mark readiness after
better-sqlite3ready.
- Initialize platform adapter before core init; mark readiness after
src/main/frontend/persist_db/node.cljs(start!,<invoke)- Keep API compatibility; update if any thread-api names or args change due to sqlite refactor.
shadow-cljs.edn(:db-worker-node)- Ensure
better-sqlite3stays external; no bundling of sqlite-wasm artifacts for Node target.
- Ensure
package.json- Add
better-sqlite3dependency; keep sqlite-wasm for browser path only.
- Add
- Tests
- Add Node integration smoke test with
better-sqlite3backing:list-db,create-or-open-db,q,transact.
- Add Node integration smoke test with
Refactor Steps (Milestones + Status)
Milestone 1: Architecture & Abstractions
- DONE 1. Inventory db-worker dependencies and classify browser-only APIs.
- DONE 2. Define a platform adapter interface (storage, kv, broadcast, websocket, crypto, timers, env flags).
- DONE 3. Extract db-worker core logic into a platform-agnostic module (e.g.
frontend.worker.db-core).
Acceptance Criteria
- Core worker module has zero direct references to
js/self,js/location,js/navigator,importScripts,BroadcastChannel, ornavigator.locks. frontend.worker.platformexists 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-testpasses.
Milestone 2: Browser Path Parity
- DONE 4. Implement
frontend.worker.platform.browser. - DONE 5. Update db-worker entry to inject the platform adapter and call core init.
- DONE 6. Route OPFS/IDB usage through the platform adapter in worker submodules.
- DONE 7. Replace direct
js/WebSocketusage with platform websocket factory.
Acceptance Criteria
- 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-testpasses.
Milestone 3: Node Path & Daemon
- DONE 8. Implement
frontend.worker.platform.nodein single-client mode (no locks or BroadcastChannel). - DONE 9. Update shared-service to no-op/single-client behavior in Node.
- DONE 10. Add Node build target in
shadow-cljs.ednfor db-worker. - DONE 11. Implement Node daemon entrypoint and HTTP server.
- TODO 12. Add a Node client in frontend to call the daemon (HTTP + SSE events).
- DONE 12a. Switch Node sqlite implementation to
better-sqlite3(no OPFS, no sqlite-wasm).
Acceptance Criteria
- Node platform adapter provides storage/kv/broadcast/websocket/crypto/timers and validates via
frontend.worker.platform. - Node sqlite adapter uses
better-sqlite3and 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 /healthzandGET /readyzreturn200 OK. POST /v1/invokehandleslist-db,create-or-open-db,q,transactin a smoke test:- test client script:
tmp_scripts/db-worker-smoke-test.clj
- test client script:
- Node client can invoke at least one RPC and receive one event (SSE).
bb dev:lint-and-testpasses.
Milestone 4: Validation
- TODO 13. Add tests: adapter unit tests + daemon integration smoke test.
- TODO 14. Verify browser worker path still works with Comlink.
Acceptance Criteria
- 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/invokewith at least one method. - Browser worker path verified with Comlink RPCs (smoke test).
bb dev:lint-and-testpasses.
Node.js Daemon Requirements
The db-worker should be runnable as a standalone process for Node.js environments.
Entry Point
- Provide a CLI entry (example:
bin/logseq-db-workerornode dist/db-worker-node.js). - CLI flags (suggested):
--host(default127.0.0.1)--port(default9101)--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(defaultinfo)--auth-token(optional; bearer token for HTTP)
Lifecycle
- Initialize platform adapter (Node).
- Initialize sqlite module and storage roots.
- Start HTTP server.
- Emit readiness when init completes.
- Graceful shutdown on SIGINT/SIGTERM (close dbs, flush logs).
HTTP API (Minimum)
Use HTTP for RPC and event delivery. Prefer a single generic RPC entrypoint to avoid one endpoint per method.
Required endpoints:
GET /healthz->200 OKwhen process is alive.GET /readyz->200 OKonly after sqlite init completes.POST /v1/invoke- Request JSON:
method: string, e.g."thread-api/create-or-open-db"directPass: booleanargsTransit: string (transit-encoded args) ORargs: array for direct pass
- Response JSON:
ok: booleanresultTransit: string (transit-encoded result) whendirectPass=falseresult: any (whendirectPass=true)error: error object if failed
- Request JSON:
Event delivery options:
GET /v1/eventsusing SSE for worker -> client events- Event payload should mirror current
postMessagepayloads infrontend.handler.worker. - Each event should be tagged with
typeandpayload.
- Event payload should mirror current
- Alternatively, provide
WS /v1/eventswith the same payload format.
Security
- If
--auth-tokenis provided, requireAuthorization: Bearer <token>for all endpoints excepthealthzandreadyz. - Bind to localhost by default.
Notes on Compatibility Gaps
- OPFS and IndexedDB do not exist in Node; file-backed storage and a Node KV store are required.
BroadcastChannelandnavigator.locksare browser-only; Node should use a simpler single-client mode.Comlinkis browser-optimized; the Node daemon should use HTTP, not Comlink.- sqlite-wasm must remain browser-only; Node uses
better-sqlite3directly. - only db-graph supported in Node db-worker
Success Criteria
- Browser build continues to work with WebWorker + Comlink.
- Node daemon can start from CLI, open a repo, and respond to at least:
list-dbcreate-or-open-dbqtransact
- A minimal client can call the daemon and receive event notifications.