Skip to content

Commit

Permalink
feat(core): Update NestJS to v10, Apollo Server v4
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The update of Apollo Server to v4 includes some breaking changes if you have
defined any custom ApolloServerPlugins. See the Apollo migration guide for full details:
https://www.apollographql.com/docs/apollo-server/migration/
  • Loading branch information
michaelbromley committed Aug 23, 2023
1 parent 10034bd commit b675fda
Show file tree
Hide file tree
Showing 22 changed files with 330 additions and 405 deletions.
1 change: 1 addition & 0 deletions packages/admin-ui/src/lib/core/src/data/data.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export function createApollo(
headers.authorization = `Bearer ${authToken}`;
}
}
headers['Apollo-Require-Preflight'] = 'true';
return { headers };
}),
createUploadLink({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { MutationUpdaterFn, SingleExecutionResult, WatchQueryFetchPolicy } from '@apollo/client/core';
import { TypedDocumentNode } from '@graphql-typed-document-node/core';
Expand All @@ -20,11 +19,7 @@ import { transformRelationCustomFieldInputs } from '../utils/transform-relation-

@Injectable()
export class BaseDataService {
constructor(
private apollo: Apollo,
private httpClient: HttpClient,
private serverConfigService: ServerConfigService,
) {}
constructor(private apollo: Apollo, private serverConfigService: ServerConfigService) {}

private get customFields(): CustomFields {
return this.serverConfigService.serverConfig.customFieldConfig || {};
Expand Down
19 changes: 8 additions & 11 deletions packages/core/e2e/apollo-server-plugin.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { ApolloServerPlugin, GraphQLRequestListener, GraphQLServerContext } from '@apollo/server';
import { mergeConfig } from '@vendure/core';
import {
ApolloServerPlugin,
GraphQLRequestContext,
GraphQLRequestListener,
GraphQLServiceContext,
} from 'apollo-server-plugin-base';
import gql from 'graphql-tag';
import path from 'path';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
Expand All @@ -24,16 +19,18 @@ class MyApolloServerPlugin implements ApolloServerPlugin {
this.willSendResponseFn = vi.fn();
}

serverWillStart(service: GraphQLServiceContext): Promise<void> | void {
async serverWillStart(service: GraphQLServerContext): Promise<void> {
MyApolloServerPlugin.serverWillStartFn(service);
}

requestDidStart(): GraphQLRequestListener | void {
async requestDidStart(): Promise<GraphQLRequestListener<any>> {
MyApolloServerPlugin.requestDidStartFn();
return {
willSendResponse(requestContext: any): Promise<void> | void {
const data = requestContext.response.data;
MyApolloServerPlugin.willSendResponseFn(data);
async willSendResponse(requestContext): Promise<void> {
const { body } = requestContext.response;
if (body.kind === 'single') {
MyApolloServerPlugin.willSendResponseFn(body.singleResult.data);
}
},
};
}
Expand Down
18 changes: 9 additions & 9 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,18 @@
"cli/**/*"
],
"dependencies": {
"@apollo/server": "^4.9.1",
"@graphql-tools/stitch": "^8.7.43",
"@nestjs/apollo": "^10.2.0",
"@nestjs/common": "9.3.9",
"@nestjs/core": "9.3.9",
"@nestjs/graphql": "10.2.0",
"@nestjs/platform-express": "9.3.9",
"@nestjs/terminus": "9.2.1",
"@nestjs/testing": "9.3.9",
"@nestjs/typeorm": "9.0.1",
"@nestjs/apollo": "^12.0.7",
"@nestjs/common": "10.2.1",
"@nestjs/core": "10.2.1",
"@nestjs/graphql": "12.0.8",
"@nestjs/platform-express": "10.2.1",
"@nestjs/terminus": "10.0.1",
"@nestjs/testing": "10.2.1",
"@nestjs/typeorm": "10.0.0",
"@types/fs-extra": "^9.0.1",
"@vendure/common": "2.1.0-next.0",
"apollo-server-express": "3.6.3",
"bcrypt": "^5.1.0",
"body-parser": "^1.19.0",
"chalk": "^4.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/api/common/graphql-value-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class GraphqlValueTransformer {
*/
transformValues(
typeTree: TypeTree,
data: Record<string, any>,
data: Record<string, unknown>,
visitorFn: (value: any, type: GraphQLNamedType) => any,
) {
this.traverse(data, (key, value, path) => {
Expand Down
16 changes: 7 additions & 9 deletions packages/core/src/api/config/configure-graphql-module.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { ApolloDriver } from '@nestjs/apollo';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { DynamicModule } from '@nestjs/common';
import { GqlModuleOptions, GraphQLModule, GraphQLTypesLoader } from '@nestjs/graphql';
import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core';
import { buildSchema, extendSchema, GraphQLSchema, printSchema, ValidationContext } from 'graphql';
import path from 'path';

import { ConfigModule } from '../../config/config.module';
import { ConfigService } from '../../config/config.service';
import { I18nModule } from '../../i18n/i18n.module';
import { I18nService } from '../../i18n/i18n.service';
import { getDynamicGraphQlModulesForPlugins } from '../../plugin/dynamic-plugin-api.module';
import { getPluginAPIExtensions } from '../../plugin/plugin-metadata';
import { ServiceModule } from '../../service/service.module';
import { ApiSharedModule, ShopApiModule } from '../api-internal-modules';
import { ApiSharedModule } from '../api-internal-modules';
import { CustomFieldRelationResolverService } from '../common/custom-field-relation-resolver.service';
import { IdCodecService } from '../common/id-codec.service';
import { AssetInterceptorPlugin } from '../middleware/asset-interceptor-plugin';
Expand Down Expand Up @@ -54,7 +52,7 @@ export interface GraphQLApiOptions {
export function configureGraphQLModule(
getOptions: (configService: ConfigService) => GraphQLApiOptions,
): DynamicModule {
return GraphQLModule.forRootAsync({
return GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
useFactory: (
configService: ConfigService,
Expand Down Expand Up @@ -90,7 +88,7 @@ async function createGraphQLOptions(
typesLoader: GraphQLTypesLoader,
customFieldRelationResolverService: CustomFieldRelationResolverService,
options: GraphQLApiOptions,
): Promise<GqlModuleOptions> {
): Promise<ApolloDriverConfig> {
const builtSchema = await buildSchemaForApi(options.apiType);
const resolvers = await generateResolvers(
configService,
Expand All @@ -107,7 +105,8 @@ async function createGraphQLOptions(
// We no longer rely on the upload facility bundled with Apollo Server, and instead
// manually configure the graphql-upload package. See https://github.com/vendure-ecommerce/vendure/issues/396
uploads: false,
playground: false,
playground: options.playground,
csrfPrevention: false,
debug: options.debug || false,
context: (req: any) => req,
// This is handled by the Express cors plugin
Expand All @@ -116,12 +115,11 @@ async function createGraphQLOptions(
new IdCodecPlugin(idCodecService),
new TranslateErrorsPlugin(i18nService),
new AssetInterceptorPlugin(configService),
...(options.playground ? [ApolloServerPluginLandingPageGraphQLPlayground()] : []),
...configService.apiOptions.apolloServerPlugins,
],
validationRules: options.validationRules,
introspection: configService.apiOptions.introspection ?? true,
} as GqlModuleOptions;
} as ApolloDriverConfig;

/**
* Generates the server's GraphQL schema by combining:
Expand Down
19 changes: 9 additions & 10 deletions packages/core/src/api/middleware/asset-interceptor-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Asset } from '@vendure/common/lib/generated-types';
import { ApolloServerPlugin, GraphQLRequestListener, GraphQLServiceContext } from 'apollo-server-plugin-base';
import { ApolloServerPlugin, GraphQLRequestListener, GraphQLServerContext } from '@apollo/server';
import { DocumentNode, GraphQLNamedType, isUnionType } from 'graphql';

import { AssetStorageStrategy } from '../../config/asset-storage-strategy/asset-storage-strategy';
Expand All @@ -23,29 +22,29 @@ export class AssetInterceptorPlugin implements ApolloServerPlugin {
}
}

async serverWillStart(service: GraphQLServiceContext): Promise<void> {
async serverWillStart(service: GraphQLServerContext): Promise<void> {
this.graphqlValueTransformer = new GraphqlValueTransformer(service.schema);
}

async requestDidStart(): Promise<GraphQLRequestListener> {
async requestDidStart(): Promise<GraphQLRequestListener<any>> {
return {
willSendResponse: async requestContext => {
const { document } = requestContext;
if (document) {
const data = requestContext.response.data;
const req = requestContext.context.req;
if (data) {
this.prefixAssetUrls(req, document, data);
const { body } = requestContext.response;
const req = requestContext.contextValue.req;
if (body.kind === 'single') {
this.prefixAssetUrls(req, document, body.singleResult.data);
}
}
},
};
}

private prefixAssetUrls(request: any, document: DocumentNode, data: Record<string, any>) {
private prefixAssetUrls(request: any, document: DocumentNode, data?: Record<string, unknown> | null) {
const typeTree = this.graphqlValueTransformer.getOutputTypeTree(document);
const toAbsoluteUrl = this.toAbsoluteUrl;
if (!toAbsoluteUrl) {
if (!toAbsoluteUrl || !data) {
return;
}
this.graphqlValueTransformer.transformValues(typeTree, data, (value, type) => {
Expand Down
19 changes: 11 additions & 8 deletions packages/core/src/api/middleware/id-codec-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ApolloServerPlugin, GraphQLRequestListener, GraphQLServerContext } from '@apollo/server';
import { isObject } from '@vendure/common/lib/shared-utils';
import { ApolloServerPlugin, GraphQLRequestListener, GraphQLServiceContext } from 'apollo-server-plugin-base';
import { DocumentNode, OperationDefinitionNode } from 'graphql';
import { DocumentNode } from 'graphql';

import { GraphqlValueTransformer } from '../common/graphql-value-transformer';
import { IdCodecService } from '../common/id-codec.service';
Expand All @@ -15,25 +15,28 @@ export class IdCodecPlugin implements ApolloServerPlugin {
private graphqlValueTransformer: GraphqlValueTransformer;
constructor(private idCodecService: IdCodecService) {}

async serverWillStart(service: GraphQLServiceContext): Promise<void> {
async serverWillStart(service: GraphQLServerContext): Promise<void> {
this.graphqlValueTransformer = new GraphqlValueTransformer(service.schema);
}

async requestDidStart(): Promise<GraphQLRequestListener> {
async requestDidStart(): Promise<GraphQLRequestListener<any>> {
return {
willSendResponse: async requestContext => {
const { document } = requestContext;
if (document) {
const data = requestContext.response.data;
if (data) {
this.encodeIdFields(document, data);
const { body } = requestContext.response;
if (body.kind === 'single') {
this.encodeIdFields(document, body.singleResult.data);
}
}
},
};
}

private encodeIdFields(document: DocumentNode, data: Record<string, any>) {
private encodeIdFields(document: DocumentNode, data?: Record<string, unknown> | null) {
if (!data) {
return;
}
const typeTree = this.graphqlValueTransformer.getOutputTypeTree(document);
this.graphqlValueTransformer.transformValues(typeTree, data, (value, type) => {
const isIdType = type && type.name === 'ID';
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/api/middleware/id-interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { IdOperators } from '@vendure/common/lib/generated-types';
import { VariableValues } from 'apollo-server-core';
import { GraphQLNamedType, GraphQLSchema, OperationDefinitionNode } from 'graphql';
import { Observable } from 'rxjs';

Expand Down Expand Up @@ -52,7 +51,7 @@ export class IdInterceptor implements NestInterceptor {
private decodeIdArguments(
graphqlValueTransformer: GraphqlValueTransformer,
definition: OperationDefinitionNode,
variables: VariableValues = {},
variables: Record<string, any> = {},
) {
const typeTree = graphqlValueTransformer.getInputTypeTree(definition);
graphqlValueTransformer.transformValues(typeTree, variables, (value, type) => {
Expand Down
14 changes: 7 additions & 7 deletions packages/core/src/api/middleware/translate-errors-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ApolloServerPlugin, GraphQLRequestListener } from 'apollo-server-plugin-base';
import { GraphQLError } from 'graphql';
import { ApolloServerPlugin, GraphQLRequestListener } from '@apollo/server';

import { I18nService } from '../../i18n/i18n.service';

Expand All @@ -10,13 +9,14 @@ import { I18nService } from '../../i18n/i18n.service';
export class TranslateErrorsPlugin implements ApolloServerPlugin {
constructor(private i18nService: I18nService) {}

async requestDidStart(): Promise<GraphQLRequestListener> {
async requestDidStart(): Promise<GraphQLRequestListener<any>> {
return {
willSendResponse: async requestContext => {
const { errors, context } = requestContext;
if (errors) {
(requestContext.response as any).errors = errors.map(err => {
return this.i18nService.translateError(context.req, err) as any;
const { errors, contextValue } = requestContext;
const { body } = requestContext.response;
if (errors && body.kind === 'single') {
body.singleResult.errors = errors.map(err => {
return this.i18nService.translateError(contextValue.req, err) as any;
});
}
},
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/config/vendure-config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ApolloServerPlugin } from '@apollo/server';
import { RenderPageOptions } from '@apollographql/graphql-playground-html';
import { DynamicModule, Type } from '@nestjs/common';
import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
import { LanguageCode } from '@vendure/common/lib/generated-types';
import { PluginDefinition } from 'apollo-server-core';
import { ValidationContext } from 'graphql';
import { DataSourceOptions } from 'typeorm';

Expand Down Expand Up @@ -96,15 +97,15 @@ export interface ApiOptions {
*
* @default false
*/
adminApiPlayground?: boolean | any;
adminApiPlayground?: boolean | RenderPageOptions;
/**
* @description
* The playground config to the shop GraphQL API
* [ApolloServer playground](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#constructoroptions-apolloserver).
*
* @default false
*/
shopApiPlayground?: boolean | any;
shopApiPlayground?: boolean | RenderPageOptions;
/**
* @description
* The debug config to the admin GraphQL API
Expand Down Expand Up @@ -186,7 +187,7 @@ export interface ApiOptions {
*
* @default []
*/
apolloServerPlugins?: PluginDefinition[];
apolloServerPlugins?: ApolloServerPlugin[];
/**
* @description
* Controls whether introspection of the GraphQL APIs is enabled. For production, it is recommended to disable
Expand Down
8 changes: 5 additions & 3 deletions packages/core/src/i18n/i18n-error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApolloError } from 'apollo-server-core';
import { GraphQLError } from 'graphql';

import { LogLevel } from '../config/logger/vendure-logger';

Expand All @@ -15,13 +15,15 @@ import { LogLevel } from '../config/logger/vendure-logger';
*
* @docsCategory errors
*/
export abstract class I18nError extends ApolloError {
export abstract class I18nError extends GraphQLError {
protected constructor(
public message: string,
public variables: { [key: string]: string | number } = {},
public code?: string,
public logLevel: LogLevel = LogLevel.Warn,
) {
super(message, code);
super(message, {
extensions: { code },
});
}
}
4 changes: 2 additions & 2 deletions packages/create/templates/vendure-config.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ export const config: VendureConfig = {
// reasons.
...(IS_DEV ? {
adminApiPlayground: {
settings: { 'request.credentials': 'include' } as any,
settings: { 'request.credentials': 'include' },
},
adminApiDebug: true,
shopApiPlayground: {
settings: { 'request.credentials': 'include' } as any,
settings: { 'request.credentials': 'include' },
},
shopApiDebug: true,
} : {}),
Expand Down
4 changes: 2 additions & 2 deletions packages/dev-server/dev-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export const devConfig: VendureConfig = {
adminApiPlayground: {
settings: {
'request.credentials': 'include',
} as any,
},
},
adminApiDebug: true,
shopApiPath: SHOP_API_PATH,
shopApiPlayground: {
settings: {
'request.credentials': 'include',
} as any,
},
},
shopApiDebug: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable, OnModuleInit } from '@nestjs/common';
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { JobState } from '@vendure/common/lib/generated-types';
import { JobQueue, JobQueueService, Logger, PluginCommonModule, VendurePlugin } from '@vendure/core';
import { gql } from 'apollo-server-core';
import { gql } from 'graphql-tag';
import { forkJoin, Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';

Expand Down
Loading

0 comments on commit b675fda

Please sign in to comment.