From f477f5e75cc80154227fb7d522bf0025d6975f21 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 12 Apr 2019 19:03:08 +0100 Subject: [PATCH] fix(ts): make 'export *' explicit; type PostGraphileOptions (#1043) `export *` doesn't work with declaration merging (apparently). Explicit exports seems to solve this. People using PostGraphileOptions in Express need a way of accessing things like `req.user` in `pgSettings` without TypeScript being upset at them. --- src/index.ts | 11 ++++- src/interfaces.ts | 45 ++++++++++++------- .../createPostGraphileHttpRequestHandler.ts | 2 +- src/postgraphile/http/subscriptions.ts | 13 +++--- src/postgraphile/postgraphile.ts | 44 ++++++++++++------ 5 files changed, 77 insertions(+), 38 deletions(-) diff --git a/src/index.ts b/src/index.ts index 382c7466f8..b8617d17f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,13 @@ -export * from './interfaces'; +export { + mixed, + Middleware, + PostGraphileOptions, + CreateRequestHandlerOptions, + GraphQLFormattedErrorExtended, + GraphQLErrorExtended, + HttpRequestHandler, + WithPostGraphileContextOptions, +} from './interfaces'; export { Plugin, diff --git a/src/interfaces.ts b/src/interfaces.ts index c5c63e45c6..7c01679aed 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -26,11 +26,20 @@ import { EventEmitter } from 'events'; */ export type mixed = {} | string | number | boolean | undefined | null; +export type Middleware = ( + req: IncomingMessage, + res: ServerResponse, + next: (err?: Error) => void, +) => void; + // Please note that the comments for this type are turned into documentation // automatically. We try and specify the options in the same order as the CLI. // Anything tagged `@middlewareOnly` will not appear in the schema-only docs. // Only comments written beginning with `//` will be put in the docs. -export interface PostGraphileOptions { +export interface PostGraphileOptions< + Request extends IncomingMessage = IncomingMessage, + Response extends ServerResponse = ServerResponse +> { // When true, PostGraphile will update the GraphQL API whenever your database // schema changes. This feature requires some changes to your database in the // form of the @@ -49,6 +58,13 @@ export interface PostGraphileOptions { subscriptions?: boolean; // [EXPERIMENTAL] Enables live-query support via GraphQL subscriptions (sends updated payload any time nested collections/records change) live?: boolean; + // [EXPERIMENTAL] If you're using websockets (subscriptions || live) then you + // may want to authenticate your users using sessions or similar. You can + // pass some simple middlewares here that will be executed against the + // websocket connection in order to perform authentication. We current only + // support express (not Koa) middlewares here. + /* @middlewareOnly */ + websocketMiddlewares?: Array; // The default Postgres role to use. If no role was provided in a provided // JWT token, this role will be used. pgDefaultRole?: string; @@ -104,7 +120,7 @@ export interface PostGraphileOptions { handleErrors?: ( errors: ReadonlyArray, req: IncomingMessage, - res: ServerResponse, + res: Response, ) => Array; // An array of [Graphile Engine](/graphile-build/plugins/) schema plugins to load // after the default plugins. @@ -217,16 +233,14 @@ export interface PostGraphileOptions { // Promise to the same) based on the incoming web request (e.g. to extract // session data). /* @middlewareOnly */ - pgSettings?: - | { [key: string]: mixed } - | ((req: IncomingMessage) => Promise<{ [key: string]: mixed }>); + pgSettings?: { [key: string]: mixed } | ((req: Request) => Promise<{ [key: string]: mixed }>); // Some Graphile Engine schema plugins may need additional information // available on the `context` argument to the resolver - you can use this // function to provide such information based on the incoming request - you // can even use this to change the response [experimental], e.g. setting // cookies. /* @middlewareOnly */ - additionalGraphQLContextFromRequest?: (req: IncomingMessage, res: ServerResponse) => Promise<{}>; + additionalGraphQLContextFromRequest?: (req: Request, res: Response) => Promise<{}>; // [experimental] Plugin hook function, enables functionality within // PostGraphile to be expanded with plugins. Generate with // `makePluginHook(plugins)` passing a list of plugin objects. @@ -240,8 +254,6 @@ export interface PostGraphileOptions { // Max query cache size in MBs of queries. Default, 50MB. /* @middlewareOnly */ queryCacheMaxSize?: number; - // Allow arbitrary extensions for consumption by plugins. - [propName: string]: any; } export interface CreateRequestHandlerOptions extends PostGraphileOptions { @@ -274,23 +286,26 @@ export type GraphQLErrorExtended = GraphQLError & { /** * A request handler for one of many different `http` frameworks. */ -export interface HttpRequestHandler { - (req: IncomingMessage, res: ServerResponse, next?: (error?: mixed) => void): Promise; - (ctx: { req: IncomingMessage; res: ServerResponse }, next: () => void): Promise; +export interface HttpRequestHandler< + Request extends IncomingMessage = IncomingMessage, + Response extends ServerResponse = ServerResponse +> { + (req: Request, res: Response, next?: (error?: mixed) => void): Promise; + (ctx: { req: Request; res: Response }, next: () => void): Promise; formatError: (e: GraphQLError) => GraphQLFormattedErrorExtended; getGraphQLSchema: () => Promise; pgPool: Pool; withPostGraphileContextFromReqRes: ( - req: IncomingMessage, - res: ServerResponse, + req: Request, + res: Response, moreOptions: any, fn: (ctx: mixed) => any, ) => Promise; options: CreateRequestHandlerOptions; handleErrors: ( errors: ReadonlyArray, - req: IncomingMessage, - res: ServerResponse, + req: Request, + res: Response, ) => Array; } diff --git a/src/postgraphile/http/createPostGraphileHttpRequestHandler.ts b/src/postgraphile/http/createPostGraphileHttpRequestHandler.ts index 21059243ae..1e8e7f4017 100644 --- a/src/postgraphile/http/createPostGraphileHttpRequestHandler.ts +++ b/src/postgraphile/http/createPostGraphileHttpRequestHandler.ts @@ -142,7 +142,7 @@ export default function createPostGraphileHttpRequestHandler( pgDefaultRole, queryCacheMaxSize = 50 * MEGABYTE, } = options; - if (options.absoluteRoutes) { + if (options['absoluteRoutes']) { throw new Error( 'Sorry - the `absoluteRoutes` setting has been replaced with `externalUrlBase` which solves the issue in a cleaner way. Please update your settings. Thank you for testing a PostGraphile pre-release 🙏', ); diff --git a/src/postgraphile/http/subscriptions.ts b/src/postgraphile/http/subscriptions.ts index 98b467b079..a987c755ab 100644 --- a/src/postgraphile/http/subscriptions.ts +++ b/src/postgraphile/http/subscriptions.ts @@ -1,5 +1,5 @@ -import { Server, ServerResponse } from 'http'; -import { HttpRequestHandler, mixed } from '../../interfaces'; +import { Server, IncomingMessage, ServerResponse } from 'http'; +import { HttpRequestHandler, mixed, Middleware } from '../../interfaces'; import { subscribe as graphqlSubscribe, ExecutionResult, @@ -9,7 +9,6 @@ import { parse, DocumentNode, } from 'graphql'; -import { RequestHandler, Request, Response } from 'express'; import * as WebSocket from 'ws'; import { SubscriptionServer, ConnectionContext, ExecutionParams } from 'subscriptions-transport-ws'; import parseUrl = require('parseurl'); @@ -88,9 +87,9 @@ export async function enhanceHttpServerWithSubscriptions( }; const applyMiddleware = async ( - middlewares: Array = [], - req: Request, - res: Response, + middlewares: Array = [], + req: IncomingMessage, + res: ServerResponse, ) => { for (const middleware of middlewares) { // TODO: add Koa support @@ -128,7 +127,7 @@ export async function enhanceHttpServerWithSubscriptions( socket.close(); } }; - await applyMiddleware(options.websocketMiddlewares || options.middlewares, req, dummyRes); + await applyMiddleware(options.websocketMiddlewares, req, dummyRes); socket['__postgraphileRes'] = dummyRes; } return { req, res: dummyRes }; diff --git a/src/postgraphile/postgraphile.ts b/src/postgraphile/postgraphile.ts index f798e8586f..cd62dc1cc0 100644 --- a/src/postgraphile/postgraphile.ts +++ b/src/postgraphile/postgraphile.ts @@ -1,4 +1,5 @@ import { Pool, PoolConfig } from 'pg'; +import { IncomingMessage, ServerResponse } from 'http'; import { GraphQLSchema } from 'graphql'; import { EventEmitter } from 'events'; import { createPostGraphileSchema, watchPostGraphileSchema } from 'postgraphile-core'; @@ -18,10 +19,13 @@ function isPlainObject(obj: any) { return false; } -export interface PostgraphileSchemaBuilder { +export interface PostgraphileSchemaBuilder< + Request extends IncomingMessage = IncomingMessage, + Response extends ServerResponse = ServerResponse +> { _emitter: EventEmitter; getGraphQLSchema: () => Promise; - options: PostGraphileOptions; + options: PostGraphileOptions; } /** @@ -29,10 +33,13 @@ export interface PostgraphileSchemaBuilder { * database to get a GraphQL schema, and then using that to create the Http * request handler. */ -export function getPostgraphileSchemaBuilder( +export function getPostgraphileSchemaBuilder< + Request extends IncomingMessage = IncomingMessage, + Response extends ServerResponse = ServerResponse +>( pgPool: Pool, schema: string | Array, - incomingOptions: PostGraphileOptions, + incomingOptions: PostGraphileOptions, ): PostgraphileSchemaBuilder { if (incomingOptions.live && incomingOptions.subscriptions == null) { // live implies subscriptions @@ -59,7 +66,7 @@ export function getPostgraphileSchemaBuilder( // Creates the Postgres schemas array. const pgSchemas: Array = Array.isArray(schema) ? schema : [schema]; - const _emitter = options._emitter || new EventEmitter(); + const _emitter: EventEmitter = options['_emitter'] || new EventEmitter(); // Creates a promise which will resolve to a GraphQL schema. Connects a // client from our pool to introspect the database. @@ -108,24 +115,33 @@ export function getPostgraphileSchemaBuilder( } } } -export default function postgraphile( +export default function postgraphile< + Request extends IncomingMessage = IncomingMessage, + Response extends ServerResponse = ServerResponse +>( poolOrConfig?: Pool | PoolConfig | string, schema?: string | Array, - options?: PostGraphileOptions, + options?: PostGraphileOptions, ): HttpRequestHandler; -export default function postgraphile( +export default function postgraphile< + Request extends IncomingMessage = IncomingMessage, + Response extends ServerResponse = ServerResponse +>( poolOrConfig?: Pool | PoolConfig | string, - options?: PostGraphileOptions, + options?: PostGraphileOptions, ): HttpRequestHandler; -export default function postgraphile( +export default function postgraphile< + Request extends IncomingMessage = IncomingMessage, + Response extends ServerResponse = ServerResponse +>( poolOrConfig?: Pool | PoolConfig | string, - schemaOrOptions?: string | Array | PostGraphileOptions, - maybeOptions?: PostGraphileOptions, + schemaOrOptions?: string | Array | PostGraphileOptions, + maybeOptions?: PostGraphileOptions, ): HttpRequestHandler { let schema: string | Array; // These are the raw options we're passed in; getPostgraphileSchemaBuilder // must process them with `pluginHook` before we can rely on them. - let incomingOptions: PostGraphileOptions; + let incomingOptions: PostGraphileOptions; // If the second argument is a string or array, it is the schemas so set the // `schema` value and try to use the third argument (or a default) for @@ -175,7 +191,7 @@ export default function postgraphile( console.error('PostgreSQL client generated error: ', err.message); }); - const { getGraphQLSchema, options, _emitter } = getPostgraphileSchemaBuilder( + const { getGraphQLSchema, options, _emitter } = getPostgraphileSchemaBuilder( pgPool, schema, incomingOptions,