configure-graphql-module.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import { DynamicModule } from '@nestjs/common';
  2. import { GqlModuleOptions, GraphQLModule, GraphQLTypesLoader } from '@nestjs/graphql';
  3. import { StockMovementType } from '@vendure/common/lib/generated-types';
  4. import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
  5. import { GraphQLUpload } from 'apollo-server-core';
  6. import { extendSchema, printSchema } from 'graphql';
  7. import { GraphQLDateTime } from 'graphql-iso-date';
  8. import GraphQLJSON from 'graphql-type-json';
  9. import path from 'path';
  10. import { ConfigModule } from '../../config/config.module';
  11. import { ConfigService } from '../../config/config.service';
  12. import { I18nModule } from '../../i18n/i18n.module';
  13. import { I18nService } from '../../i18n/i18n.service';
  14. import { getDynamicGraphQlModulesForPlugins } from '../../plugin/dynamic-plugin-api.module';
  15. import { getPluginAPIExtensions } from '../../plugin/plugin-metadata';
  16. import { ApiSharedModule } from '../api-internal-modules';
  17. import { IdCodecService } from '../common/id-codec.service';
  18. import { IdCodecPlugin } from '../middleware/id-codec-plugin';
  19. import { TranslateErrorExtension } from '../middleware/translate-errors-extension';
  20. import { generateListOptions } from './generate-list-options';
  21. import {
  22. addGraphQLCustomFields,
  23. addOrderLineCustomFieldsInput,
  24. addServerConfigCustomFields,
  25. } from './graphql-custom-fields';
  26. export interface GraphQLApiOptions {
  27. apiType: 'shop' | 'admin';
  28. typePaths: string[];
  29. apiPath: string;
  30. // tslint:disable-next-line:ban-types
  31. resolverModule: Function;
  32. }
  33. /**
  34. * Dynamically generates a GraphQLModule according to the given config options.
  35. */
  36. export function configureGraphQLModule(
  37. getOptions: (configService: ConfigService) => GraphQLApiOptions,
  38. ): DynamicModule {
  39. return GraphQLModule.forRootAsync({
  40. useFactory: (
  41. configService: ConfigService,
  42. i18nService: I18nService,
  43. idCodecService: IdCodecService,
  44. typesLoader: GraphQLTypesLoader,
  45. ) => {
  46. return createGraphQLOptions(
  47. i18nService,
  48. configService,
  49. idCodecService,
  50. typesLoader,
  51. getOptions(configService),
  52. );
  53. },
  54. inject: [ConfigService, I18nService, IdCodecService, GraphQLTypesLoader],
  55. imports: [ConfigModule, I18nModule, ApiSharedModule],
  56. });
  57. }
  58. async function createGraphQLOptions(
  59. i18nService: I18nService,
  60. configService: ConfigService,
  61. idCodecService: IdCodecService,
  62. typesLoader: GraphQLTypesLoader,
  63. options: GraphQLApiOptions,
  64. ): Promise<GqlModuleOptions> {
  65. // Prevent `Type "Node" is missing a "resolveType" resolver.` warnings.
  66. // See https://github.com/apollographql/apollo-server/issues/1075
  67. const dummyResolveType = {
  68. __resolveType() {
  69. return null;
  70. },
  71. };
  72. const stockMovementResolveType = {
  73. __resolveType(value: any) {
  74. switch (value.type) {
  75. case StockMovementType.ADJUSTMENT:
  76. return 'StockAdjustment';
  77. case StockMovementType.SALE:
  78. return 'Sale';
  79. case StockMovementType.CANCELLATION:
  80. return 'Cancellation';
  81. case StockMovementType.RETURN:
  82. return 'Return';
  83. }
  84. },
  85. };
  86. const customFieldsConfigResolveType = {
  87. __resolveType(value: any) {
  88. switch (value.type) {
  89. case 'string':
  90. return 'StringCustomFieldConfig';
  91. case 'localeString':
  92. return 'LocaleStringCustomFieldConfig';
  93. case 'int':
  94. return 'IntCustomFieldConfig';
  95. case 'float':
  96. return 'FloatCustomFieldConfig';
  97. case 'boolean':
  98. return 'BooleanCustomFieldConfig';
  99. case 'datetime':
  100. return 'DateTimeCustomFieldConfig';
  101. }
  102. },
  103. };
  104. return {
  105. path: '/' + options.apiPath,
  106. typeDefs: await createTypeDefs(options.apiType),
  107. include: [options.resolverModule, ...getDynamicGraphQlModulesForPlugins(options.apiType)],
  108. resolvers: {
  109. JSON: GraphQLJSON,
  110. DateTime: GraphQLDateTime,
  111. Node: dummyResolveType,
  112. PaginatedList: dummyResolveType,
  113. Upload: GraphQLUpload || dummyResolveType,
  114. SearchResultPrice: {
  115. __resolveType(value: any) {
  116. return value.hasOwnProperty('value') ? 'SinglePrice' : 'PriceRange';
  117. },
  118. },
  119. StockMovementItem: stockMovementResolveType,
  120. StockMovement: stockMovementResolveType,
  121. CustomFieldConfig: customFieldsConfigResolveType,
  122. CustomField: customFieldsConfigResolveType,
  123. },
  124. uploads: {
  125. maxFileSize: configService.assetOptions.uploadMaxFileSize,
  126. },
  127. playground: {
  128. settings: {
  129. 'request.credentials': 'include',
  130. } as any,
  131. },
  132. debug: true,
  133. context: (req: any) => req,
  134. extensions: [() => new TranslateErrorExtension(i18nService)],
  135. // This is handled by the Express cors plugin
  136. cors: false,
  137. plugins: [new IdCodecPlugin(idCodecService)],
  138. } as GqlModuleOptions;
  139. /**
  140. * Generates the server's GraphQL schema by combining:
  141. * 1. the default schema as defined in the source .graphql files specified by `typePaths`
  142. * 2. any custom fields defined in the config
  143. * 3. any schema extensions defined by plugins
  144. */
  145. async function createTypeDefs(apiType: 'shop' | 'admin'): Promise<string> {
  146. const customFields = configService.customFields;
  147. // Paths must be normalized to use forward-slash separators.
  148. // See https://github.com/nestjs/graphql/issues/336
  149. const normalizedPaths = options.typePaths.map(p => p.split(path.sep).join('/'));
  150. const typeDefs = await typesLoader.mergeTypesByPaths(normalizedPaths);
  151. let schema = generateListOptions(typeDefs);
  152. schema = addGraphQLCustomFields(schema, customFields, apiType === 'shop');
  153. schema = addServerConfigCustomFields(schema, customFields);
  154. schema = addOrderLineCustomFieldsInput(schema, customFields.OrderLine || []);
  155. const pluginSchemaExtensions = getPluginAPIExtensions(configService.plugins, apiType)
  156. .map(e => e.schema)
  157. .filter(notNullOrUndefined);
  158. for (const documentNode of pluginSchemaExtensions) {
  159. schema = extendSchema(schema, documentNode);
  160. }
  161. return printSchema(schema);
  162. }
  163. }