vendure-plugin.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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 } 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. /**
  45. * @description
  46. * An object which allows a plugin to extend the Vendure GraphQL API.
  47. *
  48. * @docsCategory plugin
  49. * @docsPage VendurePluginMetadata
  50. * */
  51. export interface APIExtensionDefinition {
  52. /**
  53. * @description
  54. * Extensions to the schema.
  55. *
  56. * @example
  57. * ```TypeScript
  58. * const schema = gql`extend type SearchReindexResponse {
  59. * timeTaken: Int!
  60. * indexedItemCount: Int!
  61. * }`;
  62. * ```
  63. */
  64. schema?: DocumentNode | (() => DocumentNode);
  65. /**
  66. * @description
  67. * An array of resolvers for the schema extensions. Should be defined as [Nestjs GraphQL resolver](https://docs.nestjs.com/graphql/resolvers-map)
  68. * classes, i.e. using the Nest `\@Resolver()` decorator etc.
  69. */
  70. resolvers: Array<Type<any>> | (() => Array<Type<any>>);
  71. }
  72. /**
  73. * @description
  74. * This method is called before the app bootstraps and should be used to perform any needed modifications to the {@link VendureConfig}.
  75. *
  76. * @docsCategory plugin
  77. * @docsPage VendurePluginMetadata
  78. */
  79. export type PluginConfigurationFn = (
  80. config: RuntimeVendureConfig,
  81. ) => RuntimeVendureConfig | Promise<RuntimeVendureConfig>;
  82. /**
  83. * @description
  84. * The VendurePlugin decorator is a means of configuring and/or extending the functionality of the Vendure server. A Vendure plugin is
  85. * a [Nestjs Module](https://docs.nestjs.com/modules), with optional additional metadata defining things like extensions to the GraphQL API, custom
  86. * configuration or new database entities.
  87. *
  88. * As well as configuring the app, a plugin may also extend the GraphQL schema by extending existing types or adding
  89. * entirely new types. Database entities and resolvers can also be defined to handle the extended GraphQL types.
  90. *
  91. * @example
  92. * ```TypeScript
  93. * import { Controller, Get } from '\@nestjs/common';
  94. * import { Ctx, PluginCommonModule, ProductService, RequestContext, VendurePlugin } from '\@vendure/core';
  95. *
  96. * \@Controller('products')
  97. * export class ProductsController {
  98. * constructor(private productService: ProductService) {}
  99. *
  100. * \@Get()
  101. * findAll(\@Ctx() ctx: RequestContext) {
  102. * return this.productService.findAll(ctx);
  103. * }
  104. * }
  105. *
  106. *
  107. * //A simple plugin which adds a REST endpoint for querying products.
  108. * \@VendurePlugin({
  109. * imports: [PluginCommonModule],
  110. * controllers: [ProductsController],
  111. * })
  112. * export class RestPlugin {}
  113. * ```
  114. *
  115. * @docsCategory plugin
  116. */
  117. export function VendurePlugin(pluginMetadata: VendurePluginMetadata): ClassDecorator {
  118. // tslint:disable-next-line:ban-types
  119. return (target: Function) => {
  120. for (const metadataProperty of Object.values(PLUGIN_METADATA)) {
  121. const property = metadataProperty as keyof VendurePluginMetadata;
  122. if (pluginMetadata[property] != null) {
  123. Reflect.defineMetadata(property, pluginMetadata[property], target);
  124. }
  125. }
  126. const nestModuleMetadata = pick(pluginMetadata, Object.values(MODULE_METADATA) as any);
  127. // Automatically add any of the Plugin's "providers" to the "exports" array. This is done
  128. // because when a plugin defines GraphQL resolvers, these resolvers are used to dynamically
  129. // created a new Module in the ApiModule, and if those resolvers depend on any providers,
  130. // the must be exported. See the function {@link createDynamicGraphQlModulesForPlugins}
  131. // for the implementation.
  132. // However, we must omit any global providers (https://github.com/vendure-ecommerce/vendure/issues/837)
  133. const nestGlobalProviderTokens = [APP_INTERCEPTOR, APP_FILTER, APP_GUARD, APP_PIPE];
  134. const exportedProviders = (nestModuleMetadata.providers || []).filter(provider => {
  135. if (isNamedProvider(provider)) {
  136. if (nestGlobalProviderTokens.includes(provider.provide as any)) {
  137. return false;
  138. }
  139. }
  140. return true;
  141. });
  142. nestModuleMetadata.exports = [...(nestModuleMetadata.exports || []), ...exportedProviders];
  143. Module(nestModuleMetadata)(target);
  144. };
  145. }
  146. function isNamedProvider(provider: Provider): provider is Exclude<Provider, NestType<any>> {
  147. return provider.hasOwnProperty('provide');
  148. }