elasticlink 0.8.0-beta → 1.0.0-beta.2
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.
- package/README.md +631 -819
- package/dist/index.cjs +1528 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1890 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +1889 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1474 -18
- package/dist/index.js.map +1 -0
- package/package.json +25 -19
- package/dist/aggregation.builder.d.ts +0 -6
- package/dist/aggregation.builder.d.ts.map +0 -1
- package/dist/aggregation.builder.js +0 -64
- package/dist/aggregation.types.d.ts +0 -173
- package/dist/aggregation.types.d.ts.map +0 -1
- package/dist/aggregation.types.js +0 -6
- package/dist/bulk.builder.d.ts +0 -28
- package/dist/bulk.builder.d.ts.map +0 -1
- package/dist/bulk.builder.js +0 -51
- package/dist/bulk.types.d.ts +0 -46
- package/dist/bulk.types.d.ts.map +0 -1
- package/dist/bulk.types.js +0 -6
- package/dist/field.helpers.d.ts +0 -167
- package/dist/field.helpers.d.ts.map +0 -1
- package/dist/field.helpers.js +0 -282
- package/dist/field.types.d.ts +0 -255
- package/dist/field.types.d.ts.map +0 -1
- package/dist/field.types.js +0 -6
- package/dist/index-management.builder.d.ts +0 -33
- package/dist/index-management.builder.d.ts.map +0 -1
- package/dist/index-management.builder.js +0 -64
- package/dist/index-management.types.d.ts +0 -126
- package/dist/index-management.types.d.ts.map +0 -1
- package/dist/index-management.types.js +0 -6
- package/dist/mapping.builder.d.ts +0 -46
- package/dist/mapping.builder.d.ts.map +0 -1
- package/dist/mapping.builder.js +0 -39
- package/dist/mapping.types.d.ts +0 -160
- package/dist/mapping.types.d.ts.map +0 -1
- package/dist/mapping.types.js +0 -6
- package/dist/multi-search.builder.d.ts +0 -22
- package/dist/multi-search.builder.d.ts.map +0 -1
- package/dist/multi-search.builder.js +0 -39
- package/dist/multi-search.types.d.ts +0 -36
- package/dist/multi-search.types.d.ts.map +0 -1
- package/dist/multi-search.types.js +0 -6
- package/dist/query.builder.d.ts +0 -4
- package/dist/query.builder.d.ts.map +0 -1
- package/dist/query.builder.js +0 -264
- package/dist/query.types.d.ts +0 -324
- package/dist/query.types.d.ts.map +0 -1
- package/dist/query.types.js +0 -7
- package/dist/settings.presets.d.ts +0 -98
- package/dist/settings.presets.d.ts.map +0 -1
- package/dist/settings.presets.js +0 -115
- package/dist/suggester.builder.d.ts +0 -23
- package/dist/suggester.builder.d.ts.map +0 -1
- package/dist/suggester.builder.js +0 -51
- package/dist/suggester.types.d.ts +0 -50
- package/dist/suggester.types.d.ts.map +0 -1
- package/dist/suggester.types.js +0 -6
- package/dist/vector.types.d.ts +0 -17
- package/dist/vector.types.d.ts.map +0 -1
- package/dist/vector.types.js +0 -6
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1528 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/aggregation.builder.ts
|
|
3
|
+
const sharedAggMethods = (state, rebuild) => ({
|
|
4
|
+
terms: (name, field, options) => rebuild({
|
|
5
|
+
...state,
|
|
6
|
+
[name]: { terms: {
|
|
7
|
+
field,
|
|
8
|
+
...options
|
|
9
|
+
} }
|
|
10
|
+
}),
|
|
11
|
+
dateHistogram: (name, field, options) => rebuild({
|
|
12
|
+
...state,
|
|
13
|
+
[name]: { date_histogram: {
|
|
14
|
+
field,
|
|
15
|
+
...options
|
|
16
|
+
} }
|
|
17
|
+
}),
|
|
18
|
+
range: (name, field, options) => rebuild({
|
|
19
|
+
...state,
|
|
20
|
+
[name]: { range: {
|
|
21
|
+
field,
|
|
22
|
+
...options
|
|
23
|
+
} }
|
|
24
|
+
}),
|
|
25
|
+
dateRange: (name, field, options) => rebuild({
|
|
26
|
+
...state,
|
|
27
|
+
[name]: { date_range: {
|
|
28
|
+
field,
|
|
29
|
+
...options
|
|
30
|
+
} }
|
|
31
|
+
}),
|
|
32
|
+
filters: (name, filters, options) => rebuild({
|
|
33
|
+
...state,
|
|
34
|
+
[name]: { filters: {
|
|
35
|
+
filters,
|
|
36
|
+
...options
|
|
37
|
+
} }
|
|
38
|
+
}),
|
|
39
|
+
significantTerms: (name, field, options) => rebuild({
|
|
40
|
+
...state,
|
|
41
|
+
[name]: { significant_terms: {
|
|
42
|
+
field,
|
|
43
|
+
...options
|
|
44
|
+
} }
|
|
45
|
+
}),
|
|
46
|
+
histogram: (name, field, options) => rebuild({
|
|
47
|
+
...state,
|
|
48
|
+
[name]: { histogram: {
|
|
49
|
+
field,
|
|
50
|
+
...options
|
|
51
|
+
} }
|
|
52
|
+
}),
|
|
53
|
+
avg: (name, field, options) => rebuild({
|
|
54
|
+
...state,
|
|
55
|
+
[name]: { avg: {
|
|
56
|
+
field,
|
|
57
|
+
...options
|
|
58
|
+
} }
|
|
59
|
+
}),
|
|
60
|
+
sum: (name, field, options) => rebuild({
|
|
61
|
+
...state,
|
|
62
|
+
[name]: { sum: {
|
|
63
|
+
field,
|
|
64
|
+
...options
|
|
65
|
+
} }
|
|
66
|
+
}),
|
|
67
|
+
min: (name, field, options) => rebuild({
|
|
68
|
+
...state,
|
|
69
|
+
[name]: { min: {
|
|
70
|
+
field,
|
|
71
|
+
...options
|
|
72
|
+
} }
|
|
73
|
+
}),
|
|
74
|
+
max: (name, field, options) => rebuild({
|
|
75
|
+
...state,
|
|
76
|
+
[name]: { max: {
|
|
77
|
+
field,
|
|
78
|
+
...options
|
|
79
|
+
} }
|
|
80
|
+
}),
|
|
81
|
+
cardinality: (name, field, options) => rebuild({
|
|
82
|
+
...state,
|
|
83
|
+
[name]: { cardinality: {
|
|
84
|
+
field,
|
|
85
|
+
...options
|
|
86
|
+
} }
|
|
87
|
+
}),
|
|
88
|
+
percentiles: (name, field, options) => rebuild({
|
|
89
|
+
...state,
|
|
90
|
+
[name]: { percentiles: {
|
|
91
|
+
field,
|
|
92
|
+
...options
|
|
93
|
+
} }
|
|
94
|
+
}),
|
|
95
|
+
stats: (name, field, options) => rebuild({
|
|
96
|
+
...state,
|
|
97
|
+
[name]: { stats: {
|
|
98
|
+
field,
|
|
99
|
+
...options
|
|
100
|
+
} }
|
|
101
|
+
}),
|
|
102
|
+
valueCount: (name, field, options) => rebuild({
|
|
103
|
+
...state,
|
|
104
|
+
[name]: { value_count: {
|
|
105
|
+
field,
|
|
106
|
+
...options
|
|
107
|
+
} }
|
|
108
|
+
}),
|
|
109
|
+
extendedStats: (name, field, options) => rebuild({
|
|
110
|
+
...state,
|
|
111
|
+
[name]: { extended_stats: {
|
|
112
|
+
field,
|
|
113
|
+
...options
|
|
114
|
+
} }
|
|
115
|
+
}),
|
|
116
|
+
topHits: (name, options) => rebuild({
|
|
117
|
+
...state,
|
|
118
|
+
[name]: { top_hits: { ...options } }
|
|
119
|
+
}),
|
|
120
|
+
autoDateHistogram: (name, field, options) => rebuild({
|
|
121
|
+
...state,
|
|
122
|
+
[name]: { auto_date_histogram: {
|
|
123
|
+
field,
|
|
124
|
+
...options
|
|
125
|
+
} }
|
|
126
|
+
}),
|
|
127
|
+
composite: (name, sources, options) => rebuild({
|
|
128
|
+
...state,
|
|
129
|
+
[name]: { composite: {
|
|
130
|
+
sources,
|
|
131
|
+
...options
|
|
132
|
+
} }
|
|
133
|
+
}),
|
|
134
|
+
filter: (name, query) => rebuild({
|
|
135
|
+
...state,
|
|
136
|
+
[name]: { filter: query }
|
|
137
|
+
}),
|
|
138
|
+
rareTerms: (name, field, options) => rebuild({
|
|
139
|
+
...state,
|
|
140
|
+
[name]: { rare_terms: {
|
|
141
|
+
field,
|
|
142
|
+
...options
|
|
143
|
+
} }
|
|
144
|
+
}),
|
|
145
|
+
multiTerms: (name, options) => rebuild({
|
|
146
|
+
...state,
|
|
147
|
+
[name]: { multi_terms: { ...options } }
|
|
148
|
+
}),
|
|
149
|
+
geoDistance: (name, field, options) => rebuild({
|
|
150
|
+
...state,
|
|
151
|
+
[name]: { geo_distance: {
|
|
152
|
+
field,
|
|
153
|
+
...options
|
|
154
|
+
} }
|
|
155
|
+
}),
|
|
156
|
+
geohashGrid: (name, field, options) => rebuild({
|
|
157
|
+
...state,
|
|
158
|
+
[name]: { geohash_grid: {
|
|
159
|
+
field,
|
|
160
|
+
...options
|
|
161
|
+
} }
|
|
162
|
+
}),
|
|
163
|
+
geotileGrid: (name, field, options) => rebuild({
|
|
164
|
+
...state,
|
|
165
|
+
[name]: { geotile_grid: {
|
|
166
|
+
field,
|
|
167
|
+
...options
|
|
168
|
+
} }
|
|
169
|
+
}),
|
|
170
|
+
geoBounds: (name, field, options) => rebuild({
|
|
171
|
+
...state,
|
|
172
|
+
[name]: { geo_bounds: {
|
|
173
|
+
field,
|
|
174
|
+
...options
|
|
175
|
+
} }
|
|
176
|
+
}),
|
|
177
|
+
geoCentroid: (name, field, options) => rebuild({
|
|
178
|
+
...state,
|
|
179
|
+
[name]: { geo_centroid: {
|
|
180
|
+
field,
|
|
181
|
+
...options
|
|
182
|
+
} }
|
|
183
|
+
}),
|
|
184
|
+
missing: (name, field, options) => rebuild({
|
|
185
|
+
...state,
|
|
186
|
+
[name]: { missing: {
|
|
187
|
+
field,
|
|
188
|
+
...options
|
|
189
|
+
} }
|
|
190
|
+
}),
|
|
191
|
+
topMetrics: (name, options) => rebuild({
|
|
192
|
+
...state,
|
|
193
|
+
[name]: { top_metrics: { ...options } }
|
|
194
|
+
}),
|
|
195
|
+
weightedAvg: (name, options) => rebuild({
|
|
196
|
+
...state,
|
|
197
|
+
[name]: { weighted_avg: { ...options } }
|
|
198
|
+
}),
|
|
199
|
+
bucketScript: (name, options) => rebuild({
|
|
200
|
+
...state,
|
|
201
|
+
[name]: { bucket_script: { ...options } }
|
|
202
|
+
}),
|
|
203
|
+
bucketSelector: (name, options) => rebuild({
|
|
204
|
+
...state,
|
|
205
|
+
[name]: { bucket_selector: { ...options } }
|
|
206
|
+
}),
|
|
207
|
+
derivative: (name, options) => rebuild({
|
|
208
|
+
...state,
|
|
209
|
+
[name]: { derivative: { ...options } }
|
|
210
|
+
}),
|
|
211
|
+
cumulativeSum: (name, options) => rebuild({
|
|
212
|
+
...state,
|
|
213
|
+
[name]: { cumulative_sum: { ...options } }
|
|
214
|
+
}),
|
|
215
|
+
build: () => state
|
|
216
|
+
});
|
|
217
|
+
const attachSubAgg = (state, subAggState) => {
|
|
218
|
+
const lastKey = Object.keys(state).at(-1);
|
|
219
|
+
const existing = state[lastKey].aggs ?? {};
|
|
220
|
+
return {
|
|
221
|
+
...state,
|
|
222
|
+
[lastKey]: {
|
|
223
|
+
...state[lastKey],
|
|
224
|
+
aggs: {
|
|
225
|
+
...existing,
|
|
226
|
+
...subAggState
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
function createNestedEntryBuilder(state, rebuild) {
|
|
232
|
+
return {
|
|
233
|
+
...sharedAggMethods(state, rebuild),
|
|
234
|
+
global: (name) => createAggregationBuilder({
|
|
235
|
+
...state,
|
|
236
|
+
[name]: { global: {} }
|
|
237
|
+
}),
|
|
238
|
+
nested: (name, path) => createNestedEntryBuilder({
|
|
239
|
+
...state,
|
|
240
|
+
[name]: { nested: { path } }
|
|
241
|
+
}, rebuild),
|
|
242
|
+
subAgg: (fn) => rebuild(attachSubAgg(state, fn(createNestedAggregationBuilder()).build()))
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function createNestedAggregationBuilder(state = {}) {
|
|
246
|
+
return {
|
|
247
|
+
...sharedAggMethods(state, createNestedAggregationBuilder),
|
|
248
|
+
nested: (name, path) => createNestedEntryBuilder({
|
|
249
|
+
...state,
|
|
250
|
+
[name]: { nested: { path } }
|
|
251
|
+
}, createNestedAggregationBuilder),
|
|
252
|
+
reverseNested: (name, path) => createNestedAggregationBuilder({
|
|
253
|
+
...state,
|
|
254
|
+
[name]: { reverse_nested: path ? { path } : {} }
|
|
255
|
+
}),
|
|
256
|
+
subAgg: (fn) => createNestedAggregationBuilder(attachSubAgg(state, fn(createNestedAggregationBuilder()).build()))
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function createAggregationBuilder(state = {}) {
|
|
260
|
+
return {
|
|
261
|
+
...sharedAggMethods(state, createAggregationBuilder),
|
|
262
|
+
global: (name) => createAggregationBuilder({
|
|
263
|
+
...state,
|
|
264
|
+
[name]: { global: {} }
|
|
265
|
+
}),
|
|
266
|
+
nested: (name, path) => createNestedEntryBuilder({
|
|
267
|
+
...state,
|
|
268
|
+
[name]: { nested: { path } }
|
|
269
|
+
}, createAggregationBuilder),
|
|
270
|
+
subAgg: (fn) => createAggregationBuilder(attachSubAgg(state, fn(createAggregationBuilder()).build()))
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const aggregations = (_schema) => createAggregationBuilder();
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region src/suggester.builder.ts
|
|
276
|
+
/**
|
|
277
|
+
* Creates a suggester builder
|
|
278
|
+
* @returns SuggesterBuilder instance
|
|
279
|
+
*/
|
|
280
|
+
const createSuggesterBuilder = (state = {}) => ({
|
|
281
|
+
term: (name, text, options) => {
|
|
282
|
+
return createSuggesterBuilder({
|
|
283
|
+
...state,
|
|
284
|
+
[name]: {
|
|
285
|
+
text,
|
|
286
|
+
term: options
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
},
|
|
290
|
+
phrase: (name, text, options) => {
|
|
291
|
+
return createSuggesterBuilder({
|
|
292
|
+
...state,
|
|
293
|
+
[name]: {
|
|
294
|
+
text,
|
|
295
|
+
phrase: options
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
},
|
|
299
|
+
completion: (name, prefix, options) => {
|
|
300
|
+
return createSuggesterBuilder({
|
|
301
|
+
...state,
|
|
302
|
+
[name]: {
|
|
303
|
+
prefix,
|
|
304
|
+
completion: options
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
build: () => ({ suggest: state })
|
|
309
|
+
});
|
|
310
|
+
/**
|
|
311
|
+
* Factory function to create a new suggester builder
|
|
312
|
+
* @example
|
|
313
|
+
* ```typescript
|
|
314
|
+
* const suggestions = suggest(productMappings)
|
|
315
|
+
* .term('name-suggestions', 'laptop', { field: 'name', size: 5 })
|
|
316
|
+
* .build();
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
const suggest = (_schema) => createSuggesterBuilder();
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region src/query.builder.ts
|
|
322
|
+
const resolveCondition = (c) => typeof c === "function" ? resolveCondition(c()) : typeof c === "boolean" ? c : c != null;
|
|
323
|
+
const createClauseMethods = (wrap, qualifyField = (f) => f) => ({
|
|
324
|
+
matchAll: () => wrap({ match_all: {} }),
|
|
325
|
+
matchNone: () => wrap({ match_none: {} }),
|
|
326
|
+
match: (field, value, options) => wrap({ match: { [qualifyField(field)]: options ? {
|
|
327
|
+
query: value,
|
|
328
|
+
...options
|
|
329
|
+
} : value } }),
|
|
330
|
+
multiMatch: (fields, query, options) => wrap({ multi_match: {
|
|
331
|
+
fields: fields.map(qualifyField),
|
|
332
|
+
query,
|
|
333
|
+
...options
|
|
334
|
+
} }),
|
|
335
|
+
matchPhrase: (field, query) => wrap({ match_phrase: { [qualifyField(field)]: query } }),
|
|
336
|
+
matchPhrasePrefix: (field, value, options) => wrap({ match_phrase_prefix: { [qualifyField(field)]: options ? {
|
|
337
|
+
query: value,
|
|
338
|
+
...options
|
|
339
|
+
} : value } }),
|
|
340
|
+
term: (field, value) => wrap({ term: { [qualifyField(field)]: value } }),
|
|
341
|
+
terms: (field, value) => wrap({ terms: { [qualifyField(field)]: value } }),
|
|
342
|
+
range: (field, conditions) => wrap({ range: { [qualifyField(field)]: conditions } }),
|
|
343
|
+
exists: (field) => wrap({ exists: { field: qualifyField(field) } }),
|
|
344
|
+
prefix: (field, value) => wrap({ prefix: { [qualifyField(field)]: value } }),
|
|
345
|
+
wildcard: (field, value) => wrap({ wildcard: { [qualifyField(field)]: value } }),
|
|
346
|
+
fuzzy: (field, value, options) => wrap({ fuzzy: { [qualifyField(field)]: options ? {
|
|
347
|
+
value,
|
|
348
|
+
...options
|
|
349
|
+
} : { value } } }),
|
|
350
|
+
ids: (values) => wrap({ ids: { values } }),
|
|
351
|
+
script: (options) => {
|
|
352
|
+
const { source, lang = "painless", params, boost } = options;
|
|
353
|
+
return wrap({ script: {
|
|
354
|
+
script: {
|
|
355
|
+
source,
|
|
356
|
+
lang,
|
|
357
|
+
...params !== void 0 && { params }
|
|
358
|
+
},
|
|
359
|
+
...boost !== void 0 && { boost }
|
|
360
|
+
} });
|
|
361
|
+
},
|
|
362
|
+
combinedFields: (fields, query, options) => wrap({ combined_fields: {
|
|
363
|
+
fields: fields.map(qualifyField),
|
|
364
|
+
query,
|
|
365
|
+
...options
|
|
366
|
+
} }),
|
|
367
|
+
queryString: (query, options) => wrap({ query_string: {
|
|
368
|
+
query,
|
|
369
|
+
...options
|
|
370
|
+
} }),
|
|
371
|
+
simpleQueryString: (query, options) => wrap({ simple_query_string: {
|
|
372
|
+
query,
|
|
373
|
+
...options
|
|
374
|
+
} }),
|
|
375
|
+
moreLikeThis: (fields, like, options) => wrap({ more_like_this: {
|
|
376
|
+
fields: fields.map(qualifyField),
|
|
377
|
+
like: Array.isArray(like) ? like : [like],
|
|
378
|
+
...options
|
|
379
|
+
} }),
|
|
380
|
+
matchBoolPrefix: (field, value, options) => wrap({ match_bool_prefix: { [qualifyField(field)]: options ? {
|
|
381
|
+
query: value,
|
|
382
|
+
...options
|
|
383
|
+
} : value } }),
|
|
384
|
+
regexp: (field, value, options) => wrap({ regexp: { [qualifyField(field)]: options ? {
|
|
385
|
+
value,
|
|
386
|
+
...options
|
|
387
|
+
} : value } }),
|
|
388
|
+
geoDistance: (field, center, options) => wrap({ geo_distance: {
|
|
389
|
+
[qualifyField(field)]: center,
|
|
390
|
+
...options
|
|
391
|
+
} }),
|
|
392
|
+
geoBoundingBox: (field, options) => wrap({ geo_bounding_box: { [qualifyField(field)]: options } }),
|
|
393
|
+
geoPolygon: (field, options) => wrap({ geo_polygon: { [qualifyField(field)]: options } }),
|
|
394
|
+
geoShape: (field, shape, options) => wrap({ geo_shape: { [qualifyField(field)]: {
|
|
395
|
+
shape,
|
|
396
|
+
...options
|
|
397
|
+
} } }),
|
|
398
|
+
distanceFeature: (field, options) => wrap({ distance_feature: {
|
|
399
|
+
field: qualifyField(field),
|
|
400
|
+
...options
|
|
401
|
+
} }),
|
|
402
|
+
rankFeature: (field, options) => wrap({ rank_feature: {
|
|
403
|
+
field: qualifyField(field),
|
|
404
|
+
...options
|
|
405
|
+
} }),
|
|
406
|
+
sparseVector: (field, options) => wrap({ sparse_vector: {
|
|
407
|
+
field: qualifyField(field),
|
|
408
|
+
...options
|
|
409
|
+
} }),
|
|
410
|
+
intervals: (field, options) => wrap({ intervals: { [qualifyField(field)]: options } }),
|
|
411
|
+
spanTerm: (field, value) => wrap({ span_term: { [qualifyField(field)]: { value } } }),
|
|
412
|
+
spanNear: (clauses, options) => wrap({ span_near: {
|
|
413
|
+
clauses: [...clauses],
|
|
414
|
+
...options
|
|
415
|
+
} }),
|
|
416
|
+
spanOr: (clauses) => wrap({ span_or: { clauses: [...clauses] } }),
|
|
417
|
+
spanNot: (include, exclude, options) => wrap({ span_not: {
|
|
418
|
+
include,
|
|
419
|
+
exclude,
|
|
420
|
+
...options
|
|
421
|
+
} }),
|
|
422
|
+
spanFirst: (match, end) => wrap({ span_first: {
|
|
423
|
+
match,
|
|
424
|
+
end
|
|
425
|
+
} }),
|
|
426
|
+
spanContaining: (big, little) => wrap({ span_containing: {
|
|
427
|
+
big,
|
|
428
|
+
little
|
|
429
|
+
} }),
|
|
430
|
+
spanWithin: (big, little) => wrap({ span_within: {
|
|
431
|
+
big,
|
|
432
|
+
little
|
|
433
|
+
} }),
|
|
434
|
+
spanMultiTerm: (query) => wrap({ span_multi: { match: query } }),
|
|
435
|
+
spanFieldMasking: (field, query) => wrap({ span_field_masking: {
|
|
436
|
+
field: qualifyField(field),
|
|
437
|
+
query
|
|
438
|
+
} })
|
|
439
|
+
});
|
|
440
|
+
const createClauseBuilder = (prefix) => {
|
|
441
|
+
const qualifyField = prefix ? (f) => `${prefix}.${f}` : (f) => f;
|
|
442
|
+
return {
|
|
443
|
+
...createClauseMethods((dsl) => dsl, qualifyField),
|
|
444
|
+
knn: (field, queryVector, options) => {
|
|
445
|
+
const { k, num_candidates, ...restOptions } = options;
|
|
446
|
+
return { knn: {
|
|
447
|
+
field: qualifyField(field),
|
|
448
|
+
query_vector: queryVector,
|
|
449
|
+
k,
|
|
450
|
+
num_candidates,
|
|
451
|
+
...restOptions
|
|
452
|
+
} };
|
|
453
|
+
},
|
|
454
|
+
nested: (path, fn, options) => {
|
|
455
|
+
const nestedPath = prefix ? `${prefix}.${path}` : path;
|
|
456
|
+
const nestedQuery = fn(createClauseBuilder(nestedPath));
|
|
457
|
+
return nestedQuery === void 0 ? void 0 : { nested: {
|
|
458
|
+
path: nestedPath,
|
|
459
|
+
query: nestedQuery,
|
|
460
|
+
...options
|
|
461
|
+
} };
|
|
462
|
+
},
|
|
463
|
+
functionScore: (queryFn, options) => {
|
|
464
|
+
return { function_score: {
|
|
465
|
+
query: queryFn(createClauseBuilder(prefix)) ?? { match_all: {} },
|
|
466
|
+
...options
|
|
467
|
+
} };
|
|
468
|
+
},
|
|
469
|
+
hasChild: (type, queryFn, options) => {
|
|
470
|
+
return { has_child: {
|
|
471
|
+
type,
|
|
472
|
+
query: queryFn(createClauseBuilder()) ?? { match_all: {} },
|
|
473
|
+
...options
|
|
474
|
+
} };
|
|
475
|
+
},
|
|
476
|
+
hasParent: (type, queryFn, options) => {
|
|
477
|
+
return { has_parent: {
|
|
478
|
+
parent_type: type,
|
|
479
|
+
query: queryFn(createClauseBuilder()) ?? { match_all: {} },
|
|
480
|
+
...options
|
|
481
|
+
} };
|
|
482
|
+
},
|
|
483
|
+
parentId: (type, id, options) => ({ parent_id: {
|
|
484
|
+
type,
|
|
485
|
+
id,
|
|
486
|
+
...options
|
|
487
|
+
} }),
|
|
488
|
+
when: (condition, thenFn) => resolveCondition(condition) ? thenFn(createClauseBuilder(prefix)) : void 0
|
|
489
|
+
};
|
|
490
|
+
};
|
|
491
|
+
const createQueryBuilder = (state = {}) => {
|
|
492
|
+
const q = state.query ?? {};
|
|
493
|
+
return {
|
|
494
|
+
...createClauseMethods((dsl) => createQueryBuilder({
|
|
495
|
+
...state,
|
|
496
|
+
query: dsl
|
|
497
|
+
})),
|
|
498
|
+
bool: () => createQueryBuilder({
|
|
499
|
+
...state,
|
|
500
|
+
query: { bool: {} }
|
|
501
|
+
}),
|
|
502
|
+
must: (builderFn) => {
|
|
503
|
+
const clause = builderFn(createClauseBuilder());
|
|
504
|
+
const existing = q.bool?.must ?? [];
|
|
505
|
+
return clause === void 0 ? createQueryBuilder(state) : createQueryBuilder({
|
|
506
|
+
...state,
|
|
507
|
+
query: { bool: {
|
|
508
|
+
...q.bool,
|
|
509
|
+
must: [...existing, clause]
|
|
510
|
+
} }
|
|
511
|
+
});
|
|
512
|
+
},
|
|
513
|
+
mustNot: (builderFn) => {
|
|
514
|
+
const clause = builderFn(createClauseBuilder());
|
|
515
|
+
const existing = q.bool?.must_not ?? [];
|
|
516
|
+
return clause === void 0 ? createQueryBuilder(state) : createQueryBuilder({
|
|
517
|
+
...state,
|
|
518
|
+
query: { bool: {
|
|
519
|
+
...q.bool,
|
|
520
|
+
must_not: [...existing, clause]
|
|
521
|
+
} }
|
|
522
|
+
});
|
|
523
|
+
},
|
|
524
|
+
should: (builderFn) => {
|
|
525
|
+
const clause = builderFn(createClauseBuilder());
|
|
526
|
+
const existing = q.bool?.should ?? [];
|
|
527
|
+
return clause === void 0 ? createQueryBuilder(state) : createQueryBuilder({
|
|
528
|
+
...state,
|
|
529
|
+
query: { bool: {
|
|
530
|
+
...q.bool,
|
|
531
|
+
should: [...existing, clause]
|
|
532
|
+
} }
|
|
533
|
+
});
|
|
534
|
+
},
|
|
535
|
+
filter: (builderFn) => {
|
|
536
|
+
const clause = builderFn(createClauseBuilder());
|
|
537
|
+
const existing = q.bool?.filter ?? [];
|
|
538
|
+
return clause === void 0 ? createQueryBuilder(state) : createQueryBuilder({
|
|
539
|
+
...state,
|
|
540
|
+
query: { bool: {
|
|
541
|
+
...q.bool,
|
|
542
|
+
filter: [...existing, clause]
|
|
543
|
+
} }
|
|
544
|
+
});
|
|
545
|
+
},
|
|
546
|
+
minimumShouldMatch: (value) => createQueryBuilder({
|
|
547
|
+
...state,
|
|
548
|
+
query: { bool: {
|
|
549
|
+
...q.bool,
|
|
550
|
+
minimum_should_match: value
|
|
551
|
+
} }
|
|
552
|
+
}),
|
|
553
|
+
knn: (field, queryVector, options) => {
|
|
554
|
+
const { k, num_candidates, ...restOptions } = options;
|
|
555
|
+
return createQueryBuilder({
|
|
556
|
+
...state,
|
|
557
|
+
knn: {
|
|
558
|
+
field,
|
|
559
|
+
query_vector: queryVector,
|
|
560
|
+
k,
|
|
561
|
+
num_candidates,
|
|
562
|
+
...restOptions
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
},
|
|
566
|
+
nested: (path, fn, options) => {
|
|
567
|
+
const nestedQuery = fn(createClauseBuilder(path));
|
|
568
|
+
return nestedQuery === void 0 ? createQueryBuilder(state) : createQueryBuilder({
|
|
569
|
+
...state,
|
|
570
|
+
query: { nested: {
|
|
571
|
+
path,
|
|
572
|
+
query: nestedQuery,
|
|
573
|
+
...options
|
|
574
|
+
} }
|
|
575
|
+
});
|
|
576
|
+
},
|
|
577
|
+
scriptScore: (queryFn, script, options) => {
|
|
578
|
+
const innerQuery = queryFn(createClauseBuilder()) ?? { match_all: {} };
|
|
579
|
+
const { source, lang = "painless", params } = script;
|
|
580
|
+
return createQueryBuilder({
|
|
581
|
+
...state,
|
|
582
|
+
query: { script_score: {
|
|
583
|
+
query: innerQuery,
|
|
584
|
+
script: {
|
|
585
|
+
source,
|
|
586
|
+
lang,
|
|
587
|
+
...params !== void 0 && { params }
|
|
588
|
+
},
|
|
589
|
+
...options?.min_score !== void 0 && { min_score: options.min_score },
|
|
590
|
+
...options?.boost !== void 0 && { boost: options.boost }
|
|
591
|
+
} }
|
|
592
|
+
});
|
|
593
|
+
},
|
|
594
|
+
percolate: (options) => createQueryBuilder({
|
|
595
|
+
...state,
|
|
596
|
+
query: { percolate: { ...options } }
|
|
597
|
+
}),
|
|
598
|
+
functionScore: (queryFn, options) => {
|
|
599
|
+
const innerQuery = queryFn(createClauseBuilder()) ?? { match_all: {} };
|
|
600
|
+
return createQueryBuilder({
|
|
601
|
+
...state,
|
|
602
|
+
query: { function_score: {
|
|
603
|
+
query: innerQuery,
|
|
604
|
+
...options
|
|
605
|
+
} }
|
|
606
|
+
});
|
|
607
|
+
},
|
|
608
|
+
hasChild: (type, queryFn, options) => {
|
|
609
|
+
const childQuery = queryFn(createClauseBuilder()) ?? { match_all: {} };
|
|
610
|
+
return createQueryBuilder({
|
|
611
|
+
...state,
|
|
612
|
+
query: { has_child: {
|
|
613
|
+
type,
|
|
614
|
+
query: childQuery,
|
|
615
|
+
...options
|
|
616
|
+
} }
|
|
617
|
+
});
|
|
618
|
+
},
|
|
619
|
+
hasParent: (type, queryFn, options) => {
|
|
620
|
+
const parentQuery = queryFn(createClauseBuilder()) ?? { match_all: {} };
|
|
621
|
+
return createQueryBuilder({
|
|
622
|
+
...state,
|
|
623
|
+
query: { has_parent: {
|
|
624
|
+
parent_type: type,
|
|
625
|
+
query: parentQuery,
|
|
626
|
+
...options
|
|
627
|
+
} }
|
|
628
|
+
});
|
|
629
|
+
},
|
|
630
|
+
parentId: (type, id, options) => createQueryBuilder({
|
|
631
|
+
...state,
|
|
632
|
+
query: { parent_id: {
|
|
633
|
+
type,
|
|
634
|
+
id,
|
|
635
|
+
...options
|
|
636
|
+
} }
|
|
637
|
+
}),
|
|
638
|
+
when: (condition, thenFn) => resolveCondition(condition) ? thenFn(createQueryBuilder(state)) : createQueryBuilder(state),
|
|
639
|
+
sort: (field, direction = "asc") => {
|
|
640
|
+
const existing = state.sort || [];
|
|
641
|
+
return createQueryBuilder({
|
|
642
|
+
...state,
|
|
643
|
+
sort: [...existing, { [field]: direction }]
|
|
644
|
+
});
|
|
645
|
+
},
|
|
646
|
+
from: (from) => createQueryBuilder({
|
|
647
|
+
...state,
|
|
648
|
+
from
|
|
649
|
+
}),
|
|
650
|
+
size: (size) => createQueryBuilder({
|
|
651
|
+
...state,
|
|
652
|
+
size
|
|
653
|
+
}),
|
|
654
|
+
_source: (_source) => createQueryBuilder({
|
|
655
|
+
...state,
|
|
656
|
+
_source
|
|
657
|
+
}),
|
|
658
|
+
sourceIncludes: (paths) => createQueryBuilder({
|
|
659
|
+
...state,
|
|
660
|
+
_source_includes: paths
|
|
661
|
+
}),
|
|
662
|
+
sourceExcludes: (paths) => createQueryBuilder({
|
|
663
|
+
...state,
|
|
664
|
+
_source_excludes: paths
|
|
665
|
+
}),
|
|
666
|
+
runtimeMappings: (runtime_mappings) => createQueryBuilder({
|
|
667
|
+
...state,
|
|
668
|
+
runtime_mappings
|
|
669
|
+
}),
|
|
670
|
+
docValueFields: (docvalue_fields) => createQueryBuilder({
|
|
671
|
+
...state,
|
|
672
|
+
docvalue_fields
|
|
673
|
+
}),
|
|
674
|
+
fields: (fields) => createQueryBuilder({
|
|
675
|
+
...state,
|
|
676
|
+
fields
|
|
677
|
+
}),
|
|
678
|
+
postFilter: (fn) => {
|
|
679
|
+
const clause = fn(createClauseBuilder());
|
|
680
|
+
return clause === void 0 ? createQueryBuilder(state) : createQueryBuilder({
|
|
681
|
+
...state,
|
|
682
|
+
post_filter: clause
|
|
683
|
+
});
|
|
684
|
+
},
|
|
685
|
+
scriptFields: (script_fields) => createQueryBuilder({
|
|
686
|
+
...state,
|
|
687
|
+
script_fields
|
|
688
|
+
}),
|
|
689
|
+
timeout: (timeout) => createQueryBuilder({
|
|
690
|
+
...state,
|
|
691
|
+
timeout
|
|
692
|
+
}),
|
|
693
|
+
trackScores: (track_scores) => createQueryBuilder({
|
|
694
|
+
...state,
|
|
695
|
+
track_scores
|
|
696
|
+
}),
|
|
697
|
+
explain: (explain) => createQueryBuilder({
|
|
698
|
+
...state,
|
|
699
|
+
explain
|
|
700
|
+
}),
|
|
701
|
+
minScore: (min_score) => createQueryBuilder({
|
|
702
|
+
...state,
|
|
703
|
+
min_score
|
|
704
|
+
}),
|
|
705
|
+
version: (version) => createQueryBuilder({
|
|
706
|
+
...state,
|
|
707
|
+
version
|
|
708
|
+
}),
|
|
709
|
+
seqNoPrimaryTerm: (seq_no_primary_term) => createQueryBuilder({
|
|
710
|
+
...state,
|
|
711
|
+
seq_no_primary_term
|
|
712
|
+
}),
|
|
713
|
+
trackTotalHits: (track_total_hits = true) => createQueryBuilder({
|
|
714
|
+
...state,
|
|
715
|
+
track_total_hits
|
|
716
|
+
}),
|
|
717
|
+
highlight: (fields, options) => {
|
|
718
|
+
const { pre_tags, post_tags, ...fieldOptions } = options || {};
|
|
719
|
+
const fieldValue = Object.keys(fieldOptions).length > 0 ? fieldOptions : {};
|
|
720
|
+
const highlightFields = Object.fromEntries(fields.map((field) => [field, fieldValue]));
|
|
721
|
+
return createQueryBuilder({
|
|
722
|
+
...state,
|
|
723
|
+
highlight: {
|
|
724
|
+
fields: highlightFields,
|
|
725
|
+
...pre_tags && { pre_tags },
|
|
726
|
+
...post_tags && { post_tags }
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
},
|
|
730
|
+
constantScore: (fn, options) => {
|
|
731
|
+
const clause = fn(createClauseBuilder());
|
|
732
|
+
return createQueryBuilder({
|
|
733
|
+
...state,
|
|
734
|
+
query: { constant_score: {
|
|
735
|
+
filter: clause,
|
|
736
|
+
...options
|
|
737
|
+
} }
|
|
738
|
+
});
|
|
739
|
+
},
|
|
740
|
+
searchAfter: (values) => createQueryBuilder({
|
|
741
|
+
...state,
|
|
742
|
+
search_after: values
|
|
743
|
+
}),
|
|
744
|
+
preference: (value) => createQueryBuilder({
|
|
745
|
+
...state,
|
|
746
|
+
preference: value
|
|
747
|
+
}),
|
|
748
|
+
collapse: (field, options) => createQueryBuilder({
|
|
749
|
+
...state,
|
|
750
|
+
collapse: {
|
|
751
|
+
field,
|
|
752
|
+
...options
|
|
753
|
+
}
|
|
754
|
+
}),
|
|
755
|
+
rescore: (queryFn, windowSize, options) => {
|
|
756
|
+
const rescoreQuery = queryFn(createClauseBuilder());
|
|
757
|
+
return createQueryBuilder({
|
|
758
|
+
...state,
|
|
759
|
+
rescore: {
|
|
760
|
+
window_size: windowSize,
|
|
761
|
+
query: {
|
|
762
|
+
rescore_query: rescoreQuery,
|
|
763
|
+
...options
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
},
|
|
768
|
+
storedFields: (fields) => createQueryBuilder({
|
|
769
|
+
...state,
|
|
770
|
+
stored_fields: fields
|
|
771
|
+
}),
|
|
772
|
+
terminateAfter: (count) => createQueryBuilder({
|
|
773
|
+
...state,
|
|
774
|
+
terminate_after: count
|
|
775
|
+
}),
|
|
776
|
+
pit: (id, keepAlive) => createQueryBuilder({
|
|
777
|
+
...state,
|
|
778
|
+
pit: {
|
|
779
|
+
id,
|
|
780
|
+
keep_alive: keepAlive
|
|
781
|
+
}
|
|
782
|
+
}),
|
|
783
|
+
indicesBoost: (boosts) => createQueryBuilder({
|
|
784
|
+
...state,
|
|
785
|
+
indices_boost: boosts
|
|
786
|
+
}),
|
|
787
|
+
aggs: (fn) => {
|
|
788
|
+
const builtAggs = fn(createAggregationBuilder()).build();
|
|
789
|
+
return createQueryBuilder({
|
|
790
|
+
...state,
|
|
791
|
+
aggs: builtAggs
|
|
792
|
+
});
|
|
793
|
+
},
|
|
794
|
+
suggest: (fn) => {
|
|
795
|
+
const builtSuggestions = fn(createSuggesterBuilder()).build();
|
|
796
|
+
return createQueryBuilder({
|
|
797
|
+
...state,
|
|
798
|
+
suggest: builtSuggestions.suggest
|
|
799
|
+
});
|
|
800
|
+
},
|
|
801
|
+
build: () => {
|
|
802
|
+
const { _includeQuery, ...rest } = state;
|
|
803
|
+
if (_includeQuery === false) {
|
|
804
|
+
const { query: _q, ...noQuery } = rest;
|
|
805
|
+
return noQuery;
|
|
806
|
+
}
|
|
807
|
+
return rest;
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
};
|
|
811
|
+
//#endregion
|
|
812
|
+
//#region src/mapping.builder.ts
|
|
813
|
+
const mappings = (fields, options) => {
|
|
814
|
+
return {
|
|
815
|
+
_fieldTypes: void 0,
|
|
816
|
+
_fields: void 0,
|
|
817
|
+
properties: { ...fields },
|
|
818
|
+
_mappingOptions: {
|
|
819
|
+
dynamic: "strict",
|
|
820
|
+
...options
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
};
|
|
824
|
+
//#endregion
|
|
825
|
+
//#region src/field.helpers.ts
|
|
826
|
+
const text = (options) => ({
|
|
827
|
+
type: "text",
|
|
828
|
+
...options
|
|
829
|
+
});
|
|
830
|
+
const keyword = (options) => ({
|
|
831
|
+
type: "keyword",
|
|
832
|
+
...options
|
|
833
|
+
});
|
|
834
|
+
const long = (options) => ({
|
|
835
|
+
type: "long",
|
|
836
|
+
...options
|
|
837
|
+
});
|
|
838
|
+
const integer = (options) => ({
|
|
839
|
+
type: "integer",
|
|
840
|
+
...options
|
|
841
|
+
});
|
|
842
|
+
const short = (options) => ({
|
|
843
|
+
type: "short",
|
|
844
|
+
...options
|
|
845
|
+
});
|
|
846
|
+
const byte = (options) => ({
|
|
847
|
+
type: "byte",
|
|
848
|
+
...options
|
|
849
|
+
});
|
|
850
|
+
const double = (options) => ({
|
|
851
|
+
type: "double",
|
|
852
|
+
...options
|
|
853
|
+
});
|
|
854
|
+
const float = (options) => ({
|
|
855
|
+
type: "float",
|
|
856
|
+
...options
|
|
857
|
+
});
|
|
858
|
+
const halfFloat = (options) => ({
|
|
859
|
+
type: "half_float",
|
|
860
|
+
...options
|
|
861
|
+
});
|
|
862
|
+
const scaledFloat = (options) => ({
|
|
863
|
+
type: "scaled_float",
|
|
864
|
+
...options
|
|
865
|
+
});
|
|
866
|
+
const date = (options) => ({
|
|
867
|
+
type: "date",
|
|
868
|
+
...options
|
|
869
|
+
});
|
|
870
|
+
/**
|
|
871
|
+
* Date field stored with nanosecond precision. Same API as `date()` but supports sub-millisecond timestamps.
|
|
872
|
+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/date_nanos.html
|
|
873
|
+
*/
|
|
874
|
+
const dateNanos = (options) => ({
|
|
875
|
+
type: "date_nanos",
|
|
876
|
+
...options
|
|
877
|
+
});
|
|
878
|
+
const boolean = (options) => ({
|
|
879
|
+
type: "boolean",
|
|
880
|
+
...options
|
|
881
|
+
});
|
|
882
|
+
const binary = () => ({ type: "binary" });
|
|
883
|
+
const ip = (options) => ({
|
|
884
|
+
type: "ip",
|
|
885
|
+
...options
|
|
886
|
+
});
|
|
887
|
+
const denseVector = (options) => ({
|
|
888
|
+
type: "dense_vector",
|
|
889
|
+
...options
|
|
890
|
+
});
|
|
891
|
+
/**
|
|
892
|
+
* Quantized dense vector field — wraps `denseVector()` with `int8_hnsw` index type.
|
|
893
|
+
*
|
|
894
|
+
* Quantizes float32 vectors to int8 at index time, saving ~75% memory.
|
|
895
|
+
* Recommended for vectors with dims >= 384. Original float vectors are retained
|
|
896
|
+
* in the index, enabling a two-phase search pattern:
|
|
897
|
+
*
|
|
898
|
+
* 1. Fast approximate search using quantized int8 vectors
|
|
899
|
+
* 2. Precise rescore of top-k results using retained float vectors
|
|
900
|
+
*
|
|
901
|
+
* @example
|
|
902
|
+
* // Index mapping
|
|
903
|
+
* const schema = mappings({
|
|
904
|
+
* title: text(),
|
|
905
|
+
* embedding: quantizedDenseVector({ dims: 768, similarity: 'cosine' }),
|
|
906
|
+
* }, {
|
|
907
|
+
* _source: { excludes: ['embedding'] },
|
|
908
|
+
* });
|
|
909
|
+
*
|
|
910
|
+
* // Two-phase search: fast kNN + precise rescore
|
|
911
|
+
* const result = queryBuilder(schema)
|
|
912
|
+
* .knn('embedding', queryVector, { k: 100, num_candidates: 200 })
|
|
913
|
+
* .rescore(
|
|
914
|
+
* (q) => q.scriptScore(
|
|
915
|
+
* (inner) => inner.matchAll(),
|
|
916
|
+
* { source: "cosineSimilarity(params.v, 'embedding') + 1.0", params: { v: queryVector } }
|
|
917
|
+
* ),
|
|
918
|
+
* 100
|
|
919
|
+
* )
|
|
920
|
+
* .build();
|
|
921
|
+
*/
|
|
922
|
+
const quantizedDenseVector = (options) => ({
|
|
923
|
+
type: "dense_vector",
|
|
924
|
+
...options,
|
|
925
|
+
index_options: {
|
|
926
|
+
type: "int8_hnsw",
|
|
927
|
+
...options?.index_options
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
/**
|
|
931
|
+
* Sparse vector field — stores token-weight pairs for sparse retrieval (e.g. ELSER/BM25-style models).
|
|
932
|
+
* Complement to `denseVector()`. Query with the `sparse_vector` query.
|
|
933
|
+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/sparse-vector.html
|
|
934
|
+
*/
|
|
935
|
+
const sparseVector = () => ({ type: "sparse_vector" });
|
|
936
|
+
/**
|
|
937
|
+
* Rank feature field — a single numeric feature used by the `rank_feature` query to boost relevance.
|
|
938
|
+
* Use when each document has one named signal (e.g. `pagerank`, `popularity_score`).
|
|
939
|
+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/rank-feature.html
|
|
940
|
+
*/
|
|
941
|
+
const rankFeature = (options) => ({
|
|
942
|
+
type: "rank_feature",
|
|
943
|
+
...options
|
|
944
|
+
});
|
|
945
|
+
/**
|
|
946
|
+
* Rank features field — a sparse map of numeric features used by the `rank_feature` query.
|
|
947
|
+
* Use when each document has many named signals (e.g. topic scores).
|
|
948
|
+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/rank-features.html
|
|
949
|
+
*/
|
|
950
|
+
const rankFeatures = (options) => ({
|
|
951
|
+
type: "rank_features",
|
|
952
|
+
...options
|
|
953
|
+
});
|
|
954
|
+
/**
|
|
955
|
+
* Semantic text field (ES 9.x) — ML-powered text field for semantic and hybrid search.
|
|
956
|
+
* Automatically generates and stores embeddings at index time using the configured inference endpoint.
|
|
957
|
+
*
|
|
958
|
+
* @example
|
|
959
|
+
* const schema = mappings({
|
|
960
|
+
* title: text(),
|
|
961
|
+
* body: semanticText({ inference_id: 'my-elser-endpoint' }),
|
|
962
|
+
* });
|
|
963
|
+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html
|
|
964
|
+
*/
|
|
965
|
+
const semanticText = (options) => ({
|
|
966
|
+
type: "semantic_text",
|
|
967
|
+
...options
|
|
968
|
+
});
|
|
969
|
+
/**
|
|
970
|
+
* Unsigned long field (ES 9.0+) — stores unsigned 64-bit integer values (0 to 2^64-1).
|
|
971
|
+
* Use when values exceed the `long` range.
|
|
972
|
+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html
|
|
973
|
+
*/
|
|
974
|
+
const unsignedLong = (options) => ({
|
|
975
|
+
type: "unsigned_long",
|
|
976
|
+
...options
|
|
977
|
+
});
|
|
978
|
+
const geoPoint = (options) => ({
|
|
979
|
+
type: "geo_point",
|
|
980
|
+
...options
|
|
981
|
+
});
|
|
982
|
+
const geoShape = (options) => ({
|
|
983
|
+
type: "geo_shape",
|
|
984
|
+
...options
|
|
985
|
+
});
|
|
986
|
+
const completion = (options) => ({
|
|
987
|
+
type: "completion",
|
|
988
|
+
...options
|
|
989
|
+
});
|
|
990
|
+
/**
|
|
991
|
+
* Object field — for JSON-like structured documents where sub-fields are queried with dot-notation.
|
|
992
|
+
*
|
|
993
|
+
* The most common way to model structured data (e.g. `{ address: { city, zip } }`).
|
|
994
|
+
* Sub-fields are indexed inline within the parent document — no special query wrapper needed.
|
|
995
|
+
* Query sub-fields directly using dot-notation: `.term('address.city', 'NYC')`.
|
|
996
|
+
*
|
|
997
|
+
* Use `nested()` instead when you have **arrays of objects** and need cross-field queries
|
|
998
|
+
* within each element to be accurate (e.g. tags with both a label and weight).
|
|
999
|
+
*
|
|
1000
|
+
* @example
|
|
1001
|
+
* const m = mappings({
|
|
1002
|
+
* address: object({
|
|
1003
|
+
* street: text(),
|
|
1004
|
+
* city: keyword(),
|
|
1005
|
+
* zip: keyword(),
|
|
1006
|
+
* }),
|
|
1007
|
+
* });
|
|
1008
|
+
* queryBuilder(m).term('address.city', 'NYC').build();
|
|
1009
|
+
*/
|
|
1010
|
+
function object(fields, options) {
|
|
1011
|
+
return {
|
|
1012
|
+
type: "object",
|
|
1013
|
+
...options ?? {},
|
|
1014
|
+
properties: fields
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Nested field — for **arrays of objects** where cross-field queries within each element must be accurate.
|
|
1019
|
+
*
|
|
1020
|
+
* Each nested object is stored as a separate hidden Elasticsearch document, preserving the
|
|
1021
|
+
* relationship between sub-fields within each element. Without `nested`, Elasticsearch flattens
|
|
1022
|
+
* array sub-fields and loses which values belong to the same element.
|
|
1023
|
+
*
|
|
1024
|
+
* Queries on nested fields **must** use the `.nested()` query builder method — direct dot-notation
|
|
1025
|
+
* queries will not find nested documents.
|
|
1026
|
+
*
|
|
1027
|
+
* Use `object()` instead for single structured objects (addresses, names, etc.) — it is simpler,
|
|
1028
|
+
* more efficient, and does not require a query wrapper.
|
|
1029
|
+
*
|
|
1030
|
+
* @example
|
|
1031
|
+
* const m = mappings({
|
|
1032
|
+
* tags: nested({
|
|
1033
|
+
* label: keyword(),
|
|
1034
|
+
* weight: float(),
|
|
1035
|
+
* }),
|
|
1036
|
+
* });
|
|
1037
|
+
* queryBuilder(m).nested('tags', q => q.term('label', 'sale')).build();
|
|
1038
|
+
*/
|
|
1039
|
+
function nested(fields, options) {
|
|
1040
|
+
return {
|
|
1041
|
+
type: "nested",
|
|
1042
|
+
...options ?? {},
|
|
1043
|
+
properties: fields
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
const alias = (options) => ({
|
|
1047
|
+
type: "alias",
|
|
1048
|
+
...options
|
|
1049
|
+
});
|
|
1050
|
+
const percolator = () => ({ type: "percolator" });
|
|
1051
|
+
const integerRange = (options) => ({
|
|
1052
|
+
type: "integer_range",
|
|
1053
|
+
...options
|
|
1054
|
+
});
|
|
1055
|
+
const floatRange = (options) => ({
|
|
1056
|
+
type: "float_range",
|
|
1057
|
+
...options
|
|
1058
|
+
});
|
|
1059
|
+
const longRange = (options) => ({
|
|
1060
|
+
type: "long_range",
|
|
1061
|
+
...options
|
|
1062
|
+
});
|
|
1063
|
+
const doubleRange = (options) => ({
|
|
1064
|
+
type: "double_range",
|
|
1065
|
+
...options
|
|
1066
|
+
});
|
|
1067
|
+
const dateRange = (options) => ({
|
|
1068
|
+
type: "date_range",
|
|
1069
|
+
...options
|
|
1070
|
+
});
|
|
1071
|
+
/**
|
|
1072
|
+
* IP range field — stores a range of IPv4/IPv6 addresses.
|
|
1073
|
+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/range.html
|
|
1074
|
+
*/
|
|
1075
|
+
const ipRange = (options) => ({
|
|
1076
|
+
type: "ip_range",
|
|
1077
|
+
...options
|
|
1078
|
+
});
|
|
1079
|
+
/**
|
|
1080
|
+
* No-score text field — faster and uses less disk than `text()`.
|
|
1081
|
+
* Use when you only need filter/match but not relevance scoring (e.g., logs, simple field matches).
|
|
1082
|
+
*/
|
|
1083
|
+
const matchOnlyText = (options) => ({
|
|
1084
|
+
type: "match_only_text",
|
|
1085
|
+
...options
|
|
1086
|
+
});
|
|
1087
|
+
/**
|
|
1088
|
+
* Autocomplete / typeahead field. Creates sub-fields for edge n-gram matching
|
|
1089
|
+
* out of the box. Query with `multi_match` targeting the generated sub-fields.
|
|
1090
|
+
*/
|
|
1091
|
+
const searchAsYouType = (options) => ({
|
|
1092
|
+
type: "search_as_you_type",
|
|
1093
|
+
...options
|
|
1094
|
+
});
|
|
1095
|
+
/**
|
|
1096
|
+
* Field where every document has the same value. Useful for multi-index queries
|
|
1097
|
+
* to identify the index type (e.g., `constantKeyword({ value: 'product' })`).
|
|
1098
|
+
*/
|
|
1099
|
+
const constantKeyword = (options) => ({
|
|
1100
|
+
type: "constant_keyword",
|
|
1101
|
+
...options
|
|
1102
|
+
});
|
|
1103
|
+
/**
|
|
1104
|
+
* Optimized for grep-like wildcard/regexp queries on high-cardinality or large fields.
|
|
1105
|
+
* Use instead of `keyword()` when leading wildcards (`*foo`) are needed.
|
|
1106
|
+
*/
|
|
1107
|
+
const wildcardField = (options) => ({
|
|
1108
|
+
type: "wildcard",
|
|
1109
|
+
...options
|
|
1110
|
+
});
|
|
1111
|
+
/**
|
|
1112
|
+
* Flattens complex/dynamic objects into a single field. Faster than `nested()`,
|
|
1113
|
+
* but only supports keyword-level queries on inner values.
|
|
1114
|
+
*/
|
|
1115
|
+
const flattened = (options) => ({
|
|
1116
|
+
type: "flattened",
|
|
1117
|
+
...options
|
|
1118
|
+
});
|
|
1119
|
+
/**
|
|
1120
|
+
* Token count field — stores the number of tokens produced by an analyzer.
|
|
1121
|
+
* Useful for enforcing minimum/maximum field lengths via queries or aggregations.
|
|
1122
|
+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/token-count.html
|
|
1123
|
+
*/
|
|
1124
|
+
const tokenCount = (options) => ({
|
|
1125
|
+
type: "token_count",
|
|
1126
|
+
...options
|
|
1127
|
+
});
|
|
1128
|
+
/**
|
|
1129
|
+
* Murmur3 hash field — computes and stores a murmur3 hash of field values at index time.
|
|
1130
|
+
* Requires the `mapper-murmur3` plugin.
|
|
1131
|
+
* @see https://www.elastic.co/guide/en/elasticsearch/plugins/current/mapper-murmur3.html
|
|
1132
|
+
*/
|
|
1133
|
+
const murmur3Hash = () => ({ type: "murmur3" });
|
|
1134
|
+
/**
|
|
1135
|
+
* Join field — defines parent/child relationships within a single index.
|
|
1136
|
+
* `relations` is required: maps parent names to one or more child names.
|
|
1137
|
+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html
|
|
1138
|
+
*
|
|
1139
|
+
* @example
|
|
1140
|
+
* const m = mappings({
|
|
1141
|
+
* title: text(),
|
|
1142
|
+
* relation: join({ relations: { question: 'answer' } }),
|
|
1143
|
+
* });
|
|
1144
|
+
*/
|
|
1145
|
+
const join = (options) => ({
|
|
1146
|
+
type: "join",
|
|
1147
|
+
...options
|
|
1148
|
+
});
|
|
1149
|
+
//#endregion
|
|
1150
|
+
//#region src/multi-search.builder.ts
|
|
1151
|
+
/**
|
|
1152
|
+
* Creates a multi-search builder
|
|
1153
|
+
* @returns MSearchBuilder instance
|
|
1154
|
+
*/
|
|
1155
|
+
const createMSearchBuilder = (searches = [], params = {}) => ({
|
|
1156
|
+
add: (request) => {
|
|
1157
|
+
return createMSearchBuilder([...searches, request], params);
|
|
1158
|
+
},
|
|
1159
|
+
addQuery: (body, header = {}) => {
|
|
1160
|
+
return createMSearchBuilder([...searches, {
|
|
1161
|
+
header,
|
|
1162
|
+
body
|
|
1163
|
+
}], params);
|
|
1164
|
+
},
|
|
1165
|
+
addQueryBuilder: (qb, header = {}) => {
|
|
1166
|
+
return createMSearchBuilder([...searches, {
|
|
1167
|
+
header,
|
|
1168
|
+
body: qb.build()
|
|
1169
|
+
}], params);
|
|
1170
|
+
},
|
|
1171
|
+
withParams: (next) => {
|
|
1172
|
+
return createMSearchBuilder(searches, {
|
|
1173
|
+
...params,
|
|
1174
|
+
...next
|
|
1175
|
+
});
|
|
1176
|
+
},
|
|
1177
|
+
build: () => {
|
|
1178
|
+
return `${searches.map(({ header, body }) => {
|
|
1179
|
+
return `${JSON.stringify(header || {})}\n${JSON.stringify(body)}`;
|
|
1180
|
+
}).join("\n")}\n`;
|
|
1181
|
+
},
|
|
1182
|
+
buildArray: () => {
|
|
1183
|
+
return searches.flatMap(({ header, body }) => [header || {}, body]);
|
|
1184
|
+
},
|
|
1185
|
+
buildParams: () => params
|
|
1186
|
+
});
|
|
1187
|
+
/**
|
|
1188
|
+
* Create a new multi-search builder
|
|
1189
|
+
* @example
|
|
1190
|
+
* const ms = msearch(productMappings)
|
|
1191
|
+
* .addQueryBuilder(queryBuilder(productMappings).match('name', 'shoe'), { index: 'products' })
|
|
1192
|
+
* .addQueryBuilder(queryBuilder(productMappings).match('name', 'shirt'), { index: 'products' })
|
|
1193
|
+
* .withParams({ max_concurrent_searches: 5, typed_keys: true })
|
|
1194
|
+
* .build();
|
|
1195
|
+
*/
|
|
1196
|
+
const msearch = (_schema) => createMSearchBuilder();
|
|
1197
|
+
//#endregion
|
|
1198
|
+
//#region src/bulk.builder.ts
|
|
1199
|
+
/**
|
|
1200
|
+
* Creates a bulk operations builder
|
|
1201
|
+
* @returns BulkBuilder instance
|
|
1202
|
+
*/
|
|
1203
|
+
const createBulkBuilder = (operations = []) => ({
|
|
1204
|
+
index: (doc, meta = {}) => {
|
|
1205
|
+
return createBulkBuilder([
|
|
1206
|
+
...operations,
|
|
1207
|
+
{ index: meta },
|
|
1208
|
+
doc
|
|
1209
|
+
]);
|
|
1210
|
+
},
|
|
1211
|
+
create: (doc, meta = {}) => {
|
|
1212
|
+
return createBulkBuilder([
|
|
1213
|
+
...operations,
|
|
1214
|
+
{ create: meta },
|
|
1215
|
+
doc
|
|
1216
|
+
]);
|
|
1217
|
+
},
|
|
1218
|
+
update: (meta) => {
|
|
1219
|
+
const { doc, script, upsert, doc_as_upsert, detect_noop, scripted_upsert, _source, ...header } = meta;
|
|
1220
|
+
const updateDoc = {
|
|
1221
|
+
...doc && { doc },
|
|
1222
|
+
...script && { script },
|
|
1223
|
+
...upsert && { upsert },
|
|
1224
|
+
...doc_as_upsert !== void 0 && { doc_as_upsert },
|
|
1225
|
+
...detect_noop !== void 0 && { detect_noop },
|
|
1226
|
+
...scripted_upsert !== void 0 && { scripted_upsert },
|
|
1227
|
+
..._source !== void 0 && { _source }
|
|
1228
|
+
};
|
|
1229
|
+
return createBulkBuilder([
|
|
1230
|
+
...operations,
|
|
1231
|
+
{ update: header },
|
|
1232
|
+
updateDoc
|
|
1233
|
+
]);
|
|
1234
|
+
},
|
|
1235
|
+
delete: (meta) => {
|
|
1236
|
+
return createBulkBuilder([...operations, { delete: meta }]);
|
|
1237
|
+
},
|
|
1238
|
+
build: () => {
|
|
1239
|
+
return `${operations.map((op) => JSON.stringify(op)).join("\n")}\n`;
|
|
1240
|
+
},
|
|
1241
|
+
buildArray: () => operations
|
|
1242
|
+
});
|
|
1243
|
+
/**
|
|
1244
|
+
* Create a new bulk operations builder.
|
|
1245
|
+
* `.build()` returns an NDJSON string (newline-delimited JSON) ready to POST to `/_bulk`.
|
|
1246
|
+
* `.buildArray()` returns the raw operation objects if you need to inspect or transform them.
|
|
1247
|
+
* @example
|
|
1248
|
+
* const ndjson = bulk(productMappings)
|
|
1249
|
+
* .index({ id: '1', name: 'Product 1' }, { _index: 'products', _id: '1' })
|
|
1250
|
+
* .create({ id: '2', name: 'Product 2' }, { _index: 'products', _id: '2' })
|
|
1251
|
+
* .update({ _index: 'products', _id: '3', doc: { name: 'Updated' } })
|
|
1252
|
+
* .delete({ _index: 'products', _id: '4' })
|
|
1253
|
+
* .build(); // POST to /_bulk with Content-Type: application/x-ndjson
|
|
1254
|
+
*/
|
|
1255
|
+
const bulk = (_schema) => createBulkBuilder();
|
|
1256
|
+
//#endregion
|
|
1257
|
+
//#region src/index-management.builder.ts
|
|
1258
|
+
const isMappingsSchema = (input) => "_fieldTypes" in input;
|
|
1259
|
+
/**
|
|
1260
|
+
* Creates an index builder.
|
|
1261
|
+
*
|
|
1262
|
+
* The optional generic `M` carries the field-type map from any mapping schema attached
|
|
1263
|
+
* via `.mappings(schema)`. `.mappings()` rebinds the generic to the incoming schema's
|
|
1264
|
+
* field-type map, so callers typically rely on inference rather than specifying `M` directly.
|
|
1265
|
+
*
|
|
1266
|
+
* @returns IndexBuilder instance
|
|
1267
|
+
*/
|
|
1268
|
+
const createIndexBuilder = (state = {}) => ({
|
|
1269
|
+
mappings: (schemaOrFields, inlineOptions) => {
|
|
1270
|
+
const rawProperties = isMappingsSchema(schemaOrFields) ? schemaOrFields.properties : schemaOrFields;
|
|
1271
|
+
const properties = Object.fromEntries(Object.entries(rawProperties).filter(([, v]) => v !== void 0));
|
|
1272
|
+
const mappingOptions = {
|
|
1273
|
+
dynamic: "strict",
|
|
1274
|
+
...isMappingsSchema(schemaOrFields) ? schemaOrFields._mappingOptions : void 0,
|
|
1275
|
+
...inlineOptions
|
|
1276
|
+
};
|
|
1277
|
+
return createIndexBuilder({
|
|
1278
|
+
...state,
|
|
1279
|
+
mappings: {
|
|
1280
|
+
properties,
|
|
1281
|
+
...mappingOptions.dynamic !== void 0 && { dynamic: mappingOptions.dynamic },
|
|
1282
|
+
...mappingOptions._source !== void 0 && { _source: mappingOptions._source },
|
|
1283
|
+
...mappingOptions._meta && { _meta: mappingOptions._meta }
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
},
|
|
1287
|
+
settings: (settings) => createIndexBuilder({
|
|
1288
|
+
...state,
|
|
1289
|
+
settings
|
|
1290
|
+
}),
|
|
1291
|
+
analysis: (config) => createIndexBuilder({
|
|
1292
|
+
...state,
|
|
1293
|
+
settings: {
|
|
1294
|
+
...state.settings,
|
|
1295
|
+
analysis: config
|
|
1296
|
+
}
|
|
1297
|
+
}),
|
|
1298
|
+
alias: (name, options = {}) => {
|
|
1299
|
+
const aliases = state.aliases || {};
|
|
1300
|
+
return createIndexBuilder({
|
|
1301
|
+
...state,
|
|
1302
|
+
aliases: {
|
|
1303
|
+
...aliases,
|
|
1304
|
+
[name]: options
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
},
|
|
1308
|
+
build: () => state
|
|
1309
|
+
});
|
|
1310
|
+
/**
|
|
1311
|
+
* Create a new index builder for Elasticsearch index configuration.
|
|
1312
|
+
*
|
|
1313
|
+
* Typical lifecycle:
|
|
1314
|
+
* 1. **Create index:** `indexBuilder().mappings(schema).settings(productionSearchSettings()).build()`
|
|
1315
|
+
* 2. **Bulk ingest:** Apply `fastIngestSettings()` via ES `_settings` API → bulk index →
|
|
1316
|
+
* apply `productionSearchSettings()` → `POST /index/_refresh`
|
|
1317
|
+
* 3. **Normal operations:** Query, update individual docs via `_update` API
|
|
1318
|
+
* 4. **Migration with mapping changes:** Create new index with new mappings → `POST _reindex`
|
|
1319
|
+
* from old → atomically swap alias via `POST _aliases` → delete old index
|
|
1320
|
+
*
|
|
1321
|
+
* After bulk loading, remember to refresh the index with `POST /index/_refresh`
|
|
1322
|
+
* to make all documents searchable.
|
|
1323
|
+
*
|
|
1324
|
+
* @example
|
|
1325
|
+
* const indexConfig = indexBuilder()
|
|
1326
|
+
* .mappings(productMappings)
|
|
1327
|
+
* .settings(productionSearchSettings())
|
|
1328
|
+
* .alias('products')
|
|
1329
|
+
* .build();
|
|
1330
|
+
*/
|
|
1331
|
+
const indexBuilder = () => createIndexBuilder();
|
|
1332
|
+
//#endregion
|
|
1333
|
+
//#region src/settings.presets.ts
|
|
1334
|
+
/**
|
|
1335
|
+
* Balanced production settings for search workloads.
|
|
1336
|
+
*
|
|
1337
|
+
* Defaults:
|
|
1338
|
+
* - `number_of_replicas: 1` — one replica for redundancy
|
|
1339
|
+
* - `refresh_interval: '5s'` — near-real-time search without excessive refresh overhead
|
|
1340
|
+
*
|
|
1341
|
+
* @param overrides - Override or extend any setting. Applied after defaults.
|
|
1342
|
+
*
|
|
1343
|
+
* @example
|
|
1344
|
+
* const index = indexBuilder()
|
|
1345
|
+
* .mappings(schema)
|
|
1346
|
+
* .settings(productionSearchSettings())
|
|
1347
|
+
* .build();
|
|
1348
|
+
*
|
|
1349
|
+
* @example
|
|
1350
|
+
* // With best_compression for disk savings
|
|
1351
|
+
* productionSearchSettings({ codec: 'best_compression' })
|
|
1352
|
+
*
|
|
1353
|
+
* @example
|
|
1354
|
+
* // Restore production settings after bulk ingest
|
|
1355
|
+
* await client.indices.putSettings({
|
|
1356
|
+
* index: 'my-index',
|
|
1357
|
+
* body: productionSearchSettings(),
|
|
1358
|
+
* });
|
|
1359
|
+
* await client.indices.refresh({ index: 'my-index' });
|
|
1360
|
+
*/
|
|
1361
|
+
const productionSearchSettings = (overrides) => ({
|
|
1362
|
+
number_of_replicas: 1,
|
|
1363
|
+
refresh_interval: "5s",
|
|
1364
|
+
...overrides
|
|
1365
|
+
});
|
|
1366
|
+
/**
|
|
1367
|
+
* Generate index sort settings from a field→direction (or full spec) map.
|
|
1368
|
+
*
|
|
1369
|
+
* Index-time sorting improves compression (similar values stored together) and
|
|
1370
|
+
* enables early termination when query sort matches index sort. Combine with
|
|
1371
|
+
* `trackTotalHits(false)` for fastest sorted queries.
|
|
1372
|
+
*
|
|
1373
|
+
* The returned object lives at the top level of `IndicesIndexSettings` — spread it
|
|
1374
|
+
* directly into a settings object (do NOT wrap it under an `index` key, the
|
|
1375
|
+
* `@elastic/elasticsearch` client normalises that form).
|
|
1376
|
+
*
|
|
1377
|
+
* Pass a bare `'asc' | 'desc'` for simple cases; pass `{ order, mode?, missing? }`
|
|
1378
|
+
* when you need `mode` (`min`/`max`/`median`/`avg`) or `missing` (`_first`/`_last`).
|
|
1379
|
+
*
|
|
1380
|
+
* @param fields - Map of field names to sort direction or full spec.
|
|
1381
|
+
* @returns A `{ sort: IndicesIndexSegmentSort }` fragment to spread into your settings.
|
|
1382
|
+
*
|
|
1383
|
+
* @example
|
|
1384
|
+
* const index = indexBuilder()
|
|
1385
|
+
* .mappings(schema)
|
|
1386
|
+
* .settings({
|
|
1387
|
+
* ...productionSearchSettings(),
|
|
1388
|
+
* ...indexSortSettings({ timestamp: 'desc', status: 'asc' }),
|
|
1389
|
+
* })
|
|
1390
|
+
* .build();
|
|
1391
|
+
*
|
|
1392
|
+
* @example
|
|
1393
|
+
* indexSortSettings({
|
|
1394
|
+
* price: { order: 'asc', mode: 'min', missing: '_last' },
|
|
1395
|
+
* created_at: 'desc'
|
|
1396
|
+
* })
|
|
1397
|
+
*/
|
|
1398
|
+
const indexSortSettings = (fields) => {
|
|
1399
|
+
const entries = Object.entries(fields);
|
|
1400
|
+
const normalize = (spec) => typeof spec === "string" ? { order: spec } : spec;
|
|
1401
|
+
const hasAnyMode = entries.some(([, spec]) => typeof spec !== "string" && spec.mode !== void 0);
|
|
1402
|
+
const hasAnyMissing = entries.some(([, spec]) => typeof spec !== "string" && spec.missing !== void 0);
|
|
1403
|
+
return { sort: {
|
|
1404
|
+
field: entries.map(([name]) => name),
|
|
1405
|
+
order: entries.map(([, spec]) => normalize(spec).order),
|
|
1406
|
+
...hasAnyMode && { mode: entries.map(([, spec]) => normalize(spec).mode ?? "min") },
|
|
1407
|
+
...hasAnyMissing && { missing: entries.map(([, spec]) => normalize(spec).missing ?? "_last") }
|
|
1408
|
+
} };
|
|
1409
|
+
};
|
|
1410
|
+
/**
|
|
1411
|
+
* Settings optimized for maximum indexing speed during bulk operations.
|
|
1412
|
+
*
|
|
1413
|
+
* Apply via the ES `_settings` API before starting bulk ingest, then revert
|
|
1414
|
+
* with `productionSearchSettings()` afterward. Do not use these as permanent
|
|
1415
|
+
* index settings — they trade durability and search availability for speed.
|
|
1416
|
+
*
|
|
1417
|
+
* Defaults:
|
|
1418
|
+
* - `refresh_interval: '-1'` — disables refresh (can improve reindex time by 70%+)
|
|
1419
|
+
* - `number_of_replicas: 0` — no replication overhead during ingest
|
|
1420
|
+
* - `translog.durability: 'async'` — async translog for faster writes
|
|
1421
|
+
* - `translog.sync_interval: '30s'` — less frequent translog sync
|
|
1422
|
+
*
|
|
1423
|
+
* @param overrides - Override or extend any setting. Applied after defaults.
|
|
1424
|
+
* `translog` overrides are deep-merged so individual translog keys can be
|
|
1425
|
+
* changed without clobbering the other defaults.
|
|
1426
|
+
*
|
|
1427
|
+
* @example
|
|
1428
|
+
* // Apply before bulk ingest via ES client
|
|
1429
|
+
* await client.indices.putSettings({
|
|
1430
|
+
* index: 'my-index',
|
|
1431
|
+
* body: fastIngestSettings(),
|
|
1432
|
+
* });
|
|
1433
|
+
*
|
|
1434
|
+
* @example
|
|
1435
|
+
* // With custom overrides
|
|
1436
|
+
* fastIngestSettings({ number_of_shards: 3 })
|
|
1437
|
+
*/
|
|
1438
|
+
const fastIngestSettings = (overrides) => {
|
|
1439
|
+
const { translog, ...rest } = overrides ?? {};
|
|
1440
|
+
return {
|
|
1441
|
+
number_of_replicas: 0,
|
|
1442
|
+
refresh_interval: "-1",
|
|
1443
|
+
translog: {
|
|
1444
|
+
durability: "async",
|
|
1445
|
+
sync_interval: "30s",
|
|
1446
|
+
...translog
|
|
1447
|
+
},
|
|
1448
|
+
...rest
|
|
1449
|
+
};
|
|
1450
|
+
};
|
|
1451
|
+
//#endregion
|
|
1452
|
+
//#region src/index.ts
|
|
1453
|
+
const queryBuilder = (_schema, includeQuery = true) => createQueryBuilder({ _includeQuery: includeQuery });
|
|
1454
|
+
/**
|
|
1455
|
+
* Type inference helper for vanilla JavaScript.
|
|
1456
|
+
*
|
|
1457
|
+
* **Type-only — do not use the returned value at runtime.** This function always returns
|
|
1458
|
+
* `undefined`; its declared return type is a lie used to carry `Infer<typeof schema>` into
|
|
1459
|
+
* JSDoc `@type` annotations. Accessing properties on the return value (e.g. `inferType(s).name`)
|
|
1460
|
+
* will throw `TypeError`.
|
|
1461
|
+
*
|
|
1462
|
+
* @example
|
|
1463
|
+
* const schema = mappings({ name: text() });
|
|
1464
|
+
* const _Product = inferType(schema); // undefined at runtime; type-only
|
|
1465
|
+
*
|
|
1466
|
+
* /** @type {typeof _Product} *\/
|
|
1467
|
+
* const doc = { name: 'Laptop' };
|
|
1468
|
+
*
|
|
1469
|
+
* In TypeScript, prefer `type Product = Infer<typeof schema>` directly.
|
|
1470
|
+
*/
|
|
1471
|
+
const inferType = (_schema) => void 0;
|
|
1472
|
+
//#endregion
|
|
1473
|
+
exports.aggregations = aggregations;
|
|
1474
|
+
exports.alias = alias;
|
|
1475
|
+
exports.binary = binary;
|
|
1476
|
+
exports.boolean = boolean;
|
|
1477
|
+
exports.bulk = bulk;
|
|
1478
|
+
exports.byte = byte;
|
|
1479
|
+
exports.completion = completion;
|
|
1480
|
+
exports.constantKeyword = constantKeyword;
|
|
1481
|
+
exports.date = date;
|
|
1482
|
+
exports.dateNanos = dateNanos;
|
|
1483
|
+
exports.dateRange = dateRange;
|
|
1484
|
+
exports.denseVector = denseVector;
|
|
1485
|
+
exports.double = double;
|
|
1486
|
+
exports.doubleRange = doubleRange;
|
|
1487
|
+
exports.fastIngestSettings = fastIngestSettings;
|
|
1488
|
+
exports.flattened = flattened;
|
|
1489
|
+
exports.float = float;
|
|
1490
|
+
exports.floatRange = floatRange;
|
|
1491
|
+
exports.geoPoint = geoPoint;
|
|
1492
|
+
exports.geoShape = geoShape;
|
|
1493
|
+
exports.halfFloat = halfFloat;
|
|
1494
|
+
exports.indexBuilder = indexBuilder;
|
|
1495
|
+
exports.indexSortSettings = indexSortSettings;
|
|
1496
|
+
exports.inferType = inferType;
|
|
1497
|
+
exports.integer = integer;
|
|
1498
|
+
exports.integerRange = integerRange;
|
|
1499
|
+
exports.ip = ip;
|
|
1500
|
+
exports.ipRange = ipRange;
|
|
1501
|
+
exports.join = join;
|
|
1502
|
+
exports.keyword = keyword;
|
|
1503
|
+
exports.long = long;
|
|
1504
|
+
exports.longRange = longRange;
|
|
1505
|
+
exports.mappings = mappings;
|
|
1506
|
+
exports.matchOnlyText = matchOnlyText;
|
|
1507
|
+
exports.msearch = msearch;
|
|
1508
|
+
exports.murmur3Hash = murmur3Hash;
|
|
1509
|
+
exports.nested = nested;
|
|
1510
|
+
exports.object = object;
|
|
1511
|
+
exports.percolator = percolator;
|
|
1512
|
+
exports.productionSearchSettings = productionSearchSettings;
|
|
1513
|
+
exports.quantizedDenseVector = quantizedDenseVector;
|
|
1514
|
+
exports.queryBuilder = queryBuilder;
|
|
1515
|
+
exports.rankFeature = rankFeature;
|
|
1516
|
+
exports.rankFeatures = rankFeatures;
|
|
1517
|
+
exports.scaledFloat = scaledFloat;
|
|
1518
|
+
exports.searchAsYouType = searchAsYouType;
|
|
1519
|
+
exports.semanticText = semanticText;
|
|
1520
|
+
exports.short = short;
|
|
1521
|
+
exports.sparseVector = sparseVector;
|
|
1522
|
+
exports.suggest = suggest;
|
|
1523
|
+
exports.text = text;
|
|
1524
|
+
exports.tokenCount = tokenCount;
|
|
1525
|
+
exports.unsignedLong = unsignedLong;
|
|
1526
|
+
exports.wildcardField = wildcardField;
|
|
1527
|
+
|
|
1528
|
+
//# sourceMappingURL=index.cjs.map
|