15 KiB
db-worker-node ncc Standalone Bundle Implementation Plan
Goal: Build db-worker-node.js with @vercel/ncc so the runtime can run without node_modules present next to the executable.
Architecture: Keep shadow-cljs as the source compiler for :db-worker-node, then run ncc on the generated entry and publish a single runtime artifact in dist/ that is used by CLI daemon orchestration.
Architecture: Preserve local development ergonomics by keeping static/db-worker-node.js for fast dev loops, while production and package paths resolve to the ncc artifact first.
Tech Stack: ClojureScript, shadow-cljs :node-script, @vercel/ncc, Node.js 22, yarn scripts in package.json, existing CLI daemon and doctor checks.
Related: Builds on docs/agent-guide/031-logseq-cli-doctor-command.md.
Related: Relates to docs/agent-guide/033-desktop-db-worker-node-backend.md.
Related: Relates to docs/agent-guide/035-logseq-cli-db-worker-deps-cli-decoupling.md.
Problem statement
/Users/rcmerci/gh-repos/logseq/static/db-worker-node.js is currently generated by shadow-cljs, and runtime behavior assumes dependencies are available from node_modules.
The CLI server startup path in /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/server.cljs points to ../dist/db-worker-node.js, which is currently a thin wrapper that forwards to ../static/db-worker-node.js.
This wrapper model keeps runtime coupled to workspace layout and to node_modules, so the daemon script is not independently portable.
We need a deterministic packaging path that produces one runnable artifact plus copied native assets, and we need to verify that artifact works when node_modules is absent.
Electron release packaging also needs to include the db-worker standalone bundle step, so yarn release-electron always ships the same packaged runtime artifact.
The solution must keep existing CLI and Electron daemon orchestration behavior unchanged, including lock-file semantics, owner-source semantics, and health endpoint behavior.
Current packaging map
| Area | Current behavior | Limitation |
|---|---|---|
| Build output | yarn db-worker-node:compile writes /Users/rcmerci/gh-repos/logseq/static/db-worker-node.js. |
Output is not a standalone distribution artifact. |
| Dist entry | /Users/rcmerci/gh-repos/logseq/dist/db-worker-node.js only requires ../static/db-worker-node.js. |
Runtime still depends on static output and installed dependencies. |
| Daemon spawn | /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/server.cljs spawns ../dist/db-worker-node.js. |
Spawn path is stable, but executable is not standalone. |
| Doctor check | /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/doctor.cljs checks ../static/db-worker-node.js by default. |
Diagnostic target does not match the intended distributable runtime. |
| Package manifest | /Users/rcmerci/gh-repos/logseq/package.json includes static/db-worker-node.js in files. |
Published package does not guarantee standalone daemon artifact contract. |
| Electron release | yarn release-electron does not guarantee db-worker bundle refresh before packaging. |
Desktop release artifact can drift from standalone db-worker bundle contract. |
Target packaging map
| Area | Target behavior | Verification signal |
|---|---|---|
| Bundle output | ncc emits a standalone db-worker-node runtime in /Users/rcmerci/gh-repos/logseq/dist/ with required runtime assets copied adjacent to entrypoint. |
Daemon starts and serves /healthz and /readyz without node_modules. |
| Spawn path | CLI server keeps spawning /Users/rcmerci/gh-repos/logseq/dist/db-worker-node.js as canonical runtime. |
Existing logseq.cli.server-test assertions remain green with updated contract. |
| Doctor check | Doctor defaults to the same packaged runtime path used for spawn, and does not auto-fallback to static runtime. | Doctor check path matches runtime path in tests and manual runs. |
| Dev flow | Fast local dev command remains available using static/db-worker-node.js for watch and debug workflows. |
yarn db-worker-node:compile and node ./static/db-worker-node.js still work during development. |
| Publish flow | Package files include standalone runtime assets required by ncc output. |
Installed package can execute daemon without extra dependency install. |
| Electron release | yarn release-electron runs db-worker bundle build before Electron packaging steps. |
Electron release artifact includes the same standalone db-worker runtime contract. |
Integration sketch
shadow-cljs (:db-worker-node)
-> /static/db-worker-node.js
-> ncc build step
-> /dist/db-worker-node.js
-> /dist/<ncc runtime assets, including native binaries if emitted>
logseq-cli runtime
-> logseq.cli.server/spawn-server!
-> /dist/db-worker-node.js
-> db-worker daemon HTTP + SSE API
Testing Plan
I will follow @test-driven-development and add failing tests before implementation changes in each phase.
I will add behavior tests for runtime path resolution so spawn and doctor point to the same canonical bundle target.
I will add a standalone smoke test that launches the bundled daemon from a temporary directory without node_modules and verifies /healthz, /readyz, and shutdown behavior.
I will keep existing daemon lifecycle tests green to ensure no regression in lock cleanup, owner checks, and timeout error semantics.
I will run focused tests first, then full validation with yarn cljs:lint && yarn test, and if any unexpected failures appear I will use @clojure-debug before changing behavior.
NOTE: I will write all tests before I add any implementation behavior.
Implementation plan
Phase 1: RED for runtime artifact contract.
- Add a failing test in
/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/server_test.cljsasserting canonical db-worker runtime path resolves todist/db-worker-node.jsas the production target. - Add a failing test in
/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/command/doctor_test.cljsasserting default doctor script check points to the same canonical runtime target as server spawn. - Add a failing test in
/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/command/doctor_test.cljsasserting optional dev-mode check can still validatestatic/db-worker-node.jswhen explicitly requested. - Add a new failing bundle smoke test file at
/Users/rcmerci/gh-repos/logseq/src/test/logseq/db_worker/ncc_bundle_test.cljsthat expects daemon startup success from a bundle-only temp directory. - Run
yarn cljs:test && yarn cljs:run-test -v 'logseq.cli.server-test'and confirm failures occur on the new path-contract assertions. - Run
yarn cljs:test && yarn cljs:run-test -v 'logseq.cli.command.doctor-test'and confirm failures occur on the new default-path expectations. - Run
yarn cljs:test && yarn cljs:run-test -v 'logseq.db-worker.ncc-bundle-test'and confirm standalone smoke test fails before implementation.
Phase 2: Add ncc build pipeline.
- Add
@vercel/nccto/Users/rcmerci/gh-repos/logseq/package.jsonas a dev dependency. - Add a dedicated script in
/Users/rcmerci/gh-repos/logseq/package.jsonto build:db-worker-nodein release mode before bundling. - Add a dedicated script in
/Users/rcmerci/gh-repos/logseq/package.jsonto runnccagainst/Users/rcmerci/gh-repos/logseq/static/db-worker-node.js. - Add a dedicated script in
/Users/rcmerci/gh-repos/logseq/package.jsonto normalize ncc output into/Users/rcmerci/gh-repos/logseq/dist/db-worker-node.jswith adjacent assets preserved. - If script complexity is non-trivial, add
/Users/rcmerci/gh-repos/logseq/scripts/build-db-worker-node-bundle.mjsto encapsulate output normalization and deterministic cleanup. - Add or update
yarnscripts in/Users/rcmerci/gh-repos/logseq/package.jsonfor one-command bundle build and optional local run of the bundled artifact. - Update
yarn release-electronin/Users/rcmerci/gh-repos/logseq/package.jsonso it includesdb-worker-node:release:bundlebefore Electron packaging. - Run
yarn db-worker-node:release:bundleand verifydist/db-worker-node.jsis regenerated with executable permissions preserved.
Phase 3: Align runtime path and diagnostics.
- Refactor runtime path helpers in
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/server.cljsso there is one canonical function for packaged runtime and one explicit dev fallback function. - Keep
spawn-server!in/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/server.cljsbound to the packaged runtime path to avoid ambiguity. - Update
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/doctor.cljsto default-check the same packaged runtime path used by spawn. - Add an explicit action option in
/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/doctor.cljsfor fallback static-path diagnostics used only for development troubleshooting. - Ensure doctor failure codes remain stable as
:doctor-script-missingand:doctor-script-unreadable. - Re-run
yarn cljs:test && yarn cljs:run-test -v 'logseq.cli.server-test'andyarn cljs:test && yarn cljs:run-test -v 'logseq.cli.command.doctor-test'to make the new path contract green.
Phase 4: Package manifest and docs alignment.
- Update
filesin/Users/rcmerci/gh-repos/logseq/package.jsonso packaged runtime includesdist/db-worker-node.jsand ncc-emitted adjacent assets. - Keep
static/db-worker-node.jsinclusion only if required for development workflows, and document that distinction explicitly. - Update
/Users/rcmerci/gh-repos/logseq/docs/cli/logseq-cli.mdbuild instructions to include the ncc bundle command and standalone runtime expectations. - Update daemon runtime notes in
/Users/rcmerci/gh-repos/logseq/docs/cli/logseq-cli.mdsodoctorreferences packaged runtime as primary target. - Add troubleshooting notes for native module asset copy behavior from ncc output.
Phase 5: Standalone runtime behavior verification.
- Implement bundle smoke test setup in
/Users/rcmerci/gh-repos/logseq/src/test/logseq/db_worker/ncc_bundle_test.cljsto copy only bundle artifacts into a temp directory with nonode_modules. - In that test, spawn
node ./db-worker-node.js --repo <repo> --data-dir <tmp>from the bundle-only directory. - In that test, poll
/healthzand/readyzand assert both return HTTP 200 after startup. - In that test, invoke
/v1/shutdownand assert process exits and lock file is cleaned or becomes stale-removable. - In that test, assert failure output is actionable if native binary asset is missing, to guard accidental packaging regressions.
- Run
yarn cljs:test && yarn cljs:run-test -v 'logseq.db-worker.ncc-bundle-test'and make it green.
Phase 6: Final validation and review checklist.
- Run
yarn cljs:test && yarn cljs:run-test -v 'logseq.cli.server-test'and confirm zero failures and zero errors. - Run
yarn cljs:test && yarn cljs:run-test -v 'logseq.cli.command.doctor-test'and confirm zero failures and zero errors. - Run
yarn cljs:test && yarn cljs:run-test -v 'logseq.db-worker.ncc-bundle-test'and confirm zero failures and zero errors. - Run
yarn db-worker-node:compileand verifystatic/db-worker-node.jsremains valid for local dev flow. - Run
yarn db-worker-node:release:bundleand verifydist/db-worker-node.jsstarts successfully withnode dist/db-worker-node.js --help. - Run
yarn release-electronand verify the script execution includesdb-worker-node:release:bundlebefore Electron packaging steps. - Run
yarn cljs:lint && yarn testand confirm repository review checklist passes. - Validate changed code against
@prompts/review.mdbefore merge.
Edge cases to validate during implementation
| Scenario | Expected behavior |
|---|---|
ncc emits native .node assets for better-sqlite3. |
Bundle output keeps those assets adjacent to entrypoint and runtime loads without node_modules. |
| Bundle is copied to another directory without static files. | Daemon still starts because packaged runtime no longer requires ../static/db-worker-node.js. |
| Developer runs doctor in source workspace before bundle build. | Doctor reports missing packaged artifact by default, and only checks static runtime when explicitly requested. |
dist/db-worker-node.js exists but is not readable or is a directory. |
Doctor returns :doctor-script-unreadable with path detail. |
| Bundle build is run twice. | Build output remains deterministic and stale ncc artifacts are cleaned safely. |
yarn release-electron is run directly. |
Release flow still builds db-worker standalone bundle before Electron packaging artifacts are produced. |
| CLI and Electron share lock for same repo under bundled runtime. | Existing ownership and lock semantics remain unchanged from current behavior. |
Verification commands and expected outputs
yarn cljs:test && yarn cljs:run-test -v 'logseq.cli.server-test'
yarn cljs:test && yarn cljs:run-test -v 'logseq.cli.command.doctor-test'
yarn cljs:test && yarn cljs:run-test -v 'logseq.db-worker.ncc-bundle-test'
yarn db-worker-node:compile
yarn db-worker-node:release:bundle
yarn release-electron
node dist/db-worker-node.js --help
yarn cljs:lint && yarn test
All test commands should finish with 0 failures, 0 errors.
The bundle command should finish without MODULE_NOT_FOUND for runtime dependencies.
node dist/db-worker-node.js --help should print daemon help text and exit with code 0.
Testing Details
Behavior-focused tests will validate that runtime path resolution, doctor diagnostics, and daemon startup behavior match user-visible expectations.
The standalone smoke test will verify real process startup and HTTP readiness in a bundle-only filesystem layout, rather than asserting internal helper calls.
Regression safety is provided by existing CLI server and doctor tests to ensure lock lifecycle and error-code contracts remain stable.
Implementation Details
- Keep packaged runtime path as one canonical helper shared by spawn and doctor.
- Keep dev runtime path explicit and opt-in for local diagnostics only.
- Introduce ncc build scripts with deterministic output normalization into
dist/. - Preserve local
shadow-cljsdevelopment flow and avoid slowing watch mode. - Add a dedicated standalone bundle smoke test namespace for runtime validation.
- Keep error code contracts stable for doctor and server command callers.
- Ensure package
filesinclude all ncc runtime assets required at execution time. - Document build and troubleshooting steps in CLI docs for contributors and release workflows.
- Use
@test-driven-developmentfor every behavior change and follow@clojure-debugfor unexpected failures. - Finish with full lint and test validation and checklist review from
@prompts/review.md.
Question
Resolved: choose option 1.
doctor defaults to strict packaged-runtime validation only.
doctor does not auto-fallback to static runtime without an explicit flag.