Skip to content

Commit

Permalink
feat(server): Use Bun (#476)
Browse files Browse the repository at this point in the history
  • Loading branch information
enisdenjo authored May 11, 2023
1 parent 9cda353 commit 09c7893
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 9 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,29 @@ jobs:
run: |
NODE_ENV=production node benchmark/servers/legacy_ws7.mjs &
LEGACY=1 SERVER=legacy_ws7 ./k6 run benchmark/k6.mjs
bun:
name: bun
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up node
uses: actions/setup-node@v3
with:
node-version: 18
cache: yarn
- name: Install
run: yarn install --immutable
- name: Download k6
run: |
curl https://github.com/grafana/k6/releases/download/v0.39.0/k6-v0.39.0-linux-amd64.tar.gz -L | tar xvz --strip-components 1
- name: Build
run: yarn run build:esm
- name: Set up bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Run
run: |
bun benchmark/servers/bun.ts &
SERVER=bun ./k6 run benchmark/k6.mjs
18 changes: 18 additions & 0 deletions benchmark/servers/bun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// <reference types="bun-types" />

import { schema } from './schema.mjs';
import { makeHandler } from '../../src/use/bun';
import { ports } from './ports.mjs';

Bun.serve({
fetch(req, server) {
if (server.upgrade(req)) {
return new Response();
}
return new Response(null, { status: 500 });
},
websocket: makeHandler({ schema }),
port: ports.bun,
});

console.log(`bun - listening on port ${ports.bun}...`);
1 change: 1 addition & 0 deletions benchmark/servers/ports.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export const ports = {
uWebSockets: 6541,
legacy_ws7: 6542,
'@fastify/websocket': 6544,
bun: 6545,
};
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@
"require": "./lib/use/fastify-websocket.js",
"import": "./lib/use/fastify-websocket.mjs"
},
"./lib/use/bun": {
"bun": "./lib/use/bun.mjs",
"types": "./lib/use/bun.d.ts",
"require": "./lib/use/bun.js",
"import": "./lib/use/bun.mjs"
},
"./package.json": "./package.json"
},
"files": [
Expand Down Expand Up @@ -115,6 +121,7 @@
"@typescript-eslint/eslint-plugin": "^5.59.5",
"@typescript-eslint/parser": "^5.59.5",
"babel-jest": "^29.5.0",
"bun-types": "^0.5.8",
"eslint": "^8.40.0",
"eslint-config-prettier": "^8.8.0",
"fastify": "^4.17.0",
Expand Down
11 changes: 2 additions & 9 deletions scripts/post-gendocs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,7 @@ async function fixLinksInDir(dirPath) {
}
const contents = await fsp.readFile(filePath);
const src = contents.toString();
await fsp.writeFile(
filePath,
src
// @ts-expect-error we're running this on modern node
.replaceAll('.md', ''),
);
await fsp.writeFile(filePath, src.replaceAll('.md', ''));
}
}

Expand Down Expand Up @@ -70,9 +65,7 @@ async function createRecursiveMetaFiles(dirPath) {
if (nameNoExt === 'use__fastify_websocket') {
meta[nameNoExt] = 'use/@fastify/websocket';
} else {
meta[nameNoExt] = nameNoExt
// @ts-expect-error we're running this on modern node
.replaceAll('_', '/');
meta[nameNoExt] = nameNoExt.replaceAll('_', '/');
}
}

Expand Down
92 changes: 92 additions & 0 deletions src/use/bun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/// <reference types="bun-types" />

import type { WebSocketHandler, ServerWebSocket } from 'bun';
import {
ConnectionInitMessage,
GRAPHQL_TRANSPORT_WS_PROTOCOL,
} from '../common';
import { makeServer, ServerOptions } from '../server';

/**
* Convenience export for checking the WebSocket protocol on the request in `Bun.serve`.
*/
export { handleProtocols } from '../server';

/**
* The extra that will be put in the `Context`.
*
* @category Server/bun
*/
export interface Extra {
/**
* The actual socket connection between the server and the client.
*/
readonly socket: ServerWebSocket;
}

/**
* Use the server with [Bun](https://bun.sh/).
* This is a basic starter, feel free to copy the code over and adjust it to your needs
*
* The keep-alive logic _seems_ to be handled by Bun seeing that
* they default [`sendPingsAutomatically` to `true`](https://github.com/oven-sh/bun/blob/6a163cf933542506354dc836bd92693bcae5939b/src/deps/uws.zig#L893).
*
* @category Server/bun
*/
export function makeHandler<
P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'],
E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>,
>(options: ServerOptions<P, Extra & Partial<E>>): WebSocketHandler {
const server = makeServer(options);

interface Client {
handleMessage: (data: string) => Promise<void>;
closed: (code: number, reason: string) => Promise<void>;
}
const clients = new WeakMap<ServerWebSocket, Client>();

return {
open(ws) {
const client: Client = {
handleMessage: () => {
throw new Error('Message received before handler was registered');
},
closed: () => {
throw new Error('Closed before handler was registered');
},
};

client.closed = server.opened(
{
// TODO: use protocol on socket once Bun exposes it
protocol: GRAPHQL_TRANSPORT_WS_PROTOCOL,
send: async (message) => {
// ws might have been destroyed in the meantime, send only if exists
if (clients.has(ws)) {
ws.sendText(message);
}
},
close: (code, reason) => {
if (clients.has(ws)) {
ws.close(code, reason);
}
},
onMessage: (cb) => (client.handleMessage = cb),
},
{ socket: ws } as Extra & Partial<E>,
);

clients.set(ws, client);
},
message(ws, message) {
const client = clients.get(ws);
if (!client) throw new Error('Message received for a missing client');
return client.handleMessage(String(message));
},
close(ws, code, message) {
const client = clients.get(ws);
if (!client) throw new Error('Closing a missing client');
return client.closed(code, message);
},
};
}
28 changes: 28 additions & 0 deletions website/src/pages/get-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,34 @@ fastify.listen(4000, (err) => {
});
```

#### With [Bun](https://bun.sh)

```ts
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') &&
handleProtocols(req.headers.get('sec-websocket-protocol') || '')
) {
if (server.upgrade(req)) {
return new Response();
} else {
return new Response('Internal Server Error', { status: 500 });
}
}
return new Response('Upgrade Required', { status: 426 });
},
websocket: makeHandler({ schema }),
port: 4000,
});

console.log('Listening to port 4000');
```

### Use the client

```ts
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4415,6 +4415,13 @@ __metadata:
languageName: node
linkType: hard

"bun-types@npm:^0.5.8":
version: 0.5.8
resolution: "bun-types@npm:0.5.8"
checksum: 8a4a7652b1b877381549195f1fa8d23c7f62f60f39af58b5ca060946576ed4fb728e9f249518e68a0c678a1953b85fe509b50956d7fefe5fcece5abcc6ee6a94
languageName: node
linkType: hard

"busboy@npm:1.6.0":
version: 1.6.0
resolution: "busboy@npm:1.6.0"
Expand Down Expand Up @@ -7064,6 +7071,7 @@ __metadata:
"@typescript-eslint/eslint-plugin": ^5.59.5
"@typescript-eslint/parser": ^5.59.5
babel-jest: ^29.5.0
bun-types: ^0.5.8
eslint: ^8.40.0
eslint-config-prettier: ^8.8.0
fastify: ^4.17.0
Expand Down

0 comments on commit 09c7893

Please sign in to comment.