Skip to content

Commit

Permalink
feat(server): Use Deno (#477)
Browse files Browse the repository at this point in the history
  • Loading branch information
enisdenjo authored May 12, 2023
1 parent a2cca2b commit 0ffd03b
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 1 deletion.
28 changes: 28 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,31 @@ jobs:
run: |
bun benchmark/servers/bun.ts &
SERVER=bun ./k6 run benchmark/k6.mjs
deno:
name: deno
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 deno
uses: denoland/setup-deno@v1
with:
deno-version: 1.x
- name: Cache
run: deno cache benchmark/servers/deno.ts
- name: Run
run: |
deno run -A benchmark/servers/deno.ts &
SERVER=deno ./k6 run benchmark/k6.mjs
27 changes: 27 additions & 0 deletions benchmark/servers/deno.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck TODO: figure out how to use deno types

import { serve } from 'https://deno.land/std/http/mod.ts';
import {
makeHandler,
GRAPHQL_TRANSPORT_WS_PROTOCOL,
} from '../../lib/use/deno.mjs';
import { schema } from './schema.mjs';
import { ports } from './ports.mjs';

const handler = makeHandler({ schema });

serve(
(req: Request) => {
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: ports.deno },
);
1 change: 1 addition & 0 deletions benchmark/servers/ports.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export const ports = {
legacy_ws7: 6542,
'@fastify/websocket': 6544,
bun: 6545,
deno: 6546,
};
2 changes: 1 addition & 1 deletion benchmark/servers/schema.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const schema = new GraphQLSchema({
subscribe: async function* () {
for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) {
yield { greetings: hi };
await new Promise((resolve) => setImmediate(resolve));
await new Promise((resolve) => setTimeout(resolve, 0));
}
},
},
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@
"require": "./lib/use/bun.js",
"import": "./lib/use/bun.mjs"
},
"./lib/use/deno": {
"types": "./lib/use/deno.d.ts",
"require": "./lib/use/deno.js",
"import": "./lib/use/deno.mjs"
},
"./package.json": "./package.json"
},
"files": [
Expand Down
120 changes: 120 additions & 0 deletions src/use/deno.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { makeServer, ServerOptions } from '../server';
import {
DEPRECATED_GRAPHQL_WS_PROTOCOL,
ConnectionInitMessage,
CloseCode,
} from '../common';
export { GRAPHQL_TRANSPORT_WS_PROTOCOL } from '../common';

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

/**
* Use the server with [Deno](https://deno.com/).
* This is a basic starter, feel free to copy the code over and adjust it to your needs.
*
* The keep-alive is set in `Deno.upgradeWebSocket` during the upgrade.
*
* Additionally, the required WebSocket protocol is also defined during the upgrade,
* the correct example being:
*
* ```ts
* 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 './my-schema.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 },
* );
* ```
*
* @category Server/deno
*/
export function makeHandler<
P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload'],
E extends Record<PropertyKey, unknown> = Record<PropertyKey, never>,
>(options: ServerOptions<P, Extra & Partial<E>>): (socket: WebSocket) => void {
const server = makeServer(options);
return function handle(socket) {
socket.onerror = (err) => {
console.error(
'Internal error emitted on the WebSocket socket. ' +
'Please check your implementation.',
err,
);
socket.close(CloseCode.InternalServerError, 'Internal server error');
};

let closed: (code: number, reason: string) => void = () => {
// noop
};
socket.onopen = () => {
closed = server.opened(
{
protocol: socket.protocol,
send: (msg) => socket.send(msg),
close: (code, reason) => socket.close(code, reason),
onMessage: (cb) => {
socket.onmessage = async (event) => {
try {
await cb(String(event.data));
} catch (err) {
console.error(
'Internal error occurred during message handling. ' +
'Please check your implementation.',
err,
);
socket.close(
CloseCode.InternalServerError,
'Internal server error',
);
}
};
},
},
{ socket } as Extra & Partial<E>,
);
};

socket.onclose = (event) => {
if (
event.code === CloseCode.SubprotocolNotAcceptable &&
socket.protocol === DEPRECATED_GRAPHQL_WS_PROTOCOL
)
console.warn(
`Client provided the unsupported and deprecated subprotocol "${socket.protocol}" used by subscriptions-transport-ws.` +
'Please see https://www.apollographql.com/docs/apollo-server/data/subscriptions/#switching-from-subscriptions-transport-ws.',
);
closed(event.code, event.reason);
};
};
}
32 changes: 32 additions & 0 deletions website/src/pages/get-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,38 @@ Bun.serve({
console.log('Listening to port 4000');
```

#### With [Deno](https://deno.com/runtime)

```ts
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 },
);
```

### Use the client

```ts
Expand Down

0 comments on commit 0ffd03b

Please sign in to comment.