Skip to content

Commit 244c1cb

Browse files
authored
feat(hooks): add hooks for HTTP results and GraphiQL HTML (#970)
Fixes #962 Fixes #969
1 parent c114283 commit 244c1cb

File tree

3 files changed

+81
-24
lines changed

3 files changed

+81
-24
lines changed

src/postgraphile/cli.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ import { mixed } from '../interfaces';
2424
import * as manifest from '../../package.json';
2525
import sponsors = require('../../sponsors.json');
2626

27+
// tslint:disable-next-line no-any
28+
function isString(str: any): str is string {
29+
return typeof str === 'string';
30+
}
31+
2732
const sponsor = sponsors[Math.floor(sponsors.length * Math.random())];
2833

2934
const debugCli = debugFactory('postgraphile:cli');
@@ -703,7 +708,7 @@ if (noServer) {
703708
pgPort !== 5432 ? `:${pgConfig.port || 5432}` : ''
704709
}${pgDatabase ? `/${pgDatabase}` : ''}`;
705710

706-
const information = pluginHook(
711+
const information: Array<string> = pluginHook(
707712
'cli:greeting',
708713
[
709714
`GraphQL API: ${chalk.underline.bold.blue(
@@ -718,20 +723,21 @@ if (noServer) {
718723
`Documentation: ${chalk.underline(
719724
`https://graphile.org/postgraphile/introduction/`,
720725
)}`,
721-
extractedPlugins.length === 0 &&
722-
`Join ${chalk.bold(
723-
sponsor,
724-
)} in supporting PostGraphile development: ${chalk.underline.bold.blue(
725-
`https://graphile.org/sponsor/`,
726-
)}`,
726+
extractedPlugins.length === 0
727+
? `Join ${chalk.bold(
728+
sponsor,
729+
)} in supporting PostGraphile development: ${chalk.underline.bold.blue(
730+
`https://graphile.org/sponsor/`,
731+
)}`
732+
: null,
727733
],
728734
{
729735
options: postgraphileOptions,
730736
middleware,
731737
port: actualPort,
732738
chalk,
733739
},
734-
).filter(Boolean);
740+
).filter(isString);
735741
console.log(information.map(msg => ` ‣ ${msg}`).join('\n'));
736742

737743
console.log('');

src/postgraphile/http/createPostGraphileHttpRequestHandler.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import favicon from '../../assets/favicon.ico';
4343
* The GraphiQL HTML file as a string. We need it to be a string, because we
4444
* will use a regular expression to replace some variables.
4545
*/
46-
import origGraphiqlHtml from '../../assets/graphiql.html';
46+
import baseGraphiqlHtml from '../../assets/graphiql.html';
4747

4848
/**
4949
* When writing JSON to the browser, we need to be careful that it doesn't get
@@ -165,6 +165,8 @@ export default function createPostGraphileHttpRequestHandler(
165165

166166
const pluginHook = pluginHookFromOptions(options);
167167

168+
const origGraphiqlHtml = pluginHook('postgraphile:graphiql:html', baseGraphiqlHtml, { options });
169+
168170
if (pgDefaultRole && typeof pgSettings === 'function') {
169171
throw new Error(
170172
'pgDefaultRole cannot be combined with pgSettings(req) - please remove pgDefaultRole and instead always return a `role` key from pgSettings(req).',
@@ -325,7 +327,7 @@ export default function createPostGraphileHttpRequestHandler(
325327
return result;
326328
} else {
327329
const source = new Source(queryString, 'GraphQL Http Request');
328-
let queryDocumentAst;
330+
let queryDocumentAst: DocumentNode | void;
329331

330332
// Catch an errors while parsing so that we can set the `statusCode` to
331333
// 400. Otherwise we don’t need to parse this way.
@@ -721,6 +723,14 @@ export default function createPostGraphileHttpRequestHandler(
721723
if (!isEmpty(meta)) {
722724
result.meta = meta;
723725
}
726+
result = pluginHook('postgraphile:http:result', result, {
727+
options,
728+
returnArray,
729+
queryDocumentAst: queryDocumentAst!,
730+
req,
731+
// We don't pass `res` here because this is for just a single
732+
// result; if you need that, use postgraphile:http:end.
733+
});
724734
// Log the query. If this debugger isn’t enabled, don’t run it.
725735
if (
726736
!options.disableQueryLog &&
@@ -783,8 +793,24 @@ export default function createPostGraphileHttpRequestHandler(
783793
}
784794

785795
res.setHeader('Content-Type', 'application/json; charset=utf-8');
796+
const { statusCode, result } = pluginHook(
797+
'postgraphile:http:end',
798+
{
799+
statusCode: res.statusCode,
800+
result: returnArray ? results : results[0]!,
801+
},
802+
{
803+
options,
804+
returnArray,
805+
req,
806+
res,
807+
},
808+
);
786809

787-
res.end(JSON.stringify(returnArray ? results : results[0]!));
810+
if (statusCode) {
811+
res.statusCode = statusCode;
812+
}
813+
res.end(JSON.stringify(result));
788814

789815
if (debugRequest.enabled)
790816
debugRequest('GraphQL ' + (returnArray ? 'queries' : 'query') + ' request finished.');

src/postgraphile/pluginHook.ts

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,22 @@ import { version } from '../../package.json';
66
import * as graphql from 'graphql';
77

88
export type HookFn<T> = (arg: T, context: {}) => T;
9-
export type PluginHookFn = <T>(hookName: string, argument: T, context?: {}) => T;
9+
export type PluginHookFn = <TArgument, TContext = {}>(
10+
hookName: string,
11+
argument: TArgument,
12+
context?: TContext,
13+
) => TArgument;
14+
15+
export interface PostGraphileHTTPResult {
16+
statusCode?: number;
17+
result?: object;
18+
errors?: Array<object>;
19+
meta?: object;
20+
}
21+
export interface PostGraphileHTTPEnd {
22+
statusCode?: number;
23+
result: object | Array<object>;
24+
}
1025
export interface PostGraphilePlugin {
1126
init?: HookFn<null>;
1227

@@ -25,11 +40,14 @@ export interface PostGraphilePlugin {
2540
'cli:library:options'?: HookFn<{}>;
2641
'cli:server:middleware'?: HookFn<HttpRequestHandler>;
2742
'cli:server:created'?: HookFn<Server>;
28-
'cli:greeting'?: HookFn<Array<string | void>>;
43+
'cli:greeting'?: HookFn<Array<string | null | void>>;
2944

3045
'postgraphile:options'?: HookFn<PostGraphileOptions>;
3146
'postgraphile:validationRules:static'?: HookFn<ReadonlyArray<typeof graphql.specifiedRules>>;
47+
'postgraphile:graphiql:html'?: HookFn<string>;
3248
'postgraphile:http:handler'?: HookFn<IncomingMessage>;
49+
'postgraphile:http:result'?: HookFn<PostGraphileHTTPResult>;
50+
'postgraphile:http:end'?: HookFn<PostGraphileHTTPEnd>;
3351
'postgraphile:httpParamsList'?: HookFn<Array<object>>;
3452
'postgraphile:validationRules'?: HookFn<ReadonlyArray<typeof graphql.specifiedRules>>; // AVOID THIS where possible; use 'postgraphile:validationRules:static' instead.
3553
'postgraphile:middleware'?: HookFn<HttpRequestHandler>;
@@ -89,18 +107,25 @@ function memoizeHook<T>(hook: HookFn<T>): HookFn<T> {
89107
};
90108
}
91109

110+
function shouldMemoizeHook(hookName: HookName) {
111+
return hookName === 'withPostGraphileContext';
112+
}
113+
92114
function makeHook<T>(plugins: Array<PostGraphilePlugin>, hookName: HookName): HookFn<T> {
93-
return memoizeHook<T>(
94-
plugins.reduce((previousHook: HookFn<T>, plugin: {}) => {
95-
if (typeof plugin[hookName] === 'function') {
96-
return (argument: T, context: {}) => {
97-
return plugin[hookName](previousHook(argument, context), context);
98-
};
99-
} else {
100-
return previousHook;
101-
}
102-
}, identityHook),
103-
);
115+
const combinedHook = plugins.reduce((previousHook: HookFn<T>, plugin: {}) => {
116+
if (typeof plugin[hookName] === 'function') {
117+
return (argument: T, context: {}) => {
118+
return plugin[hookName](previousHook(argument, context), context);
119+
};
120+
} else {
121+
return previousHook;
122+
}
123+
}, identityHook);
124+
if (shouldMemoizeHook(hookName)) {
125+
return memoizeHook<T>(combinedHook);
126+
} else {
127+
return combinedHook;
128+
}
104129
}
105130

106131
export function makePluginHook(plugins: Array<PostGraphilePlugin>): PluginHookFn {

0 commit comments

Comments
 (0)