vendure-plugin.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import { Module, Provider, Type as NestType } from '@nestjs/common';
  2. import { MODULE_METADATA } from '@nestjs/common/constants';
  3. import { ModuleMetadata } from '@nestjs/common/interfaces';
  4. import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
  5. import { pick } from '@vendure/common/lib/pick';
  6. import { Type } from '@vendure/common/lib/shared-types';
  7. import { DocumentNode, GraphQLScalarType } from 'graphql';
  8. import { RuntimeVendureConfig } from '../config/vendure-config';
  9. import { PLUGIN_METADATA } from './plugin-metadata';
  10. /**
  11. * @description
  12. * Defines the metadata of a Vendure plugin. This interface is an superset of the [Nestjs ModuleMetadata](https://docs.nestjs.com/modules)
  13. * (which allows the definition of `imports`, `exports`, `providers` and `controllers`), which means
  14. * that any Nestjs Module is a valid Vendure plugin. In addition, the VendurePluginMetadata allows the definition of
  15. * extra properties specific to Vendure.
  16. *
  17. * @docsCategory plugin
  18. * @docsPage VendurePluginMetadata
  19. */
  20. export interface VendurePluginMetadata extends ModuleMetadata {
  21. /**
  22. * @description
  23. * A function which can modify the {@link VendureConfig} object before the server bootstraps.
  24. */
  25. configuration?: PluginConfigurationFn;
  26. /**
  27. * @description
  28. * The plugin may extend the default Vendure GraphQL shop api by providing extended
  29. * schema definitions and any required resolvers.
  30. */
  31. shopApiExtensions?: APIExtensionDefinition;
  32. /**
  33. * @description
  34. * The plugin may extend the default Vendure GraphQL admin api by providing extended
  35. * schema definitions and any required resolvers.
  36. */
  37. adminApiExtensions?: APIExtensionDefinition;
  38. /**
  39. * @description
  40. * The plugin may define custom [TypeORM database entities](https://typeorm.io/#/entities).
  41. */
  42. entities?: Array<Type<any>> | (() => Array<Type<any>>);
  43. /**
  44. * @description
  45. * The plugin should define a valid [semver version string](https://www.npmjs.com/package/semver) to indicate which versions of
  46. * Vendure core it is compatible with. Attempting to use a plugin with an incompatible
  47. * version of Vendure will result in an error and the server will be unable to bootstrap.
  48. *
  49. * If a plugin does not define this property, a message will be logged on bootstrap that the plugin is not
  50. * guaranteed to be compatible with the current version of Vendure.
  51. *
  52. * To effectively disable this check for a plugin, you can use an overly-permissive string such as `>0.0.0`.
  53. *
  54. * @example
  55. * ```ts
  56. * compatibility: '^2.0.0'
  57. * ```
  58. *
  59. * @since 2.0.0
  60. */
  61. compatibility?: string;
  62. }
  63. /**
  64. * @description
  65. * An object which allows a plugin to extend the Vendure GraphQL API.
  66. *
  67. * @docsCategory plugin
  68. * @docsPage VendurePluginMetadata
  69. * */
  70. export interface APIExtensionDefinition {
  71. /**
  72. * @description
  73. * Extensions to the schema.
  74. *
  75. * @example
  76. * ```ts
  77. * const schema = gql`extend type SearchReindexResponse {
  78. * timeTaken: Int!
  79. * indexedItemCount: Int!
  80. * }`;
  81. * ```
  82. */
  83. schema?: DocumentNode | (() => DocumentNode | undefined);
  84. /**
  85. * @description
  86. * An array of resolvers for the schema extensions. Should be defined as [Nestjs GraphQL resolver](https://docs.nestjs.com/graphql/resolvers-map)
  87. * classes, i.e. using the Nest `\@Resolver()` decorator etc.
  88. */
  89. resolvers?: Array<Type<any>> | (() => Array<Type<any>>);
  90. /**
  91. * @description
  92. * A map of GraphQL scalar types which should correspond to any custom scalars defined in your schema.
  93. * Read more about defining custom scalars in the
  94. * [Apollo Server Custom Scalars docs](https://www.apollographql.com/docs/apollo-server/schema/custom-scalars)
  95. *
  96. * @since 1.7.0
  97. */
  98. scalars?: Record<string, GraphQLScalarType> | (() => Record<string, GraphQLScalarType>);
  99. }
  100. /**
  101. * @description
  102. * This method is called before the app bootstraps and should be used to perform any needed modifications to the {@link VendureConfig}.
  103. *
  104. * @docsCategory plugin
  105. * @docsPage VendurePluginMetadata
  106. */
  107. export type PluginConfigurationFn = (
  108. config: RuntimeVendureConfig,
  109. ) => RuntimeVendureConfig | Promise<RuntimeVendureConfig>;
  110. /**
  111. * @description
  112. * The VendurePlugin decorator is a means of configuring and/or extending the functionality of the Vendure server. A Vendure plugin is
  113. * a [Nestjs Module](https://docs.nestjs.com/modules), with optional additional metadata defining things like extensions to the GraphQL API, custom
  114. * configuration or new database entities.
  115. *
  116. * As well as configuring the app, a plugin may also extend the GraphQL schema by extending existing types or adding
  117. * entirely new types. Database entities and resolvers can also be defined to handle the extended GraphQL types.
  118. *
  119. * @example
  120. * ```ts
  121. * import { Controller, Get } from '\@nestjs/common';
  122. * import { Ctx, PluginCommonModule, ProductService, RequestContext, VendurePlugin } from '\@vendure/core';
  123. *
  124. * \@Controller('products')
  125. * export class ProductsController {
  126. * constructor(private productService: ProductService) {}
  127. *
  128. * \@Get()
  129. * findAll(\@Ctx() ctx: RequestContext) {
  130. * return this.productService.findAll(ctx);
  131. * }
  132. * }
  133. *
  134. *
  135. * //A simple plugin which adds a REST endpoint for querying products.
  136. * \@VendurePlugin({
  137. * imports: [PluginCommonModule],
  138. * controllers: [ProductsController],
  139. * })
  140. * export class RestPlugin {}
  141. * ```
  142. *
  143. * @docsCategory plugin
  144. */
  145. export function VendurePlugin(pluginMetadata: VendurePluginMetadata): ClassDecorator {
  146. // eslint-disable-next-line @typescript-eslint/ban-types
  147. return (target: Function) => {
  148. for (const metadataProperty of Object.values(PLUGIN_METADATA)) {
  149. const property = metadataProperty as keyof VendurePluginMetadata;
  150. if (pluginMetadata[property] != null) {
  151. Reflect.defineMetadata(property, pluginMetadata[property], target);
  152. }
  153. }
  154. const nestModuleMetadata = pick(pluginMetadata, Object.values(MODULE_METADATA) as any);
  155. // Automatically add any of the Plugin's "providers" to the "exports" array. This is done
  156. // because when a plugin defines GraphQL resolvers, these resolvers are used to dynamically
  157. // created a new Module in the ApiModule, and if those resolvers depend on any providers,
  158. // the must be exported. See the function {@link createDynamicGraphQlModulesForPlugins}
  159. // for the implementation.
  160. // However, we must omit any global providers (https://github.com/vendure-ecommerce/vendure/issues/837)
  161. const nestGlobalProviderTokens = [APP_INTERCEPTOR, APP_FILTER, APP_GUARD, APP_PIPE];
  162. const exportedProviders = (nestModuleMetadata.providers || []).filter(provider => {
  163. if (isNamedProvider(provider)) {
  164. if (nestGlobalProviderTokens.includes(provider.provide as any)) {
  165. return false;
  166. }
  167. }
  168. return true;
  169. });
  170. nestModuleMetadata.exports = [...(nestModuleMetadata.exports || []), ...exportedProviders];
  171. Module(nestModuleMetadata)(target);
  172. };
  173. }
  174. function isNamedProvider(provider: Provider): provider is Exclude<Provider, NestType<any>> {
  175. return provider.hasOwnProperty('provide');
  176. }