NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #2686 most downloaded on npm
Coherent, zero-dependency, lazy, simple, GraphQL over WebSocket Protocol compliant server and client
Last release 10 days ago
24 Sep 2026
Release timing varies
gaps range from 9 days to 7 months
Nearly every release is documented
notes for 60 of the last 60 stable releases
16 versions withdrawn
withdrawn after publishing
9 years old
185 releases · first in 2017
Nothing published for this version
Nothing published for this version
Nothing published for this version
One column per quarter.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
This version does not contain any code changes.
#611 6a5fde1 Thanks @enisdenjo! - No more workspaces
This version does not contain any code changes.
Nothing published for this version
Nothing published for this version
This version does not contain any code changes.
#607 a629ec7 Thanks @enisdenjo! - Release with changesets
This version does not contain any code changes.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
server: Return all subscriptions regardless of the return invocation order
client: Use TerminatedCloseEvent class extending an Error for rejecting promises when terminating (74b4ceb), closes #531
client: Use closures instead of bindings (with this)
client: correct close code for Bad Gateway reason
server: Acknowledge connection before notifying the client to avoid race conditions with slow sends (#506) (8cb82bd), closes #501
client: Async iterator for subscriptions
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'ws://localhost:4000/graphql',
});
// query
(async () => {
const query = client.iterate({
query: '{ hello }',
});
const { value } = await query.next();
expect(value).toEqual({ hello: 'world' });
})();
// subscription
(async () => {
const subscription = client.iterate({
query: 'subscription { greetings }',
});
for await (const event of subscription) {
expect(event).toEqual({ greetings: 'Hi' });
// complete a running subscription by breaking the iterator loop
break;
}
})();
Remove unnecessary bun-types directives (e9a56f7), closes #478
```ts import { makeHandler, handleProtocols } from 'graphql-ws/lib/use/lib/bun'; import { schema } from './previous-step';
import { makeHandler, handleProtocols } from 'graphql-ws/lib/use/lib/bun';
import { schema } from './previous-step';
Bun.serve({
fetch(req, server) {
const [path, _search] = req.url.split('?');
if (!path.endsWith('/graphql')) {
return new Response('Not Found', { status: 404 });
}
if (req.headers.get('upgrade') != 'websocket') {
return new Response('Upgrade Required', { status: 426 });
}
if (handleProtocols(req.headers.get('sec-websocket-protocol') || '')) {
return new Response('Bad Request', { status: 404 });
}
if (!server.upgrade(req)) {
return new Response('Internal Server Error', { status: 500 });
}
return new Response();
},
websocket: makeHandler({ schema }),
port: 4000,
});
console.log('Listening to port 4000');
import { serve } from 'https://deno.land/std/http/mod.ts';
import {
makeHandler,
GRAPHQL_TRANSPORT_WS_PROTOCOL,
} from 'https://esm.sh/graphql-ws/lib/use/deno';
import { schema } from './previous-step.ts';
const handler = makeHandler({ schema });
serve(
(req: Request) => {
const [path, _search] = req.url.split('?');
if (!path.endsWith('/graphql')) {
return new Response('Not Found', { status: 404 });
}
if (req.headers.get('upgrade') != 'websocket') {
return new Response('Upgrade Required', { status: 426 });
}
const { socket, response } = Deno.upgradeWebSocket(req, {
protocol: GRAPHQL_TRANSPORT_WS_PROTOCOL,
idleTimeout: 12_000,
});
handler(socket);
return response;
},
{ port: 4000 },
);
Add file extensions to imports/exports in ESM type definitions
Allow null payloads in messages (#456) (eeb0265), closes #450
ws,uWebSockets,@fastify/websocket: Handle internal errors that are not instances of Error (#442) (9884889), closes #441
Reorder types paths in package.json for better import resolution
server: Shouldn't send a complete message if client sent it (331fe47), closes #403
client: Provide subscribe payload in generateID (d0bc6e1), closes #398
## 5.10.2 (2022-09-12) ### Performance Improvements * Easier message parser
client: Debounce close by lazyCloseTimeout (c332837), closes #388
server: Use @fastify/websocket (#382) (dd755b0), closes #381
Add types path to package.json exports
Descriptive invalid message errors (b46379e), closes #366
server: Should clean up subscription reservations on abrupt errors without relying on connection close
client: isFatalConnectionProblem defaults to undefined for using shouldRetry
client: Deprecate isFatalConnectionProblem option in favour of shouldRetry
isFatalConnectionProblem option in favour of shouldRetry (d8dcf21)import { createClient } from 'graphql-ws';
import { waitForHealthy } from './my-servers';
const client = createClient({
url: 'ws://any.retry:4000/graphql',
// by default the client will immediately fail on any non-fatal
// `CloseEvent` problem thrown during the connection phase
//
// see `retryAttempts` documentation about which `CloseEvent`s are
// considered fatal regardless
shouldRetry: () => true,
// or pre v5.8.0:
// isFatalConnectionProblem: () => false,
});
client: Terminate the WebSocket abruptly and immediately (53ad515), closes #290
import { createClient } from 'graphql-ws';
let timedOut;
const client = createClient({
url: 'ws://terminate.me:4000/on-pong-timeout',
keepAlive: 10_000, // ping server every 10 seconds
on: {
ping: (received) => {
if (!received /* sent */) {
timedOut = setTimeout(() => {
// a close event `4499: Terminated` is issued to the current WebSocket and an
// artificial `{ code: 4499, reason: 'Terminated', wasClean: false }` close-event-like
// object is immediately emitted without waiting for the one coming from `WebSocket.onclose`
//
// calling terminate is not considered fatal and a connection retry will occur as expected
//
// see: https://github.com/enisdenjo/graphql-ws/discussions/290
client.terminate();
}, 5_000);
}
},
pong: (received) => {
if (received) {
clearTimeout(timedOut);
}
},
},
});
Warn about subscriptions-transport-ws clients and provide migration link (e080739), closes #339, related #325
client: Stop execution if connectionParams took too long and the server kicked the client off (1e94e45), closes #331
server: handleProtocols accepts arrays too and gracefully rejects other types (98dec1a), closes #318
server: Handle upgrade requests with multiple subprotocols and omit Sec-WebSocket-Protocol header if none supported
TypeScript generic for connection init payload (connectionParams)
client: Limit client emitted error close message size
fastify-websocket: Handle connection and socket emitted errors
client: Distinguish client connection closes
client: Don't complete after connection error
server: Limit internal server error close message size
Sink's next callback always receives an ExecutionResult
client: Specify and fail on fatal internal WebSocket close codes
4406 close code for unsupported subprotocol (1002 is an internal WebSocket close code) (df85281)4500 close code for internal server errors (1011 is an internal WebSocket close code) (3c0316d)client: ConnectionInit payload is absent if connectionParams returns nothing
server: Optional onPing and onPong message type listeners
onPing and onPong message type listeners (f36066f)import ws from 'ws'; // yarn add ws
import { makeServer, stringifyMessage, MessageType, CloseCode } from 'graphql-ws';
import { schema } from './my-graphql-schema';
// make
const server = makeServer({ schema });
// create websocket server
const wsServer = new ws.Server({
port: 4000,
path: '/graphql',
});
// implement
wsServer.on('connection', (socket, request) => {
// subprotocol pinger because WS level ping/pongs might not be available
let pinger, pongWait;
function ping() {
if (socket.readyState === socket.OPEN) {
// send the subprotocol level ping message
socket.send(stringifyMessage({ type: MessageType.Ping }));
// wait for the pong for 6 seconds and then terminate
pongWait = setTimeout(() => {
clearInterval(pinger);
socket.close();
}, 6_000);
}
}
// ping the client on an interval every 12 seconds
pinger = setInterval(() => ping(), 12_000);
// a new socket opened, let graphql-ws take over
const closed = server.opened(
{
protocol: socket.protocol, // will be validated
send: (data) => socket.send(data),
close: (code, reason) => socket.close(code, reason),
onMessage: (cb) =>
socket.on('message', async (event) => {
try {
// wait for the the operation to complete
// - if init message, waits for connect
// - if query/mutation, waits for result
// - if subscription, waits for complete
await cb(event.toString());
} catch (err) {
// all errors that could be thrown during the
// execution of operations will be caught here
socket.close(CloseCode.InternalServerError, err.message);
}
}),
// pong received, clear termination timeout
onPong: () => clearTimeout(pongWait),
},
// pass values to the `extra` field in the context
{ socket, request },
);
// notify server that the socket closed and stop the pinger
socket.once('close', (code, reason) => {
clearTimeout(pongWait);
clearInterval(pinger);
closed(code, reason);
});
});
Your coding agent can read these notes before it upgrades. Set up the MCP server →