mirror of
https://github.com/logseq/logseq.git
synced 2026-05-15 16:32:21 +00:00
319 lines
18 KiB
Markdown
319 lines
18 KiB
Markdown
# db-worker-node `invoke-main-thread` Removal and Worker-Local Refactor Plan
|
|
|
|
Goal: Remove all production `invoke-main-thread` usage from db worker logic and make `db-worker-node` fully self-contained for API execution.
|
|
|
|
Goal: Refactor each current main-thread API dependency into db-worker-owned implementations that work in both Node and Browser runtimes.
|
|
|
|
Goal: For UI-dependent interactions, replace direct worker->main-thread invocation with a request/response protocol where worker sends `postMessage`, and main thread actively calls db-worker thread APIs to respond.
|
|
|
|
Goal: Enforce request isolation with `request-id` across concurrent UI requests.
|
|
|
|
Architecture: Keep runtime-specific logic behind worker platform adapters (`frontend.worker.platform`, `frontend.worker.platform.node`, `frontend.worker.platform.browser`) and keep shared logic runtime-agnostic.
|
|
|
|
Related:
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/state.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/ui_request.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_worker_node.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_worker.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_core.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/assets.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/auth.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/crypt.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/handler/assets.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/handler/e2ee.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/handler/user.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/persist_db/browser.cljs`
|
|
- `/Users/rcmerci/gh-repos/logseq/src/main/frontend/handler/worker.cljs`
|
|
|
|
## Problem statement
|
|
|
|
`db-worker-node` currently sets a main-thread stub that always rejects (`main-thread is not available in db-worker-node`).
|
|
|
|
Historically, shared worker modules called `worker-state/<invoke-main-thread` for auth refresh, asset upload/download metadata, E2EE key/password operations, and search-idle checks.
|
|
|
|
That created runtime divergence:
|
|
- Browser worker could call main-thread APIs through Comlink.
|
|
- Node daemon could not, so behavior depended on fallback code quality or failed outright.
|
|
|
|
Target state: db worker owns all non-UI logic and uses only explicit message protocol for UI interactions.
|
|
|
|
## Progress snapshot (current)
|
|
|
|
### Completed migrations
|
|
|
|
- Auth refresh dependency on `:thread-api/ensure-id&access-token` was removed from worker sync auth path.
|
|
- Asset upload/download/metadata dependencies on main-thread thread APIs were removed and replaced by worker-local implementations through platform adapters.
|
|
- Search idle dependency on `:thread-api/input-idle?` was removed from `db_core`; it now uses a **main-thread push + worker TTL cache** model:
|
|
- main thread periodically pushes `:thread-atom/search-input-idle-status` with `{repo {:idle? ... :ts ...}}`
|
|
- worker consumes that state with a TTL window and falls back to non-blocking behavior when state is missing/stale.
|
|
- E2EE UI-interaction path in `frontend.worker.sync.crypt` was migrated to a worker-owned request manager:
|
|
- worker now emits `:db-worker/ui-request` with `request-id`
|
|
- main thread responds via `:thread-api/resolve-ui-request` / `:thread-api/reject-ui-request`
|
|
- worker crypt flow no longer directly calls `worker-state/<invoke-main-thread`
|
|
- pending UI requests are cancelled on close/shutdown paths (`close-db`, `db-sync-close-db`, `unsafe-unlink-db`, browser `stop-db-worker!`)
|
|
|
|
### Remaining direct `invoke-main-thread` production usages
|
|
|
|
- None in production worker modules.
|
|
- `frontend.worker.state/<invoke-main-thread` remains as legacy infrastructure, but has no active production call sites.
|
|
|
|
## Historical inventory of `invoke-main-thread` dependencies (baseline before migration)
|
|
|
|
| Call site | Current main-thread API | Category | Runtime risk in `db-worker-node` |
|
|
| --- | --- | --- | --- |
|
|
| `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/auth.cljs` (`<resolve-ws-token`) | `:thread-api/ensure-id&access-token` | Auth/token refresh | Token refresh path may fail without worker-local refresh |
|
|
| `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/assets.cljs` (`upload-remote-asset!`) | `:thread-api/rtc-upload-asset` | Asset I/O + network | Upload path depends on main-thread asset code |
|
|
| `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/assets.cljs` (`download-remote-asset!`) | `:thread-api/rtc-download-asset` | Asset I/O + network | Download path depends on main-thread asset code |
|
|
| `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/assets.cljs` (`request-asset-download!`) | `:thread-api/get-asset-file-metadata` | Asset metadata | Existence/checksum check depends on main thread |
|
|
| `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/crypt.cljs` (`<native-save-password-text!`) | `:thread-api/native-save-e2ee-password` | Password persistence | Native keychain path unavailable in node daemon |
|
|
| `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/crypt.cljs` (`<native-read-password-text`) | `:thread-api/native-get-e2ee-password` | Password persistence | Same |
|
|
| `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/crypt.cljs` (`<native-delete-password-text!`) | `:thread-api/native-delete-e2ee-password` | Password persistence | Same |
|
|
| `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/crypt.cljs` (`<generate-and-upload-user-rsa-key-pair!`) | `:thread-api/request-e2ee-password` | UI prompt | Requires explicit UI handshake |
|
|
| `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/crypt.cljs` (`<decrypt-private-key`) | `:thread-api/decrypt-user-e2ee-private-key` | UI/session-dependent decrypt | Must be worker-local + UI fallback protocol |
|
|
| `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync/crypt.cljs` (`:thread-api/init-user-rsa-key-pair`) | `:thread-api/request-e2ee-password` | UI prompt | Same as above |
|
|
| `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_core.cljs` (`<wait-for-search-index-idle!`) | `:thread-api/input-idle?` | UI state query | Tight loop currently requires main-thread sync point |
|
|
|
|
## Design constraints and invariants
|
|
|
|
1. `db-worker-node` must never depend on main-thread APIs.
|
|
2. Shared worker code must run in both Node and Browser runtimes.
|
|
3. UI interactions must use explicit async protocol with `request-id` correlation.
|
|
4. Error shapes must remain stable (or be intentionally versioned) across transport layers.
|
|
5. Migration should be incremental with behavior-compatible phases.
|
|
|
|
## Target architecture
|
|
|
|
### 1) Remove worker-side direct main-thread invocation from business paths
|
|
|
|
Keep `frontend.worker.state/<invoke-main-thread` only as legacy infrastructure for browser compatibility during migration, then shrink/remove usage in production worker modules.
|
|
|
|
Acceptance target after migration:
|
|
- No production call sites to `<invoke-main-thread` outside temporary migration guards.
|
|
- `db-worker-node` main-thread stub remains reject-only and never hit in normal flows.
|
|
|
|
### 2) Move main-thread business logic into worker-owned modules
|
|
|
|
#### Auth refresh (`:thread-api/ensure-id&access-token`)
|
|
- Move token refresh implementation into worker sync auth module.
|
|
- Reuse current refresh token and auth state from worker state (`:auth/id-token`, `:auth/refresh-token`) that is already synchronized.
|
|
- Keep main thread responsible only for UI/session orchestration, not worker internal token lifecycle.
|
|
|
|
#### Asset operations (`:thread-api/rtc-upload-asset`, `:thread-api/rtc-download-asset`, `:thread-api/get-asset-file-metadata`)
|
|
- Introduce worker-side asset I/O + transfer module (shared, runtime-agnostic).
|
|
- Move file read/write/checksum/upload/download logic from main-thread handler into worker with platform adapter calls.
|
|
- Keep progress events emitted from worker via existing broadcast channel.
|
|
|
|
#### E2EE password storage (`native-save/get/delete`)
|
|
- Replace main-thread keychain dependency with worker platform capabilities.
|
|
- Add explicit storage abstraction in platform adapters for encrypted password persistence.
|
|
- Keep existing encrypted payload format unchanged where possible.
|
|
|
|
#### Private key decrypt (`:thread-api/decrypt-user-e2ee-private-key`)
|
|
- Make worker-local decrypt path primary.
|
|
- Request UI password only when worker-local credentials are unavailable.
|
|
|
|
### 3) UI request/response protocol (request-id isolated)
|
|
|
|
For UI-dependent operations, worker should not call main-thread thread APIs directly.
|
|
|
|
Use this flow:
|
|
|
|
```text
|
|
1) Worker needs UI input
|
|
-> postMessage event to main thread
|
|
{:type :db-worker/ui-request
|
|
:request-id <uuid-v4>
|
|
:action <keyword>
|
|
:payload <map>
|
|
:timeout-ms 60000}
|
|
|
|
2) Main thread handles UI action (prompt/dialog/user interaction)
|
|
|
|
3) Main thread actively calls db-worker thread API:
|
|
:thread-api/resolve-ui-request [request-id result]
|
|
or
|
|
:thread-api/reject-ui-request [request-id error]
|
|
|
|
4) Worker resolves or rejects the pending promise by request-id.
|
|
```
|
|
|
|
Required worker internals:
|
|
- `*ui-requests-in-flight` map keyed by `request-id`.
|
|
- `request-id` is generated as UUID v4.
|
|
- default request timeout is `60000ms` (60s), with optional per-action override.
|
|
- timeout handling + cleanup.
|
|
- duplicate/late response protection.
|
|
- cancellation support for graph switch or worker shutdown.
|
|
|
|
Required acceptance:
|
|
- concurrent requests do not cross-resolve.
|
|
- timed-out request cannot be resolved later.
|
|
- all terminal states remove map entries.
|
|
|
|
#### UI interaction contract: headless vs interactive
|
|
|
|
The implementation uses two explicit modes instead of implicit fallback behavior.
|
|
|
|
- **Headless mode (default for `db-worker-node`/CLI):**
|
|
- If an operation requires UI input and no interactive channel is available, return a typed error.
|
|
- Error code should be stable and machine-readable: `:ui-interaction-required`.
|
|
- Error payload should include at least `:action` and optional `:hint` (for example, configured password fallback).
|
|
|
|
- **Interactive mode (browser app):**
|
|
- Worker emits `:db-worker/ui-request` with `request-id`.
|
|
- Main thread performs UI interaction and actively calls worker thread APIs to resolve or reject the request.
|
|
- Worker resumes flow only when matching `request-id` is resolved.
|
|
|
|
This dual contract keeps headless behavior deterministic while preserving async interactive UX in browser runtime.
|
|
|
|
### 4) Platform abstraction updates for Node/Browser parity
|
|
|
|
Extend `frontend.worker.platform` contract with only capabilities required by migrated APIs.
|
|
|
|
Likely additions:
|
|
- secure encrypted secret storage (or equivalent persisted encrypted blob)
|
|
- binary asset read/write/stat helpers
|
|
- HTTP transfer helpers with progress hooks (if not already available in shared runtime)
|
|
- monotonic clock/timer helpers for timeout handling
|
|
|
|
Node implementation: `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/platform/node.cljs`.
|
|
|
|
Browser implementation: `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/platform/browser.cljs`.
|
|
|
|
## Per-API migration matrix
|
|
|
|
| Legacy main-thread API | New worker-owned implementation | UI request protocol needed? | Node/Browser notes |
|
|
| --- | --- | --- | --- |
|
|
| `:thread-api/ensure-id&access-token` | Worker-local refresh function in sync auth module | No | Must work without UI in node daemon |
|
|
| `:thread-api/rtc-upload-asset` | Worker asset upload API | No | Use platform file + HTTP adapters |
|
|
| `:thread-api/rtc-download-asset` | Worker asset download API | No | Same |
|
|
| `:thread-api/get-asset-file-metadata` | Worker metadata/checksum API | No | Same |
|
|
| `:thread-api/native-save-e2ee-password` | Worker secure persistence API | No | Use platform adapter implementations |
|
|
| `:thread-api/native-get-e2ee-password` | Worker secure read API | No | Same |
|
|
| `:thread-api/native-delete-e2ee-password` | Worker secure delete API | No | Same |
|
|
| `:thread-api/request-e2ee-password` | Worker emits UI request and waits on `resolve-ui-request` | Yes | Node can return typed `:ui-interaction-required` unless configured fallback exists |
|
|
| `:thread-api/decrypt-user-e2ee-private-key` | Worker-local decrypt + optional UI request for password | Yes (fallback) | Keep headless path for node |
|
|
| `:thread-api/input-idle?` | Main thread pushes idle-state updates; worker consumes a local TTL cache for indexing decisions | Yes | Push+TTL is the default model to avoid high-frequency request storms |
|
|
|
|
## Implementation phases
|
|
|
|
### Phase 0: Safety rails and observability
|
|
|
|
1. Add explicit metric/log points for any call to `<invoke-main-thread` in worker code.
|
|
2. Add request-id propagation fields to db-worker-node invoke/event logs for traceability.
|
|
3. Add temporary feature flag to switch between old and new path per migrated domain.
|
|
|
|
### Phase 1: Non-UI API migration
|
|
|
|
1. Migrate auth refresh from main thread to worker.
|
|
2. Migrate asset metadata/upload/download to worker module + platform adapters.
|
|
3. Migrate E2EE encrypted password persistence to worker platform APIs.
|
|
4. Keep compatibility shims temporarily, but default to worker-local path.
|
|
|
|
Exit criteria:
|
|
- No non-UI flow depends on main thread.
|
|
- CLI and db-worker-node sync paths pass regression tests.
|
|
|
|
### Phase 2: UI request protocol
|
|
|
|
1. Introduce generic worker UI request manager (`request-id`, timeout, cleanup).
|
|
2. Add worker thread APIs for resolving/rejecting UI requests.
|
|
3. Add main-thread handler wiring in `frontend.handler.worker` for `:db-worker/ui-request` events.
|
|
4. Route E2EE prompt/decrypt UI-dependent flows through new protocol.
|
|
|
|
Exit criteria:
|
|
- concurrent password requests are isolated by `request-id`.
|
|
- graph switch/shutdown does not leak pending requests.
|
|
|
|
### Phase 3: Search idle and remaining UI-state dependencies
|
|
|
|
1. Replace `:thread-api/input-idle?` direct invocation with **main-thread push + worker TTL cache** model.
|
|
2. Define TTL window and stale-state behavior for indexing loops.
|
|
3. Validate search indexing throughput and UI responsiveness.
|
|
|
|
Exit criteria:
|
|
- no direct worker->main-thread API invocation in search flow.
|
|
- push+TTL idle-state model is active and covered by tests.
|
|
- no regression in search build behavior.
|
|
|
|
### Phase 4: Remove legacy dependencies
|
|
|
|
1. Remove remaining production usages of `worker-state/<invoke-main-thread` in worker sync/core modules.
|
|
2. Update `non-repo-methods` and API inventory if method ownership changed.
|
|
3. Keep browser compatibility wrappers only if still required by bootstrapping internals.
|
|
|
|
Final exit criteria:
|
|
- `invoke-main-thread` usage in production code is zero (or explicitly limited to bootstrap-only internals with no business logic).
|
|
- `db-worker-node` full API suite works without main-thread API dependencies.
|
|
|
|
## Tests and verification plan
|
|
|
|
### Unit tests
|
|
|
|
- Worker auth tests: token refresh in worker without main-thread stubs.
|
|
- Worker asset tests: metadata/upload/download via worker module and platform mocks.
|
|
- Worker crypt tests: password storage/decrypt paths do not call main thread.
|
|
- UI request manager tests: request-id isolation, timeout, duplicate response rejection.
|
|
|
|
### Integration tests
|
|
|
|
- Browser flow: UI request roundtrip (`postMessage` -> main thread -> `resolve-ui-request`).
|
|
- Node flow: same API paths run headless and return typed errors when UI is mandatory.
|
|
- Search indexing flow: idle-state protocol behavior under repeated checks.
|
|
|
|
### Regression checks
|
|
|
|
- `eca__grep` gate: no production `worker-state/<invoke-main-thread` call sites in migrated modules.
|
|
- `db-worker-node` daemon invoke smoke tests for sync + e2ee + asset paths.
|
|
- Existing CLI and worker tests remain green.
|
|
|
|
## Risks and mitigations
|
|
|
|
1. **E2EE flow regressions**
|
|
- Mitigation: migrate with feature flags and run dedicated crypt regression suite first.
|
|
|
|
2. **Asset transfer behavior drift**
|
|
- Mitigation: keep payload format/checksum semantics unchanged during extraction.
|
|
|
|
3. **Request map leaks / stale requests**
|
|
- Mitigation: enforce timeout + shutdown cleanup + finalizer assertions.
|
|
|
|
4. **Search performance impact**
|
|
- Mitigation: add request throttling and local TTL cache for idle-state checks.
|
|
|
|
5. **Protocol mismatch between main thread and worker**
|
|
- Mitigation: define strict message schema and validate fields at both ends.
|
|
|
|
## Acceptance criteria
|
|
|
|
1. Every current production `invoke-main-thread` dependency listed in this plan has a worker-owned replacement.
|
|
2. `db-worker-node` runs without any required main-thread API bridge.
|
|
3. UI-dependent flows use `postMessage` request + main-thread active callback to worker thread API.
|
|
4. `request-id` enforces isolation for concurrent UI requests.
|
|
5. In headless mode, UI-required operations return typed error `:ui-interaction-required` with stable machine-readable payload.
|
|
6. Node and Browser runtimes both pass targeted tests for migrated paths.
|
|
7. No unresolved pending UI requests remain after graph close or worker shutdown.
|
|
|
|
## Suggested execution order
|
|
|
|
1. Auth + non-UI crypt storage.
|
|
2. Asset API migration.
|
|
3. Generic UI request protocol.
|
|
4. E2EE prompt/decrypt migration.
|
|
5. Search idle-state migration.
|
|
6. Legacy call-site removal and cleanup.
|
|
|
|
## Confirmed decisions
|
|
|
|
1. `request-id` format: UUID v4.
|
|
2. Default UI request timeout: 60s (`60000ms`), optional per-action override.
|
|
3. Search idle state model: main-thread push + worker TTL cache.
|
|
4. Headless behavior: UI-required operations return typed error `:ui-interaction-required`.
|
|
5. Execution order: follow the phased order defined in this document.
|
|
|
|
## Notes
|
|
|
|
This plan intentionally prioritizes behavior parity and migration safety over immediate deep refactor of all related modules.
|
|
|
|
The protocol and platform abstraction should be stabilized first; broad code movement should follow once test harnesses are in place.
|