default-search-plugin.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import { Provider } from '@nestjs/common';
  2. import { SearchReindexResponse } from '@vendure/common/lib/generated-types';
  3. import { CREATING_VENDURE_APP } from '@vendure/common/lib/shared-constants';
  4. import { Type } from '@vendure/common/lib/shared-types';
  5. import gql from 'graphql-tag';
  6. import { idsAreEqual } from '../../common/utils';
  7. import { APIExtensionDefinition, VendurePlugin } from '../../config';
  8. import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
  9. import { Product } from '../../entity/product/product.entity';
  10. import { EventBus } from '../../event-bus/event-bus';
  11. import { CatalogModificationEvent } from '../../event-bus/events/catalog-modification-event';
  12. import { CollectionModificationEvent } from '../../event-bus/events/collection-modification-event';
  13. import { TaxRateModificationEvent } from '../../event-bus/events/tax-rate-modification-event';
  14. import { SearchService } from '../../service/services/search.service';
  15. import { SEARCH_PLUGIN_OPTIONS } from './constants';
  16. import { AdminFulltextSearchResolver, ShopFulltextSearchResolver } from './fulltext-search.resolver';
  17. import { FulltextSearchService } from './fulltext-search.service';
  18. import { SearchIndexService } from './indexer/search-index.service';
  19. import { SearchIndexItem } from './search-index-item.entity';
  20. export interface DefaultSearchReindexResponse extends SearchReindexResponse {
  21. timeTaken: number;
  22. indexedItemCount: number;
  23. }
  24. /**
  25. * @description
  26. * Options for configuring the DefaultSearchPlugin.
  27. *
  28. * @docsCategory DefaultSearchPlugin
  29. */
  30. export interface DefaultSearchPluginOptions {
  31. /**
  32. * @description
  33. * By default, the DefaultSearchPlugin will spawn a background process which is responsible
  34. * for updating the search index. By setting this option to `false`, indexing will be
  35. * performed on the main server process instead. Usually this is undesirable as performance will
  36. * be degraded during indexing, but the option is useful for certain debugging and testing scenarios.
  37. * @default true
  38. */
  39. runInForkedProcess: boolean;
  40. }
  41. /**
  42. * @description
  43. * The DefaultSearchPlugin provides a full-text Product search based on the full-text searching capabilities of the
  44. * underlying database.
  45. *
  46. * The DefaultSearchPlugin is bundled with the `@vendure/core` package. If you are not using an alternative search
  47. * plugin, then make sure this one is used, otherwise you will not be able to search products via the [`search` query](/docs/graphql-api/shop/queries#search).
  48. *
  49. * @example
  50. * ```ts
  51. * import { DefaultSearchPlugin } from '@vendure/core';
  52. *
  53. * const config: VendureConfig = {
  54. * // Add an instance of the plugin to the plugins array
  55. * plugins: [
  56. * new DefaultSearchPlugin(),
  57. * ],
  58. * };
  59. * ```
  60. *
  61. * {{% alert "warning" %}}
  62. * Note that the quality of the fulltext search capabilities varies depending on the underlying database being used. For example, the MySQL & Postgres implementations will typically yield better results than the SQLite implementation.
  63. * {{% /alert %}}
  64. *
  65. * @docsCategory DefaultSearchPlugin
  66. */
  67. export class DefaultSearchPlugin implements VendurePlugin {
  68. private readonly options: DefaultSearchPluginOptions;
  69. constructor(options?: DefaultSearchPluginOptions) {
  70. const defaultOptions: DefaultSearchPluginOptions = {
  71. runInForkedProcess: true,
  72. };
  73. this.options = { ...defaultOptions, ...options };
  74. if (process.env[CREATING_VENDURE_APP]) {
  75. // For the "create" step we will not run the indexer in a forked process as this
  76. // can cause issues with sqlite locking.
  77. this.options.runInForkedProcess = false;
  78. }
  79. }
  80. /** @internal */
  81. async onBootstrap(inject: <T>(type: Type<T>) => T): Promise<void> {
  82. const eventBus = inject(EventBus);
  83. const searchIndexService = inject(SearchIndexService);
  84. eventBus.subscribe(CatalogModificationEvent, event => {
  85. if (event.entity instanceof Product || event.entity instanceof ProductVariant) {
  86. return searchIndexService.updateProductOrVariant(event.ctx, event.entity);
  87. }
  88. });
  89. eventBus.subscribe(CollectionModificationEvent, event => {
  90. return searchIndexService.updateVariantsById(event.ctx, event.productVariantIds);
  91. });
  92. eventBus.subscribe(TaxRateModificationEvent, event => {
  93. const defaultTaxZone = event.ctx.channel.defaultTaxZone;
  94. if (defaultTaxZone && idsAreEqual(defaultTaxZone.id, event.taxRate.zone.id)) {
  95. return searchIndexService.reindex(event.ctx).start();
  96. }
  97. });
  98. await searchIndexService.connect();
  99. }
  100. /** @internal */
  101. extendAdminAPI(): APIExtensionDefinition {
  102. return {
  103. resolvers: [AdminFulltextSearchResolver],
  104. schema: gql`
  105. extend type SearchReindexResponse {
  106. timeTaken: Int!
  107. indexedItemCount: Int!
  108. }
  109. `,
  110. };
  111. }
  112. /** @internal */
  113. extendShopAPI(): APIExtensionDefinition {
  114. return {
  115. resolvers: [ShopFulltextSearchResolver],
  116. schema: gql`
  117. extend type SearchReindexResponse {
  118. timeTaken: Int!
  119. indexedItemCount: Int!
  120. }
  121. `,
  122. };
  123. }
  124. /** @internal */
  125. defineEntities(): Array<Type<any>> {
  126. return [SearchIndexItem];
  127. }
  128. /** @internal */
  129. defineProviders(): Provider[] {
  130. return [
  131. FulltextSearchService,
  132. SearchIndexService,
  133. { provide: SearchService, useClass: FulltextSearchService },
  134. { provide: SEARCH_PLUGIN_OPTIONS, useFactory: () => this.options },
  135. ];
  136. }
  137. }