13 KiB
db-worker-node Node Built-in SQLite Migration Implementation Plan
Goal: Replace better-sqlite3 in db-worker-node with Node.js built-in node:sqlite while keeping logseq-cli behavior and db-worker thread-api contracts unchanged.
Architecture: Keep the existing platform adapter boundary in /Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/platform/node.cljs and swap only the Node SQLite backend implementation to a compatibility wrapper around DatabaseSync and StatementSync.
Architecture: Preserve daemon lifecycle and lock ownership semantics, then update bundle/test/doc assumptions that currently depend on native .node assets from better-sqlite3.
Tech Stack: ClojureScript, shadow-cljs :node-script, Node.js >=22.20.0, node:sqlite, @vercel/ncc, logseq-cli HTTP transport.
Related: Builds on docs/agent-guide/033-desktop-db-worker-node-backend.md and docs/agent-guide/036-db-worker-node-ncc-bundling.md.
Related: Relates to docs/agent-guide/task--db-worker-nodejs-compatible.md and docs/cli/logseq-cli.md.
Problem statement
db-worker-node currently requires better-sqlite3 from /Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/platform/node.cljs.
logseq-cli and Electron desktop both depend on this daemon runtime through /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/server.cljs, so backend driver replacement must not change daemon API behavior.
The current ncc bundle plan and tests assume native .node assets are emitted and copied next to dist/db-worker-node.js, which is specific to better-sqlite3 and must be revised after migration.
Node.js in this repository is already pinned to >=22.20.0 in /Users/rcmerci/gh-repos/logseq/package.json, so node:sqlite is available, but it is still experimental and emits runtime warnings that we need to account for.
Current implementation map
| Area | Current implementation | Migration impact |
|---|---|---|
| Node sqlite adapter | /Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/platform/node.cljs wraps better-sqlite3 with custom exec and transaction behavior. |
Replace constructor and statement execution with node:sqlite API while preserving wrapper contract. |
| Daemon runtime contract | /Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_worker_node.cljs exposes /healthz, /readyz, /v1/invoke, /v1/events. |
No protocol change allowed. |
| CLI runtime spawn | /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/server.cljs spawns dist/db-worker-node.js. |
Behavior must remain unchanged. |
| Bundle smoke tests | /Users/rcmerci/gh-repos/logseq/src/test/logseq/db_worker/ncc_bundle_test.cljs expects a missing native .node asset failure mode. |
Replace asset assertions to match built-in sqlite runtime with zero native assets. |
| Dependency declaration | /Users/rcmerci/gh-repos/logseq/package.json includes better-sqlite3. |
Remove dependency and refresh lockfile. |
| CLI documentation | /Users/rcmerci/gh-repos/logseq/docs/cli/logseq-cli.md references dist/build/Release/better_sqlite3.node. |
Update build output description and troubleshooting text. |
Target architecture
logseq-cli / electron
-> db-worker-node HTTP API
-> frontend.worker.db-core
-> frontend.worker.platform.node sqlite wrapper
-> node:sqlite DatabaseSync
-> graph-dir/*.sqlite files
The wrapper contract consumed by db-core remains open-db, exec, transaction, and close.
The wrapper implementation changes from better-sqlite3 to node:sqlite internals only.
Testing Plan
I will use @test-driven-development and add failing tests first for adapter behavior and bundle assumptions before modifying runtime code.
I will add focused Node adapter tests for parameter binding, array-row reads, commit behavior, and rollback behavior so the compatibility wrapper is behavior-locked.
I will keep existing daemon smoke tests and CLI sqlite import/export integration tests as end-to-end regression guards.
I will update ncc bundle tests so they validate standalone runtime startup without relying on native .node artifacts.
I will run bb dev:test for focused namespaces first, then run bb dev:lint-and-test for the repository checklist.
I will use @clojure-debug if any ClojureScript test fails unexpectedly while porting the adapter.
NOTE: I will write all tests before I add any implementation behavior.
Implementation plan
Phase 1: Lock behavior with failing tests.
- Add a new test namespace at
/Users/rcmerci/gh-repos/logseq/src/test/frontend/worker/platform_node_test.cljsto cover Node sqlite wrapper behavior in isolation. - Add a failing test that
execwith SQL string creates schema and writes data through the wrapper. - Add a failing test that
execwith{:sql ... :bind ... :rowMode "array"}returns array rows in the same shape used byrestore-data-from-addr. - Add a failing test that named bindings with
$nameand:namestyles are both accepted by the wrapper. - Add a failing test that wrapper
transactioncommits writes on success. - Add a failing test that wrapper
transactionrolls back writes when callback throws. - Add a failing test in
/Users/rcmerci/gh-repos/logseq/src/test/logseq/db_worker/ncc_bundle_test.cljsasserting bundled runtime can start even when bundle manifest has zero assets. - Replace the current missing-native-asset expectation test with a failing test that checks bundle manifest format and actionable errors for missing manifest or missing entry script instead.
- Run
bb dev:test -v 'frontend.worker.platform-node-test'and confirm failures before implementation. - Run
bb dev:test -v 'logseq.db-worker.ncc-bundle-test'and confirm failures before implementation.
Phase 2: Port Node sqlite adapter to node:sqlite.
- Edit
/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/platform/node.cljsto replace"better-sqlite3"require with"node:sqlite". - Build a
DatabaseSyncconstructor resolver compatible with Shadow-CLJS interop and avoid default-export assumptions. - Keep
open-sqlite-dbasync shape unchanged and createDatabaseSyncafter ensuring parent directory exists. - Re-implement statement execution so
:rowMode "array"maps toStatementSync#setReturnArrays(true). - Re-implement positional and named parameter passing for array and object binds without changing db-core callsites.
- Preserve existing bind key normalization behavior for
$nameand:nameforms to avoid hidden regressions. - Re-implement wrapper
transactionsemantics using explicit SQL transaction control with rollback on exceptions. - Add nested-transaction safety via savepoint naming or equivalent deterministic strategy to avoid partial writes from nested calls.
- Keep wrapper
closeidempotent and compatible with existing shutdown paths indb-coreand daemon stop. - Keep all public platform map keys unchanged in
node-platform.
Phase 3: Update dependency and bundle assumptions.
- Remove
better-sqlite3from/Users/rcmerci/gh-repos/logseq/package.jsondependencies. - Run
yarn installto refresh/Users/rcmerci/gh-repos/logseq/yarn.lockand verifybetter-sqlite3is removed from runtime dependency graph. - Verify no remaining runtime require for
better-sqlite3viarg -n "better-sqlite3" /Users/rcmerci/gh-repos/logseq/src /Users/rcmerci/gh-repos/logseq/package.json /Users/rcmerci/gh-repos/logseq/yarn.lock. - Keep
/Users/rcmerci/gh-repos/logseq/scripts/package.jsonunchanged in this task because scope is limited tologseq-clianddb-worker-noderuntime paths. - Update
/Users/rcmerci/gh-repos/logseq/scripts/build-db-worker-node-bundle.mjsonly if manifest handling needs explicit support for empty asset arrays.
Phase 4: Refresh tests and docs around runtime packaging.
- Update
/Users/rcmerci/gh-repos/logseq/src/test/logseq/db_worker/ncc_bundle_test.cljsto stop asserting a required.nodeasset exists. - Keep startup smoke test that runs copied
db-worker-node.jsfrom a temporary runtime directory with nonode_modules. - Add assertions that
/healthz,/readyz, and/v1/shutdownstill work under bundled runtime. - Update
/Users/rcmerci/gh-repos/logseq/docs/cli/logseq-cli.mdbuild section to removebetter_sqlite3.noderuntime-asset example. - Update
/Users/rcmerci/gh-repos/logseq/docs/cli/logseq-cli.mdtroubleshooting text to describe expected bundle output when native assets are absent. - Update references in
/Users/rcmerci/gh-repos/logseq/docs/agent-guide/task--db-worker-nodejs-compatible.mdto reflect that Node runtime now uses built-in sqlite.
Phase 5: Run regression and verification commands.
- Run
bb dev:test -v 'frontend.worker.platform-node-test'and expect0 failures, 0 errors. - Run
bb dev:test -v 'frontend.worker.db-worker-node-test/db-worker-node-daemon-smoke-test'and expect daemon startup and query path to pass. - Run
bb dev:test -v 'frontend.worker.db-worker-node-test/db-worker-node-import-db-base64'and expect sqlite export/import behavior to pass. - Run
bb dev:test -v 'logseq.db-worker.ncc-bundle-test'and expect standalone bundle smoke tests to pass. - Run
bb dev:test -v 'logseq.cli.integration-test/test-cli-graph-export-import-sqlite'and expect end-to-end CLI sqlite flow to pass. - Run
clojure -M:cljs compile db-worker-node logseq-cliand expect successful node-script builds. - Run
yarn db-worker-node:release:bundleand verifydist/db-worker-node.jsstill starts withnode ./dist/db-worker-node.js --help. - Run
bb dev:lint-and-testand expect full lint and test checks to pass. - Review changed files against
/Users/rcmerci/gh-repos/logseq/prompts/review.mdchecklist before merge.
Edge cases to validate during implementation
| Scenario | Expected behavior |
|---|---|
Named parameter binding uses $name keys from current callsites. |
Statement executes without bind-key mismatch errors. |
Named parameter binding uses :name keys from normalized callsites. |
Statement executes and returns same data as before. |
rowMode is "array" for kv restore reads. |
First row remains index-addressable for existing first and destructuring logic. |
| Transaction callback throws mid-write. | All writes in that transaction scope are rolled back. |
| Nested transaction callback occurs inside outer transaction. | Inner failure does not commit partial data and outer behavior is deterministic. |
| ncc bundle emits zero extra assets. | Bundle tests and CLI docs still treat runtime as valid standalone output. |
| Daemon starts under Node 22 and emits experimental sqlite warning. | Warning does not break health/readiness checks or CLI invoke flow. |
| Graph sqlite import/export uses large payloads. | Base64 transport and file writes still preserve binary integrity. |
Decisions confirmed
- Keep Node experimental
node:sqlitewarning output as-is for this migration phase; warning suppression and logging policy changes are out of scope. - Scope is limited to
logseq-clianddb-worker-node; do not modify/Users/rcmerci/gh-repos/logseq/scripts/package.jsonin this task. - No temporary fallback flag to
better-sqlite3; enforce one-way migration to built-innode:sqlite. - Treat Node.js
>=22.20.0as a hard prerequisite for local and CI runtime to ensurenode:sqliteavailability.
Testing Details
The adapter unit tests will validate observable behavior for SQL execution, parameter binding, row shape, and transaction semantics instead of testing internal helper structure.
The daemon smoke tests will validate real process startup and thread-api calls so platform wiring and lock behavior stay stable.
The CLI integration sqlite export/import test will verify user-visible behavior from command surface to db-worker storage backend.
The bundle tests will validate standalone runtime packaging assumptions that changed because native .node assets are no longer required.
Implementation Details
- Keep
db-worker-nodeHTTP and SSE API contracts unchanged. - Keep platform adapter keys unchanged to avoid db-core callsite churn.
- Implement a compatibility wrapper over
DatabaseSyncinstead of refactoring db-core. - Preserve bind normalization semantics for backward compatibility.
- Implement explicit rollback-safe transaction handling with nested safety.
- Remove
better-sqlite3only from main runtime dependency declarations. - Update bundle tests to assert behavior, not driver-specific artifact names.
- Update CLI docs and historical planning notes that mention native sqlite assets.
- Require Node.js
>=22.20.0in local and CI verification environments. - Run focused tests first, then repository-wide lint and tests.
- Follow
@test-driven-developmentand@clojure-debugfor implementation and debugging workflow.