feat: add WakeSocketServer/WakeSocketClient — cross-process wake (same machine)#21
Merged
Conversation
…e machine) The OutboxWaker's notify() is in-memory, so it can't reach a worker running as a separate process — the classic app + start:worker split sharing one database. This bridges it over a unix domain socket (Windows: \\.\pipe\… name) using node:net alone: the worker listens and feeds incoming connections into its OutboxWaker; producers hold a WakeSocketClient on the same path and notify() after the enqueueing transaction commits. The connection itself is the signal — no payload protocol to version. notify() is fire-and-forget and never throws into the request path; polling remains the backstop, so a failed or missed wake only costs one poll interval. The server recovers a stale socket path left by a crashed predecessor (bind refused + probe refused → unlink + rebind) and refuses a path a LIVE server owns. The shared WakeSignal interface lets producers swap OutboxWaker ↔ WakeSocketClient without touching domain code. Dialect-agnostic and zero new dependencies. For the SQLite store this covers every supported deployment — processes sharing a SQLite file are on one machine by definition. A cross-MACHINE wake (Postgres LISTEN/NOTIFY) stays the planned follow-up. - wake-socket.ts: server (listen/stale-recovery/conflict/close) + client, 100% covered - outbox-waker.ts: WakeSignal interface; OutboxWaker implements it - 9 new specs incl. the latched no-lost-wakeup path over a real socket - README: two-tier wake story + env-shared-path recipe; CHANGELOG entry
📊
|
| Metric | PR | Base | Diff | |
|---|---|---|---|---|
| Statements | ████████████████████ |
1841/1841 (100%) |
1677/1677 (100%) |
⚪ 0% |
| Branches | ████████████████████ |
236/236 (100%) |
205/205 (100%) |
⚪ 0% |
| Functions | ████████████████████ |
78/78 (100%) |
67/67 (100%) |
⚪ 0% |
| Lines | ████████████████████ |
1841/1841 (100%) |
1677/1677 (100%) |
⚪ 0% |
🧾 Changed files
| File | Statements | Branches | Diff | |
|---|---|---|---|---|
messaging/outbox-waker.ts |
████████████████████ |
84/84 (100%) |
13/13 (100%) |
⚪ 0% |
messaging/wake-socket.ts |
████████████████████ |
154/154 (100%) |
31/31 (100%) |
🆕 new |
Updated for 7e631ac | Compared against base branch
⏱️ Performance Report
🐢 Slowest test suites
🐌 Slowest individual tests
Updated for |
🧠 Cognitive Complexity Report
🧩 Most complex functions
🧾 Changed files
Updated for |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Completes the wake story from #20 for multiple processes on the same machine — the classic deployment where the HTTP app and the outbox worker (
start:worker) are separate processes sharing one database.OutboxWaker.notify()is in-memory and can't cross that boundary; this PR bridges it over a unix domain socket (Windows: a\\.\pipe\…name), built onnode:netalone — zero new dependencies, dialect-agnostic.Design
notify()never throws into the request path; a failed wake (worker down, path missing) goes toonErrorand only costs one poll interval — polling stays the delivery backstop, correctness never depends on the wake.listen()distinguishes a stale socket path left by a crashed predecessor (bind refused + probe connect refused → unlink + rebind) from a live conflict (probe connects → throws).WakeSignalinterface ({ notify(): void }): producers depend on the shape, so switching topology — single process (OutboxWaker) ↔ app + worker (WakeSocketClient) — never touches domain code.LISTEN/NOTIFY) remains the planned follow-up.Tests / gates
close(), accepted-socket error guard, client error paths (with and withoutonError).wake-socket.tsandoutbox-waker.ts(114 hermetic tests pass); complexity ≤15; core typecheck clean.infra:up && test:full→ 114/114 hermetic and real Postgres + MySQL specs 3/3, 0 skipped.Note: the
Release and security checksjob is expected red onsecurity:audit:docs— pre-existing docusaurus website advisories (websocket-driver/uuid, advisory-DB drift since 2026-07-13), unrelated to the package; the published-tarball audit is green. Per the guidelines, website advisories don't block.No version bump; lands on
mainalongside #20 for the next release (0.4.0).