Skip to content

Commit 4a7ab32

Browse files
Merge branch 'luas10c-upgrade/apollo-v4' into next
2 parents 2c50d95 + 8dac4de commit 4a7ab32

File tree

70 files changed

+2143
-3969
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+2143
-3969
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ test-schema.graphql
1919

2020
# dist
2121
packages/**/dist
22-
**/*.tsbuildinfo
22+
**/*.tsbuildinfo
23+
24+
.yarn/

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"prepare": "husky install"
2727
},
2828
"resolutions": {
29-
"graphql": "15.8.0"
29+
"graphql": "16.6.0"
3030
},
3131
"devDependencies": {
3232
"@commitlint/cli": "17.4.2",
@@ -45,7 +45,7 @@
4545
"eslint-config-prettier": "8.6.0",
4646
"eslint-plugin-import": "2.27.5",
4747
"eslint-plugin-prettier": "4.2.1",
48-
"graphql": "15.8.0",
48+
"graphql": "16.6.0",
4949
"graphql-subscriptions": "2.0.0",
5050
"husky": "8.0.3",
5151
"jest": "29.4.1",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './apollo.constants';

packages/apollo/lib/decorators/plugin.decorator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { SetMetadata } from '@nestjs/common';
2-
import { PLUGIN_METADATA } from '../apollo.constants';
2+
import { PLUGIN_METADATA } from '../constants';
33

44
/**
55
* Decorator that marks a class as an Apollo plugin.

packages/apollo/lib/drivers/apollo-base.driver.ts

Lines changed: 88 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,35 @@
1-
import { HttpStatus } from '@nestjs/common';
21
import { loadPackage } from '@nestjs/common/utils/load-package.util';
32
import { isFunction } from '@nestjs/common/utils/shared.utils';
43
import { AbstractGraphQLDriver } from '@nestjs/graphql';
5-
import {
6-
ApolloError,
7-
ApolloServerBase,
8-
ApolloServerPluginLandingPageDisabled,
9-
ApolloServerPluginLandingPageGraphQLPlayground,
10-
AuthenticationError,
11-
ForbiddenError,
12-
PluginDefinition,
13-
UserInputError,
14-
} from 'apollo-server-core';
4+
155
import { GraphQLError, GraphQLFormattedError } from 'graphql';
166
import * as omit from 'lodash.omit';
177
import { ApolloDriverConfig } from '../interfaces';
188
import { createAsyncIterator } from '../utils/async-iterator.util';
199

20-
const apolloPredefinedExceptions: Partial<
21-
Record<HttpStatus, typeof ApolloError | typeof UserInputError>
22-
> = {
23-
[HttpStatus.BAD_REQUEST]: UserInputError,
24-
[HttpStatus.UNAUTHORIZED]: AuthenticationError,
25-
[HttpStatus.FORBIDDEN]: ForbiddenError,
10+
import { ApolloServer, type BaseContext } from '@apollo/server';
11+
import { ApolloServerErrorCode } from '@apollo/server/errors';
12+
import { expressMiddleware } from '@apollo/server/express4';
13+
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default';
14+
import * as express from 'express';
15+
import * as http from 'node:http';
16+
17+
import { fastifyApolloHandler } from '@as-integrations/fastify';
18+
import { HttpStatus } from '@nestjs/common';
19+
20+
const apolloPredefinedExceptions: Partial<Record<HttpStatus, string>> = {
21+
[HttpStatus.BAD_REQUEST]: ApolloServerErrorCode.BAD_USER_INPUT,
22+
[HttpStatus.UNAUTHORIZED]: 'UNAUTHENTICATED',
23+
[HttpStatus.FORBIDDEN]: 'FORBIDDEN',
2624
};
2725

2826
export abstract class ApolloBaseDriver<
2927
T extends Record<string, any> = ApolloDriverConfig,
3028
> extends AbstractGraphQLDriver<T> {
31-
protected _apolloServer: ApolloServerBase;
29+
protected apolloServer: ApolloServer<BaseContext>;
3230

33-
get instance(): ApolloServerBase {
34-
return this._apolloServer;
31+
get instance(): ApolloServer<BaseContext> {
32+
return this.apolloServer;
3533
}
3634

3735
public async start(apolloOptions: T) {
@@ -48,7 +46,7 @@ export abstract class ApolloBaseDriver<
4846
}
4947

5048
public stop() {
51-
return this._apolloServer?.stop();
49+
return this.apolloServer?.stop();
5250
}
5351

5452
public async mergeDefaultOptions(options: T): Promise<T> {
@@ -67,11 +65,7 @@ export abstract class ApolloBaseDriver<
6765
typeof options.playground === 'object' ? options.playground : undefined;
6866
defaults = {
6967
...defaults,
70-
plugins: [
71-
ApolloServerPluginLandingPageGraphQLPlayground(
72-
playgroundOptions,
73-
) as PluginDefinition,
74-
],
68+
plugins: [ApolloServerPluginLandingPageLocalDefault(playgroundOptions)],
7569
};
7670
} else if (
7771
(options.playground === undefined &&
@@ -80,7 +74,7 @@ export abstract class ApolloBaseDriver<
8074
) {
8175
defaults = {
8276
...defaults,
83-
plugins: [ApolloServerPluginLandingPageDisabled() as PluginDefinition],
77+
plugins: [ApolloServerPluginLandingPageLocalDefault()],
8478
};
8579
}
8680

@@ -115,68 +109,79 @@ export abstract class ApolloBaseDriver<
115109
);
116110
}
117111

118-
protected async registerExpress(
119-
apolloOptions: T,
120-
{ preStartHook }: { preStartHook?: () => void } = {},
121-
) {
122-
const { ApolloServer } = loadPackage(
123-
'apollo-server-express',
124-
'GraphQLModule',
125-
() => require('apollo-server-express'),
126-
);
127-
const { disableHealthCheck, path, onHealthCheck, cors, bodyParserConfig } =
128-
apolloOptions;
112+
protected async registerExpress(options: T, hooks?: any) {
113+
if (hooks?.preStartHook) {
114+
hooks?.preStartHook();
115+
}
116+
117+
const cors = loadPackage('cors', null, () => require('cors'));
118+
119+
const { path, typeDefs, resolvers, schema } = options;
129120

130121
const httpAdapter = this.httpAdapterHost.httpAdapter;
131122
const app = httpAdapter.getInstance();
123+
const httpServer = http.createServer(app);
132124

133-
preStartHook?.();
125+
// Set up Apollo Server
126+
const server = new ApolloServer({
127+
typeDefs,
128+
resolvers,
129+
schema,
130+
...options,
131+
/**
132+
* @TODO
133+
* should remove serverWillStart from default plugins.
134+
* after include plugins here
135+
*/
136+
// TODO: fix - dont override plugins
137+
// plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
138+
});
134139

135-
const apolloServer = new ApolloServer(apolloOptions as any);
136-
await apolloServer.start();
140+
await server.start();
137141

138-
apolloServer.applyMiddleware({
139-
app,
142+
app.use(
140143
path,
141-
disableHealthCheck,
142-
onHealthCheck,
143-
cors,
144-
bodyParserConfig,
145-
});
144+
cors(options.cors),
145+
express.json(),
146+
expressMiddleware(server),
147+
);
146148

147-
this._apolloServer = apolloServer;
149+
this.apolloServer = server;
148150
}
149151

150-
protected async registerFastify(
151-
apolloOptions: T,
152-
{ preStartHook }: { preStartHook?: () => void } = {},
153-
) {
154-
const { ApolloServer } = loadPackage(
155-
'apollo-server-fastify',
156-
'GraphQLModule',
157-
() => require('apollo-server-fastify'),
152+
protected async registerFastify(options: T, hooks?: any) {
153+
if (hooks?.preStartHook) {
154+
hooks?.preStartHook();
155+
}
156+
157+
const cors = loadPackage('@fastify/cors', null, () =>
158+
require('@fastify/cors'),
158159
);
159160

160161
const httpAdapter = this.httpAdapterHost.httpAdapter;
161162
const app = httpAdapter.getInstance();
162163

163-
preStartHook?.();
164-
const apolloServer = new ApolloServer(apolloOptions as any);
165-
await apolloServer.start();
166-
167-
const { disableHealthCheck, onHealthCheck, cors, bodyParserConfig, path } =
168-
apolloOptions;
169-
await app.register(
170-
apolloServer.createHandler({
171-
disableHealthCheck,
172-
onHealthCheck,
173-
cors,
174-
bodyParserConfig,
175-
path,
176-
}),
177-
);
164+
const { path, typeDefs, resolvers, schema } = options;
165+
const server = new ApolloServer<BaseContext>({
166+
typeDefs,
167+
resolvers,
168+
schema,
169+
...options,
170+
// TODO: fix - dont override plugin
171+
//plugins: [fastifyApolloDrainPlugin(app)],
172+
});
178173

179-
this._apolloServer = apolloServer;
174+
await server.start();
175+
176+
app.route({
177+
url: path,
178+
method: ['GET', 'POST', 'OPTIONS'],
179+
handler: fastifyApolloHandler(server),
180+
});
181+
182+
await app.register(cors, options.cors);
183+
184+
this.apolloServer = server;
180185
}
181186

182187
private wrapFormatErrorFn(options: T) {
@@ -201,22 +206,26 @@ export abstract class ApolloBaseDriver<
201206
const exceptionRef = originalError?.extensions?.exception;
202207
const isHttpException =
203208
exceptionRef?.response?.statusCode && exceptionRef?.status;
204-
205209
if (!isHttpException) {
206210
return originalError as GraphQLFormattedError;
207211
}
208-
let error: ApolloError;
212+
let error: GraphQLError;
209213

210214
const httpStatus = exceptionRef?.status;
211215
if (httpStatus in apolloPredefinedExceptions) {
212-
error = new apolloPredefinedExceptions[httpStatus](
213-
exceptionRef?.message,
214-
);
216+
error = new GraphQLError(exceptionRef?.message, {
217+
extensions: {
218+
code: apolloPredefinedExceptions[httpStatus],
219+
},
220+
});
215221
} else {
216-
error = new ApolloError(exceptionRef.message, httpStatus?.toString());
222+
error = new GraphQLError(exceptionRef.message, httpStatus?.toString());
217223
}
218224

219225
error.stack = exceptionRef?.stacktrace;
226+
//TODO: we need to verify if previous behavior is to be kept
227+
// if so we must open a PR on Apollo to include response inside the raised exception
228+
//https://github.com/apollographql/apollo-server/blob/e6d0d6d9cbd78d4914adf2abb04d84710991849a/packages/server/src/errorNormalize.ts#L58
220229
error.extensions['response'] = exceptionRef?.response;
221230
return error;
222231
};

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

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,8 @@ import {
44
GqlOptionsFactory,
55
SubscriptionConfig,
66
} from '@nestjs/graphql';
7-
import {
8-
ApolloServerPluginLandingPageGraphQLPlaygroundOptions,
9-
Config,
10-
GraphQLExecutor,
11-
} from 'apollo-server-core';
127
import { GraphQLSchema } from 'graphql';
8+
import { CorsOptions } from 'cors';
139

1410
export interface ServerRegistration {
1511
/**
@@ -20,7 +16,7 @@ export interface ServerRegistration {
2016
/**
2117
* CORS configuration
2218
*/
23-
cors?: any | boolean;
19+
cors?: CorsOptions;
2420

2521
/**
2622
* Body-parser configuration
@@ -39,15 +35,15 @@ export interface ServerRegistration {
3935
}
4036

4137
export interface ApolloDriverConfig
42-
extends Omit<Config, 'typeDefs'>,
38+
extends Omit</*Config*/ any, 'typeDefs'>,
4339
ServerRegistration,
4440
Omit<GqlModuleOptions, 'context'> {
4541
/**
4642
* Executor factory function
4743
*/
4844
executorFactory?: (
4945
schema: GraphQLSchema,
50-
) => GraphQLExecutor | Promise<GraphQLExecutor>;
46+
) => /*GraphQLExecutor | Promise<GraphQLExecutor>*/ any;
5147

5248
/**
5349
* If enabled, "subscriptions-transport-ws" will be automatically registered.
@@ -62,7 +58,7 @@ export interface ApolloDriverConfig
6258
/**
6359
* GraphQL playground options.
6460
*/
65-
playground?: boolean | ApolloServerPluginLandingPageGraphQLPlaygroundOptions;
61+
playground?: boolean;
6662

6763
/**
6864
* If enabled, will register a global interceptor that automatically maps

packages/apollo/lib/services/plugins-explorer.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
22
import { ModulesContainer } from '@nestjs/core/injector/modules-container';
33
import { BaseExplorerService, GqlModuleOptions } from '@nestjs/graphql';
4-
import { PLUGIN_METADATA } from '../apollo.constants';
4+
import { PLUGIN_METADATA } from '../constants';
55

66
export class PluginsExplorerService extends BaseExplorerService {
77
constructor(private readonly modulesContainer: ModulesContainer) {
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
import { INestApplicationContext } from '@nestjs/common';
22
import { GraphQLModule } from '@nestjs/graphql';
3-
import { ApolloServerBase } from 'apollo-server-core';
43
import { ApolloDriver } from '..';
54

5+
import { ApolloServer, type BaseContext } from '@apollo/server';
6+
7+
type GetApolloServer = (
8+
app: INestApplicationContext,
9+
) => ApolloServer<BaseContext>;
10+
611
/**
712
* Returns the underlying ApolloServer instance for a given application.
813
* @param app Nest application reference
914
* @returns Apollo Server instance used by the host application
1015
*/
11-
export function getApolloServer(
12-
app: INestApplicationContext,
13-
): ApolloServerBase {
16+
export const getApolloServer: GetApolloServer = (app) => {
1417
try {
1518
const graphqlModule = app.get<GraphQLModule<ApolloDriver>>(GraphQLModule);
1619
return graphqlModule.graphQlAdapter?.instance;
1720
} catch (error) {}
21+
1822
throw new Error(
1923
`Nest could not find the "GraphQLModule" in your application's container. Please, double-check if it's registered in the given application.`,
2024
);
21-
}
25+
};

0 commit comments

Comments
 (0)