Skip to content

Commit

Permalink
fix(handler): Correct typings and support for http2
Browse files Browse the repository at this point in the history
  • Loading branch information
enisdenjo committed Dec 6, 2022
1 parent 8a2bbdd commit 08d6ca3
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 21 deletions.
22 changes: 18 additions & 4 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ graphql-sse

- [ExecutionContext](README.md#executioncontext)
- [Handler](README.md#handler)
- [NodeRequest](README.md#noderequest)
- [NodeResponse](README.md#noderesponse)
- [OperationResult](README.md#operationresult)
- [StreamData](README.md#streamdata)
- [StreamDataForID](README.md#streamdataforid)
Expand Down Expand Up @@ -211,8 +213,8 @@ ___

| Name | Type |
| :------ | :------ |
| `Request` | extends `IncomingMessage` = `IncomingMessage` |
| `Response` | extends `ServerResponse` = `ServerResponse` |
| `Request` | extends [`NodeRequest`](README.md#noderequest) = [`NodeRequest`](README.md#noderequest) |
| `Response` | extends [`NodeResponse`](README.md#noderesponse) = [`NodeResponse`](README.md#noderesponse) |

#### Type declaration

Expand Down Expand Up @@ -273,6 +275,18 @@ beware that the `body` argument will be consumed **only** if it's an object.

___

### NodeRequest

Ƭ **NodeRequest**: `IncomingMessage` \| `Http2ServerRequest`

___

### NodeResponse

Ƭ **NodeResponse**: `ServerResponse` \| `Http2ServerResponse`

___

### OperationResult

Ƭ **OperationResult**: `Promise`<`AsyncGenerator`<[`ExecutionResult`](interfaces/ExecutionResult.md) \| [`ExecutionPatchResult`](interfaces/ExecutionPatchResult.md)\> \| `AsyncIterable`<[`ExecutionResult`](interfaces/ExecutionResult.md) \| [`ExecutionPatchResult`](interfaces/ExecutionPatchResult.md)\> \| [`ExecutionResult`](interfaces/ExecutionResult.md)\> \| `AsyncGenerator`<[`ExecutionResult`](interfaces/ExecutionResult.md) \| [`ExecutionPatchResult`](interfaces/ExecutionPatchResult.md)\> \| `AsyncIterable`<[`ExecutionResult`](interfaces/ExecutionResult.md) \| [`ExecutionPatchResult`](interfaces/ExecutionPatchResult.md)\> \| [`ExecutionResult`](interfaces/ExecutionResult.md)
Expand All @@ -292,8 +306,8 @@ Read more about the Protocol in the PROTOCOL.md documentation file.

| Name | Type |
| :------ | :------ |
| `Request` | extends `IncomingMessage`<`Request`\> = `IncomingMessage` |
| `Response` | extends `ServerResponse`<`IncomingMessage`, `Response`\> = `ServerResponse`<`IncomingMessage`\> |
| `Request` | extends [`NodeRequest`](README.md#noderequest) = [`NodeRequest`](README.md#noderequest) |
| `Response` | extends [`NodeResponse`](README.md#noderesponse) = [`NodeResponse`](README.md#noderesponse) |

#### Parameters

Expand Down
4 changes: 2 additions & 2 deletions docs/interfaces/HandlerOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

| Name | Type |
| :------ | :------ |
| `Request` | extends `IncomingMessage` = `IncomingMessage` |
| `Response` | extends `ServerResponse` = `ServerResponse` |
| `Request` | extends [`NodeRequest`](../README.md#noderequest) = [`NodeRequest`](../README.md#noderequest) |
| `Response` | extends [`NodeResponse`](../README.md#noderesponse) = [`NodeResponse`](../README.md#noderesponse) |

## Table of contents

Expand Down
18 changes: 18 additions & 0 deletions src/__tests__/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { eventStream } from './utils/eventStream';
import { createClient, createHandler } from '../index';
import { TOKEN_HEADER_KEY, TOKEN_QUERY_KEY } from '../common';
import http from 'http';
import http2 from 'http2';
import { schema } from './fixtures/simple';
import fetch from 'node-fetch';
import express from 'express';
Expand Down Expand Up @@ -406,6 +407,23 @@ describe('distinct connections mode', () => {
});
});

describe('http2', () => {
// ts-only-test
it.skip('should work as advertised in the readme', async () => {
const handler = createHandler({ schema });
http2.createSecureServer(
{
key: 'localhost-privkey.pem',
cert: 'localhost-cert.pem',
},
(req, res) => {
if (req.url.startsWith('/graphql/stream')) return handler(req, res);
return res.writeHead(404).end();
},
);
});
});

describe('express', () => {
it('should work as advertised in the readme', async () => {
const app = express();
Expand Down
4 changes: 3 additions & 1 deletion src/__tests__/utils/tserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ export interface TServer {
}

export async function startTServer(
options: Partial<HandlerOptions> = {},
options: Partial<
HandlerOptions<http.IncomingMessage, http.ServerResponse>
> = {},
): Promise<TServer> {
const emitter = new EventEmitter();

Expand Down
42 changes: 28 additions & 14 deletions src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import type { IncomingMessage, ServerResponse } from 'http';
import type { Http2ServerRequest, Http2ServerResponse } from 'http2';
import {
ExecutionArgs,
getOperationAST,
Expand All @@ -27,6 +28,16 @@ import {
TOKEN_QUERY_KEY,
} from './common';

/**
* @category Server
*/
export type NodeRequest = IncomingMessage | Http2ServerRequest;

/**
* @category Server
*/
export type NodeResponse = ServerResponse | Http2ServerResponse;

/**
* A concrete GraphQL execution context value type.
*
Expand Down Expand Up @@ -60,8 +71,8 @@ export type OperationResult =

/** @category Server */
export interface HandlerOptions<
Request extends IncomingMessage = IncomingMessage,
Response extends ServerResponse = ServerResponse,
Request extends NodeRequest = NodeRequest,
Response extends NodeResponse = NodeResponse,
> {
/**
* The GraphQL schema on which the operations will
Expand Down Expand Up @@ -280,13 +291,13 @@ export interface HandlerOptions<
* @category Server
*/
export type Handler<
Request extends IncomingMessage = IncomingMessage,
Response extends ServerResponse = ServerResponse,
Request extends NodeRequest = NodeRequest,
Response extends NodeResponse = NodeResponse,
> = (req: Request, res: Response, body?: unknown) => Promise<void>;

interface Stream<
Request extends IncomingMessage = IncomingMessage,
Response extends ServerResponse = ServerResponse,
Request extends NodeRequest = NodeRequest,
Response extends NodeResponse = NodeResponse,
> {
/**
* Does the stream have an open connection to some client.
Expand Down Expand Up @@ -327,8 +338,8 @@ interface Stream<
* @category Server
*/
export function createHandler<
Request extends IncomingMessage = IncomingMessage,
Response extends ServerResponse = ServerResponse,
Request extends NodeRequest = NodeRequest,
Response extends NodeResponse = NodeResponse,
>(options: HandlerOptions<Request, Response>): Handler<Request, Response> {
const {
schema,
Expand Down Expand Up @@ -363,7 +374,7 @@ export function createHandler<
onDisconnect,
} = options;

const streams: Record<string, Stream> = {};
const streams: Record<string, Stream<Request, Response>> = {};

function createStream(token: string | null): Stream<Request, Response> {
let request: Request | null = null,
Expand All @@ -376,10 +387,11 @@ export function createHandler<
AsyncGenerator<unknown> | AsyncIterable<unknown> | null
> = {};

function write(msg: unknown) {
function write(msg: string) {
return new Promise<boolean>((resolve, reject) => {
if (disposed || !response || !response.writable) return resolve(false);
response.write(msg, (err) => {
// @ts-expect-error both ServerResponse and Http2ServerResponse have this write signature
response.write(msg, 'utf-8', (err) => {
if (err) return reject(err);
resolve(true);
});
Expand Down Expand Up @@ -444,7 +456,7 @@ export function createHandler<
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('X-Accel-Buffering', 'no');
if (req.httpVersionMajor < 2) res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
if ('flushHeaders' in res) res.flushHeaders();

// write an empty message because some browsers (like Firefox and Safari)
// dont accept the header flush
Expand Down Expand Up @@ -595,6 +607,7 @@ export function createHandler<
? 'application/json; charset=utf-8'
: 'application/graphql+json; charset=utf-8',
})
// @ts-expect-error both ServerResponse and Http2ServerResponse have this write signature
.write(JSON.stringify({ errors: validationErrs }));
res.end();
return;
Expand Down Expand Up @@ -693,6 +706,7 @@ export function createHandler<
streams[token] = createStream(token);
res
.writeHead(201, { 'Content-Type': 'text/plain; charset=utf-8' })
// @ts-expect-error both ServerResponse and Http2ServerResponse have this write signature
.write(token);
res.end();
return;
Expand Down Expand Up @@ -721,7 +735,7 @@ export function createHandler<
return;
} else if (req.method !== 'GET' && req.method !== 'POST') {
// only POSTs and GETs are accepted at this point
res.writeHead(405, undefined, { Allow: 'GET, POST, PUT, DELETE' }).end();
res.writeHead(405, { Allow: 'GET, POST, PUT, DELETE' }).end();
return;
} else if (!stream) {
// for all other requests, streams must exist to attach the result onto
Expand Down Expand Up @@ -793,7 +807,7 @@ export function createHandler<
};
}

async function parseReq<Request extends IncomingMessage>(
async function parseReq<Request extends NodeRequest>(
req: Request,
body: unknown,
): Promise<RequestParams> {
Expand Down

0 comments on commit 08d6ca3

Please sign in to comment.