WebSocket
An effect-native WebSocket client — open a socket, stream incoming frames, and send codec-encoded values.
@ozaco/std/wsstd:ws is the socket counterpart to std:fetch: a WebSocket CLIENT built on effects. connect() opens a socket and resolves once it is OPEN (or fails with 'ws/connect' if the handshake errors), handing back a Connection that exposes incoming frames as an effect Stream, a send operation, a close, and a closed Future.
Message framing mirrors the server gateway and, like std:fetch, runs through the registered std:codec: strings and binary go over the wire as-is, while every other value is codec-encoded on send and codec-decoded on receive. A codec (e.g. JsonCodec) must be installed in scope. Incoming frames are buffered in a queue and decoded lazily as they are pulled, so nothing is lost between arrival and consumption.
The underlying constructor is injectable through the wsImpl context — it defaults to the platform global WebSocket and can be swapped for a loopback socket or custom transport in tests. connect forces binaryType = 'arraybuffer' so binary frames arrive consistently as ArrayBuffer across Bun, Node and the browser.
Client
Open a connection.
connect
Open a WebSocket and resolve once it is OPEN, yielding a `Connection`.
const connect: (url: string | URL, options?: WsDef.Options) => Future<WsDef.Connection>Fails with 'ws/unsupported' if no WebSocket implementation is available, or 'ws/connect' if the socket errors during the handshake. Headers (options.headers) use the Bun/Node options-object constructor form; the browser WebSocket cannot set handshake headers, so authenticate with a ?token= query param there. A codec must be installed for non-string/binary payloads.
import { streamForEach } from 'std:effect'
import { connect } from '@ozaco/std/ws'
import { JsonCodec } from 'std:codec/impl/json'
yield* install(JsonCodec)
const socket = yield* connect('wss://example.com/chat', {
headers: { authorization: 'Bearer …' },
})
yield* socket.send({ event: 'join', room: 'general' })
// consume decoded frames until the socket closes
yield* streamForEach(socket.messages, function* (msg) {
console.log('frame', msg)
})
const { code, reason } = yield* socket.closedContext
Inject the underlying WebSocket constructor.
wsImpl
Effect context holding the `WebSocket` constructor `connect()` dispatches through.
const wsImpl: Context<WsDef.Ctor>Defaults to the platform global WebSocket. Override it for tests, a loopback socket, or a custom transport with wsImpl.set(Ctor) or wsImpl.with(Ctor, op) in the running scope; connect reads it via wsImpl.get() so the default applies when unset.
import { wsImpl } from '@ozaco/std/ws'
yield* wsImpl.with(FakeSocket, function* () {
const socket = yield* connect('ws://test')
// ...
})Types
The `WsDef` namespace: the connection surface and its supporting shapes.
WsDef.Connection
A live socket: its message `Stream`, `send`, `close`, `closed` Future and the native escape hatch.
interface Connection {
readonly native: SocketLike
readonly url: string
readonly readyState: number
send(data: unknown): Operation<void>
readonly messages: Stream<unknown, StreamClose>
close(code?: number, reason?: string): Operation<void>
readonly closed: Future<CloseInfo>
}send encodes non-string/binary values through the registered codec; messages yields codec-decoded frames and closes true on a clean close or with a failure on error; closed resolves with the close code/reason.
WsDef.Options
Handshake options for `connect`: sub-protocols and headers.
interface Options {
protocols?: string | string[]
headers?: Record<string, string>
}headers are honored by Bun’s and Node’s global WebSocket; the browser WebSocket cannot set handshake headers.
WsDef.CloseInfo
Why/how the socket closed.
interface CloseInfo {
code: number
reason: string
}WsDef.SocketLike
The WHATWG `WebSocket` subset the browser, Bun and Node globals all satisfy.
interface SocketLike {
send(data: string | ArrayBufferLike | ArrayBufferView): void
close(code?: number, reason?: string): void
readonly readyState: number
binaryType: 'blob' | 'arraybuffer'
onopen: ((event: AnyType) => void) | null
onmessage: ((event: { data: AnyType }) => void) | null
onerror: ((event: AnyType) => void) | null
onclose: ((event: { code?: number; reason?: string }) => void) | null
}WsDef.Ctor
The injectable `WebSocket` constructor type (standard or Bun/Node options-object form).
type Ctor = new (
url: string | URL,
options?:
| string
| string[]
| { protocols?: string | string[]; headers?: Record<string, string> },
) => SocketLikeWsDef.StreamClose
The value the message stream settles with: `true` on a clean close, or a failure on error.
type StreamClose = true | Result.Failure<unknown>WsDef.Error
Errors surfaced by `connect`. Thrown faults pass through as `unknown`; the only tags it raises are `'ws/unsupported'` and `'ws/connect'`.
type Error = unknown