diff --git a/docs/README.md b/docs/README.md index 460b91b6..c9f7547a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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) @@ -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 @@ -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) @@ -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 diff --git a/docs/interfaces/HandlerOptions.md b/docs/interfaces/HandlerOptions.md index a680f998..58b7297f 100644 --- a/docs/interfaces/HandlerOptions.md +++ b/docs/interfaces/HandlerOptions.md @@ -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 diff --git a/src/__tests__/handler.ts b/src/__tests__/handler.ts index 93ce387c..fbb4cd68 100644 --- a/src/__tests__/handler.ts +++ b/src/__tests__/handler.ts @@ -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'; @@ -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(); diff --git a/src/__tests__/utils/tserver.ts b/src/__tests__/utils/tserver.ts index e91b4979..3f7f8219 100644 --- a/src/__tests__/utils/tserver.ts +++ b/src/__tests__/utils/tserver.ts @@ -46,7 +46,9 @@ export interface TServer { } export async function startTServer( - options: Partial = {}, + options: Partial< + HandlerOptions + > = {}, ): Promise { const emitter = new EventEmitter(); diff --git a/src/handler.ts b/src/handler.ts index ffd006c7..0ca4f122 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -5,6 +5,7 @@ */ import type { IncomingMessage, ServerResponse } from 'http'; +import type { Http2ServerRequest, Http2ServerResponse } from 'http2'; import { ExecutionArgs, getOperationAST, @@ -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. * @@ -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 @@ -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; 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. @@ -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): Handler { const { schema, @@ -363,7 +374,7 @@ export function createHandler< onDisconnect, } = options; - const streams: Record = {}; + const streams: Record> = {}; function createStream(token: string | null): Stream { let request: Request | null = null, @@ -376,10 +387,11 @@ export function createHandler< AsyncGenerator | AsyncIterable | null > = {}; - function write(msg: unknown) { + function write(msg: string) { return new Promise((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); }); @@ -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 @@ -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; @@ -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; @@ -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 @@ -793,7 +807,7 @@ export function createHandler< }; } -async function parseReq( +async function parseReq( req: Request, body: unknown, ): Promise {