Skip to content

Commit

Permalink
feat: Use uWebSockets.js (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
enisdenjo committed Jun 1, 2023
1 parent aa63493 commit 5089f46
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 1 deletion.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,21 @@ app.listen({ port: 4000 });
console.log('Listening to port 4000');
```

##### With [`uWebSockets.js`](https://github.com/uNetworking/uWebSockets.js)

```js
import uWS from 'uWebSockets.js'; // yarn add uWebSockets.js@uNetworking/uWebSockets.js#<version>
import { createHandler } from 'graphql-http/lib/use/uWebSockets';
import { schema } from './previous-step';

uWS
.App()
.any('/graphql', createHandler({ schema }))
.listen(4000, () => {
console.log('Listening to port 4000');
});
```

##### With [`Deno`](https://deno.land/)

```ts
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ graphql-http
- [use/http2](modules/use_http2.md)
- [use/koa](modules/use_koa.md)
- [use/node](modules/use_node.md)
- [use/uWebSockets](modules/use_uWebSockets.md)
19 changes: 19 additions & 0 deletions docs/interfaces/use_uWebSockets.RequestContext.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[graphql-http](../README.md) / [use/uWebSockets](../modules/use_uWebSockets.md) / RequestContext

# Interface: RequestContext

[use/uWebSockets](../modules/use_uWebSockets.md).RequestContext

The context in the request for the handler.

## Table of contents

### Properties

- [res](use_uWebSockets.RequestContext.md#res)

## Properties

### res

**res**: `HttpResponse`
82 changes: 82 additions & 0 deletions docs/modules/use_uWebSockets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
[graphql-http](../README.md) / use/uWebSockets

# Module: use/uWebSockets

## Table of contents

### Interfaces

- [RequestContext](../interfaces/use_uWebSockets.RequestContext.md)

### Type Aliases

- [HandlerOptions](use_uWebSockets.md#handleroptions)

### Functions

- [createHandler](use_uWebSockets.md#createhandler)

## Server/uWebSockets

### HandlerOptions

Ƭ **HandlerOptions**<`Context`\>: [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`HttpRequest`, [`RequestContext`](../interfaces/use_uWebSockets.RequestContext.md), `Context`\>

Handler options when using the http adapter.

#### Type parameters

| Name | Type |
| :------ | :------ |
| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` |

___

### createHandler

**createHandler**<`Context`\>(`options`): (`res`: `HttpResponse`, `req`: `HttpRequest`) => `Promise`<`void`\>

Create a GraphQL over HTTP spec compliant request handler for
the Node environment [uWebSockets.js module](https://github.com/uNetworking/uWebSockets.js/).

```js
import uWS from 'uWebSockets.js'; // yarn add uWebSockets.js@uNetworking/uWebSockets.js#<version>
import { createHandler } from 'graphql-http/lib/use/uWebSockets';
import { schema } from './my-graphql-schema';

uWS
.App()
.any('/graphql', createHandler({ schema }))
.listen(4000, () => {
console.log('Listening to port 4000');
});
```

#### Type parameters

| Name | Type |
| :------ | :------ |
| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` |

#### Parameters

| Name | Type |
| :------ | :------ |
| `options` | [`HandlerOptions`](use_uWebSockets.md#handleroptions)<`Context`\> |

#### Returns

`fn`

▸ (`res`, `req`): `Promise`<`void`\>

##### Parameters

| Name | Type |
| :------ | :------ |
| `res` | `HttpResponse` |
| `req` | `HttpRequest` |

##### Returns

`Promise`<`void`\>
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
"require": "./lib/use/koa.js",
"import": "./lib/use/koa.mjs"
},
"./lib/use/uWebSockets": {
"types": "./lib/use/uWebSockets.d.ts",
"require": "./lib/use/uWebSockets.js",
"import": "./lib/use/uWebSockets.mjs"
},
"./package.json": "./package.json"
},
"types": "lib/index.d.ts",
Expand Down Expand Up @@ -147,7 +152,8 @@
"tslib": "^2.5.2",
"typedoc": "^0.24.7",
"typedoc-plugin-markdown": "^3.15.3",
"typescript": "^5.0.4"
"typescript": "^5.0.4",
"uWebSockets.js": "uNetworking/uWebSockets.js#v20.30.0"
},
"resolutions": {
"npm/libnpmversion": "^3.0.6"
Expand Down
40 changes: 40 additions & 0 deletions src/__tests__/use.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import fastify from 'fastify';
import Koa from 'koa';
import mount from 'koa-mount';
import { createServerAdapter } from '@whatwg-node/server';
import uWS from 'uWebSockets.js';
import { startDisposableServer } from './utils/tserver';
import { serverAudits } from '../audits';
import { schema } from './fixtures/simple';
Expand All @@ -14,6 +15,7 @@ import { createHandler as createExpressHandler } from '../use/express';
import { createHandler as createFastifyHandler } from '../use/fastify';
import { createHandler as createFetchHandler } from '../use/fetch';
import { createHandler as createKoaHandler } from '../use/koa';
import { createHandler as createUWSHandler } from '../use/uWebSockets';

describe('http', () => {
const [url, , dispose] = startDisposableServer(
Expand Down Expand Up @@ -219,3 +221,41 @@ describe('koa', () => {
await dispose();
});
});

describe('uWebSockets.js', () => {
let url = '';
let appListenSocket: uWS.us_listen_socket;
beforeAll(async () => {
// get available port by starting a temporary server
const [availableUrl, availablePort, dispose] = startDisposableServer(
http.createServer(),
);
await dispose();
url = availableUrl;

new Promise<void>((resolve, reject) => {
uWS
.App()
.any('/', createUWSHandler({ schema }))
.listen(availablePort, (listenSocket) => {
if (!listenSocket) {
reject(new Error('Unavailable uWS listen socket'));
} else {
appListenSocket = listenSocket;
resolve();
}
});
});
});

afterAll(() => uWS.us_listen_socket_close(appListenSocket));

for (const audit of serverAudits({ url: () => url, fetchFn: fetch })) {
it(audit.name, async () => {
const result = await audit.fn();
if (result.status !== 'ok') {
throw result.reason;
}
});
}
});
102 changes: 102 additions & 0 deletions src/use/uWebSockets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type { HttpRequest, HttpResponse } from 'uWebSockets.js';
import {
createHandler as createRawHandler,
HandlerOptions as RawHandlerOptions,
OperationContext,
} from '../handler';

/**
* The context in the request for the handler.
*
* @category Server/uWebSockets
*/
export interface RequestContext {
res: HttpResponse;
}

/**
* Handler options when using the http adapter.
*
* @category Server/uWebSockets
*/
export type HandlerOptions<Context extends OperationContext = undefined> =
RawHandlerOptions<HttpRequest, RequestContext, Context>;

/**
* Create a GraphQL over HTTP spec compliant request handler for
* the Node environment [uWebSockets.js module](https://github.com/uNetworking/uWebSockets.js/).
*
* ```js
* import uWS from 'uWebSockets.js'; // yarn add uWebSockets.js@uNetworking/uWebSockets.js#<version>
* import { createHandler } from 'graphql-http/lib/use/uWebSockets';
* import { schema } from './my-graphql-schema';
*
* uWS
* .App()
* .any('/graphql', createHandler({ schema }))
* .listen(4000, () => {
* console.log('Listening to port 4000');
* });
* ```
*
* @category Server/uWebSockets
*/
export function createHandler<Context extends OperationContext = undefined>(
options: HandlerOptions<Context>,
): (res: HttpResponse, req: HttpRequest) => Promise<void> {
const handle = createRawHandler(options);
return async function requestListener(res, req) {
let aborted = false;
res.onAborted(() => (aborted = true));
try {
let url = req.getUrl();
const query = req.getQuery();
if (query) {
url += '?' + query;
}
const [body, init] = await handle({
url,
method: req.getMethod().toUpperCase(),
headers: { get: (key) => req.getHeader(key) },
body: () =>
new Promise<string>((resolve) => {
let body = '';
if (aborted) {
resolve(body);
} else {
res.onData((chunk, isLast) => {
body += Buffer.from(chunk, 0, chunk.byteLength).toString();
if (isLast) {
resolve(body);
}
});
}
}),
raw: req,
context: { res },
});
if (!aborted) {
res.writeStatus(`${init.status} ${init.statusText}`);
for (const [key, val] of Object.entries(init.headers || {})) {
res.writeHeader(key, val);
}
if (body) {
res.end(body);
} else {
res.endWithoutBody();
}
}
} catch (err) {
// The handler shouldnt throw errors.
// If you wish to handle them differently, consider implementing your own request handler.
console.error(
'Internal error occurred during request handling. ' +
'Please check your implementation.',
err,
);
if (!aborted) {
res.writeStatus('500 Internal Server Error').endWithoutBody();
}
}
};
}
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7650,6 +7650,7 @@ __metadata:
typedoc: ^0.24.7
typedoc-plugin-markdown: ^3.15.3
typescript: ^5.0.4
uWebSockets.js: "uNetworking/uWebSockets.js#v20.30.0"
peerDependencies:
graphql: ">=0.11 <=16"
languageName: unknown
Expand Down Expand Up @@ -13246,6 +13247,13 @@ __metadata:
languageName: node
linkType: hard

"uWebSockets.js@uNetworking/uWebSockets.js#v20.30.0":
version: 20.30.0
resolution: "uWebSockets.js@https://github.com/uNetworking/uWebSockets.js.git#commit=d39d4181daf5b670d44cbc1b18f8c28c85fd4142"
checksum: e8584a9aa00ea378647fe4530631ad10240d61bb7fa70aff4f44cab3b066432e404358d4d80a77c5c56e25029ae6dc6f4de13673da89c38cb4d8228acde74187
languageName: node
linkType: hard

"ua-parser-js@npm:^0.7.30":
version: 0.7.35
resolution: "ua-parser-js@npm:0.7.35"
Expand Down

0 comments on commit 5089f46

Please sign in to comment.