# @moq/web-socket-stream

> A WebSocketStream polyfill: a plain WebSocket wrapped as { readable, writable } streams with backpressure

Latest version **0.1.1** (published 2026-07-21) · (MIT OR Apache-2.0) license · 0 weekly downloads

## Install

```sh
npm install @moq/web-socket-stream
pnpm add @moq/web-socket-stream
yarn add @moq/web-socket-stream
bun add @moq/web-socket-stream
```

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; has provenance; recently updated; high maintenance score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.1.1 |
| Published | 2026-07-21 |
| First published | 2026-06-30 |
| Weekly downloads | 0 |
| License | (MIT OR Apache-2.0) |
| TypeScript types | bundled |
| Module format | ESM |
| Dependencies | 0 |
| Unpacked size | 29.4 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 269 |
| Author | Luke Curley |
| Maintainers | kixelated |
| Keywords | websocketstream, websocket, polyfill, ponyfill, streams, backpressure |

## Links

- npm: https://www.npmjs.com/package/@moq/web-socket-stream
- Repository: https://github.com/moq-dev/web-transport
- Homepage: https://github.com/moq-dev/web-transport#readme
- Issues: https://github.com/moq-dev/web-transport/issues
- npm.io page: https://npm.io/package/@moq/web-socket-stream

## Alternatives

- [@opentelemetry/exporter-zipkin](https://npm.io/package/@opentelemetry/exporter-zipkin.md) — 14.8M weekly downloads
- [pusher-js](https://npm.io/package/pusher-js.md) — 2.0M weekly downloads
- [browserify](https://npm.io/package/browserify.md) — 1.7M weekly downloads
- [sqs-consumer](https://npm.io/package/sqs-consumer.md) — 1.7M weekly downloads
- [@sanity/eventsource](https://npm.io/package/@sanity/eventsource.md) — 930.8K weekly downloads

## Recent versions

- 0.1.1 (latest) — 2026-07-21
- 0.1.0 — 2026-06-30

## README

# @moq/web-socket-stream

A polyfill for the [`WebSocketStream`](https://github.com/ricea/websocketstream-explainer) API, which exposes a WebSocket as a `{ readable, writable }` pair of streams with backpressure on the writable.

`WebSocketStream` currently ships only in Chromium. This package wraps a plain `WebSocket` to present the same surface (`opened`, `closed`, `close()`) on every platform — browsers, Node, and Bun.

> **Note:** a ponyfill can only *approximate* write backpressure by polling `WebSocket.bufferedAmount` against a high-water mark; only the native API observes the real send buffer. Use [`openWebSocketStream`](#prefer-native) to get the native implementation when it's available.

## Install

```bash
npm install @moq/web-socket-stream
```

## Usage

### Prefer native

`openWebSocketStream` returns the native `WebSocketStream` when present and falls back to this ponyfill otherwise:

```ts
import { openWebSocketStream } from "@moq/web-socket-stream"

const wss = openWebSocketStream("wss://example.com", { protocols: ["my-proto"] })
const { readable, writable, protocol } = await wss.opened

const reader = readable.getReader()
const writer = writable.getWriter()
await writer.ready          // backpressure
await writer.write(new Uint8Array([1, 2, 3]))
```

### Global polyfill

Install as a global `WebSocketStream` if the platform doesn't ship one:

```ts
import { install } from "@moq/web-socket-stream"

// Only installs if native WebSocketStream is unavailable
install()

const wss = new WebSocketStream("wss://example.com")
```

### Node / Bun

Modern Node and Bun expose a global `WebSocket`, which is used automatically. To use a specific implementation (e.g. the [`ws`](https://www.npmjs.com/package/ws) package), inject it — this forces the ponyfill:

```ts
import WebSocket from "ws"
import { WebSocketStream } from "@moq/web-socket-stream"

const wss = new WebSocketStream("wss://example.com", { webSocket: WebSocket, highWaterMark: 32 * 1024 })
```

### Adopting an existing socket

A server has no URL to dial: the socket arrives from an HTTP upgrade, already open and with its
subprotocol negotiated. `adopt` wraps one in place, resolving `opened` without waiting for an
`onopen` that has already fired:

```ts
import { WebSocketStream } from "@moq/web-socket-stream"

const { socket, response } = Deno.upgradeWebSocket(req)
const wss = WebSocketStream.adopt(socket)
const { readable, writable } = await wss.opened
```

Adoption takes ownership of the socket's event handlers, so adopt it before anything else reads from
it — messages delivered beforehand are dropped by the platform rather than buffered.

## License

Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT license](LICENSE-MIT) at your option.

---
_Source: https://npm.io/package/@moq/web-socket-stream · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
