Skip to content

Drop-in compat

durablews/compat exports a class shaped like the native WebSocket, with the durable core underneath: automatic reconnection with full-jitter backoff, bounded queueing, opt-in heartbeat. It exists for two audiences:

  1. App code that constructs sockets directly — the reconnecting-websocket / partysocket crowd. Migration is one line.
  2. Libraries with a webSocketImpl-style injection point — graphql-ws, y-websocket, realtime SDKs. Inject durability into tools that never learn durablews exists.
import { WebSocket } from "durablews/compat";
const ws = new WebSocket("wss://example.com/socket");
ws.onmessage = (event) => console.log(event.data);
ws.send("hello");

The class is also exported as DurableWebSocket if you prefer not to shadow the global name.

// graphql-ws
import { createClient } from "graphql-ws";
import { WebSocket } from "durablews/compat";
const client = createClient({
url: "wss://example.com/graphql",
webSocketImpl: WebSocket
});
// y-websocket
import { WebsocketProvider } from "y-websocket";
import { WebSocket } from "durablews/compat";
const provider = new WebsocketProvider(url, room, doc, {
WebSocketPolyfill: WebSocket
});

The third constructor argument takes the durablews config (everything except codec and schema — compat is deliberately wire-faithful, byte in, byte out):

const ws = new WebSocket(url, undefined, {
reconnect: { maxDelay: 10_000 },
queue: { maxSize: 1000 },
heartbeat: { interval: 15_000 }
});

And the full durablews client sits underneath as the escape hatch:

ws.client.on("reconnecting", ({ attempt }) => showBanner(attempt));
ws.client.on("drop", ({ data }) => log("not sent", data));

A drop-in that quietly diverges is worse than one that tells you where. The deviations, exhaustively:

BehaviorNative WebSocketdurablews/compat
readyState across disconnectsone-shot: never leaves CLOSEDreturns to CONNECTING during automatic reconnects — the entire point
send() while CONNECTINGthrows InvalidStateErrorqueues, flushes in order on open
close eventsonce, at the endonce per dropped connection (each followed by reconnection)
protocol / extensionsnegotiated valuesalways ""
bufferedAmountbytes pending on the socketalways 0
binaryType set after constructionreconfigures the socketemulated: Blob frames are converted on delivery (order-preserving, one copy). Prefer { binaryType: "arraybuffer" } in the options, which configures the socket itself
send() after close()silently discardedsilently discarded (matched)

If one of these deviations matters to your integration, use the primary defineClient API instead — it doesn’t pretend to be a one-shot socket, so none of these tensions exist there.