Skip to content

Commit

Permalink
fix(handler): Support generics for requests and responses
Browse files Browse the repository at this point in the history
  • Loading branch information
enisdenjo committed Nov 11, 2021
1 parent e10f15e commit 9ab10c0
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 67 deletions.
31 changes: 22 additions & 9 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,14 @@ ___

### Handler

Ƭ **Handler**: (`req`: `IncomingMessage`, `res`: `ServerResponse`, `body?`: `unknown`) => `Promise`<`void`\>
Ƭ **Handler**<`Request`, `Response`\>: (`req`: `Request`, `res`: `Response`, `body?`: `unknown`) => `Promise`<`void`\>

#### Type parameters

| Name | Type |
| :------ | :------ |
| `Request` | extends `IncomingMessage``IncomingMessage` |
| `Response` | extends `ServerResponse``ServerResponse` |

#### Type declaration

Expand Down Expand Up @@ -207,16 +214,15 @@ http.createServer(async (req, res) => {

Note that some libraries, like fastify, parse the body before reaching the handler.
In such cases all request 'data' events are already consumed. Use this `body` argument
too pass in the read body and avoid listening for the 'data' events internally.

**`category`** Server
too pass in the read body and avoid listening for the 'data' events internally. Do
beware that the `body` argument will be consumed **only** if it's an object.

##### Parameters

| Name | Type |
| :------ | :------ |
| `req` | `IncomingMessage` |
| `res` | `ServerResponse` |
| `req` | `Request` |
| `res` | `Response` |
| `body?` | `unknown` |

##### Returns
Expand All @@ -233,19 +239,26 @@ ___

### createHandler

**createHandler**(`options`): [`Handler`](README.md#handler)
**createHandler**<`Request`, `Response`\>(`options`): [`Handler`](README.md#handler)<`Request`, `Response`\>

Makes a Protocol complient HTTP GraphQL server handler. The handler can
be used with your favourite server library.

Read more about the Protocol in the PROTOCOL.md documentation file.

#### Type parameters

| Name | Type |
| :------ | :------ |
| `Request` | extends `IncomingMessage`<`Request`\>`IncomingMessage` |
| `Response` | extends `ServerResponse`<`Response`\>`ServerResponse` |

#### Parameters

| Name | Type |
| :------ | :------ |
| `options` | [`HandlerOptions`](interfaces/HandlerOptions.md) |
| `options` | [`HandlerOptions`](interfaces/HandlerOptions.md)<`Request`, `Response`\> |

#### Returns

[`Handler`](README.md#handler)
[`Handler`](README.md#handler)<`Request`, `Response`\>
37 changes: 22 additions & 15 deletions docs/interfaces/HandlerOptions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
[graphql-sse](../README.md) / HandlerOptions

# Interface: HandlerOptions
# Interface: HandlerOptions<Request, Response\>

## Type parameters

| Name | Type |
| :------ | :------ |
| `Request` | extends `IncomingMessage``IncomingMessage` |
| `Response` | extends `ServerResponse``ServerResponse` |

## Table of contents

Expand All @@ -27,7 +34,7 @@

### context

`Optional` **context**: [`ExecutionContext`](../README.md#executioncontext) \| (`req`: `IncomingMessage`, `args`: `ExecutionArgs`) => [`ExecutionContext`](../README.md#executioncontext) \| `Promise`<[`ExecutionContext`](../README.md#executioncontext)\>
`Optional` **context**: [`ExecutionContext`](../README.md#executioncontext) \| (`req`: `Request`, `args`: `ExecutionArgs`) => [`ExecutionContext`](../README.md#executioncontext) \| `Promise`<[`ExecutionContext`](../README.md#executioncontext)\>

A value which is provided to every resolver and holds
important contextual information like the currently
Expand All @@ -42,7 +49,7 @@ ___

### schema

`Optional` **schema**: `GraphQLSchema` \| (`req`: `IncomingMessage`, `args`: `Omit`<`ExecutionArgs`, ``"schema"``\>) => `GraphQLSchema` \| `Promise`<`GraphQLSchema`\>
`Optional` **schema**: `GraphQLSchema` \| (`req`: `Request`, `args`: `Omit`<`ExecutionArgs`, ``"schema"``\>) => `GraphQLSchema` \| `Promise`<`GraphQLSchema`\>

The GraphQL schema on which the operations will
be executed and validated against.
Expand Down Expand Up @@ -120,8 +127,8 @@ further execution.

| Name | Type |
| :------ | :------ |
| `req` | `IncomingMessage` |
| `res` | `ServerResponse` |
| `req` | `Request` |
| `res` | `Response` |

#### Returns

Expand Down Expand Up @@ -166,7 +173,7 @@ request.

| Name | Type |
| :------ | :------ |
| `req` | `IncomingMessage` |
| `req` | `Request` |
| `args` | `ExecutionArgs` |

#### Returns
Expand All @@ -186,7 +193,7 @@ accepted, and after all pending messages have been flushed.

| Name | Type |
| :------ | :------ |
| `req` | `IncomingMessage` |
| `req` | `Request` |

#### Returns

Expand All @@ -210,8 +217,8 @@ further execution.

| Name | Type |
| :------ | :------ |
| `req` | `IncomingMessage` |
| `res` | `ServerResponse` |
| `req` | `Request` |
| `res` | `Response` |

#### Returns

Expand All @@ -230,7 +237,7 @@ accepting the stream.

| Name | Type |
| :------ | :------ |
| `req` | `IncomingMessage` |
| `req` | `Request` |

#### Returns

Expand Down Expand Up @@ -258,7 +265,7 @@ request.

| Name | Type |
| :------ | :------ |
| `req` | `IncomingMessage` |
| `req` | `Request` |
| `args` | `ExecutionArgs` |
| `result` | [`ExecutionResult`](ExecutionResult.md)<`Record`<`string`, `unknown`\>, `Record`<`string`, `unknown`\>\> \| [`ExecutionPatchResult`](ExecutionPatchResult.md)<`unknown`, `Record`<`string`, `unknown`\>\> |

Expand Down Expand Up @@ -295,8 +302,8 @@ request.

| Name | Type |
| :------ | :------ |
| `req` | `IncomingMessage` |
| `res` | `ServerResponse` |
| `req` | `Request` |
| `res` | `Response` |
| `args` | `ExecutionArgs` |
| `result` | [`OperationResult`](../README.md#operationresult) |

Expand Down Expand Up @@ -332,8 +339,8 @@ and supply the appropriate GraphQL operation execution arguments.

| Name | Type |
| :------ | :------ |
| `req` | `IncomingMessage` |
| `res` | `ServerResponse` |
| `req` | `Request` |
| `res` | `Response` |
| `params` | [`RequestParams`](RequestParams.md) |

#### Returns
Expand Down
85 changes: 42 additions & 43 deletions src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ export type OperationResult =
| ExecutionResult;

/** @category Server */
export interface HandlerOptions {
export interface HandlerOptions<
Request extends IncomingMessage = IncomingMessage,
Response extends ServerResponse = ServerResponse,
> {
/**
* The GraphQL schema on which the operations will
* be executed and validated against.
Expand All @@ -73,7 +76,7 @@ export interface HandlerOptions {
schema?:
| GraphQLSchema
| ((
req: IncomingMessage,
req: Request,
args: Omit<ExecutionArgs, 'schema'>,
) => Promise<GraphQLSchema> | GraphQLSchema);
/**
Expand All @@ -89,7 +92,7 @@ export interface HandlerOptions {
context?:
| ExecutionContext
| ((
req: IncomingMessage,
req: Request,
args: ExecutionArgs,
) => Promise<ExecutionContext> | ExecutionContext);
/**
Expand Down Expand Up @@ -121,8 +124,8 @@ export interface HandlerOptions {
* @default 'req.headers["x-graphql-event-stream-token"] || req.url.searchParams["token"] || generateRandomUUID()' // https://gist.github.com/jed/982883
*/
authenticate?: (
req: IncomingMessage,
res: ServerResponse,
req: Request,
res: Response,
) => Promise<string | undefined | void> | string | undefined | void;
/**
* Called when a new event stream is connecting BEFORE it is accepted.
Expand All @@ -133,15 +136,12 @@ export interface HandlerOptions {
* you should do so using the provided `res` argument which will stop
* further execution.
*/
onConnecting?: (
req: IncomingMessage,
res: ServerResponse,
) => Promise<void> | void;
onConnecting?: (req: Request, res: Response) => Promise<void> | void;
/**
* Called when a new event stream has been succesfully connected and
* accepted, and after all pending messages have been flushed.
*/
onConnected?: (req: IncomingMessage) => Promise<void> | void;
onConnected?: (req: Request) => Promise<void> | void;
/**
* The subscribe callback executed right after processing the request
* before proceeding with the GraphQL operation execution.
Expand All @@ -162,8 +162,8 @@ export interface HandlerOptions {
* and supply the appropriate GraphQL operation execution arguments.
*/
onSubscribe?: (
req: IncomingMessage,
res: ServerResponse,
req: Request,
res: Response,
params: RequestParams,
) => Promise<ExecutionArgs | void> | ExecutionArgs | void;
/**
Expand All @@ -187,8 +187,8 @@ export interface HandlerOptions {
* request.
*/
onOperation?: (
req: IncomingMessage,
res: ServerResponse,
req: Request,
res: Response,
args: ExecutionArgs,
result: OperationResult,
) => Promise<OperationResult | void> | OperationResult | void;
Expand All @@ -206,7 +206,7 @@ export interface HandlerOptions {
* request.
*/
onNext?: (
req: IncomingMessage,
req: Request,
args: ExecutionArgs,
result: ExecutionResult | ExecutionPatchResult,
) =>
Expand All @@ -225,15 +225,12 @@ export interface HandlerOptions {
* First argument, the request, is always the GraphQL operation
* request.
*/
onComplete?: (
req: IncomingMessage,
args: ExecutionArgs,
) => Promise<void> | void;
onComplete?: (req: Request, args: ExecutionArgs) => Promise<void> | void;
/**
* Called when an event stream has disconnected right before the
* accepting the stream.
*/
onDisconnect?: (req: IncomingMessage) => Promise<void> | void;
onDisconnect?: (req: Request) => Promise<void> | void;
}

/**
Expand Down Expand Up @@ -273,17 +270,20 @@ export interface HandlerOptions {
*
* Note that some libraries, like fastify, parse the body before reaching the handler.
* In such cases all request 'data' events are already consumed. Use this `body` argument
* too pass in the read body and avoid listening for the 'data' events internally.
* too pass in the read body and avoid listening for the 'data' events internally. Do
* beware that the `body` argument will be consumed **only** if it's an object.
*
* @category Server
*/
export type Handler = (
req: IncomingMessage,
res: ServerResponse,
body?: unknown,
) => Promise<void>;

interface Stream {
export type Handler<
Request extends IncomingMessage = IncomingMessage,
Response extends ServerResponse = ServerResponse,
> = (req: Request, res: Response, body?: unknown) => Promise<void>;

interface Stream<
Request extends IncomingMessage = IncomingMessage,
Response extends ServerResponse = ServerResponse,
> {
/**
* Does the stream have an open connection to some client.
*/
Expand All @@ -298,12 +298,12 @@ interface Stream {
/**
* Use this connection for streaming.
*/
use(req: IncomingMessage, res: ServerResponse): Promise<void>;
use(req: Request, res: Response): Promise<void>;
/**
* Stream from provided execution result to used connection.
*/
from(
operationReq: IncomingMessage, // holding the operation request (not necessarily the event stream)
operationReq: Request, // holding the operation request (not necessarily the event stream)
args: ExecutionArgs,
result:
| AsyncGenerator<ExecutionResult | ExecutionPatchResult>
Expand All @@ -322,7 +322,10 @@ interface Stream {
*
* @category Server
*/
export function createHandler(options: HandlerOptions): Handler {
export function createHandler<
Request extends IncomingMessage = IncomingMessage,
Response extends ServerResponse = ServerResponse,
>(options: HandlerOptions<Request, Response>): Handler<Request, Response> {
const {
schema,
context,
Expand Down Expand Up @@ -359,9 +362,9 @@ export function createHandler(options: HandlerOptions): Handler {

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

function createStream(token: string | null): Stream {
let request: IncomingMessage | null = null,
response: ServerResponse | null = null,
function createStream(token: string | null): Stream<Request, Response> {
let request: Request | null = null,
response: Response | null = null,
pinger: ReturnType<typeof setInterval>,
disposed = false;
const pendingMsgs: string[] = [];
Expand Down Expand Up @@ -502,8 +505,8 @@ export function createHandler(options: HandlerOptions): Handler {
}

async function prepare(
req: IncomingMessage,
res: ServerResponse,
req: Request,
res: Response,
params: RequestParams,
): Promise<[args: ExecutionArgs, perform: () => OperationResult] | void> {
let args: ExecutionArgs, operation: OperationTypeNode;
Expand Down Expand Up @@ -603,11 +606,7 @@ export function createHandler(options: HandlerOptions): Handler {
];
}

return async function handler(
req: IncomingMessage,
res: ServerResponse,
body: unknown,
) {
return async function handler(req: Request, res: Response, body: unknown) {
// authenticate first and acquire unique identification token
const token = await authenticate(req, res);
if (res.writableEnded) return;
Expand Down Expand Up @@ -756,8 +755,8 @@ export function createHandler(options: HandlerOptions): Handler {
};
}

async function parseReq(
req: IncomingMessage,
async function parseReq<Request extends IncomingMessage = IncomingMessage>(
req: Request,
body: unknown,
): Promise<RequestParams> {
const params: Partial<RequestParams> = {};
Expand Down

0 comments on commit 9ab10c0

Please sign in to comment.