A WebSocket library for Effect-TS that provides both client and server functionality with runtime portability across Node.js, Bun, and browser environments.
This library follows Effect's ecosystem patterns with a mono-repo structure:
effect-websocket(core): Runtime-agnostic interfaces and typeseffect-websocket-node: Node.js implementation using thewspackageeffect-websocket-bun: Bun implementation using native WebSocket APIs
Static Methods:
WebSocketClient.make(url, protocols?, reconnectionOptions?): Create a WebSocket clientWebSocketClient.withClient(url, callback, reconnectionOptions?): Create and use a WebSocket client with automatic cleanup (when protocols not needed)WebSocketClient.withClient(url, protocols, callback, reconnectionOptions?): Create and use a WebSocket client with automatic cleanup (when protocols needed)
Instance Methods:
send(message): Send a messagemessages: Stream of incoming messagesevents: Stream of WebSocket events (open, close, error, message, reconnecting, reconnect_failed)close(): Close the connectionreadyState: Current connection stateisReconnecting: Check if currently attempting reconnectionreconnectAttempts: Get number of reconnection attempts made with proper error handling and streaming.
# For Node.js
bun add effect-websocket effect-websocket-node @effect/platform effect
# For Bun
bun add effect-websocket effect-websocket-bun @effect/platform effect- API Reference: Comprehensive API documentation with examples
- Examples: See the
packages/core/examples/directory for complete working examples:client.ts: Basic client usageserver.ts: Basic server usageadvanced-client.ts: Client with reconnection and error handlingbinary-data.ts: Handling binary messages
import { Effect, Stream } from "effect"
import { WebSocketClient } from "effect-websocket"
const program = Effect.scoped(
WebSocketClient.withClient("ws://localhost:8080", (client) =>
Effect.gen(function* () {
// Send a message
yield* client.send("Hello!")
// Listen for messages
yield* Stream.runForEach(client.messages, (message) => {
console.log("Received:", message)
return Effect.succeed(undefined)
})
// Listen for events (including reconnection events)
yield* Stream.runForEach(client.events, (event) => {
switch (event._tag) {
case "open":
console.log("Connected")
break
case "close":
console.log("Disconnected:", event.code, event.reason)
break
case "reconnecting":
console.log(`Reconnecting (attempt ${event.attempt})`)
break
case "reconnect_failed":
console.log("Reconnection failed after", event.attempt, "attempts")
break
case "error":
console.log("Error:", event)
break
}
return Effect.succeed(undefined)
})
yield* Effect.never
})
)
)
Effect.runPromise(program)import { Effect, Stream } from "effect"
import { WebSocketServer } from "effect-websocket"
// Import platform-specific implementation
import { makeWebSocketServer } from "effect-websocket-node" // or "effect-websocket-bun"
const program = Effect.scoped(
makeWebSocketServer({ port: 8080 }, (server) =>
Effect.gen(function* () {
console.log("WebSocket server started on port 8080")
// Handle new connections
yield* Stream.runForEach(server.connections, (connection) => {
console.log("New connection:", connection.id)
// Handle messages from this connection
return Stream.runForEach(connection.messages, (message) => {
console.log(`Message from ${connection.id}:`, message)
// Echo the message back
return connection.send(`Echo: ${message}`)
})
})
yield* Effect.never
})
)
)
Effect.runPromise(program)Static Methods:
WebSocketClient.make(url, protocols?, reconnectionOptions?): Create a WebSocket clientWebSocketClient.withClient(url, protocols?, f, reconnectionOptions?): Create and use a WebSocket client with automatic cleanup
Instance Methods:
send(message): Send a messagemessages: Stream of incoming messagesevents: Stream of WebSocket events (open, close, error, message, reconnecting, reconnect_failed)close(): Close the connectionreadyState: Current connection stateisReconnecting: Check if currently attempting reconnectionreconnectAttempts: Get number of reconnection attempts made
Reconnection Options:
interface ReconnectionOptions {
enabled: boolean // Enable/disable automatic reconnection
initialDelay: number // Initial delay in milliseconds (default: 1000)
maxDelay: number // Maximum delay in milliseconds (default: 30000)
maxAttempts: number // Maximum attempts (0 = unlimited, default: 10)
backoffMultiplier: number // Delay multiplier per attempt (default: 2)
jitter: boolean // Add randomness to delay (default: true)
}Static Methods:
WebSocketServer.make(options): Create a WebSocket serverWebSocketServer.withServer(options, f): Create and use a WebSocket server with automatic cleanup
Instance Methods:
connections: Stream of new connectionsmessages: Stream of messages from all connections (with connectionId)close(): Close the server
# Run all tests
bun run test
# Run tests in CI mode (non-watch)
bun run test:packages
# Run specific test file
bun run test packages/core/test/WebSocketClient.test.ts# Build all packages
bun run build:packages
# Clean build artifacts
bun run cleanEach package can be published independently:
# Publish core package
cd packages/core && npm publish
# Publish Node.js package
cd packages/node && npm publish
# Publish Bun package
cd packages/bun && npm publishSee the packages/core/examples/ directory for complete client and server examples.
# Client example (requires a running server)
cd packages/core/examples && bun run client.ts
# Server example
cd packages/core/examples && bun run server.tsThis is a mono-repo using workspaces. Make sure to:
- Run tests before committing:
bun run test:packages - Update examples if you change the API
- Test on both Node.js and Bun runtimes
- Follow Effect's coding patterns and error handling
MIT