Skip to content

feat(proxy): support unix socket targets out of the box - #197

Merged
pi0 merged 3 commits into
mainfrom
feat/socket
Jul 2, 2026
Merged

pi0 merged 3 commits into
mainfrom
feat/socket

Conversation

@pi0x

@pi0x pi0x commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

Proxy to a Unix-socket upstream by returning a ws+unix://<socketPath>:<pathname> target — works out of the box on Node, Bun, and Deno, no custom WebSocket constructor required:

import { createWebSocketProxy } from "crossws";

const hooks = createWebSocketProxy({
  target: "ws+unix:///var/run/backend.sock:/chat",
});

The path before the : is the socket file; the path after it is the request path forwarded upstream.

How it dials

The proxy dials through a single static import of the runtime WebSocket client:

import runtimeWebSocket from "crossws/websocket";

The crossws/websocket export conditions resolve the right client per runtime, and each client owns its own scheme handling — so the proxy has no runtime detection (globalThis checks) and no dynamic import(). Because the import is statically analyzable, a bundle built for a given runtime tree-shakes the others (ws/node:* never enter a Deno or browser bundle):

  • Bun — global WebSocket dials ws+unix: natively.
  • Node — global (undici) WebSocket rejects the scheme, so the Node client routes ws+unix: through ws (bundled with crossws, no extra dependency), keeping the native global for ws:/wss:.
  • Deno — global WebSocket rejects the scheme, so the Deno client routes the transport through Deno.createHttpClient's unix client (unstable — needs --unstable-net).

crossws/websocket wrappers

New per-runtime entries make ws+unix: (and, on Deno, custom dialing options) work through the crossws/websocket client itself, not just the proxy:

  • src/websocket/deno.ts — subclasses the global; rewrites ws+unix: to a plain ws:// target dialed through a unix Deno.createHttpClient, and relays a third options object into Deno's second-argument form.
  • src/websocket/node.ts — a Proxy over the native global that routes ws+unix: (and everything on Node < 22) through ws, preserving the global's static members.
  • src/websocket/bun.ts — re-exports the global (Bun dials ws+unix: natively).

The ./websocket bun/deno export conditions now point at these wrappers (were native). crossws/websocket is kept external in the build (a bare specifier in dist) so the consumer's runtime/bundler resolves the condition; prepare: obuild --stub makes dist re-export src so in-repo dev and tests resolve to source without a build.

webSocketOptions

Adds a webSocketOptions option (static or per-peer resolver) that passes extra dialing options to the upstream client — the escape hatch for transport options the URL can't express (e.g. ws/undici createConnection/dispatcher/agent, or a custom Deno.createHttpClient client). Merged with headers, with headers winning on conflict.

Tests

  • Node ws+unix: proxy path covered in-process (test/proxy.test.ts), routed through the Node client.
  • Deno + Bun out-of-the-box proxy path covered by spawned fixtures (test/proxy-runtimes.test.ts).
  • crossws/websocket Deno + Bun wrappers covered by spawned fixtures (test/websocket-runtimes.test.ts).
  • Full suite green (220 passed / 6 skipped); build verified to keep ws/node:* out of the Deno/browser wrappers and the eager edge graph.

🤖 Generated with Claude Code

Support `ws+unix://<socketPath>:<pathname>` proxy targets across Node,
Deno, and Bun with no custom `WebSocket` constructor: Bun dials natively,
Deno routes via `Deno.createHttpClient`'s unix `client`, and Node dials
with a lazily-imported (prebundled, edge-safe) `ws` client.

Also adds `webSocketOptions` to merge per-peer options into the upstream
constructor as an escape hatch for other transports.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 1, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6a6914f0-447b-4c0c-9135-2492e6525b9c

📥 Commits

Reviewing files that changed from the base of the PR and between b22628d and 03a83d4.

📒 Files selected for processing (14)
  • build.config.ts
  • docs/1.guide/8.proxy.md
  • package.json
  • src/proxy.ts
  • src/websocket/bun.ts
  • src/websocket/deno.ts
  • src/websocket/node.ts
  • test/fixture/proxy-unix.bun.ts
  • test/fixture/proxy-unix.deno.ts
  • test/fixture/ws-unix.bun.ts
  • test/fixture/ws-unix.deno.ts
  • test/proxy-runtimes.test.ts
  • test/proxy.test.ts
  • test/websocket-runtimes.test.ts
🔥 Files not summarized due to errors (14)
  • test/fixture/proxy-unix.bun.ts: Server error: no LLM provider could handle the message
  • test/fixture/ws-unix.bun.ts: Server error: no LLM provider could handle the message
  • docs/1.guide/8.proxy.md: Server error: no LLM provider could handle the message
  • test/fixture/ws-unix.deno.ts: Server error: no LLM provider could handle the message
  • src/proxy.ts: Server error: no LLM provider could handle the message
  • src/websocket/bun.ts: Server error: no LLM provider could handle the message
  • test/fixture/proxy-unix.deno.ts: Server error: no LLM provider could handle the message
  • src/websocket/deno.ts: Server error: no LLM provider could handle the message
  • test/proxy-runtimes.test.ts: Server error: no LLM provider could handle the message
  • test/websocket-runtimes.test.ts: Server error: no LLM provider could handle the message
  • build.config.ts: Server error: no LLM provider could handle the message
  • src/websocket/node.ts: Server error: no LLM provider could handle the message
  • package.json: Server error: no LLM provider could handle the message
  • test/proxy.test.ts: Server error: no LLM provider could handle the message

📝 Walkthrough

[!WARNING]

Walkthrough skipped

File diffs could not be summarized.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/socket

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.

pi0 and others added 2 commits July 2, 2026 09:41
…lients

Delegate `ws+unix:` dialing to the runtime `crossws/websocket` client instead
of embedding runtime logic in the proxy, so a Unix-socket upstream works out of
the box on Node, Bun, and Deno.

- add `src/websocket/deno.ts` and `src/websocket/bun.ts` wrappers; Deno routes
  `ws+unix:` through a unix `Deno.createHttpClient` client, Bun dials natively
- make `src/websocket/node.ts` route `ws+unix:` through `ws` (Proxy over the
  native global) while keeping undici for `ws:`/`wss:`
- proxy `_planDial` lazily imports the matching wrapper per runtime; the chunks
  stay out of the runtime-agnostic entry's eager graph
- point the `crossws/websocket` bun/deno export conditions at the new wrappers
- add cross-runtime tests for the wrappers and the OOTB Deno proxy path

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…time dispatch

Replace the proxy's runtime detection (`globalThis.{Bun,Deno}` checks + dynamic
`import()` of per-runtime clients) with a single static
`import runtimeWebSocket from "crossws/websocket"`. The export conditions resolve
the right client per runtime, and because the import is statically analyzable a
bundle built for a given runtime tree-shakes the others (e.g. `ws`/`node:*`
never enter a Deno or browser bundle).

- proxy: delete `_planDial`/`_DialPlan`/`_constructUpstream`/`optionsAsSecondArg`
  and the async plan machinery; `_dialUpstream` is now one synchronous function
  with a uniform `new ctor(url, protocols[, options])` call
- deno client: accept a third options object and relay it into Deno's
  second-argument form (custom `client`, headers, …)
- mark `crossws/websocket` external so it stays a bare specifier in `dist`
- add `prepare: obuild --stub` so `dist` re-exports `src` for in-repo dev/tests
  (no `development` condition / tsconfig / vitest / fixture flags needed)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pi0 pi0 changed the title feat(proxy): out-of-the-box unix socket targets feat(proxy): support unix socket targets out of the box Jul 2, 2026
@pi0
pi0 marked this pull request as ready for review July 2, 2026 11:21
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pi0
pi0 merged commit 57e99cd into main Jul 2, 2026
6 of 7 checks passed
@pi0
pi0 deleted the feat/socket branch July 2, 2026 11:21
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.

2 participants