Skip to content

Commit 50dde67

Browse files
Merge pull request #2546 from tugascript/tugascript/add-mercurius-hooks-object
feat(mercurius): add mercurius hooks
2 parents cd65d44 + 689c77a commit 50dde67

21 files changed

+717
-6
lines changed

packages/mercurius/lib/drivers/mercurius-federation.driver.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IncomingMessage, Server, ServerResponse } from 'http';
99
import mercurius from 'mercurius';
1010
import { MercuriusDriverConfig } from '../interfaces/mercurius-driver-config.interface';
1111
import { buildMercuriusFederatedSchema } from '../utils/build-mercurius-federated-schema.util';
12+
import { registerMercuriusHooks } from '../utils/register-mercurius-hooks.util';
1213
import { registerMercuriusPlugin } from '../utils/register-mercurius-plugin.util';
1314

1415
@Injectable()
@@ -29,7 +30,7 @@ export class MercuriusFederationDriver extends AbstractGraphQLDriver<MercuriusDr
2930
}
3031

3132
public async start(options: MercuriusDriverConfig) {
32-
const { plugins, ...adapterOptions } =
33+
const { plugins, hooks, ...adapterOptions } =
3334
await this.graphqlFederationFactory.mergeWithSchema(
3435
options,
3536
buildMercuriusFederatedSchema,
@@ -53,6 +54,7 @@ export class MercuriusFederationDriver extends AbstractGraphQLDriver<MercuriusDr
5354
...adapterOptions,
5455
});
5556
await registerMercuriusPlugin(app, plugins);
57+
await registerMercuriusHooks(app, hooks);
5658
}
5759

5860
/* eslit-disable-next-line @typescript-eslint/no-empty-function */

packages/mercurius/lib/drivers/mercurius-gateway.driver.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { FastifyInstance, FastifyLoggerInstance } from 'fastify';
33
import { IncomingMessage, Server, ServerResponse } from 'http';
44
import mercurius from 'mercurius';
55
import { MercuriusDriverConfig } from '../interfaces/mercurius-driver-config.interface';
6+
import { registerMercuriusHooks } from '../utils/register-mercurius-hooks.util';
67
import { registerMercuriusPlugin } from '../utils/register-mercurius-plugin.util';
78

89
export class MercuriusGatewayDriver extends AbstractGraphQLDriver<MercuriusDriverConfig> {
@@ -23,12 +24,13 @@ export class MercuriusGatewayDriver extends AbstractGraphQLDriver<MercuriusDrive
2324
throw new Error(`No support for current HttpAdapter: ${platformName}`);
2425
}
2526

26-
const { plugins, ...mercuriusOptions } = options;
27+
const { plugins, hooks, ...mercuriusOptions } = options;
2728
const app = httpAdapter.getInstance<FastifyInstance>();
2829
await app.register(mercurius, {
2930
...mercuriusOptions,
3031
});
3132
await registerMercuriusPlugin(app, plugins);
33+
await registerMercuriusHooks(app, hooks);
3234
}
3335

3436
public async stop(): Promise<void> {}

packages/mercurius/lib/drivers/mercurius.driver.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { printSchema } from 'graphql';
55
import { IncomingMessage, Server, ServerResponse } from 'http';
66
import mercurius from 'mercurius';
77
import { MercuriusDriverConfig } from '../interfaces/mercurius-driver-config.interface';
8+
import { registerMercuriusHooks } from '../utils/register-mercurius-hooks.util';
89
import { registerMercuriusPlugin } from '../utils/register-mercurius-plugin.util';
910

1011
export class MercuriusDriver extends AbstractGraphQLDriver<MercuriusDriverConfig> {
@@ -18,7 +19,7 @@ export class MercuriusDriver extends AbstractGraphQLDriver<MercuriusDriverConfig
1819
}
1920

2021
public async start(mercuriusOptions: MercuriusDriverConfig) {
21-
const { plugins, ...options } =
22+
const { plugins, hooks, ...options } =
2223
await this.graphQlFactory.mergeWithSchema<MercuriusDriverConfig>(
2324
mercuriusOptions,
2425
);
@@ -41,6 +42,7 @@ export class MercuriusDriver extends AbstractGraphQLDriver<MercuriusDriverConfig
4142
...options,
4243
});
4344
await registerMercuriusPlugin(app, plugins);
45+
await registerMercuriusHooks(app, hooks);
4446
}
4547

4648
public async stop(): Promise<void> {}

packages/mercurius/lib/interfaces/mercurius-driver-config.interface.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import {
44
GqlOptionsFactory,
55
} from '@nestjs/graphql';
66
import { MercuriusOptions } from 'mercurius';
7+
import { MercuriusHooks } from './mercurius-hook.interface';
78
import { MercuriusPlugins } from './mercurius-plugin.interface';
89

910
export type MercuriusDriverConfig = GqlModuleOptions &
1011
MercuriusOptions &
11-
MercuriusPlugins;
12+
MercuriusPlugins &
13+
MercuriusHooks;
1214

1315
export type MercuriusDriverConfigFactory =
1416
GqlOptionsFactory<MercuriusDriverConfig>;

packages/mercurius/lib/interfaces/mercurius-gateway-driver-config.interface.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import {
44
GqlOptionsFactory,
55
} from '@nestjs/graphql';
66
import { MercuriusCommonOptions, MercuriusGatewayOptions } from 'mercurius';
7+
import { MercuriusHooks } from './mercurius-hook.interface';
78
import { MercuriusPlugin } from './mercurius-plugin.interface';
89

910
export type MercuriusGatewayDriverConfig = GqlModuleOptions &
1011
MercuriusCommonOptions &
1112
MercuriusGatewayOptions &
12-
MercuriusPlugin;
13+
MercuriusPlugin &
14+
MercuriusHooks;
1315

1416
export type MercuriusGatewayDriverConfigFactory =
1517
GqlOptionsFactory<MercuriusGatewayDriverConfig>;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {
2+
MercuriusContext,
3+
onGatewayReplaceSchemaHookHandler,
4+
onResolutionHookHandler,
5+
onSubscriptionEndHookHandler,
6+
onSubscriptionResolutionHookHandler,
7+
preExecutionHookHandler,
8+
preGatewayExecutionHookHandler,
9+
preGatewaySubscriptionExecutionHookHandler,
10+
preParsingHookHandler,
11+
preSubscriptionExecutionHookHandler,
12+
preSubscriptionParsingHookHandler,
13+
preValidationHookHandler,
14+
} from 'mercurius';
15+
16+
export interface MercuriusHooksObject<
17+
Context extends MercuriusContext = MercuriusContext,
18+
> {
19+
preParsing?:
20+
| preParsingHookHandler<Context>
21+
| preParsingHookHandler<Context>[];
22+
preValidation?:
23+
| preValidationHookHandler<Context>
24+
| preValidationHookHandler<Context>[];
25+
preExecution?:
26+
| preExecutionHookHandler<Context>
27+
| preExecutionHookHandler<Context>[];
28+
onResolution?:
29+
| onResolutionHookHandler<Context>
30+
| onResolutionHookHandler<Context>[];
31+
preSubscriptionParsing?:
32+
| preSubscriptionParsingHookHandler<Context>
33+
| preSubscriptionParsingHookHandler<Context>[];
34+
preSubscriptionExecution?:
35+
| preSubscriptionExecutionHookHandler<Context>
36+
| preSubscriptionExecutionHookHandler<Context>[];
37+
onSubscriptionResolution?:
38+
| onSubscriptionResolutionHookHandler<Context>
39+
| onSubscriptionResolutionHookHandler<Context>[];
40+
onSubscriptionEnd?:
41+
| onSubscriptionEndHookHandler<Context>
42+
| onSubscriptionEndHookHandler<Context>[];
43+
}
44+
45+
export interface MercuriusHooks<
46+
Context extends MercuriusContext = MercuriusContext,
47+
> {
48+
hooks?: MercuriusHooksObject<Context>;
49+
}
50+
51+
export interface MercuriusGatewayHooksObject<
52+
Context extends MercuriusContext = MercuriusContext,
53+
> extends MercuriusHooksObject<Context> {
54+
preGatewayExecution?:
55+
| preGatewayExecutionHookHandler<Context>
56+
| preGatewayExecutionHookHandler<Context>[];
57+
preGatewaySubscriptionExecution?:
58+
| preGatewaySubscriptionExecutionHookHandler<Context>
59+
| preGatewaySubscriptionExecutionHookHandler<Context>[];
60+
onGatewayReplaceSchema?:
61+
| onGatewayReplaceSchemaHookHandler
62+
| onGatewayReplaceSchemaHookHandler[];
63+
}
64+
65+
export interface MercuriusGatewayHooks<
66+
Context extends MercuriusContext = MercuriusContext,
67+
> {
68+
hooks?: MercuriusGatewayHooksObject<Context>;
69+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { FastifyInstance } from 'fastify';
2+
import { MercuriusGatewayHooksObject } from '../interfaces/mercurius-hook.interface';
3+
import { isArray, isNull, isUndefined } from './validation.util';
4+
5+
export function registerMercuriusHooks(
6+
app: FastifyInstance,
7+
hooks?: MercuriusGatewayHooksObject | null,
8+
): void {
9+
if (isUndefined(hooks) || isNull(hooks)) {
10+
return;
11+
}
12+
13+
Object.entries(hooks).forEach(([hookName, hookFn]: [any, any]) => {
14+
if (isUndefined(hookFn) || isNull(hookFn)) {
15+
return;
16+
}
17+
18+
if (isArray<any>(hookFn)) {
19+
hookFn.forEach((fn) => app.graphql.addHook(hookName, fn));
20+
return;
21+
}
22+
23+
app.graphql.addHook(hookName, hookFn);
24+
});
25+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { INestApplication } from '@nestjs/common';
2+
import { NestFactory } from '@nestjs/core';
3+
import { FastifyAdapter } from '@nestjs/platform-fastify';
4+
import * as request from 'supertest';
5+
import { ApplicationModule } from '../hooks/base-array/hooks.module';
6+
import { MockLogger } from '../hooks/mocks/logger.mock';
7+
8+
describe('Base hooks in array format', () => {
9+
let app: INestApplication;
10+
let logger: MockLogger;
11+
12+
beforeEach(async () => {
13+
logger = new MockLogger();
14+
app = await NestFactory.create(ApplicationModule, new FastifyAdapter(), {
15+
logger,
16+
});
17+
await app.init();
18+
await app.getHttpAdapter().getInstance().ready();
19+
});
20+
21+
it('hooks should be triggered', async () => {
22+
await request(app.getHttpServer())
23+
.post('/graphql')
24+
.send({
25+
operationName: null,
26+
variables: {},
27+
query: '{ getAnimalName }',
28+
})
29+
.expect(200, {
30+
data: {
31+
getAnimalName: 'cat',
32+
},
33+
});
34+
expect(logger.warn).toHaveBeenCalledTimes(8);
35+
expect(logger.warn).toHaveBeenNthCalledWith(
36+
1,
37+
'preParsing1',
38+
'GqlConfigService',
39+
);
40+
expect(logger.warn).toHaveBeenNthCalledWith(
41+
2,
42+
'preParsing2',
43+
'GqlConfigService',
44+
);
45+
expect(logger.warn).toHaveBeenNthCalledWith(
46+
3,
47+
'preValidation1',
48+
'GqlConfigService',
49+
);
50+
expect(logger.warn).toHaveBeenNthCalledWith(
51+
4,
52+
'preValidation2',
53+
'GqlConfigService',
54+
);
55+
expect(logger.warn).toHaveBeenNthCalledWith(
56+
5,
57+
'preExecution1',
58+
'GqlConfigService',
59+
);
60+
expect(logger.warn).toHaveBeenNthCalledWith(
61+
6,
62+
'preExecution2',
63+
'GqlConfigService',
64+
);
65+
expect(logger.warn).toHaveBeenNthCalledWith(
66+
7,
67+
'onResolution1',
68+
'GqlConfigService',
69+
);
70+
expect(logger.warn).toHaveBeenNthCalledWith(
71+
8,
72+
'onResolution2',
73+
'GqlConfigService',
74+
);
75+
});
76+
77+
afterEach(async () => {
78+
await app.close();
79+
});
80+
});
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { INestApplication } from '@nestjs/common';
2+
import { NestFactory } from '@nestjs/core';
3+
import { FastifyAdapter } from '@nestjs/platform-fastify';
4+
import * as request from 'supertest';
5+
import { ApplicationModule } from '../hooks/base/hooks.module';
6+
import { MockLogger } from '../hooks/mocks/logger.mock';
7+
8+
describe('Base hooks', () => {
9+
let app: INestApplication;
10+
let logger: MockLogger;
11+
12+
beforeEach(async () => {
13+
logger = new MockLogger();
14+
app = await NestFactory.create(ApplicationModule, new FastifyAdapter(), {
15+
logger,
16+
});
17+
await app.init();
18+
await app.getHttpAdapter().getInstance().ready();
19+
});
20+
21+
it('hooks should be triggered', async () => {
22+
await request(app.getHttpServer())
23+
.post('/graphql')
24+
.send({
25+
operationName: null,
26+
variables: {},
27+
query: '{ getAnimalName }',
28+
})
29+
.expect(200, {
30+
data: {
31+
getAnimalName: 'cat',
32+
},
33+
});
34+
expect(logger.warn).toHaveBeenCalledTimes(4);
35+
expect(logger.warn).toHaveBeenNthCalledWith(
36+
1,
37+
'preParsing',
38+
'GqlConfigService',
39+
);
40+
expect(logger.warn).toHaveBeenNthCalledWith(
41+
2,
42+
'preValidation',
43+
'GqlConfigService',
44+
);
45+
expect(logger.warn).toHaveBeenNthCalledWith(
46+
3,
47+
'preExecution',
48+
'GqlConfigService',
49+
);
50+
expect(logger.warn).toHaveBeenNthCalledWith(
51+
4,
52+
'onResolution',
53+
'GqlConfigService',
54+
);
55+
});
56+
57+
afterEach(async () => {
58+
await app.close();
59+
});
60+
});

0 commit comments

Comments
 (0)