options.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. import { ClientOptions } from '@elastic/elasticsearch';
  2. import { DeepRequired, ID, Product, ProductVariant } from '@vendure/core';
  3. import deepmerge from 'deepmerge';
  4. import { CustomMapping, ElasticSearchInput } from './types';
  5. /**
  6. * @description
  7. * Configuration options for the {@link ElasticsearchPlugin}.
  8. *
  9. * @docsCategory ElasticsearchPlugin
  10. * @docsPage ElasticsearchOptions
  11. */
  12. export interface ElasticsearchOptions {
  13. /**
  14. * @description
  15. * The host of the Elasticsearch server. May also be specified in `clientOptions.node`.
  16. *
  17. * @default 'http://localhost'
  18. */
  19. host?: string;
  20. /**
  21. * @description
  22. * The port of the Elasticsearch server. May also be specified in `clientOptions.node`.
  23. *
  24. * @default 9200
  25. */
  26. port?: number;
  27. /**
  28. * @description
  29. * Options to pass directly to the
  30. * [Elasticsearch Node.js client](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html). For example, to
  31. * set authentication or other more advanced options.
  32. * Note that if the `node` or `nodes` option is specified, it will override the values provided in the `host` and `port` options.
  33. */
  34. clientOptions?: ClientOptions;
  35. /**
  36. * @description
  37. * Prefix for the indices created by the plugin.
  38. *
  39. * @default
  40. * 'vendure-'
  41. */
  42. indexPrefix?: string;
  43. /**
  44. * @description
  45. * Batch size for bulk operations (e.g. when rebuilding the indices).
  46. *
  47. * @default
  48. * 2000
  49. */
  50. batchSize?: number;
  51. /**
  52. * @description
  53. * Configuration of the internal Elasticseach query.
  54. */
  55. searchConfig?: SearchConfig;
  56. /**
  57. * @description
  58. * Custom mappings may be defined which will add the defined data to the
  59. * Elasticsearch index and expose that data via the SearchResult GraphQL type,
  60. * adding a new `customMappings` field.
  61. *
  62. * The `graphQlType` property may be one of `String`, `Int`, `Float`, `Boolean` and
  63. * can be appended with a `!` to indicate non-nullable fields.
  64. *
  65. * This config option defines custom mappings which are accessible when the "groupByProduct"
  66. * input options is set to `true`.
  67. *
  68. * @example
  69. * ```TypeScript
  70. * customProductMappings: {
  71. * variantCount: {
  72. * graphQlType: 'Int!',
  73. * valueFn: (product, variants) => variants.length,
  74. * },
  75. * reviewRating: {
  76. * graphQlType: 'Float',
  77. * valueFn: product => (product.customFields as any).reviewRating,
  78. * },
  79. * }
  80. * ```
  81. *
  82. * @example
  83. * ```SDL
  84. * query SearchProducts($input: SearchInput!) {
  85. * search(input: $input) {
  86. * totalItems
  87. * items {
  88. * productId
  89. * productName
  90. * customMappings {
  91. * ...on CustomProductMappings {
  92. * variantCount
  93. * reviewRating
  94. * }
  95. * }
  96. * }
  97. * }
  98. * }
  99. * ```
  100. */
  101. customProductMappings?: {
  102. [fieldName: string]: CustomMapping<[Product, ProductVariant[]]>;
  103. };
  104. /**
  105. * @description
  106. * This config option defines custom mappings which are accessible when the "groupByProduct"
  107. * input options is set to `false`.
  108. *
  109. * @example
  110. * ```SDL
  111. * query SearchProducts($input: SearchInput!) {
  112. * search(input: $input) {
  113. * totalItems
  114. * items {
  115. * productId
  116. * productName
  117. * customMappings {
  118. * ...on CustomProductVariantMappings {
  119. * weight
  120. * }
  121. * }
  122. * }
  123. * }
  124. * }
  125. * ```
  126. */
  127. customProductVariantMappings?: {
  128. [fieldName: string]: CustomMapping<[ProductVariant]>;
  129. };
  130. }
  131. /**
  132. * @description
  133. * Configuration options for the internal Elasticsearch query which is generated when performing a search.
  134. *
  135. * @docsCategory ElasticsearchPlugin
  136. * @docsPage ElasticsearchOptions
  137. */
  138. export interface SearchConfig {
  139. /**
  140. * @description
  141. * The maximum number of FacetValues to return from the search query. Internally, this
  142. * value sets the "size" property of an Elasticsearch aggregation.
  143. *
  144. * @default
  145. * 50
  146. */
  147. facetValueMaxSize?: number;
  148. // prettier-ignore
  149. /**
  150. * @description
  151. * Defines the
  152. * [multi match type](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#multi-match-types)
  153. * used when matching against a search term.
  154. *
  155. * @default
  156. * 'best_fields'
  157. */
  158. multiMatchType?: 'best_fields' | 'most_fields' | 'cross_fields' | 'phrase' | 'phrase_prefix' | 'bool_prefix';
  159. /**
  160. * @description
  161. * Set custom boost values for particular fields when matching against a search term.
  162. */
  163. boostFields?: BoostFieldsConfig;
  164. /**
  165. * @description
  166. * The interval used to group search results into buckets according to price range. For example, setting this to
  167. * `2000` will group into buckets every $20.00:
  168. *
  169. * ```JSON
  170. * {
  171. * "data": {
  172. * "search": {
  173. * "totalItems": 32,
  174. * "priceRange": {
  175. * "buckets": [
  176. * {
  177. * "to": 2000,
  178. * "count": 21
  179. * },
  180. * {
  181. * "to": 4000,
  182. * "count": 7
  183. * },
  184. * {
  185. * "to": 6000,
  186. * "count": 3
  187. * },
  188. * {
  189. * "to": 12000,
  190. * "count": 1
  191. * }
  192. * ]
  193. * }
  194. * }
  195. * }
  196. * }
  197. * ```
  198. */
  199. priceRangeBucketInterval?: number;
  200. /**
  201. * @description
  202. * This config option allows the the modification of the whole (already built) search query. This allows
  203. * for e.g. wildcard / fuzzy searches on the index.
  204. *
  205. * @example
  206. * ```TypeScript
  207. * mapQuery: (query, input, searchConfig, channelId, enabledOnly){
  208. * if(query.bool.must){
  209. * delete query.bool.must;
  210. * }
  211. * query.bool.should = [
  212. * {
  213. * query_string: {
  214. * query: "*" + term + "*",
  215. * fields: [
  216. * `productName^${searchConfig.boostFields.productName}`,
  217. * `productVariantName^${searchConfig.boostFields.productVariantName}`,
  218. * ]
  219. * }
  220. * },
  221. * {
  222. * multi_match: {
  223. * query: term,
  224. * type: searchConfig.multiMatchType,
  225. * fields: [
  226. * `description^${searchConfig.boostFields.description}`,
  227. * `sku^${searchConfig.boostFields.sku}`,
  228. * ],
  229. * },
  230. * },
  231. * ];
  232. *
  233. * return query;
  234. * }
  235. * ```
  236. */
  237. mapQuery?: (
  238. query: any,
  239. input: ElasticSearchInput,
  240. searchConfig: DeepRequired<SearchConfig>,
  241. channelId: ID,
  242. enabledOnly: boolean,
  243. ) => any;
  244. }
  245. /**
  246. * @description
  247. * Configuration for [boosting](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html#field-boost)
  248. * the scores of given fields when performing a search against a term.
  249. *
  250. * Boosting a field acts as a score multiplier for matches against that field.
  251. *
  252. * @docsCategory ElasticsearchPlugin
  253. * @docsPage ElasticsearchOptions
  254. */
  255. export interface BoostFieldsConfig {
  256. /**
  257. * @description
  258. * Defines the boost factor for the productName field.
  259. *
  260. * @default 1
  261. */
  262. productName?: number;
  263. /**
  264. * @description
  265. * Defines the boost factor for the productVariantName field.
  266. *
  267. * @default 1
  268. */
  269. productVariantName?: number;
  270. /**
  271. * @description
  272. * Defines the boost factor for the description field.
  273. *
  274. * @default 1
  275. */
  276. description?: number;
  277. /**
  278. * @description
  279. * Defines the boost factor for the sku field.
  280. *
  281. * @default 1
  282. */
  283. sku?: number;
  284. }
  285. export type ElasticsearchRuntimeOptions = DeepRequired<Omit<ElasticsearchOptions, 'clientOptions'>> & {
  286. clientOptions?: ClientOptions;
  287. };
  288. export const defaultOptions: ElasticsearchRuntimeOptions = {
  289. host: 'http://localhost',
  290. port: 9200,
  291. indexPrefix: 'vendure-',
  292. batchSize: 2000,
  293. searchConfig: {
  294. facetValueMaxSize: 50,
  295. multiMatchType: 'best_fields',
  296. boostFields: {
  297. productName: 1,
  298. productVariantName: 1,
  299. description: 1,
  300. sku: 1,
  301. },
  302. priceRangeBucketInterval: 1000,
  303. mapQuery: query => query,
  304. },
  305. customProductMappings: {},
  306. customProductVariantMappings: {},
  307. };
  308. export function mergeWithDefaults(userOptions: ElasticsearchOptions): ElasticsearchRuntimeOptions {
  309. const { clientOptions, ...pluginOptions } = userOptions;
  310. const merged = deepmerge(defaultOptions, pluginOptions) as ElasticsearchRuntimeOptions;
  311. return { ...merged, clientOptions };
  312. }