Skip to content

server: shared ServerHost so http and ws transports can share one socket - #303

Merged
denny-il merged 2 commits into
mainfrom
dev/shared-server-host
Aug 2, 2026
Merged

denny-il merged 2 commits into
mainfrom
dev/shared-server-host

Conversation

@denny-il

@denny-il denny-il commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Extracts socket ownership out of the transports into a new @nmtjs/server package so the HTTP and WS transports can listen on a single socket instead of two.

  • Per-runtime createServerHost({ listen, tls, maxRequestBodySize, runtime }) (@nmtjs/server/node|bun|deno) owns everything previously duplicated across the six transport adapters: listen/TLS, /healthy, upgrade-vs-HTTP dispatch, the uWS→fetch body translation, crossws adapter creation, send-status interpretation and the uWS ws-behavior defaults.
  • Lifecycle is refcounted: the socket binds on the first registrant's start() and closes on the last stop(). The runtime server is materialized at bind time from collected registrations (required by Bun's all-options-at-serve() constraint; also makes stop→start rebind cleanly).
  • Transports become tenants. Options are now OneOf<[{ listen, tls, runtime }, { server }]> — standalone mode is unchanged, shared mode is additive:
const server = createServerHost({ listen: { port: 3000 } })
transports: {
  http: { transport: HttpTransport, options: { server, cors: true } },
  ws:   { transport: WsTransport,   options: { server } },
}

Gateway, protocol and both transport cores required no changes. Both workers report the same bound URL, so the gateway registers one address under both http and ws proxyable types, which the Neem proxy's runtimeName:type:url upstream keying already handles.

Breaking changes

  • WS behavior options hoisted: runtime.ws → top-level ws (runtime now consistently means server-level runtime options, mirroring the HTTP transport; on Bun the former runtime.server is now runtime).
  • Removed the WS cors option — declared but never read anywhere (browsers don't apply CORS to WS upgrades).
  • Unix socket URLs unified to http+unix:// / https+unix:// (node HTTP previously reported bare unix://, WS reported proto+unix://).

Notes

  • PayloadTooLargeError and the response helpers moved to @nmtjs/server and are re-exported from the transports — the 413 path matches by instanceof, so class identity must be shared.
  • uWS must stay external when bundling workers under Neem: it loads its .node binary via a runtime-computed require the bundler cannot see. The e2e fixture demonstrates the pattern (build: { rolldown: { external: ['uWebSockets.js'] } } + uWS in node_modules).

Tests

  • packages/server: shared-host suite (HTTP + WS + /healthy on one socket, refcounted stop, rebind, no-tenant 404s, registration guards) plus the relocated uWS chunked-stream dispatcher tests.
  • packages/ws-transport/tests/shared-server.spec.ts: both real transports mounted on one host — same reported URL, HTTP RPC and WS messages both served, one worker stopping doesn't kill the other's socket.
  • packages/neem: unit test pinning same-URL dual-type upstream keying; e2e fixture boots a real Gateway (both transports, one host) behind the real native proxy and verifies both upstream types on one URL, HTTP RPC, /healthy and WS upgrade through the proxy port.

Full workspace suite, typecheck, oxlint and oxfmt are green.

Summary by CodeRabbit

  • New Features

    • Added a shared server package supporting Node.js, Bun, and Deno.
    • HTTP and WebSocket transports can now share one server and listening address.
    • Added support for mounting transports onto an existing server.
    • Added consistent health checks, lifecycle management, request handling, and WebSocket configuration across runtimes.
  • Bug Fixes

    • Improved shared-server proxy routing so HTTP and WebSocket endpoints remain distinct even when they use the same address.
  • Tests

    • Added coverage for shared servers, concurrent HTTP/WebSocket operation, lifecycle behavior, streaming, and proxying.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@denny-il, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ddb8056-3b0d-4494-84b4-2698f3d28ec1

📥 Commits

Reviewing files that changed from the base of the PR and between 2b0172a and 7600bd4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (41)
  • packages/http-transport/package.json
  • packages/http-transport/src/adapter.ts
  • packages/http-transport/src/constants.ts
  • packages/http-transport/src/runtimes/bun.ts
  • packages/http-transport/src/runtimes/deno.ts
  • packages/http-transport/src/runtimes/node.ts
  • packages/http-transport/src/types.ts
  • packages/http-transport/src/utils.ts
  • packages/http-transport/tests/_helpers/test-utils.ts
  • packages/http-transport/tests/node-runtime.spec.ts
  • packages/neem/package.json
  • packages/neem/tests/e2e/fixtures/cases/shared-server/api.planner.ts
  • packages/neem/tests/e2e/fixtures/cases/shared-server/api.runtime.ts
  • packages/neem/tests/e2e/fixtures/cases/shared-server/neem.config.ts
  • packages/neem/tests/e2e/fixtures/cases/shared-server/shared-server.worker.ts
  • packages/neem/tests/e2e/shared-server.spec.ts
  • packages/neem/tests/unit/proxy.spec.ts
  • packages/server/package.json
  • packages/server/src/host.ts
  • packages/server/src/index.ts
  • packages/server/src/runtimes/bun.ts
  • packages/server/src/runtimes/deno.ts
  • packages/server/src/runtimes/node.ts
  • packages/server/src/types.ts
  • packages/server/src/utils.ts
  • packages/server/tests/chunked-stream.spec.ts
  • packages/server/tests/shared-host.spec.ts
  • packages/server/tsconfig.build.json
  • packages/server/tsconfig.json
  • packages/server/vitest.config.ts
  • packages/ws-transport/package.json
  • packages/ws-transport/src/adapter.ts
  • packages/ws-transport/src/runtimes/bun.ts
  • packages/ws-transport/src/runtimes/deno.ts
  • packages/ws-transport/src/runtimes/node.ts
  • packages/ws-transport/src/types.ts
  • packages/ws-transport/src/utils.ts
  • packages/ws-transport/tests/shared-server.spec.ts
  • packages/ws-transport/tests/stream-e2e.spec.ts
  • tsconfig.build.json
  • tsconfig.json
📝 Walkthrough

Walkthrough

Changes

Shared server hosting

Layer / File(s) Summary
Server contracts and packaging
packages/server/package.json, packages/server/src/types.ts, packages/server/src/utils.ts, packages/server/tsconfig*.json, packages/server/vitest.config.ts
Adds the @nmtjs/server package with shared host, runtime, request, WebSocket, response, and build contracts.
Host lifecycle and runtime implementations
packages/server/src/host.ts, packages/server/src/runtimes/*, packages/server/tests/*
Adds reference-counted host lifecycle management and Bun, Deno, and Node implementations with HTTP, WebSocket, health, shutdown, and streaming behavior.
HTTP and WebSocket transport integration
packages/http-transport/src/*, packages/ws-transport/src/*, packages/*/tests/*, tsconfig*.json
Updates both transports to use shared server types, host adapters, runtime factories, response helpers, and shared-host tests.
Neem shared-server validation
packages/neem/tests/e2e/fixtures/cases/shared-server/*, packages/neem/tests/e2e/shared-server.spec.ts, packages/neem/tests/unit/proxy.spec.ts
Adds a shared HTTP/WebSocket server fixture and validates proxy routing, host discovery, health checks, and distinct upstream entries.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: introducing a shared ServerHost so HTTP and WS transports can share one socket.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/shared-server-host

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Extract socket ownership from the transports into a new @nmtjs/server
package: per-runtime hosts (uWS/Bun/Deno) own listen/TLS, /healthy,
upgrade-vs-HTTP dispatch and the uWS fetch translation, with refcounted
start/stop. Transports become tenants that either own a private host
(listen mode, unchanged behavior) or mount onto a shared one via the new
`server` option, so HTTP and WS serve from a single listen address and
the gateway reports one URL under both proxyable types.

- transports' runtime adapters shrink to thin host registrations
- WS behavior options hoisted to top-level `ws` (runtime.ws before);
  dead WS `cors` option removed; unix URLs unified to proto+unix://
- neem e2e: shared-server fixture drives the native proxy end-to-end
  (same URL under http+ws types, HTTP RPC, /healthy and WS upgrade
  through the proxy port); proxy unit test pins same-URL dual-type
  upstream keying
- uWS must stay external when bundling workers under neem: its .node
  binary is loaded via a runtime-computed require
@denny-il
denny-il force-pushed the dev/shared-server-host branch from 2b0172a to 4a8d084 Compare July 30, 2026 07:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/neem/tests/e2e/shared-server.spec.ts`:
- Around line 76-84: Bound each WebSocket attempt in the promise created by
waitFor: add a per-attempt timer that closes the WebSocket and resolves false if
neither onopen nor onerror fires, ensuring waitFor can continue to its existing
timeout. Update the connection logic around the WebSocket constructor and settle
the promise only once.

In `@packages/server/src/host.ts`:
- Around line 59-85: Update stop() so it does not clear this.#bound before the
in-flight bind promise settles and teardown completes; retain the promise
reference throughout the await and close sequence to prevent concurrent start()
calls from initiating a second bind. Adjust the final state cleanup only after
the existing bound.catch(...) and close() operations finish, while preserving
start()’s rollback behavior for failed binds.

In `@packages/server/src/runtimes/bun.ts`:
- Around line 59-61: Update the Bun runtime’s `/healthy` handling in the routes
configuration around Object.assign so it intercepts the path independently of
the HTTP method, matching the method-agnostic behavior of the Deno host. Ensure
methods such as HEAD reach the host-owned health check instead of falling
through to the general fetch handler.
- Around line 42-85: Update the return logic after Bun.serve in the runtime
startup method to detect Unix-socket listeners and convert Bun’s unix:///path
format into the established http+unix:// or https+unix:// address format,
selecting the scheme from TLS configuration. Preserve the existing
server.url.href return behavior for TCP listeners.

In `@packages/server/src/runtimes/node.ts`:
- Around line 190-217: The catch block around the request body stream and
fetchHandler must map PayloadTooLargeError to a 413 response instead of
InternalServerErrorHttpResponse(). Detect that error before generic
logging/fallback handling, assign the corresponding 413 helper response, and
preserve the existing 500 behavior for all other errors.

In `@packages/ws-transport/tests/shared-server.spec.ts`:
- Around line 95-101: Move the openSocket liveness check from the finally block
into the try block before teardown, and leave the finally block responsible only
for stopping httpWorker and wsWorker. Ensure wsWorker.stop(wsParams) executes
even when the socket probe rejects, preserving the original failure and
preventing the host from remaining bound.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fb9da62d-9e52-49c7-972c-ebc429c7de9c

📥 Commits

Reviewing files that changed from the base of the PR and between 0e5c7a8 and 2b0172a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (41)
  • packages/http-transport/package.json
  • packages/http-transport/src/adapter.ts
  • packages/http-transport/src/constants.ts
  • packages/http-transport/src/runtimes/bun.ts
  • packages/http-transport/src/runtimes/deno.ts
  • packages/http-transport/src/runtimes/node.ts
  • packages/http-transport/src/types.ts
  • packages/http-transport/src/utils.ts
  • packages/http-transport/tests/_helpers/test-utils.ts
  • packages/http-transport/tests/node-runtime.spec.ts
  • packages/neem/package.json
  • packages/neem/tests/e2e/fixtures/cases/shared-server/api.planner.ts
  • packages/neem/tests/e2e/fixtures/cases/shared-server/api.runtime.ts
  • packages/neem/tests/e2e/fixtures/cases/shared-server/neem.config.ts
  • packages/neem/tests/e2e/fixtures/cases/shared-server/shared-server.worker.ts
  • packages/neem/tests/e2e/shared-server.spec.ts
  • packages/neem/tests/unit/proxy.spec.ts
  • packages/server/package.json
  • packages/server/src/host.ts
  • packages/server/src/index.ts
  • packages/server/src/runtimes/bun.ts
  • packages/server/src/runtimes/deno.ts
  • packages/server/src/runtimes/node.ts
  • packages/server/src/types.ts
  • packages/server/src/utils.ts
  • packages/server/tests/chunked-stream.spec.ts
  • packages/server/tests/shared-host.spec.ts
  • packages/server/tsconfig.build.json
  • packages/server/tsconfig.json
  • packages/server/vitest.config.ts
  • packages/ws-transport/package.json
  • packages/ws-transport/src/adapter.ts
  • packages/ws-transport/src/runtimes/bun.ts
  • packages/ws-transport/src/runtimes/deno.ts
  • packages/ws-transport/src/runtimes/node.ts
  • packages/ws-transport/src/types.ts
  • packages/ws-transport/src/utils.ts
  • packages/ws-transport/tests/shared-server.spec.ts
  • packages/ws-transport/tests/stream-e2e.spec.ts
  • tsconfig.build.json
  • tsconfig.json

Comment thread packages/neem/tests/e2e/shared-server.spec.ts
Comment thread packages/server/src/host.ts
Comment thread packages/server/src/runtimes/bun.ts
Comment thread packages/server/src/runtimes/bun.ts
Comment thread packages/server/src/runtimes/node.ts
Comment thread packages/ws-transport/tests/shared-server.spec.ts Outdated
- host: a start() racing the final stop() now reclaims the live socket
  instead of binding a second server the stop orphans; close() is
  serialized against rebinds
- bun host: unix sockets report proto+unix:// like the other runtimes;
  /healthy responds to any method (deno already did, node switched to
  .any as well)
- node host: PayloadTooLargeError from the body cap maps to 413 when the
  tenant rethrows it instead of a generic 500
- tests: race regression for the host, bounded per-attempt WS connect in
  the neem e2e, teardown ordering fix in the shared-server transport
  spec
@denny-il
denny-il merged commit d6b361a into main Aug 2, 2026
5 checks passed
@denny-il
denny-il deleted the dev/shared-server-host branch August 2, 2026 07:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant