options.ts 9.7 KB

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