elasticlink 0.2.1-beta → 0.3.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +162 -163
  2. package/dist/aggregation.builder.d.ts +4 -2
  3. package/dist/aggregation.builder.d.ts.map +1 -1
  4. package/dist/aggregation.builder.js +1 -1
  5. package/dist/aggregation.types.d.ts +15 -14
  6. package/dist/aggregation.types.d.ts.map +1 -1
  7. package/dist/bulk.builder.d.ts +115 -1
  8. package/dist/bulk.builder.d.ts.map +1 -1
  9. package/dist/bulk.builder.js +1 -1
  10. package/dist/field.helpers.d.ts +39 -44
  11. package/dist/field.helpers.d.ts.map +1 -1
  12. package/dist/field.helpers.js +15 -19
  13. package/dist/field.types.d.ts +35 -5
  14. package/dist/field.types.d.ts.map +1 -1
  15. package/dist/field.types.js +3 -3
  16. package/dist/index-management.builder.d.ts +5 -12
  17. package/dist/index-management.builder.d.ts.map +1 -1
  18. package/dist/index-management.builder.js +10 -32
  19. package/dist/index-management.types.d.ts +10 -64
  20. package/dist/index-management.types.d.ts.map +1 -1
  21. package/dist/index-management.types.js +0 -1
  22. package/dist/index.d.ts +16 -12
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +10 -8
  25. package/dist/mapping.builder.d.ts +24 -0
  26. package/dist/mapping.builder.d.ts.map +1 -0
  27. package/dist/mapping.builder.js +23 -0
  28. package/dist/mapping.types.d.ts +82 -0
  29. package/dist/mapping.types.d.ts.map +1 -0
  30. package/dist/mapping.types.js +6 -0
  31. package/dist/multi-search.builder.d.ts +5 -3
  32. package/dist/multi-search.builder.d.ts.map +1 -1
  33. package/dist/multi-search.builder.js +2 -2
  34. package/dist/multi-search.types.d.ts +7 -6
  35. package/dist/multi-search.types.d.ts.map +1 -1
  36. package/dist/query.builder.d.ts +3 -7
  37. package/dist/query.builder.d.ts.map +1 -1
  38. package/dist/query.builder.js +30 -92
  39. package/dist/query.types.d.ts +85 -235
  40. package/dist/query.types.d.ts.map +1 -1
  41. package/dist/query.types.js +2 -1
  42. package/dist/suggester.builder.d.ts +5 -3
  43. package/dist/suggester.builder.d.ts.map +1 -1
  44. package/dist/suggester.builder.js +2 -2
  45. package/dist/suggester.types.d.ts +5 -4
  46. package/dist/suggester.types.d.ts.map +1 -1
  47. package/package.json +7 -7
  48. package/dist/__tests__/aggregation-builder.test.d.ts +0 -2
  49. package/dist/__tests__/aggregation-builder.test.d.ts.map +0 -1
  50. package/dist/__tests__/aggregation-builder.test.js +0 -662
  51. package/dist/__tests__/bulk.test.d.ts +0 -2
  52. package/dist/__tests__/bulk.test.d.ts.map +0 -1
  53. package/dist/__tests__/bulk.test.js +0 -684
  54. package/dist/__tests__/examples.test.d.ts +0 -2
  55. package/dist/__tests__/examples.test.d.ts.map +0 -1
  56. package/dist/__tests__/examples.test.js +0 -2006
  57. package/dist/__tests__/fixtures/finance.d.ts +0 -26
  58. package/dist/__tests__/fixtures/finance.d.ts.map +0 -1
  59. package/dist/__tests__/fixtures/finance.js +0 -32
  60. package/dist/__tests__/fixtures/legal.d.ts +0 -8
  61. package/dist/__tests__/fixtures/legal.d.ts.map +0 -1
  62. package/dist/__tests__/fixtures/legal.js +0 -20
  63. package/dist/__tests__/fixtures/real-estate.d.ts +0 -19
  64. package/dist/__tests__/fixtures/real-estate.d.ts.map +0 -1
  65. package/dist/__tests__/fixtures/real-estate.js +0 -20
  66. package/dist/__tests__/index-management.test.d.ts +0 -2
  67. package/dist/__tests__/index-management.test.d.ts.map +0 -1
  68. package/dist/__tests__/index-management.test.js +0 -1148
  69. package/dist/__tests__/integration/aggregation.integration.test.d.ts +0 -2
  70. package/dist/__tests__/integration/aggregation.integration.test.d.ts.map +0 -1
  71. package/dist/__tests__/integration/aggregation.integration.test.js +0 -200
  72. package/dist/__tests__/integration/bulk.integration.test.d.ts +0 -2
  73. package/dist/__tests__/integration/bulk.integration.test.d.ts.map +0 -1
  74. package/dist/__tests__/integration/bulk.integration.test.js +0 -93
  75. package/dist/__tests__/integration/fixtures/finance.d.ts +0 -17
  76. package/dist/__tests__/integration/fixtures/finance.d.ts.map +0 -1
  77. package/dist/__tests__/integration/fixtures/finance.js +0 -42
  78. package/dist/__tests__/integration/fixtures/legal.d.ts +0 -21
  79. package/dist/__tests__/integration/fixtures/legal.d.ts.map +0 -1
  80. package/dist/__tests__/integration/fixtures/legal.js +0 -52
  81. package/dist/__tests__/integration/fixtures/real-estate.d.ts +0 -7
  82. package/dist/__tests__/integration/fixtures/real-estate.d.ts.map +0 -1
  83. package/dist/__tests__/integration/fixtures/real-estate.js +0 -22
  84. package/dist/__tests__/integration/helpers.d.ts +0 -15
  85. package/dist/__tests__/integration/helpers.d.ts.map +0 -1
  86. package/dist/__tests__/integration/helpers.js +0 -21
  87. package/dist/__tests__/integration/index-management.integration.test.d.ts +0 -2
  88. package/dist/__tests__/integration/index-management.integration.test.d.ts.map +0 -1
  89. package/dist/__tests__/integration/index-management.integration.test.js +0 -79
  90. package/dist/__tests__/integration/multi-search.integration.test.d.ts +0 -2
  91. package/dist/__tests__/integration/multi-search.integration.test.d.ts.map +0 -1
  92. package/dist/__tests__/integration/multi-search.integration.test.js +0 -55
  93. package/dist/__tests__/integration/query.integration.test.d.ts +0 -2
  94. package/dist/__tests__/integration/query.integration.test.d.ts.map +0 -1
  95. package/dist/__tests__/integration/query.integration.test.js +0 -118
  96. package/dist/__tests__/integration/suggester.integration.test.d.ts +0 -2
  97. package/dist/__tests__/integration/suggester.integration.test.d.ts.map +0 -1
  98. package/dist/__tests__/integration/suggester.integration.test.js +0 -48
  99. package/dist/__tests__/multi-search.test.d.ts +0 -2
  100. package/dist/__tests__/multi-search.test.d.ts.map +0 -1
  101. package/dist/__tests__/multi-search.test.js +0 -336
  102. package/dist/__tests__/query-builder.test.d.ts +0 -2
  103. package/dist/__tests__/query-builder.test.d.ts.map +0 -1
  104. package/dist/__tests__/query-builder.test.js +0 -5624
  105. package/dist/__tests__/suggester.test.d.ts +0 -2
  106. package/dist/__tests__/suggester.test.d.ts.map +0 -1
  107. package/dist/__tests__/suggester.test.js +0 -1001
@@ -1,2006 +0,0 @@
1
- import { query, aggregations, suggest, msearch, bulk, indexBuilder, text, keyword, integer, float, date, scaledFloat, halfFloat, denseVector } from '..';
2
- describe('Real-world Usage Examples', () => {
3
- describe('E-commerce Product Search', () => {
4
- it('should build a basic product search query', () => {
5
- const searchTerm = 'equity';
6
- const result = query()
7
- .match('name', searchTerm, { operator: 'and', boost: 2 })
8
- .from(0)
9
- .size(20)
10
- .build();
11
- expect(result).toMatchInlineSnapshot(`
12
- {
13
- "from": 0,
14
- "query": {
15
- "match": {
16
- "name": {
17
- "boost": 2,
18
- "operator": "and",
19
- "query": "equity",
20
- },
21
- },
22
- },
23
- "size": 20,
24
- }
25
- `);
26
- });
27
- it('should build an advanced product search with filters and highlighting', () => {
28
- const searchTerm = 'large-cap tech';
29
- const category = 'technology';
30
- const minPrice = 1_000_000_000;
31
- const maxPrice = 5_000_000_000;
32
- const result = query()
33
- .bool()
34
- .must((q) => q.match('name', searchTerm, { operator: 'and', boost: 2 }))
35
- .should((q) => q.fuzzy('description', searchTerm, { fuzziness: 'AUTO' }))
36
- .filter((q) => q.term('asset_class', category))
37
- .filter((q) => q.range('market_cap', {
38
- gte: minPrice,
39
- lte: maxPrice
40
- }))
41
- .minimumShouldMatch(0)
42
- .highlight(['name', 'description'], {
43
- fragment_size: 150,
44
- number_of_fragments: 2,
45
- pre_tags: ['<mark>'],
46
- post_tags: ['</mark>']
47
- })
48
- .timeout('5s')
49
- .trackScores(true)
50
- .from(0)
51
- .size(20)
52
- .sort('market_cap', 'asc')
53
- .build();
54
- expect(result).toMatchInlineSnapshot(`
55
- {
56
- "from": 0,
57
- "highlight": {
58
- "fields": {
59
- "description": {
60
- "fragment_size": 150,
61
- "number_of_fragments": 2,
62
- },
63
- "name": {
64
- "fragment_size": 150,
65
- "number_of_fragments": 2,
66
- },
67
- },
68
- "post_tags": [
69
- "</mark>",
70
- ],
71
- "pre_tags": [
72
- "<mark>",
73
- ],
74
- },
75
- "query": {
76
- "bool": {
77
- "filter": [
78
- {
79
- "term": {
80
- "asset_class": "technology",
81
- },
82
- },
83
- {
84
- "range": {
85
- "market_cap": {
86
- "gte": 1000000000,
87
- "lte": 5000000000,
88
- },
89
- },
90
- },
91
- ],
92
- "minimum_should_match": 0,
93
- "must": [
94
- {
95
- "match": {
96
- "name": {
97
- "boost": 2,
98
- "operator": "and",
99
- "query": "large-cap tech",
100
- },
101
- },
102
- },
103
- ],
104
- "should": [
105
- {
106
- "fuzzy": {
107
- "description": {
108
- "fuzziness": "AUTO",
109
- "value": "large-cap tech",
110
- },
111
- },
112
- },
113
- ],
114
- },
115
- },
116
- "size": 20,
117
- "sort": [
118
- {
119
- "market_cap": "asc",
120
- },
121
- ],
122
- "timeout": "5s",
123
- "track_scores": true,
124
- }
125
- `);
126
- });
127
- it('should build a dynamic product search with conditional filters', () => {
128
- const searchTerm = 'equity';
129
- const selectedCategory = 'technology';
130
- const minPrice = undefined;
131
- const maxPrice = undefined;
132
- const selectedTags = ['large-cap', 'sp500'];
133
- const result = query()
134
- .bool()
135
- .must((q) => q.when(searchTerm, (q2) => q2.match('name', searchTerm, {
136
- operator: 'and',
137
- boost: 2
138
- })) || q.matchAll())
139
- .filter((q) => q.when(selectedCategory, (q2) => q2.term('asset_class', selectedCategory)) || q.matchAll())
140
- .filter((q) => q.when(minPrice && maxPrice, (q2) => q2.range('market_cap', {
141
- gte: minPrice,
142
- lte: maxPrice
143
- })) || q.matchAll())
144
- .filter((q) => q.when(selectedTags && selectedTags.length > 0, (q2) => q2.terms('tags', selectedTags)) || q.matchAll())
145
- .timeout('5s')
146
- .from(0)
147
- .size(20)
148
- .build();
149
- expect(result).toMatchInlineSnapshot(`
150
- {
151
- "from": 0,
152
- "query": {
153
- "bool": {
154
- "filter": [
155
- {
156
- "term": {
157
- "asset_class": "technology",
158
- },
159
- },
160
- {
161
- "match_all": {},
162
- },
163
- {
164
- "terms": {
165
- "tags": [
166
- "large-cap",
167
- "sp500",
168
- ],
169
- },
170
- },
171
- ],
172
- "must": [
173
- {
174
- "match": {
175
- "name": {
176
- "boost": 2,
177
- "operator": "and",
178
- "query": "equity",
179
- },
180
- },
181
- },
182
- ],
183
- },
184
- },
185
- "size": 20,
186
- "timeout": "5s",
187
- }
188
- `);
189
- });
190
- it('should build an autocomplete-style product search', () => {
191
- const userInput = 'equ';
192
- const result = query()
193
- .bool()
194
- .must((q) => q.matchPhrasePrefix('name', userInput, { max_expansions: 20 }))
195
- .highlight(['name'], { fragment_size: 100 })
196
- .trackTotalHits(true)
197
- .from(0)
198
- .size(10)
199
- .build();
200
- expect(result).toMatchInlineSnapshot(`
201
- {
202
- "from": 0,
203
- "highlight": {
204
- "fields": {
205
- "name": {
206
- "fragment_size": 100,
207
- },
208
- },
209
- },
210
- "query": {
211
- "bool": {
212
- "must": [
213
- {
214
- "match_phrase_prefix": {
215
- "name": {
216
- "max_expansions": 20,
217
- "query": "equ",
218
- },
219
- },
220
- },
221
- ],
222
- },
223
- },
224
- "size": 10,
225
- "track_total_hits": true,
226
- }
227
- `);
228
- });
229
- });
230
- describe('Content/Article Search', () => {
231
- it('should build a blog article search with full-text and meta filters', () => {
232
- const searchTerm = 'elasticsearch performance';
233
- const authorName = 'john';
234
- const startDate = '2024-01-01';
235
- const result = query()
236
- .bool()
237
- .must((q) => q.multiMatch(['title', 'content'], searchTerm, {
238
- type: 'best_fields',
239
- operator: 'and'
240
- }))
241
- .should((q) => q.fuzzy('author', authorName, { fuzziness: 'AUTO', boost: 2 }))
242
- .filter((q) => q.range('published_date', {
243
- gte: startDate
244
- }))
245
- .minimumShouldMatch(0)
246
- .highlight(['title', 'content'], {
247
- fragment_size: 200,
248
- number_of_fragments: 3,
249
- pre_tags: ['<em>'],
250
- post_tags: ['</em>']
251
- })
252
- .timeout('10s')
253
- .trackTotalHits(10000)
254
- .from(0)
255
- .size(15)
256
- .sort('published_date', 'desc')
257
- .build();
258
- expect(result).toMatchInlineSnapshot(`
259
- {
260
- "from": 0,
261
- "highlight": {
262
- "fields": {
263
- "content": {
264
- "fragment_size": 200,
265
- "number_of_fragments": 3,
266
- },
267
- "title": {
268
- "fragment_size": 200,
269
- "number_of_fragments": 3,
270
- },
271
- },
272
- "post_tags": [
273
- "</em>",
274
- ],
275
- "pre_tags": [
276
- "<em>",
277
- ],
278
- },
279
- "query": {
280
- "bool": {
281
- "filter": [
282
- {
283
- "range": {
284
- "published_date": {
285
- "gte": "2024-01-01",
286
- },
287
- },
288
- },
289
- ],
290
- "minimum_should_match": 0,
291
- "must": [
292
- {
293
- "multi_match": {
294
- "fields": [
295
- "title",
296
- "content",
297
- ],
298
- "operator": "and",
299
- "query": "elasticsearch performance",
300
- "type": "best_fields",
301
- },
302
- },
303
- ],
304
- "should": [
305
- {
306
- "fuzzy": {
307
- "author": {
308
- "boost": 2,
309
- "fuzziness": "AUTO",
310
- "value": "john",
311
- },
312
- },
313
- },
314
- ],
315
- },
316
- },
317
- "size": 15,
318
- "sort": [
319
- {
320
- "published_date": "desc",
321
- },
322
- ],
323
- "timeout": "10s",
324
- "track_total_hits": 10000,
325
- }
326
- `);
327
- });
328
- it('should build a search for articles with a specific author', () => {
329
- const authorName = 'jane';
330
- const result = query()
331
- .bool()
332
- .must((q) => q.match('author', authorName))
333
- .filter((q) => q.range('published_date', {
334
- gte: '2024-01-01'
335
- }))
336
- .highlight(['title'], {
337
- fragment_size: 150,
338
- pre_tags: ['<strong>'],
339
- post_tags: ['</strong>']
340
- })
341
- .from(0)
342
- .size(20)
343
- .build();
344
- expect(result).toMatchInlineSnapshot(`
345
- {
346
- "from": 0,
347
- "highlight": {
348
- "fields": {
349
- "title": {
350
- "fragment_size": 150,
351
- },
352
- },
353
- "post_tags": [
354
- "</strong>",
355
- ],
356
- "pre_tags": [
357
- "<strong>",
358
- ],
359
- },
360
- "query": {
361
- "bool": {
362
- "filter": [
363
- {
364
- "range": {
365
- "published_date": {
366
- "gte": "2024-01-01",
367
- },
368
- },
369
- },
370
- ],
371
- "must": [
372
- {
373
- "match": {
374
- "author": "jane",
375
- },
376
- },
377
- ],
378
- },
379
- },
380
- "size": 20,
381
- }
382
- `);
383
- });
384
- });
385
- describe('Document Management Search', () => {
386
- it('should build a document search with ID-based filtering', () => {
387
- const documentIds = ['doc-123', 'doc-456', 'doc-789'];
388
- const searchTerm = 'meeting notes';
389
- const result = query()
390
- .bool()
391
- .must((q) => q.ids(documentIds))
392
- .should((q) => q.when(searchTerm, (q2) => q2.multiMatch(['title', 'content'], searchTerm, {
393
- operator: 'and'
394
- })) || q.matchAll())
395
- .minimumShouldMatch(0)
396
- .highlight(['title', 'content'], {
397
- fragment_size: 100,
398
- number_of_fragments: 2
399
- })
400
- .from(0)
401
- .size(50)
402
- .build();
403
- expect(result).toMatchInlineSnapshot(`
404
- {
405
- "from": 0,
406
- "highlight": {
407
- "fields": {
408
- "content": {
409
- "fragment_size": 100,
410
- "number_of_fragments": 2,
411
- },
412
- "title": {
413
- "fragment_size": 100,
414
- "number_of_fragments": 2,
415
- },
416
- },
417
- },
418
- "query": {
419
- "bool": {
420
- "minimum_should_match": 0,
421
- "must": [
422
- {
423
- "ids": {
424
- "values": [
425
- "doc-123",
426
- "doc-456",
427
- "doc-789",
428
- ],
429
- },
430
- },
431
- ],
432
- "should": [
433
- {
434
- "multi_match": {
435
- "fields": [
436
- "title",
437
- "content",
438
- ],
439
- "operator": "and",
440
- "query": "meeting notes",
441
- },
442
- },
443
- ],
444
- },
445
- },
446
- "size": 50,
447
- }
448
- `);
449
- });
450
- it('should build a dynamic document search with multiple conditional filters', () => {
451
- const searchTerm = 'quarterly report';
452
- const startDate = '2024-01-01';
453
- const endDate = undefined;
454
- const result = query()
455
- .bool()
456
- .must((q) => q.when(searchTerm, (q2) => q2.match('content', searchTerm, {
457
- operator: 'and'
458
- })) || q.matchAll())
459
- .filter((q) => q.when(true, (q2) => q2.terms('tags', ['finance'])) || q.matchAll())
460
- .filter((q) => q.when(startDate && endDate, (q2) => q2.range('published_date', {
461
- gte: startDate,
462
- lte: endDate
463
- })) || q.matchAll())
464
- .filter((q) => q.matchAll())
465
- .explain(true)
466
- .trackTotalHits(true)
467
- .from(0)
468
- .size(25)
469
- .sort('published_date', 'desc')
470
- .build();
471
- expect(result).toMatchInlineSnapshot(`
472
- {
473
- "explain": true,
474
- "from": 0,
475
- "query": {
476
- "bool": {
477
- "filter": [
478
- {
479
- "terms": {
480
- "tags": [
481
- "finance",
482
- ],
483
- },
484
- },
485
- {
486
- "match_all": {},
487
- },
488
- {
489
- "match_all": {},
490
- },
491
- ],
492
- "must": [
493
- {
494
- "match": {
495
- "content": {
496
- "operator": "and",
497
- "query": "quarterly report",
498
- },
499
- },
500
- },
501
- ],
502
- },
503
- },
504
- "size": 25,
505
- "sort": [
506
- {
507
- "published_date": "desc",
508
- },
509
- ],
510
- "track_total_hits": true,
511
- }
512
- `);
513
- });
514
- });
515
- describe('Search UX Patterns', () => {
516
- it('should build a search-as-you-type query', () => {
517
- const userTypedPrefix = 'ela';
518
- const result = query()
519
- .matchPhrasePrefix('name', userTypedPrefix, { max_expansions: 50 })
520
- .highlight(['name'], {
521
- fragment_size: 80,
522
- pre_tags: ['<em>'],
523
- post_tags: ['</em>']
524
- })
525
- .from(0)
526
- .size(5)
527
- .timeout('2s')
528
- .build();
529
- expect(result).toMatchInlineSnapshot(`
530
- {
531
- "from": 0,
532
- "highlight": {
533
- "fields": {
534
- "name": {
535
- "fragment_size": 80,
536
- },
537
- },
538
- "post_tags": [
539
- "</em>",
540
- ],
541
- "pre_tags": [
542
- "<em>",
543
- ],
544
- },
545
- "query": {
546
- "match_phrase_prefix": {
547
- "name": {
548
- "max_expansions": 50,
549
- "query": "ela",
550
- },
551
- },
552
- },
553
- "size": 5,
554
- "timeout": "2s",
555
- }
556
- `);
557
- });
558
- it('should build a faceted search query', () => {
559
- const searchTerm = 'gaming';
560
- const facetFilters = {
561
- categories: ['technology', 'computing'],
562
- priceRange: { min: 500, max: 3000 },
563
- minRating: 4
564
- };
565
- const result = query()
566
- .bool()
567
- .must((q) => q.match('name', searchTerm, { boost: 2, operator: 'and' }))
568
- .filter((q) => q.term('asset_class', facetFilters.categories[0]))
569
- .filter((q) => q.range('market_cap', {
570
- gte: facetFilters.priceRange.min,
571
- lte: facetFilters.priceRange.max
572
- }))
573
- .filter((q) => q.range('credit_rating', { gte: facetFilters.minRating }))
574
- .highlight(['name'], { fragment_size: 100 })
575
- .timeout('5s')
576
- .from(0)
577
- .size(20)
578
- .sort('market_cap', 'asc')
579
- .build();
580
- expect(result).toMatchInlineSnapshot(`
581
- {
582
- "from": 0,
583
- "highlight": {
584
- "fields": {
585
- "name": {
586
- "fragment_size": 100,
587
- },
588
- },
589
- },
590
- "query": {
591
- "bool": {
592
- "filter": [
593
- {
594
- "term": {
595
- "asset_class": "technology",
596
- },
597
- },
598
- {
599
- "range": {
600
- "market_cap": {
601
- "gte": 500,
602
- "lte": 3000,
603
- },
604
- },
605
- },
606
- {
607
- "range": {
608
- "credit_rating": {
609
- "gte": 4,
610
- },
611
- },
612
- },
613
- ],
614
- "must": [
615
- {
616
- "match": {
617
- "name": {
618
- "boost": 2,
619
- "operator": "and",
620
- "query": "gaming",
621
- },
622
- },
623
- },
624
- ],
625
- },
626
- },
627
- "size": 20,
628
- "sort": [
629
- {
630
- "market_cap": "asc",
631
- },
632
- ],
633
- "timeout": "5s",
634
- }
635
- `);
636
- });
637
- it('should build an error-resilient search with typo tolerance', () => {
638
- const userQuery = 'laptpo'; // Intentional typo
639
- const result = query()
640
- .bool()
641
- .must((q) => q.fuzzy('name', userQuery, {
642
- fuzziness: 'AUTO',
643
- boost: 2
644
- }))
645
- .should((q) => q.fuzzy('description', userQuery, { fuzziness: 'AUTO' }))
646
- .minimumShouldMatch(1)
647
- .highlight(['name', 'description'])
648
- .explain(true)
649
- .from(0)
650
- .size(10)
651
- .build();
652
- expect(result).toMatchInlineSnapshot(`
653
- {
654
- "explain": true,
655
- "from": 0,
656
- "highlight": {
657
- "fields": {
658
- "description": {},
659
- "name": {},
660
- },
661
- },
662
- "query": {
663
- "bool": {
664
- "minimum_should_match": 1,
665
- "must": [
666
- {
667
- "fuzzy": {
668
- "name": {
669
- "boost": 2,
670
- "fuzziness": "AUTO",
671
- "value": "laptpo",
672
- },
673
- },
674
- },
675
- ],
676
- "should": [
677
- {
678
- "fuzzy": {
679
- "description": {
680
- "fuzziness": "AUTO",
681
- "value": "laptpo",
682
- },
683
- },
684
- },
685
- ],
686
- },
687
- },
688
- "size": 10,
689
- }
690
- `);
691
- });
692
- });
693
- describe('Aggregations & Geo Queries', () => {
694
- it('should aggregate products by category with price statistics', () => {
695
- const agg = aggregations()
696
- .terms('by_category', 'asset_class', { size: 10 })
697
- .subAgg((sub) => sub
698
- .avg('average_price', 'market_cap')
699
- .max('highest_price', 'market_cap')
700
- .min('lowest_price', 'market_cap'))
701
- .build();
702
- expect(agg).toMatchInlineSnapshot(`
703
- {
704
- "by_category": {
705
- "aggs": {
706
- "average_price": {
707
- "avg": {
708
- "field": "market_cap",
709
- },
710
- },
711
- "highest_price": {
712
- "max": {
713
- "field": "market_cap",
714
- },
715
- },
716
- "lowest_price": {
717
- "min": {
718
- "field": "market_cap",
719
- },
720
- },
721
- },
722
- "terms": {
723
- "field": "asset_class",
724
- "size": 10,
725
- },
726
- },
727
- }
728
- `);
729
- });
730
- it('should analyze products sold over time with daily breakdown', () => {
731
- const agg = aggregations()
732
- .dateHistogram('sales_timeline', 'listed_date', {
733
- interval: 'day',
734
- min_doc_count: 1
735
- })
736
- .subAgg((sub) => sub
737
- .sum('daily_revenue', 'market_cap')
738
- .cardinality('unique_categories', 'asset_class', {
739
- precision_threshold: 100
740
- }))
741
- .build();
742
- expect(agg).toMatchInlineSnapshot(`
743
- {
744
- "sales_timeline": {
745
- "aggs": {
746
- "daily_revenue": {
747
- "sum": {
748
- "field": "market_cap",
749
- },
750
- },
751
- "unique_categories": {
752
- "cardinality": {
753
- "field": "asset_class",
754
- "precision_threshold": 100,
755
- },
756
- },
757
- },
758
- "date_histogram": {
759
- "field": "listed_date",
760
- "interval": "day",
761
- "min_doc_count": 1,
762
- },
763
- },
764
- }
765
- `);
766
- });
767
- it('should find restaurants near a location', () => {
768
- const result = query()
769
- .match('cuisine', 'italian')
770
- .geoDistance('location', { lat: 40.7128, lon: -74.006 }, { distance: '5km' })
771
- .size(20)
772
- .build();
773
- expect(result).toMatchInlineSnapshot(`
774
- {
775
- "query": {
776
- "geo_distance": {
777
- "distance": "5km",
778
- "location": {
779
- "lat": 40.7128,
780
- "lon": -74.006,
781
- },
782
- },
783
- },
784
- "size": 20,
785
- }
786
- `);
787
- });
788
- it('should search in a geographic bounding box', () => {
789
- const result = query()
790
- .geoBoundingBox('location', {
791
- top_left: { lat: 40.8, lon: -74.1 },
792
- bottom_right: { lat: 40.7, lon: -74.0 }
793
- })
794
- .build();
795
- expect(result).toMatchInlineSnapshot(`
796
- {
797
- "query": {
798
- "geo_bounding_box": {
799
- "location": {
800
- "bottom_right": {
801
- "lat": 40.7,
802
- "lon": -74,
803
- },
804
- "top_left": {
805
- "lat": 40.8,
806
- "lon": -74.1,
807
- },
808
- },
809
- },
810
- },
811
- }
812
- `);
813
- });
814
- it('should find products matching a pattern', () => {
815
- const result = query()
816
- .regexp('asset_class', 'elec.*', { flags: 'CASE_INSENSITIVE' })
817
- .build();
818
- expect(result).toMatchInlineSnapshot(`
819
- {
820
- "query": {
821
- "regexp": {
822
- "asset_class": {
823
- "flags": "CASE_INSENSITIVE",
824
- "value": "elec.*",
825
- },
826
- },
827
- },
828
- }
829
- `);
830
- });
831
- it('should use constant_score for efficient filtering', () => {
832
- const result = query()
833
- .constantScore((q) => q.term('asset_class', 'technology'), {
834
- boost: 1.2
835
- })
836
- .build();
837
- expect(result).toMatchInlineSnapshot(`
838
- {
839
- "query": {
840
- "constant_score": {
841
- "boost": 1.2,
842
- "filter": {
843
- "term": {
844
- "asset_class": "technology",
845
- },
846
- },
847
- },
848
- },
849
- }
850
- `);
851
- });
852
- it('should combine geo search with aggregations for store analytics', () => {
853
- const queryResult = query()
854
- .match('name', 'convenience')
855
- .geoDistance('coordinates', { lat: 40.7128, lon: -74.006 }, { distance: '10km' })
856
- .build();
857
- const agg = aggregations()
858
- .terms('by_district', 'district', { size: 5 })
859
- .subAgg((sub) => sub
860
- .avg('avg_rating', 'rating')
861
- .valueCount('total_items', 'item_count'))
862
- .build();
863
- expect(queryResult.query?.geo_distance).toBeDefined();
864
- expect(agg.by_district).toBeDefined();
865
- });
866
- });
867
- describe('Vector Search & Semantic Search', () => {
868
- it('should build a basic semantic product search', () => {
869
- // Simulated embedding vector for "wireless headphones"
870
- const searchEmbedding = [0.23, 0.45, 0.67, 0.12, 0.89, 0.34, 0.56, 0.78];
871
- const result = query()
872
- .knn('embedding', searchEmbedding, {
873
- k: 10,
874
- num_candidates: 100
875
- })
876
- .size(10)
877
- ._source(['name', 'description', 'market_cap', 'prospectus_url'])
878
- .build();
879
- expect(result).toMatchInlineSnapshot(`
880
- {
881
- "_source": [
882
- "name",
883
- "description",
884
- "market_cap",
885
- "prospectus_url",
886
- ],
887
- "knn": {
888
- "field": "embedding",
889
- "k": 10,
890
- "num_candidates": 100,
891
- "query_vector": [
892
- 0.23,
893
- 0.45,
894
- 0.67,
895
- 0.12,
896
- 0.89,
897
- 0.34,
898
- 0.56,
899
- 0.78,
900
- ],
901
- },
902
- "size": 10,
903
- }
904
- `);
905
- });
906
- it('should build semantic search with category filtering', () => {
907
- const queryVector = [0.1, 0.2, 0.3, 0.4, 0.5];
908
- const result = query()
909
- .knn('embedding', queryVector, {
910
- k: 20,
911
- num_candidates: 200,
912
- filter: {
913
- bool: {
914
- must: [{ term: { category: 'technology' } }],
915
- filter: [{ range: { price: { gte: 100, lte: 1000 } } }]
916
- }
917
- }
918
- })
919
- .size(20)
920
- .build();
921
- expect(result.knn?.filter).toBeDefined();
922
- expect(result.knn?.filter.bool.must).toHaveLength(1);
923
- expect(result.knn?.filter.bool.filter).toHaveLength(1);
924
- });
925
- it('should build image similarity search', () => {
926
- // Simulated 512-dimensional image embedding (e.g., from ResNet)
927
- const imageEmbedding = new Array(512)
928
- .fill(0)
929
- .map((_, i) => Math.sin(i / 100));
930
- const result = query()
931
- .knn('embedding', imageEmbedding, {
932
- k: 50,
933
- num_candidates: 500,
934
- similarity: 0.7,
935
- boost: 1.2
936
- })
937
- .size(50)
938
- ._source(['isin', 'name', 'prospectus_url'])
939
- .build();
940
- expect(result.knn?.query_vector).toHaveLength(512);
941
- expect(result.knn?.similarity).toBe(0.7);
942
- expect(result.knn?.boost).toBe(1.2);
943
- });
944
- it('should build product recommendation engine query', () => {
945
- // Current product's embedding
946
- const currentProductEmbedding = [0.45, 0.23, 0.67, 0.89, 0.12];
947
- const result = query()
948
- .knn('embedding', currentProductEmbedding, {
949
- k: 10,
950
- num_candidates: 100,
951
- filter: {
952
- bool: {
953
- must_not: [{ term: { id: 'current-product-123' } }],
954
- must: [{ term: { category: 'technology' } }]
955
- }
956
- }
957
- })
958
- .size(10)
959
- ._source(['isin', 'name', 'market_cap', 'prospectus_url'])
960
- .build();
961
- expect(result.knn?.filter?.bool?.must_not).toBeDefined();
962
- expect(result.size).toBe(10);
963
- });
964
- it('should build semantic document search with aggregations', () => {
965
- // Search embedding for "machine learning best practices"
966
- const queryEmbedding = new Array(384).fill(0).map((_, i) => i / 384);
967
- const result = query()
968
- .knn('embedding', queryEmbedding, {
969
- k: 50,
970
- num_candidates: 500,
971
- filter: {
972
- range: {
973
- published_date: { gte: '2023-01-01' }
974
- }
975
- }
976
- })
977
- .aggs((agg) => agg
978
- .terms('top_authors', 'author', { size: 10 })
979
- .terms('popular_tags', 'tags', { size: 20 }))
980
- .size(20)
981
- .build();
982
- expect(result.knn?.query_vector).toHaveLength(384);
983
- expect(result.aggs?.top_authors).toBeDefined();
984
- expect(result.aggs?.popular_tags).toBeDefined();
985
- });
986
- it('should build multilingual semantic search', () => {
987
- // Embedding from multilingual model (e.g., multilingual-E5)
988
- const multilingualEmbedding = new Array(768)
989
- .fill(0)
990
- .map(() => Math.random());
991
- const result = query()
992
- .knn('embedding', multilingualEmbedding, {
993
- k: 30,
994
- num_candidates: 300,
995
- boost: 1.5
996
- })
997
- .size(30)
998
- ._source(['title', 'content', 'author'])
999
- .highlight(['title', 'content'], {
1000
- fragment_size: 150,
1001
- number_of_fragments: 3
1002
- })
1003
- .build();
1004
- expect(result.knn?.query_vector).toHaveLength(768);
1005
- expect(result.highlight).toBeDefined();
1006
- });
1007
- it('should build OpenAI embedding search (1536 dimensions)', () => {
1008
- // OpenAI text-embedding-ada-002 produces 1536-dimensional vectors
1009
- const openaiEmbedding = new Array(1536).fill(0).map(() => Math.random());
1010
- const result = query()
1011
- .knn('embedding', openaiEmbedding, {
1012
- k: 10,
1013
- num_candidates: 100,
1014
- filter: {
1015
- bool: {
1016
- must: [{ term: { author: 'john-doe' } }]
1017
- }
1018
- }
1019
- })
1020
- .size(10)
1021
- .from(0)
1022
- .build();
1023
- expect(result.knn?.query_vector).toHaveLength(1536);
1024
- expect(result.knn?.filter).toBeDefined();
1025
- });
1026
- it('should build hybrid semantic + price ranking', () => {
1027
- const productEmbedding = [0.5, 0.3, 0.8, 0.2, 0.6];
1028
- const result = query()
1029
- .knn('embedding', productEmbedding, {
1030
- k: 100,
1031
- num_candidates: 1000,
1032
- filter: {
1033
- bool: {
1034
- filter: [
1035
- { range: { price: { gte: 50 } } },
1036
- { term: { category: 'technology' } }
1037
- ]
1038
- }
1039
- }
1040
- })
1041
- .size(20)
1042
- .sort('market_cap', 'asc')
1043
- ._source(['isin', 'name', 'market_cap'])
1044
- .build();
1045
- expect(result.knn?.filter).toBeDefined();
1046
- expect(result.sort).toMatchInlineSnapshot(`
1047
- [
1048
- {
1049
- "market_cap": "asc",
1050
- },
1051
- ]
1052
- `);
1053
- });
1054
- it('should build semantic search with quality thresholding', () => {
1055
- const queryVector = [0.7, 0.2, 0.5, 0.9, 0.1];
1056
- const result = query()
1057
- .knn('embedding', queryVector, {
1058
- k: 20,
1059
- num_candidates: 200,
1060
- similarity: 0.85 // Only return highly similar results
1061
- })
1062
- .size(20)
1063
- .minScore(0.8) // Additional relevance threshold
1064
- .build();
1065
- expect(result.knn?.similarity).toBe(0.85);
1066
- expect(result.min_score).toBe(0.8);
1067
- });
1068
- it('should build "more like this" recommendation query', () => {
1069
- // Reference item's embedding
1070
- const referenceEmbedding = [0.33, 0.66, 0.22, 0.88, 0.44];
1071
- const excludeIds = ['ref-item-1', 'ref-item-2', 'ref-item-3'];
1072
- const result = query()
1073
- .knn('embedding', referenceEmbedding, {
1074
- k: 15,
1075
- num_candidates: 150,
1076
- filter: {
1077
- bool: {
1078
- must_not: excludeIds.map((id) => ({ term: { id } }))
1079
- }
1080
- }
1081
- })
1082
- .size(15)
1083
- ._source(['isin', 'name', 'description', 'market_cap', 'asset_class'])
1084
- .build();
1085
- expect(result.knn?.filter?.bool?.must_not).toHaveLength(3);
1086
- expect(result.size).toBe(15);
1087
- });
1088
- });
1089
- describe('Script Queries & Custom Scoring', () => {
1090
- it('should build dynamic price filter with script', () => {
1091
- const result = query()
1092
- .bool()
1093
- .must((q) => q.match('name', 'laptop'))
1094
- .filter((q) => q.script({
1095
- source: "doc['price'].value > params.threshold",
1096
- params: { threshold: 500 }
1097
- }))
1098
- .build();
1099
- expect(result.query?.bool?.filter).toHaveLength(1);
1100
- expect(result.query?.bool?.filter[0].script).toBeDefined();
1101
- });
1102
- it('should build custom popularity scoring', () => {
1103
- const result = query()
1104
- .scriptScore((q) => q.match('name', 'smartphone'), {
1105
- source: "_score * Math.log(2 + doc['popularity'].value)"
1106
- })
1107
- .size(20)
1108
- .build();
1109
- expect(result.query?.script_score?.query?.match).toBeDefined();
1110
- expect(result.query?.script_score?.script?.source).toContain('popularity');
1111
- });
1112
- it('should build weighted quality + popularity score', () => {
1113
- const result = query()
1114
- .scriptScore((q) => q.multiMatch(['name'], 'premium headphones', {
1115
- type: 'best_fields'
1116
- }), {
1117
- source: `
1118
- double quality = doc['quality_score'].value;
1119
- double popularity = doc['popularity'].value;
1120
- return _score * (quality * 0.7 + popularity * 0.3);
1121
- `.trim(),
1122
- params: {}
1123
- }, { min_score: 5.0 })
1124
- .size(10)
1125
- .build();
1126
- expect(result.query?.script_score?.min_score).toBe(5.0);
1127
- });
1128
- it('should build personalized recommendation scoring', () => {
1129
- const userPreferences = {
1130
- price_weight: 0.3,
1131
- quality_weight: 0.5,
1132
- popularity_weight: 0.2
1133
- };
1134
- const result = query()
1135
- .scriptScore((q) => q.term('id', 'prod-123'), {
1136
- source: `
1137
- double price_score = 1.0 / (1.0 + doc['price'].value / 1000);
1138
- double quality_score = doc['quality_score'].value / 10.0;
1139
- double popularity_score = Math.log(1 + doc['popularity'].value) / 10.0;
1140
-
1141
- return _score * (
1142
- price_score * params.price_weight +
1143
- quality_score * params.quality_weight +
1144
- popularity_score * params.popularity_weight
1145
- );
1146
- `.trim(),
1147
- params: userPreferences
1148
- })
1149
- .size(50)
1150
- .build();
1151
- expect(result.query?.script_score?.script?.params).toEqual(userPreferences);
1152
- });
1153
- it('should build time-decay scoring for trending products', () => {
1154
- const result = query()
1155
- .scriptScore((q) => q.matchAll(), {
1156
- source: `
1157
- double views = doc['views'].value;
1158
- double rating = doc['rating'].value;
1159
- return Math.log(1 + views) * rating;
1160
- `.trim()
1161
- }, { min_score: 1.0, boost: 1.5 })
1162
- .sort('popularity', 'desc')
1163
- .size(20)
1164
- .build();
1165
- expect(result.query?.script_score?.boost).toBe(1.5);
1166
- expect(result.sort).toEqual([{ popularity: 'desc' }]);
1167
- });
1168
- });
1169
- describe('Percolate Queries & Alert Matching', () => {
1170
- it('should match log entry against saved alert rules', () => {
1171
- const logEntry = {
1172
- level: 'ERROR',
1173
- message: 'Database connection failed',
1174
- timestamp: '2024-01-15T10:30:00Z',
1175
- source: 'api-server'
1176
- };
1177
- const result = query()
1178
- .percolate({
1179
- field: 'query',
1180
- document: logEntry
1181
- })
1182
- .size(100)
1183
- .build();
1184
- expect(result.query?.percolate?.document).toEqual(logEntry);
1185
- });
1186
- it('should classify multiple documents', () => {
1187
- const articles = [
1188
- { title: 'AI Breakthrough', content: 'Machine learning advances' },
1189
- { title: 'Market Update', content: 'Stock prices surge' },
1190
- { title: 'Sports News', content: 'Team wins championship' }
1191
- ];
1192
- const result = query()
1193
- .percolate({
1194
- field: 'query',
1195
- documents: articles
1196
- })
1197
- ._source(['name', 'category'])
1198
- .size(50)
1199
- .build();
1200
- expect(result.query?.percolate?.documents).toHaveLength(3);
1201
- expect(result._source).toContain('category');
1202
- });
1203
- it('should match against stored document', () => {
1204
- const result = query()
1205
- .percolate({
1206
- field: 'query',
1207
- index: 'user_content',
1208
- id: 'content-789',
1209
- routing: 'user-123'
1210
- })
1211
- .size(20)
1212
- .build();
1213
- expect(result.query?.percolate?.index).toBe('user_content');
1214
- expect(result.query?.percolate?.routing).toBe('user-123');
1215
- });
1216
- it('should build security alert system', () => {
1217
- const securityEvent = {
1218
- event_type: 'unauthorized_access',
1219
- severity: 'high',
1220
- ip_address: '192.168.1.100',
1221
- user_id: 'unknown',
1222
- timestamp: '2024-01-15T14:00:00Z',
1223
- attempted_resource: '/admin/users'
1224
- };
1225
- const result = query()
1226
- .percolate({
1227
- field: 'query',
1228
- document: securityEvent,
1229
- name: 'security_event_check'
1230
- })
1231
- ._source(['name', 'severity'])
1232
- .sort('severity', 'desc')
1233
- .size(100)
1234
- .build();
1235
- expect(result.query?.percolate?.name).toBe('security_event_check');
1236
- expect(result.query?.percolate?.document?.severity).toBe('high');
1237
- });
1238
- it('should build content recommendation engine', () => {
1239
- const userPreferences = {
1240
- interests: ['technology', 'science', 'programming'],
1241
- reading_level: 'advanced',
1242
- preferred_length: 'medium'
1243
- };
1244
- const result = query()
1245
- .percolate({
1246
- field: 'query',
1247
- document: userPreferences
1248
- })
1249
- ._source(['name', 'category'])
1250
- .size(50)
1251
- .build();
1252
- expect(result.query?.percolate?.document?.interests).toHaveLength(3);
1253
- });
1254
- it('should build real-time monitoring system', () => {
1255
- const metrics = {
1256
- cpu_usage: 85,
1257
- memory_usage: 92,
1258
- disk_usage: 78,
1259
- response_time_ms: 1500,
1260
- error_rate: 0.05,
1261
- timestamp: '2024-01-15T15:00:00Z'
1262
- };
1263
- const result = query()
1264
- .percolate({
1265
- field: 'query',
1266
- document: metrics,
1267
- preference: '_local'
1268
- })
1269
- .sort('severity', 'desc')
1270
- .size(100)
1271
- .build();
1272
- expect(result.query?.percolate?.document?.cpu_usage).toBe(85);
1273
- expect(result.query?.percolate?.preference).toBe('_local');
1274
- });
1275
- });
1276
- describe('Multi-Search', () => {
1277
- it('should build dashboard with multiple product searches', () => {
1278
- // Top selling products query
1279
- const topSelling = query()
1280
- .bool()
1281
- .filter((q) => q.range('created_at', { gte: 'now-30d' }))
1282
- .sort('sales_count', 'desc')
1283
- .size(10)
1284
- .build();
1285
- // New arrivals query
1286
- const newArrivals = query()
1287
- .matchAll()
1288
- .sort('created_at', 'desc')
1289
- .size(10)
1290
- .build();
1291
- // Electronics deals query
1292
- const electronicsDeals = query()
1293
- .bool()
1294
- .filter((q) => q.term('category', 'technology'))
1295
- .filter((q) => q.range('price', { lte: 500 }))
1296
- .sort('price', 'asc')
1297
- .size(5)
1298
- .build();
1299
- const ndjson = msearch()
1300
- .addQuery(topSelling, { index: 'instruments' })
1301
- .addQuery(newArrivals, { index: 'instruments' })
1302
- .addQuery(electronicsDeals, { index: 'instruments' })
1303
- .build();
1304
- expect(ndjson).toMatchInlineSnapshot(`
1305
- "{"index":"instruments"}
1306
- {"query":{"bool":{"filter":[{"range":{"created_at":{"gte":"now-30d"}}}]}},"sort":[{"sales_count":"desc"}],"size":10}
1307
- {"index":"instruments"}
1308
- {"query":{"match_all":{}},"sort":[{"created_at":"desc"}],"size":10}
1309
- {"index":"instruments"}
1310
- {"query":{"bool":{"filter":[{"term":{"category":"technology"}},{"range":{"price":{"lte":500}}}]}},"sort":[{"price":"asc"}],"size":5}
1311
- "
1312
- `);
1313
- });
1314
- it('should search across multiple tenant indices', () => {
1315
- const searchQuery = query()
1316
- .match('content', 'important')
1317
- .size(20)
1318
- .build();
1319
- const result = msearch()
1320
- .addQuery(searchQuery, { index: 'tenant-001-docs' })
1321
- .addQuery(searchQuery, { index: 'tenant-002-docs' })
1322
- .addQuery(searchQuery, { index: 'tenant-003-docs' })
1323
- .buildArray();
1324
- expect(result).toMatchInlineSnapshot(`
1325
- [
1326
- {
1327
- "index": "tenant-001-docs",
1328
- },
1329
- {
1330
- "query": {
1331
- "match": {
1332
- "content": "important",
1333
- },
1334
- },
1335
- "size": 20,
1336
- },
1337
- {
1338
- "index": "tenant-002-docs",
1339
- },
1340
- {
1341
- "query": {
1342
- "match": {
1343
- "content": "important",
1344
- },
1345
- },
1346
- "size": 20,
1347
- },
1348
- {
1349
- "index": "tenant-003-docs",
1350
- },
1351
- {
1352
- "query": {
1353
- "match": {
1354
- "content": "important",
1355
- },
1356
- },
1357
- "size": 20,
1358
- },
1359
- ]
1360
- `);
1361
- });
1362
- });
1363
- describe('Bulk Operations', () => {
1364
- it('should build product catalog import', () => {
1365
- const products = [
1366
- {
1367
- sku: 'LAP-001',
1368
- name: 'Gaming Laptop',
1369
- price: 1299,
1370
- category: 'technology',
1371
- stock: 15
1372
- },
1373
- {
1374
- sku: 'MOU-002',
1375
- name: 'Wireless Mouse',
1376
- price: 29,
1377
- category: 'accessories',
1378
- stock: 50
1379
- },
1380
- {
1381
- sku: 'KEY-003',
1382
- name: 'Mechanical Keyboard',
1383
- price: 149,
1384
- category: 'accessories',
1385
- stock: 30
1386
- }
1387
- ];
1388
- let bulkBuilder = bulk();
1389
- for (const product of products) {
1390
- bulkBuilder = bulkBuilder.index(product, {
1391
- _index: 'instruments',
1392
- _id: product.sku
1393
- });
1394
- }
1395
- const ndjson = bulkBuilder.build();
1396
- expect(ndjson).toMatchInlineSnapshot(`
1397
- "{"index":{"_index":"instruments","_id":"LAP-001"}}
1398
- {"sku":"LAP-001","name":"Gaming Laptop","price":1299,"category":"technology","stock":15}
1399
- {"index":{"_index":"instruments","_id":"MOU-002"}}
1400
- {"sku":"MOU-002","name":"Wireless Mouse","price":29,"category":"accessories","stock":50}
1401
- {"index":{"_index":"instruments","_id":"KEY-003"}}
1402
- {"sku":"KEY-003","name":"Mechanical Keyboard","price":149,"category":"accessories","stock":30}
1403
- "
1404
- `);
1405
- });
1406
- it('should build price update batch with script', () => {
1407
- // Apply 10% discount to specific products
1408
- const productIds = ['prod-1', 'prod-2', 'prod-3', 'prod-4', 'prod-5'];
1409
- let bulkBuilder = bulk();
1410
- for (const id of productIds) {
1411
- bulkBuilder = bulkBuilder.update({
1412
- _index: 'instruments',
1413
- _id: id,
1414
- script: {
1415
- source: 'ctx._source.price *= params.discount',
1416
- params: { discount: 0.9 }
1417
- }
1418
- });
1419
- }
1420
- const ndjson = bulkBuilder.build();
1421
- expect(ndjson).toMatchInlineSnapshot(`
1422
- "{"update":{"_index":"instruments","_id":"prod-1"}}
1423
- {"script":{"source":"ctx._source.price *= params.discount","params":{"discount":0.9}}}
1424
- {"update":{"_index":"instruments","_id":"prod-2"}}
1425
- {"script":{"source":"ctx._source.price *= params.discount","params":{"discount":0.9}}}
1426
- {"update":{"_index":"instruments","_id":"prod-3"}}
1427
- {"script":{"source":"ctx._source.price *= params.discount","params":{"discount":0.9}}}
1428
- {"update":{"_index":"instruments","_id":"prod-4"}}
1429
- {"script":{"source":"ctx._source.price *= params.discount","params":{"discount":0.9}}}
1430
- {"update":{"_index":"instruments","_id":"prod-5"}}
1431
- {"script":{"source":"ctx._source.price *= params.discount","params":{"discount":0.9}}}
1432
- "
1433
- `);
1434
- });
1435
- it('should build mixed CRUD operations', () => {
1436
- const bulkOp = bulk()
1437
- // Add new items
1438
- .create({ id: 'new-1', name: 'New Product', quantity: 100 }, { _index: 'inventory', _id: 'new-1' })
1439
- // Update stock levels
1440
- .update({
1441
- _index: 'inventory',
1442
- _id: 'existing-1',
1443
- doc: { quantity: 50 }
1444
- })
1445
- // Replace item entirely
1446
- .index({ id: 'replace-1', name: 'Replaced Product', quantity: 25 }, { _index: 'inventory', _id: 'replace-1' })
1447
- // Remove discontinued items
1448
- .delete({ _index: 'inventory', _id: 'discontinued-1' })
1449
- .build();
1450
- expect(bulkOp).toMatchInlineSnapshot(`
1451
- "{"create":{"_index":"inventory","_id":"new-1"}}
1452
- {"id":"new-1","name":"New Product","quantity":100}
1453
- {"update":{"_index":"inventory","_id":"existing-1"}}
1454
- {"doc":{"quantity":50}}
1455
- {"index":{"_index":"inventory","_id":"replace-1"}}
1456
- {"id":"replace-1","name":"Replaced Product","quantity":25}
1457
- {"delete":{"_index":"inventory","_id":"discontinued-1"}}
1458
- "
1459
- `);
1460
- });
1461
- it('should build upsert operations for sync', () => {
1462
- const updates = [
1463
- { id: 'doc-1', data: 'Updated content', updated_at: '2024-01-15' },
1464
- { id: 'doc-2', data: 'New content', updated_at: '2024-01-15' }
1465
- ];
1466
- let bulkBuilder = bulk();
1467
- for (const update of updates) {
1468
- bulkBuilder = bulkBuilder.update({
1469
- _index: 'documents',
1470
- _id: update.id,
1471
- doc: update,
1472
- upsert: update // Insert if doesn't exist
1473
- });
1474
- }
1475
- const ndjson = bulkBuilder.build();
1476
- expect(ndjson).toMatchInlineSnapshot(`
1477
- "{"update":{"_index":"documents","_id":"doc-1"}}
1478
- {"doc":{"id":"doc-1","data":"Updated content","updated_at":"2024-01-15"},"upsert":{"id":"doc-1","data":"Updated content","updated_at":"2024-01-15"}}
1479
- {"update":{"_index":"documents","_id":"doc-2"}}
1480
- {"doc":{"id":"doc-2","data":"New content","updated_at":"2024-01-15"},"upsert":{"id":"doc-2","data":"New content","updated_at":"2024-01-15"}}
1481
- "
1482
- `);
1483
- });
1484
- });
1485
- describe('Index Management', () => {
1486
- it('should build e-commerce product index', () => {
1487
- const indexConfig = indexBuilder()
1488
- .mappings({
1489
- sku: 'keyword',
1490
- name: text({
1491
- analyzer: 'standard',
1492
- fields: { keyword: { type: 'keyword' } }
1493
- }),
1494
- description: text({ analyzer: 'english' }),
1495
- price: scaledFloat({ scaling_factor: 100 }),
1496
- category: 'keyword',
1497
- brand: 'keyword',
1498
- tags: 'keyword',
1499
- rating: halfFloat(),
1500
- reviewCount: 'integer',
1501
- inStock: 'boolean',
1502
- createdAt: 'date'
1503
- })
1504
- .settings({
1505
- number_of_shards: 3,
1506
- number_of_replicas: 2,
1507
- refresh_interval: '1s'
1508
- })
1509
- .alias('instruments')
1510
- .build();
1511
- expect(indexConfig).toMatchInlineSnapshot(`
1512
- {
1513
- "aliases": {
1514
- "instruments": {},
1515
- },
1516
- "mappings": {
1517
- "properties": {
1518
- "brand": {
1519
- "type": "keyword",
1520
- },
1521
- "category": {
1522
- "type": "keyword",
1523
- },
1524
- "createdAt": {
1525
- "type": "date",
1526
- },
1527
- "description": {
1528
- "analyzer": "english",
1529
- "type": "text",
1530
- },
1531
- "inStock": {
1532
- "type": "boolean",
1533
- },
1534
- "name": {
1535
- "analyzer": "standard",
1536
- "fields": {
1537
- "keyword": {
1538
- "type": "keyword",
1539
- },
1540
- },
1541
- "type": "text",
1542
- },
1543
- "price": {
1544
- "scaling_factor": 100,
1545
- "type": "scaled_float",
1546
- },
1547
- "rating": {
1548
- "type": "half_float",
1549
- },
1550
- "reviewCount": {
1551
- "type": "integer",
1552
- },
1553
- "sku": {
1554
- "type": "keyword",
1555
- },
1556
- "tags": {
1557
- "type": "keyword",
1558
- },
1559
- },
1560
- },
1561
- "settings": {
1562
- "number_of_replicas": 2,
1563
- "number_of_shards": 3,
1564
- "refresh_interval": "1s",
1565
- },
1566
- }
1567
- `);
1568
- });
1569
- it('should build vector search index with HNSW', () => {
1570
- const indexConfig = indexBuilder()
1571
- .mappings({
1572
- title: 'text',
1573
- content: 'text',
1574
- embedding: denseVector({
1575
- dims: 768,
1576
- index: true,
1577
- similarity: 'cosine',
1578
- index_options: {
1579
- type: 'hnsw',
1580
- m: 16,
1581
- ef_construction: 100
1582
- }
1583
- }),
1584
- category: 'keyword'
1585
- })
1586
- .settings({
1587
- number_of_shards: 1,
1588
- number_of_replicas: 0
1589
- })
1590
- .build();
1591
- expect(indexConfig).toMatchInlineSnapshot(`
1592
- {
1593
- "mappings": {
1594
- "properties": {
1595
- "category": {
1596
- "type": "keyword",
1597
- },
1598
- "content": {
1599
- "type": "text",
1600
- },
1601
- "embedding": {
1602
- "dims": 768,
1603
- "index": true,
1604
- "index_options": {
1605
- "ef_construction": 100,
1606
- "m": 16,
1607
- "type": "hnsw",
1608
- },
1609
- "similarity": "cosine",
1610
- "type": "dense_vector",
1611
- },
1612
- "title": {
1613
- "type": "text",
1614
- },
1615
- },
1616
- },
1617
- "settings": {
1618
- "number_of_replicas": 0,
1619
- "number_of_shards": 1,
1620
- },
1621
- }
1622
- `);
1623
- });
1624
- it('should build time-series index with aliases', () => {
1625
- const indexConfig = indexBuilder()
1626
- .mappings({
1627
- timestamp: 'date',
1628
- level: 'keyword',
1629
- message: text({ analyzer: 'standard' }),
1630
- service: 'keyword',
1631
- trace_id: 'keyword'
1632
- })
1633
- .settings({
1634
- number_of_shards: 1,
1635
- number_of_replicas: 1,
1636
- refresh_interval: '5s'
1637
- })
1638
- .alias('logs-current', { is_write_index: true })
1639
- .alias('logs-all')
1640
- .build();
1641
- expect(indexConfig).toMatchInlineSnapshot(`
1642
- {
1643
- "aliases": {
1644
- "logs-all": {},
1645
- "logs-current": {
1646
- "is_write_index": true,
1647
- },
1648
- },
1649
- "mappings": {
1650
- "properties": {
1651
- "level": {
1652
- "type": "keyword",
1653
- },
1654
- "message": {
1655
- "analyzer": "standard",
1656
- "type": "text",
1657
- },
1658
- "service": {
1659
- "type": "keyword",
1660
- },
1661
- "timestamp": {
1662
- "type": "date",
1663
- },
1664
- "trace_id": {
1665
- "type": "keyword",
1666
- },
1667
- },
1668
- },
1669
- "settings": {
1670
- "number_of_replicas": 1,
1671
- "number_of_shards": 1,
1672
- "refresh_interval": "5s",
1673
- },
1674
- }
1675
- `);
1676
- });
1677
- it('should build multi-field text index for search', () => {
1678
- const indexConfig = indexBuilder()
1679
- .mappings({
1680
- title: text({
1681
- analyzer: 'english',
1682
- fields: {
1683
- exact: { type: 'keyword' },
1684
- raw: { type: 'text', analyzer: 'standard' }
1685
- }
1686
- }),
1687
- author: 'keyword',
1688
- content: text({ analyzer: 'english' }),
1689
- publishedAt: 'date'
1690
- })
1691
- .settings({
1692
- number_of_shards: 2,
1693
- number_of_replicas: 1
1694
- })
1695
- .build();
1696
- expect(indexConfig).toMatchInlineSnapshot(`
1697
- {
1698
- "mappings": {
1699
- "properties": {
1700
- "author": {
1701
- "type": "keyword",
1702
- },
1703
- "content": {
1704
- "analyzer": "english",
1705
- "type": "text",
1706
- },
1707
- "publishedAt": {
1708
- "type": "date",
1709
- },
1710
- "title": {
1711
- "analyzer": "english",
1712
- "fields": {
1713
- "exact": {
1714
- "type": "keyword",
1715
- },
1716
- "raw": {
1717
- "analyzer": "standard",
1718
- "type": "text",
1719
- },
1720
- },
1721
- "type": "text",
1722
- },
1723
- },
1724
- },
1725
- "settings": {
1726
- "number_of_replicas": 1,
1727
- "number_of_shards": 2,
1728
- },
1729
- }
1730
- `);
1731
- });
1732
- it('should build a professional domain index using field helpers', () => {
1733
- const indexConfig = indexBuilder()
1734
- .mappings({
1735
- title: text({ analyzer: 'english' }),
1736
- practice_area: keyword(),
1737
- billing_rate: integer(),
1738
- risk_score: float(),
1739
- opened_at: date()
1740
- })
1741
- .settings({
1742
- number_of_shards: 2,
1743
- number_of_replicas: 1,
1744
- refresh_interval: '5s'
1745
- })
1746
- .alias('matters-current', { is_write_index: true })
1747
- .alias('matters-all')
1748
- .build();
1749
- expect(indexConfig).toMatchInlineSnapshot(`
1750
- {
1751
- "aliases": {
1752
- "matters-all": {},
1753
- "matters-current": {
1754
- "is_write_index": true,
1755
- },
1756
- },
1757
- "mappings": {
1758
- "properties": {
1759
- "billing_rate": {
1760
- "type": "integer",
1761
- },
1762
- "opened_at": {
1763
- "type": "date",
1764
- },
1765
- "practice_area": {
1766
- "type": "keyword",
1767
- },
1768
- "risk_score": {
1769
- "type": "float",
1770
- },
1771
- "title": {
1772
- "analyzer": "english",
1773
- "type": "text",
1774
- },
1775
- },
1776
- },
1777
- "settings": {
1778
- "number_of_replicas": 1,
1779
- "number_of_shards": 2,
1780
- "refresh_interval": "5s",
1781
- },
1782
- }
1783
- `);
1784
- });
1785
- });
1786
- describe('Aggregations — Portfolio Analytics', () => {
1787
- it('should aggregate fixed-income instruments by sector with yield metrics', () => {
1788
- const result = query()
1789
- .bool()
1790
- .filter((q) => q.term('asset_class', 'fixed-income'))
1791
- .filter((q) => q.range('yield_rate', { gte: 3.0 }))
1792
- .aggs((agg) => agg
1793
- .terms('by_sector', 'sector', { size: 10 })
1794
- .subAgg((sub) => sub.avg('avg_yield', 'yield_rate').max('max_price', 'price')))
1795
- .size(0)
1796
- .build();
1797
- expect(result).toMatchInlineSnapshot(`
1798
- {
1799
- "aggs": {
1800
- "by_sector": {
1801
- "aggs": {
1802
- "avg_yield": {
1803
- "avg": {
1804
- "field": "yield_rate",
1805
- },
1806
- },
1807
- "max_price": {
1808
- "max": {
1809
- "field": "price",
1810
- },
1811
- },
1812
- },
1813
- "terms": {
1814
- "field": "sector",
1815
- "size": 10,
1816
- },
1817
- },
1818
- },
1819
- "query": {
1820
- "bool": {
1821
- "filter": [
1822
- {
1823
- "term": {
1824
- "asset_class": "fixed-income",
1825
- },
1826
- },
1827
- {
1828
- "range": {
1829
- "yield_rate": {
1830
- "gte": 3,
1831
- },
1832
- },
1833
- },
1834
- ],
1835
- },
1836
- },
1837
- "size": 0,
1838
- }
1839
- `);
1840
- });
1841
- it('should build a date histogram with percentile sub-aggregation', () => {
1842
- const result = aggregations()
1843
- .dateHistogram('listings_over_time', 'listed_date', {
1844
- interval: 'quarter',
1845
- min_doc_count: 1
1846
- })
1847
- .subAgg((sub) => sub.percentiles('yield_percentiles', 'yield_rate', {
1848
- percents: [25, 50, 75, 95]
1849
- }))
1850
- .build();
1851
- expect(result).toMatchInlineSnapshot(`
1852
- {
1853
- "listings_over_time": {
1854
- "aggs": {
1855
- "yield_percentiles": {
1856
- "percentiles": {
1857
- "field": "yield_rate",
1858
- "percents": [
1859
- 25,
1860
- 50,
1861
- 75,
1862
- 95,
1863
- ],
1864
- },
1865
- },
1866
- },
1867
- "date_histogram": {
1868
- "field": "listed_date",
1869
- "interval": "quarter",
1870
- "min_doc_count": 1,
1871
- },
1872
- },
1873
- }
1874
- `);
1875
- });
1876
- it('should build a price histogram with stats sub-aggregation', () => {
1877
- const result = aggregations()
1878
- .histogram('price_buckets', 'price', {
1879
- interval: 100,
1880
- min_doc_count: 1
1881
- })
1882
- .subAgg((sub) => sub.stats('yield_stats', 'yield_rate'))
1883
- .build();
1884
- expect(result).toMatchInlineSnapshot(`
1885
- {
1886
- "price_buckets": {
1887
- "aggs": {
1888
- "yield_stats": {
1889
- "stats": {
1890
- "field": "yield_rate",
1891
- },
1892
- },
1893
- },
1894
- "histogram": {
1895
- "field": "price",
1896
- "interval": 100,
1897
- "min_doc_count": 1,
1898
- },
1899
- },
1900
- }
1901
- `);
1902
- });
1903
- });
1904
- describe('Multi-Search with Per-Query Aggregations', () => {
1905
- it('should batch two aggregation queries in one request', () => {
1906
- const condoSearch = query()
1907
- .bool()
1908
- .filter((q) => q.term('property_class', 'condo'))
1909
- .filter((q) => q.range('list_price', { lte: 2_000_000 }))
1910
- .aggs((agg) => agg.avg('avg_price', 'list_price'))
1911
- .size(0)
1912
- .build();
1913
- const townhouseSearch = query()
1914
- .bool()
1915
- .filter((q) => q.term('property_class', 'townhouse'))
1916
- .aggs((agg) => agg.avg('avg_price', 'list_price').min('min_price', 'list_price'))
1917
- .size(0)
1918
- .build();
1919
- const ndjson = msearch()
1920
- .addQuery(condoSearch, { index: 'listings' })
1921
- .addQuery(townhouseSearch, { index: 'listings' })
1922
- .build();
1923
- expect(ndjson).toMatchInlineSnapshot(`
1924
- "{"index":"listings"}
1925
- {"query":{"bool":{"filter":[{"term":{"property_class":"condo"}},{"range":{"list_price":{"lte":2000000}}}]}},"aggs":{"avg_price":{"avg":{"field":"list_price"}}},"size":0}
1926
- {"index":"listings"}
1927
- {"query":{"bool":{"filter":[{"term":{"property_class":"townhouse"}}]}},"aggs":{"avg_price":{"avg":{"field":"list_price"}},"min_price":{"min":{"field":"list_price"}}},"size":0}
1928
- "
1929
- `);
1930
- });
1931
- });
1932
- describe('Suggest — Standalone Builder', () => {
1933
- it('should build a completion autocomplete request', () => {
1934
- const result = suggest()
1935
- .completion('autocomplete', 'kap', {
1936
- field: 'name_suggest',
1937
- size: 5,
1938
- skip_duplicates: true
1939
- })
1940
- .build();
1941
- expect(result).toMatchInlineSnapshot(`
1942
- {
1943
- "suggest": {
1944
- "autocomplete": {
1945
- "completion": {
1946
- "field": "name_suggest",
1947
- "size": 5,
1948
- "skip_duplicates": true,
1949
- },
1950
- "prefix": "kap",
1951
- },
1952
- },
1953
- }
1954
- `);
1955
- });
1956
- it('should build a term spell-check request', () => {
1957
- const result = suggest()
1958
- .term('spelling', 'wiliams', {
1959
- field: 'name',
1960
- size: 3,
1961
- suggest_mode: 'popular'
1962
- })
1963
- .build();
1964
- expect(result).toMatchInlineSnapshot(`
1965
- {
1966
- "suggest": {
1967
- "spelling": {
1968
- "term": {
1969
- "field": "name",
1970
- "size": 3,
1971
- "suggest_mode": "popular",
1972
- },
1973
- "text": "wiliams",
1974
- },
1975
- },
1976
- }
1977
- `);
1978
- });
1979
- it('should build a combined autocomplete and spell-check request', () => {
1980
- const result = suggest()
1981
- .completion('autocomplete', 'kap', { field: 'name_suggest', size: 5 })
1982
- .term('spelling', 'wiliams', { field: 'name', size: 3 })
1983
- .build();
1984
- expect(result).toMatchInlineSnapshot(`
1985
- {
1986
- "suggest": {
1987
- "autocomplete": {
1988
- "completion": {
1989
- "field": "name_suggest",
1990
- "size": 5,
1991
- },
1992
- "prefix": "kap",
1993
- },
1994
- "spelling": {
1995
- "term": {
1996
- "field": "name",
1997
- "size": 3,
1998
- },
1999
- "text": "wiliams",
2000
- },
2001
- },
2002
- }
2003
- `);
2004
- });
2005
- });
2006
- });