Skip to content

Commit 5089f46

Browse files
authored
feat: Use uWebSockets.js (#91)
1 parent aa63493 commit 5089f46

File tree

8 files changed

+274
-1
lines changed

8 files changed

+274
-1
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,21 @@ app.listen({ port: 4000 });
158158
console.log('Listening to port 4000');
159159
```
160160

161+
##### With [`uWebSockets.js`](https://github.com/uNetworking/uWebSockets.js)
162+
163+
```js
164+
import uWS from 'uWebSockets.js'; // yarn add uWebSockets.js@uNetworking/uWebSockets.js#<version>
165+
import { createHandler } from 'graphql-http/lib/use/uWebSockets';
166+
import { schema } from './previous-step';
167+
168+
uWS
169+
.App()
170+
.any('/graphql', createHandler({ schema }))
171+
.listen(4000, () => {
172+
console.log('Listening to port 4000');
173+
});
174+
```
175+
161176
##### With [`Deno`](https://deno.land/)
162177

163178
```ts

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ graphql-http
1919
- [use/http2](modules/use_http2.md)
2020
- [use/koa](modules/use_koa.md)
2121
- [use/node](modules/use_node.md)
22+
- [use/uWebSockets](modules/use_uWebSockets.md)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[graphql-http](../README.md) / [use/uWebSockets](../modules/use_uWebSockets.md) / RequestContext
2+
3+
# Interface: RequestContext
4+
5+
[use/uWebSockets](../modules/use_uWebSockets.md).RequestContext
6+
7+
The context in the request for the handler.
8+
9+
## Table of contents
10+
11+
### Properties
12+
13+
- [res](use_uWebSockets.RequestContext.md#res)
14+
15+
## Properties
16+
17+
### res
18+
19+
**res**: `HttpResponse`

docs/modules/use_uWebSockets.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
[graphql-http](../README.md) / use/uWebSockets
2+
3+
# Module: use/uWebSockets
4+
5+
## Table of contents
6+
7+
### Interfaces
8+
9+
- [RequestContext](../interfaces/use_uWebSockets.RequestContext.md)
10+
11+
### Type Aliases
12+
13+
- [HandlerOptions](use_uWebSockets.md#handleroptions)
14+
15+
### Functions
16+
17+
- [createHandler](use_uWebSockets.md#createhandler)
18+
19+
## Server/uWebSockets
20+
21+
### HandlerOptions
22+
23+
Ƭ **HandlerOptions**<`Context`\>: [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`HttpRequest`, [`RequestContext`](../interfaces/use_uWebSockets.RequestContext.md), `Context`\>
24+
25+
Handler options when using the http adapter.
26+
27+
#### Type parameters
28+
29+
| Name | Type |
30+
| :------ | :------ |
31+
| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` |
32+
33+
___
34+
35+
### createHandler
36+
37+
**createHandler**<`Context`\>(`options`): (`res`: `HttpResponse`, `req`: `HttpRequest`) => `Promise`<`void`\>
38+
39+
Create a GraphQL over HTTP spec compliant request handler for
40+
the Node environment [uWebSockets.js module](https://github.com/uNetworking/uWebSockets.js/).
41+
42+
```js
43+
import uWS from 'uWebSockets.js'; // yarn add uWebSockets.js@uNetworking/uWebSockets.js#<version>
44+
import { createHandler } from 'graphql-http/lib/use/uWebSockets';
45+
import { schema } from './my-graphql-schema';
46+
47+
uWS
48+
.App()
49+
.any('/graphql', createHandler({ schema }))
50+
.listen(4000, () => {
51+
console.log('Listening to port 4000');
52+
});
53+
```
54+
55+
#### Type parameters
56+
57+
| Name | Type |
58+
| :------ | :------ |
59+
| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` |
60+
61+
#### Parameters
62+
63+
| Name | Type |
64+
| :------ | :------ |
65+
| `options` | [`HandlerOptions`](use_uWebSockets.md#handleroptions)<`Context`\> |
66+
67+
#### Returns
68+
69+
`fn`
70+
71+
▸ (`res`, `req`): `Promise`<`void`\>
72+
73+
##### Parameters
74+
75+
| Name | Type |
76+
| :------ | :------ |
77+
| `res` | `HttpResponse` |
78+
| `req` | `HttpRequest` |
79+
80+
##### Returns
81+
82+
`Promise`<`void`\>

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@
7070
"require": "./lib/use/koa.js",
7171
"import": "./lib/use/koa.mjs"
7272
},
73+
"./lib/use/uWebSockets": {
74+
"types": "./lib/use/uWebSockets.d.ts",
75+
"require": "./lib/use/uWebSockets.js",
76+
"import": "./lib/use/uWebSockets.mjs"
77+
},
7378
"./package.json": "./package.json"
7479
},
7580
"types": "lib/index.d.ts",
@@ -147,7 +152,8 @@
147152
"tslib": "^2.5.2",
148153
"typedoc": "^0.24.7",
149154
"typedoc-plugin-markdown": "^3.15.3",
150-
"typescript": "^5.0.4"
155+
"typescript": "^5.0.4",
156+
"uWebSockets.js": "uNetworking/uWebSockets.js#v20.30.0"
151157
},
152158
"resolutions": {
153159
"npm/libnpmversion": "^3.0.6"

src/__tests__/use.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import fastify from 'fastify';
55
import Koa from 'koa';
66
import mount from 'koa-mount';
77
import { createServerAdapter } from '@whatwg-node/server';
8+
import uWS from 'uWebSockets.js';
89
import { startDisposableServer } from './utils/tserver';
910
import { serverAudits } from '../audits';
1011
import { schema } from './fixtures/simple';
@@ -14,6 +15,7 @@ import { createHandler as createExpressHandler } from '../use/express';
1415
import { createHandler as createFastifyHandler } from '../use/fastify';
1516
import { createHandler as createFetchHandler } from '../use/fetch';
1617
import { createHandler as createKoaHandler } from '../use/koa';
18+
import { createHandler as createUWSHandler } from '../use/uWebSockets';
1719

1820
describe('http', () => {
1921
const [url, , dispose] = startDisposableServer(
@@ -219,3 +221,41 @@ describe('koa', () => {
219221
await dispose();
220222
});
221223
});
224+
225+
describe('uWebSockets.js', () => {
226+
let url = '';
227+
let appListenSocket: uWS.us_listen_socket;
228+
beforeAll(async () => {
229+
// get available port by starting a temporary server
230+
const [availableUrl, availablePort, dispose] = startDisposableServer(
231+
http.createServer(),
232+
);
233+
await dispose();
234+
url = availableUrl;
235+
236+
new Promise<void>((resolve, reject) => {
237+
uWS
238+
.App()
239+
.any('/', createUWSHandler({ schema }))
240+
.listen(availablePort, (listenSocket) => {
241+
if (!listenSocket) {
242+
reject(new Error('Unavailable uWS listen socket'));
243+
} else {
244+
appListenSocket = listenSocket;
245+
resolve();
246+
}
247+
});
248+
});
249+
});
250+
251+
afterAll(() => uWS.us_listen_socket_close(appListenSocket));
252+
253+
for (const audit of serverAudits({ url: () => url, fetchFn: fetch })) {
254+
it(audit.name, async () => {
255+
const result = await audit.fn();
256+
if (result.status !== 'ok') {
257+
throw result.reason;
258+
}
259+
});
260+
}
261+
});

src/use/uWebSockets.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import type { HttpRequest, HttpResponse } from 'uWebSockets.js';
2+
import {
3+
createHandler as createRawHandler,
4+
HandlerOptions as RawHandlerOptions,
5+
OperationContext,
6+
} from '../handler';
7+
8+
/**
9+
* The context in the request for the handler.
10+
*
11+
* @category Server/uWebSockets
12+
*/
13+
export interface RequestContext {
14+
res: HttpResponse;
15+
}
16+
17+
/**
18+
* Handler options when using the http adapter.
19+
*
20+
* @category Server/uWebSockets
21+
*/
22+
export type HandlerOptions<Context extends OperationContext = undefined> =
23+
RawHandlerOptions<HttpRequest, RequestContext, Context>;
24+
25+
/**
26+
* Create a GraphQL over HTTP spec compliant request handler for
27+
* the Node environment [uWebSockets.js module](https://github.com/uNetworking/uWebSockets.js/).
28+
*
29+
* ```js
30+
* import uWS from 'uWebSockets.js'; // yarn add uWebSockets.js@uNetworking/uWebSockets.js#<version>
31+
* import { createHandler } from 'graphql-http/lib/use/uWebSockets';
32+
* import { schema } from './my-graphql-schema';
33+
*
34+
* uWS
35+
* .App()
36+
* .any('/graphql', createHandler({ schema }))
37+
* .listen(4000, () => {
38+
* console.log('Listening to port 4000');
39+
* });
40+
* ```
41+
*
42+
* @category Server/uWebSockets
43+
*/
44+
export function createHandler<Context extends OperationContext = undefined>(
45+
options: HandlerOptions<Context>,
46+
): (res: HttpResponse, req: HttpRequest) => Promise<void> {
47+
const handle = createRawHandler(options);
48+
return async function requestListener(res, req) {
49+
let aborted = false;
50+
res.onAborted(() => (aborted = true));
51+
try {
52+
let url = req.getUrl();
53+
const query = req.getQuery();
54+
if (query) {
55+
url += '?' + query;
56+
}
57+
const [body, init] = await handle({
58+
url,
59+
method: req.getMethod().toUpperCase(),
60+
headers: { get: (key) => req.getHeader(key) },
61+
body: () =>
62+
new Promise<string>((resolve) => {
63+
let body = '';
64+
if (aborted) {
65+
resolve(body);
66+
} else {
67+
res.onData((chunk, isLast) => {
68+
body += Buffer.from(chunk, 0, chunk.byteLength).toString();
69+
if (isLast) {
70+
resolve(body);
71+
}
72+
});
73+
}
74+
}),
75+
raw: req,
76+
context: { res },
77+
});
78+
if (!aborted) {
79+
res.writeStatus(`${init.status} ${init.statusText}`);
80+
for (const [key, val] of Object.entries(init.headers || {})) {
81+
res.writeHeader(key, val);
82+
}
83+
if (body) {
84+
res.end(body);
85+
} else {
86+
res.endWithoutBody();
87+
}
88+
}
89+
} catch (err) {
90+
// The handler shouldnt throw errors.
91+
// If you wish to handle them differently, consider implementing your own request handler.
92+
console.error(
93+
'Internal error occurred during request handling. ' +
94+
'Please check your implementation.',
95+
err,
96+
);
97+
if (!aborted) {
98+
res.writeStatus('500 Internal Server Error').endWithoutBody();
99+
}
100+
}
101+
};
102+
}

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7650,6 +7650,7 @@ __metadata:
76507650
typedoc: ^0.24.7
76517651
typedoc-plugin-markdown: ^3.15.3
76527652
typescript: ^5.0.4
7653+
uWebSockets.js: "uNetworking/uWebSockets.js#v20.30.0"
76537654
peerDependencies:
76547655
graphql: ">=0.11 <=16"
76557656
languageName: unknown
@@ -13246,6 +13247,13 @@ __metadata:
1324613247
languageName: node
1324713248
linkType: hard
1324813249

13250+
"uWebSockets.js@uNetworking/uWebSockets.js#v20.30.0":
13251+
version: 20.30.0
13252+
resolution: "uWebSockets.js@https://github.com/uNetworking/uWebSockets.js.git#commit=d39d4181daf5b670d44cbc1b18f8c28c85fd4142"
13253+
checksum: e8584a9aa00ea378647fe4530631ad10240d61bb7fa70aff4f44cab3b066432e404358d4d80a77c5c56e25029ae6dc6f4de13673da89c38cb4d8228acde74187
13254+
languageName: node
13255+
linkType: hard
13256+
1324913257
"ua-parser-js@npm:^0.7.30":
1325013258
version: 0.7.35
1325113259
resolution: "ua-parser-js@npm:0.7.35"

0 commit comments

Comments
 (0)