elasticlink 0.2.2-beta → 0.4.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.
- package/README.md +223 -177
- package/dist/__tests__/aggregation-builder.test.d.ts +2 -0
- package/dist/__tests__/aggregation-builder.test.d.ts.map +1 -0
- package/dist/__tests__/aggregation-builder.test.js +622 -0
- package/dist/__tests__/bulk.test.d.ts +2 -0
- package/dist/__tests__/bulk.test.d.ts.map +1 -0
- package/dist/__tests__/bulk.test.js +679 -0
- package/dist/__tests__/examples.test.d.ts +2 -0
- package/dist/__tests__/examples.test.d.ts.map +1 -0
- package/dist/__tests__/examples.test.js +2123 -0
- package/dist/__tests__/fixtures/finance.d.ts +58 -0
- package/dist/__tests__/fixtures/finance.d.ts.map +1 -0
- package/dist/__tests__/fixtures/finance.js +73 -0
- package/dist/__tests__/fixtures/legal.d.ts +14 -0
- package/dist/__tests__/fixtures/legal.d.ts.map +1 -0
- package/dist/__tests__/fixtures/legal.js +27 -0
- package/dist/__tests__/fixtures/real-estate.d.ts +31 -0
- package/dist/__tests__/fixtures/real-estate.d.ts.map +1 -0
- package/dist/__tests__/fixtures/real-estate.js +39 -0
- package/dist/__tests__/index-management.test.d.ts +2 -0
- package/dist/__tests__/index-management.test.d.ts.map +1 -0
- package/dist/__tests__/index-management.test.js +1699 -0
- package/dist/__tests__/integration/aggregation.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/aggregation.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/aggregation.integration.test.js +188 -0
- package/dist/__tests__/integration/bulk.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/bulk.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/bulk.integration.test.js +90 -0
- package/dist/__tests__/integration/fixtures/finance.d.ts +37 -0
- package/dist/__tests__/integration/fixtures/finance.d.ts.map +1 -0
- package/dist/__tests__/integration/fixtures/finance.js +58 -0
- package/dist/__tests__/integration/fixtures/legal.d.ts +38 -0
- package/dist/__tests__/integration/fixtures/legal.d.ts.map +1 -0
- package/dist/__tests__/integration/fixtures/legal.js +65 -0
- package/dist/__tests__/integration/fixtures/real-estate.d.ts +17 -0
- package/dist/__tests__/integration/fixtures/real-estate.d.ts.map +1 -0
- package/dist/__tests__/integration/fixtures/real-estate.js +28 -0
- package/dist/__tests__/integration/helpers.d.ts +15 -0
- package/dist/__tests__/integration/helpers.d.ts.map +1 -0
- package/dist/__tests__/integration/helpers.js +21 -0
- package/dist/__tests__/integration/index-management.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/index-management.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/index-management.integration.test.js +67 -0
- package/dist/__tests__/integration/multi-search.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/multi-search.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/multi-search.integration.test.js +49 -0
- package/dist/__tests__/integration/query.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/query.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/query.integration.test.js +101 -0
- package/dist/__tests__/integration/suggester.integration.test.d.ts +2 -0
- package/dist/__tests__/integration/suggester.integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/suggester.integration.test.js +42 -0
- package/dist/__tests__/multi-search.test.d.ts +2 -0
- package/dist/__tests__/multi-search.test.d.ts.map +1 -0
- package/dist/__tests__/multi-search.test.js +325 -0
- package/dist/__tests__/query-builder-extensions.test.d.ts +2 -0
- package/dist/__tests__/query-builder-extensions.test.d.ts.map +1 -0
- package/dist/__tests__/query-builder-extensions.test.js +436 -0
- package/dist/__tests__/query-builder.test.d.ts +2 -0
- package/dist/__tests__/query-builder.test.d.ts.map +1 -0
- package/dist/__tests__/query-builder.test.js +5482 -0
- package/dist/__tests__/settings-presets.test.d.ts +2 -0
- package/dist/__tests__/settings-presets.test.d.ts.map +1 -0
- package/dist/__tests__/settings-presets.test.js +183 -0
- package/dist/__tests__/suggester.test.d.ts +2 -0
- package/dist/__tests__/suggester.test.d.ts.map +1 -0
- package/dist/__tests__/suggester.test.js +1006 -0
- package/dist/aggregation.builder.d.ts +4 -2
- package/dist/aggregation.builder.d.ts.map +1 -1
- package/dist/aggregation.builder.js +102 -95
- package/dist/aggregation.types.d.ts +16 -14
- package/dist/aggregation.types.d.ts.map +1 -1
- package/dist/bulk.builder.d.ts +126 -2
- package/dist/bulk.builder.d.ts.map +1 -1
- package/dist/bulk.builder.js +11 -12
- package/dist/bulk.types.d.ts.map +1 -1
- package/dist/field.helpers.d.ts +92 -42
- package/dist/field.helpers.d.ts.map +1 -1
- package/dist/field.helpers.js +99 -20
- package/dist/field.types.d.ts +113 -6
- package/dist/field.types.d.ts.map +1 -1
- package/dist/field.types.js +3 -3
- package/dist/index-management.builder.d.ts +19 -14
- package/dist/index-management.builder.d.ts.map +1 -1
- package/dist/index-management.builder.js +36 -34
- package/dist/index-management.types.d.ts +32 -62
- package/dist/index-management.types.d.ts.map +1 -1
- package/dist/index-management.types.js +0 -1
- package/dist/index.d.ts +18 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -8
- package/dist/mapping.builder.d.ts +37 -0
- package/dist/mapping.builder.d.ts.map +1 -0
- package/dist/mapping.builder.js +38 -0
- package/dist/mapping.types.d.ts +105 -0
- package/dist/mapping.types.d.ts.map +1 -0
- package/dist/mapping.types.js +6 -0
- package/dist/multi-search.builder.d.ts +5 -3
- package/dist/multi-search.builder.d.ts.map +1 -1
- package/dist/multi-search.builder.js +6 -4
- package/dist/multi-search.types.d.ts +7 -6
- package/dist/multi-search.types.d.ts.map +1 -1
- package/dist/query.builder.d.ts +3 -11
- package/dist/query.builder.d.ts.map +1 -1
- package/dist/query.builder.js +118 -259
- package/dist/query.types.d.ts +140 -236
- package/dist/query.types.d.ts.map +1 -1
- package/dist/query.types.js +2 -1
- package/dist/settings.presets.d.ts +98 -0
- package/dist/settings.presets.d.ts.map +1 -0
- package/dist/settings.presets.js +115 -0
- package/dist/suggester.builder.d.ts +5 -3
- package/dist/suggester.builder.d.ts.map +1 -1
- package/dist/suggester.builder.js +3 -2
- package/dist/suggester.types.d.ts +5 -4
- package/dist/suggester.types.d.ts.map +1 -1
- package/dist/vector.types.d.ts.map +1 -1
- package/package.json +12 -9
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ elasticlink simplifies building Elasticsearch queries and index management in Ty
|
|
|
13
13
|
|
|
14
14
|
## Features
|
|
15
15
|
|
|
16
|
-
- **
|
|
16
|
+
- **Mapping-Aware Type Safety**: Define ES field types once via `mappings()`, get compile-time constraints — `match()` only accepts text fields, `term()` only keyword fields, etc.
|
|
17
17
|
- **Fluent API**: Chainable query builder with intuitive method names
|
|
18
18
|
- **Zero Runtime Overhead**: Compiles directly to Elasticsearch DSL objects
|
|
19
19
|
- **Well-Tested**: Comprehensive unit and integration test suite against live Elasticsearch
|
|
@@ -22,12 +22,14 @@ elasticlink simplifies building Elasticsearch queries and index management in Ty
|
|
|
22
22
|
|
|
23
23
|
## Compatibility
|
|
24
24
|
|
|
25
|
-
| elasticlink | Node.js
|
|
26
|
-
|
|
27
|
-
| 0.
|
|
28
|
-
| 0.
|
|
25
|
+
| elasticlink | Node.js | Elasticsearch |
|
|
26
|
+
|-------------|-------------|---------------|
|
|
27
|
+
| 0.4.0-beta | 20, 22, 24 | 9.x (≥9.0.0) |
|
|
28
|
+
| 0.3.0-beta | 20, 22, 24 | 9.x (≥9.0.0) |
|
|
29
|
+
| 0.2.0-beta | 20, 22 | 9.x (≥9.0.0) |
|
|
30
|
+
| 0.1.0-beta | 20, 22 | 9.x (≥9.0.0) |
|
|
29
31
|
|
|
30
|
-
Tested against the versions listed. Peer dependency is `@elastic/elasticsearch >=9.0.0`.
|
|
32
|
+
Tested against the versions listed. Peer dependency is `@elastic/elasticsearch >=9.0.0`.
|
|
31
33
|
|
|
32
34
|
## Installation
|
|
33
35
|
|
|
@@ -42,18 +44,21 @@ Requires Node.js 20+ and `@elastic/elasticsearch` 9.x as a peer dependency.
|
|
|
42
44
|
## Quick Start
|
|
43
45
|
|
|
44
46
|
```typescript
|
|
45
|
-
import { query } from 'elasticlink';
|
|
47
|
+
import { query, mappings, text, keyword, float, type Infer } from 'elasticlink';
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
name:
|
|
50
|
-
price:
|
|
51
|
-
category:
|
|
52
|
-
};
|
|
49
|
+
// Define field types once — this is the single source of truth
|
|
50
|
+
const productMappings = mappings({
|
|
51
|
+
name: text(),
|
|
52
|
+
price: float(),
|
|
53
|
+
category: keyword(),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Derive a TS type from mappings (optional, for use elsewhere)
|
|
57
|
+
type Product = Infer<typeof productMappings>;
|
|
53
58
|
|
|
54
|
-
// Build a type-safe query
|
|
55
|
-
const q = query
|
|
56
|
-
.match('name', 'laptop'
|
|
59
|
+
// Build a type-safe query — field constraints are enforced at compile time
|
|
60
|
+
const q = query(productMappings)
|
|
61
|
+
.match('name', 'laptop') // ✅ 'name' is a text field
|
|
57
62
|
.range('price', { gte: 500, lte: 2000 })
|
|
58
63
|
.from(0)
|
|
59
64
|
.size(20)
|
|
@@ -72,7 +77,12 @@ const response = await client.search({ index: 'products', ...q });
|
|
|
72
77
|
- `match(field, value, options?)` - Full-text search
|
|
73
78
|
- `multiMatch(fields, query, options?)` - Search multiple fields
|
|
74
79
|
- `matchPhrase(field, query)` - Exact phrase matching
|
|
75
|
-
- `matchPhrasePrefix(field, query, options?)` - Prefix phrase matching
|
|
80
|
+
- `matchPhrasePrefix(field, query, options?)` - Prefix phrase matching
|
|
81
|
+
- `matchBoolPrefix(field, value, options?)` - Analyze and build a bool query from query terms, with the last term as a prefix (search-as-you-type)
|
|
82
|
+
- `combinedFields(fields, query, options?)` - Search across multiple fields treating them as one combined field
|
|
83
|
+
- `queryString(query, options?)` - Lucene query string syntax
|
|
84
|
+
- `simpleQueryString(query, options?)` - Simplified query string syntax with limited operators
|
|
85
|
+
- `moreLikeThis(fields, like, options?)` - Find documents similar to a given document or text
|
|
76
86
|
- `term(field, value)` - Exact term matching
|
|
77
87
|
- `terms(field, values)` - Multiple exact values
|
|
78
88
|
- `range(field, conditions)` - Range queries (gte, lte, gt, lt)
|
|
@@ -112,12 +122,12 @@ const response = await client.search({ index: 'products', ...q });
|
|
|
112
122
|
### Boolean Logic
|
|
113
123
|
|
|
114
124
|
```typescript
|
|
115
|
-
query
|
|
125
|
+
query(productMappings)
|
|
116
126
|
.bool()
|
|
117
127
|
.must(q => q.match('name', 'laptop')) // AND
|
|
118
128
|
.filter(q => q.range('price', { gte: 500 }))
|
|
119
|
-
.should(q => q.term('
|
|
120
|
-
.mustNot(q => q.term('
|
|
129
|
+
.should(q => q.term('category', 'featured')) // OR
|
|
130
|
+
.mustNot(q => q.term('category', 'discontinued')) // NOT
|
|
121
131
|
.minimumShouldMatch(1)
|
|
122
132
|
.build();
|
|
123
133
|
```
|
|
@@ -130,7 +140,7 @@ Build queries dynamically based on runtime values:
|
|
|
130
140
|
const searchTerm = getUserInput();
|
|
131
141
|
const minPrice = getMinPrice();
|
|
132
142
|
|
|
133
|
-
query
|
|
143
|
+
query(productMappings)
|
|
134
144
|
.bool()
|
|
135
145
|
.must(q =>
|
|
136
146
|
q.when(searchTerm, q => q.match('name', searchTerm)) || q.matchAll()
|
|
@@ -144,7 +154,7 @@ query<Product>()
|
|
|
144
154
|
### Query Parameters
|
|
145
155
|
|
|
146
156
|
```typescript
|
|
147
|
-
query
|
|
157
|
+
query(productMappings)
|
|
148
158
|
.match('name', 'laptop')
|
|
149
159
|
.from(0) // Pagination offset
|
|
150
160
|
.size(20) // Results per page
|
|
@@ -176,9 +186,9 @@ Aggregations can be combined with queries or used standalone:
|
|
|
176
186
|
```typescript
|
|
177
187
|
import { query, aggregations } from 'elasticlink';
|
|
178
188
|
|
|
179
|
-
// Combined query + aggregations
|
|
180
|
-
const result = query
|
|
181
|
-
.
|
|
189
|
+
// Combined query + aggregations (inline aggs auto-thread mappings)
|
|
190
|
+
const result = query(productMappings)
|
|
191
|
+
.term('category', 'electronics')
|
|
182
192
|
.aggs(agg =>
|
|
183
193
|
agg
|
|
184
194
|
.terms('by_category', 'category', { size: 10 })
|
|
@@ -187,8 +197,8 @@ const result = query<Product>()
|
|
|
187
197
|
.size(20)
|
|
188
198
|
.build();
|
|
189
199
|
|
|
190
|
-
// Standalone aggregations (no query)
|
|
191
|
-
const aggsOnly = query
|
|
200
|
+
// Standalone aggregations (no query) — use query(mappings, false)
|
|
201
|
+
const aggsOnly = query(productMappings, false)
|
|
192
202
|
.aggs(agg =>
|
|
193
203
|
agg
|
|
194
204
|
.terms('by_category', 'category')
|
|
@@ -200,37 +210,31 @@ const aggsOnly = query<Product>(false)
|
|
|
200
210
|
.build();
|
|
201
211
|
|
|
202
212
|
// Standalone aggregation builder (for manual composition)
|
|
203
|
-
const standaloneAgg = aggregations
|
|
204
|
-
.
|
|
205
|
-
.
|
|
206
|
-
sub.sum('daily_revenue', 'price')
|
|
207
|
-
.cardinality('unique_categories', 'category')
|
|
208
|
-
)
|
|
213
|
+
const standaloneAgg = aggregations(productMappings)
|
|
214
|
+
.avg('avg_price', 'price')
|
|
215
|
+
.terms('by_category', 'category', { size: 10 })
|
|
209
216
|
.build();
|
|
210
217
|
```
|
|
211
218
|
|
|
212
219
|
### Vector Search & Semantic Search
|
|
213
220
|
|
|
214
|
-
**Requires Elasticsearch 8.0+**
|
|
215
|
-
|
|
216
221
|
KNN (k-nearest neighbors) queries enable semantic search using vector embeddings from machine learning models.
|
|
217
222
|
|
|
218
223
|
```typescript
|
|
219
|
-
import { query } from 'elasticlink';
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
};
|
|
224
|
+
import { query, mappings, text, keyword, float, denseVector } from 'elasticlink';
|
|
225
|
+
|
|
226
|
+
const productWithEmbeddingMappings = mappings({
|
|
227
|
+
name: text(),
|
|
228
|
+
description: text(),
|
|
229
|
+
price: float(),
|
|
230
|
+
category: keyword(),
|
|
231
|
+
embedding: denseVector({ dims: 384 }),
|
|
232
|
+
});
|
|
229
233
|
|
|
230
234
|
// Basic semantic search
|
|
231
235
|
const searchEmbedding = [0.23, 0.45, 0.67, 0.12, 0.89]; // From your ML model
|
|
232
236
|
|
|
233
|
-
const result = query
|
|
237
|
+
const result = query(productWithEmbeddingMappings)
|
|
234
238
|
.knn('embedding', searchEmbedding, {
|
|
235
239
|
k: 10, // Return top 10 nearest neighbors
|
|
236
240
|
num_candidates: 100 // Consider 100 candidates per shard
|
|
@@ -239,7 +243,7 @@ const result = query<Product>()
|
|
|
239
243
|
.build();
|
|
240
244
|
|
|
241
245
|
// Semantic search with filters
|
|
242
|
-
const filtered = query
|
|
246
|
+
const filtered = query(productWithEmbeddingMappings)
|
|
243
247
|
.knn('embedding', searchEmbedding, {
|
|
244
248
|
k: 20,
|
|
245
249
|
num_candidates: 200,
|
|
@@ -256,7 +260,7 @@ const filtered = query<Product>()
|
|
|
256
260
|
.build();
|
|
257
261
|
|
|
258
262
|
// Hybrid search with aggregations
|
|
259
|
-
const hybridSearch = query
|
|
263
|
+
const hybridSearch = query(productWithEmbeddingMappings)
|
|
260
264
|
.knn('embedding', searchEmbedding, {
|
|
261
265
|
k: 100,
|
|
262
266
|
num_candidates: 1000,
|
|
@@ -304,18 +308,17 @@ const mapping: DenseVectorOptions = {
|
|
|
304
308
|
**Note:** Scripts must be enabled in Elasticsearch configuration. Use with caution as they can impact performance.
|
|
305
309
|
|
|
306
310
|
```typescript
|
|
307
|
-
import { query } from 'elasticlink';
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
};
|
|
311
|
+
import { query, mappings, text, keyword, float, long } from 'elasticlink';
|
|
312
|
+
|
|
313
|
+
const scoredProductMappings = mappings({
|
|
314
|
+
name: text(),
|
|
315
|
+
price: float(),
|
|
316
|
+
popularity: long(),
|
|
317
|
+
quality_score: float(),
|
|
318
|
+
});
|
|
316
319
|
|
|
317
320
|
// Script-based filtering
|
|
318
|
-
const filtered = query
|
|
321
|
+
const filtered = query(scoredProductMappings)
|
|
319
322
|
.bool()
|
|
320
323
|
.must((q) => q.match('name', 'laptop'))
|
|
321
324
|
.filter((q) =>
|
|
@@ -327,7 +330,7 @@ const filtered = query<Product>()
|
|
|
327
330
|
.build();
|
|
328
331
|
|
|
329
332
|
// Custom scoring with script_score
|
|
330
|
-
const customScored = query
|
|
333
|
+
const customScored = query(scoredProductMappings)
|
|
331
334
|
.scriptScore(
|
|
332
335
|
(q) => q.match('name', 'smartphone'),
|
|
333
336
|
{
|
|
@@ -349,14 +352,16 @@ const customScored = query<Product>()
|
|
|
349
352
|
Percolate queries enable reverse search - match documents against stored queries. Perfect for alerting, content classification, and saved searches.
|
|
350
353
|
|
|
351
354
|
```typescript
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
355
|
+
import { query, mappings, keyword, percolator } from 'elasticlink';
|
|
356
|
+
|
|
357
|
+
const alertRuleMappings = mappings({
|
|
358
|
+
query: percolator(),
|
|
359
|
+
name: keyword(),
|
|
360
|
+
severity: keyword(),
|
|
361
|
+
});
|
|
357
362
|
|
|
358
363
|
// Match document against stored queries
|
|
359
|
-
const alerts = query
|
|
364
|
+
const alerts = query(alertRuleMappings)
|
|
360
365
|
.percolate({
|
|
361
366
|
field: 'query',
|
|
362
367
|
document: {
|
|
@@ -381,16 +386,16 @@ const alerts = query<AlertRule>()
|
|
|
381
386
|
Elasticsearch Suggesters provide spell-checking, phrase correction, and autocomplete functionality. Perfect for search-as-you-type experiences and fixing user typos.
|
|
382
387
|
|
|
383
388
|
```typescript
|
|
384
|
-
import { query, suggest } from 'elasticlink';
|
|
389
|
+
import { query, suggest, mappings, text, keyword, completion } from 'elasticlink';
|
|
385
390
|
|
|
386
|
-
|
|
387
|
-
name:
|
|
388
|
-
description:
|
|
389
|
-
suggest_field:
|
|
390
|
-
};
|
|
391
|
+
const searchableMappings = mappings({
|
|
392
|
+
name: text(),
|
|
393
|
+
description: text(),
|
|
394
|
+
suggest_field: completion(), // Must be type: completion
|
|
395
|
+
});
|
|
391
396
|
|
|
392
397
|
// Term suggester - Fix typos in individual terms
|
|
393
|
-
const termSuggestions = suggest
|
|
398
|
+
const termSuggestions = suggest(searchableMappings)
|
|
394
399
|
.term('name-suggestions', 'laptpo', {
|
|
395
400
|
field: 'name',
|
|
396
401
|
size: 5,
|
|
@@ -401,7 +406,7 @@ const termSuggestions = suggest<Product>()
|
|
|
401
406
|
.build();
|
|
402
407
|
|
|
403
408
|
// Completion suggester - Fast autocomplete
|
|
404
|
-
const autocomplete = suggest
|
|
409
|
+
const autocomplete = suggest(searchableMappings)
|
|
405
410
|
.completion('autocomplete', 'lap', {
|
|
406
411
|
field: 'suggest_field',
|
|
407
412
|
size: 10,
|
|
@@ -416,7 +421,7 @@ const autocomplete = suggest<Product>()
|
|
|
416
421
|
.build();
|
|
417
422
|
|
|
418
423
|
// Combine with query - Search with autocomplete
|
|
419
|
-
const searchWithSuggestions = query
|
|
424
|
+
const searchWithSuggestions = query(searchableMappings)
|
|
420
425
|
.match('name', 'laptpo')
|
|
421
426
|
.suggest((s) =>
|
|
422
427
|
s.term('spelling-correction', 'laptpo', {
|
|
@@ -442,24 +447,24 @@ Batch multiple search requests in a single API call using the NDJSON format.
|
|
|
442
447
|
```typescript
|
|
443
448
|
import { query, msearch } from 'elasticlink';
|
|
444
449
|
|
|
445
|
-
const laptopQuery = query
|
|
450
|
+
const laptopQuery = query(productMappings)
|
|
446
451
|
.match('name', 'laptop')
|
|
447
452
|
.range('price', { gte: 500, lte: 2000 })
|
|
448
453
|
.build();
|
|
449
454
|
|
|
450
|
-
const phoneQuery = query
|
|
455
|
+
const phoneQuery = query(productMappings)
|
|
451
456
|
.match('name', 'smartphone')
|
|
452
457
|
.range('price', { gte: 300, lte: 1000 })
|
|
453
458
|
.build();
|
|
454
459
|
|
|
455
460
|
// Build as NDJSON string for Elasticsearch API
|
|
456
|
-
const ndjson = msearch
|
|
461
|
+
const ndjson = msearch(productMappings)
|
|
457
462
|
.addQuery(laptopQuery, { index: 'products', preference: '_local' })
|
|
458
463
|
.addQuery(phoneQuery, { index: 'products', preference: '_local' })
|
|
459
464
|
.build();
|
|
460
465
|
|
|
461
466
|
// Or build as array of objects
|
|
462
|
-
const array = msearch
|
|
467
|
+
const array = msearch(productMappings)
|
|
463
468
|
.addQuery(laptopQuery, { index: 'products' })
|
|
464
469
|
.addQuery(phoneQuery, { index: 'products' })
|
|
465
470
|
.buildArray();
|
|
@@ -487,16 +492,16 @@ const array = msearch<Product>()
|
|
|
487
492
|
Batch create, index, update, and delete operations efficiently.
|
|
488
493
|
|
|
489
494
|
```typescript
|
|
490
|
-
import { bulk } from 'elasticlink';
|
|
495
|
+
import { bulk, mappings, keyword, text, float } from 'elasticlink';
|
|
491
496
|
|
|
492
|
-
|
|
493
|
-
id:
|
|
494
|
-
name:
|
|
495
|
-
price:
|
|
496
|
-
category:
|
|
497
|
-
};
|
|
497
|
+
const productMappings = mappings({
|
|
498
|
+
id: keyword(),
|
|
499
|
+
name: text(),
|
|
500
|
+
price: float(),
|
|
501
|
+
category: keyword(),
|
|
502
|
+
});
|
|
498
503
|
|
|
499
|
-
const bulkOp = bulk
|
|
504
|
+
const bulkOp = bulk(productMappings)
|
|
500
505
|
// Index (create or replace)
|
|
501
506
|
.index(
|
|
502
507
|
{ id: '1', name: 'Laptop Pro', price: 1299, category: 'electronics' },
|
|
@@ -546,24 +551,18 @@ const bulkOp = bulk<Product>()
|
|
|
546
551
|
Configure index mappings, settings, and aliases declaratively.
|
|
547
552
|
|
|
548
553
|
```typescript
|
|
549
|
-
import { indexBuilder, keyword, integer, float, date, text } from 'elasticlink';
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
title:
|
|
553
|
-
practice_area:
|
|
554
|
-
billing_rate:
|
|
555
|
-
risk_score:
|
|
556
|
-
opened_at:
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
const indexConfig = indexBuilder
|
|
560
|
-
.mappings(
|
|
561
|
-
title: text({ analyzer: 'english' }),
|
|
562
|
-
practice_area: keyword(),
|
|
563
|
-
billing_rate: integer(),
|
|
564
|
-
risk_score: float(),
|
|
565
|
-
opened_at: date()
|
|
566
|
-
})
|
|
554
|
+
import { indexBuilder, mappings, keyword, integer, float, date, text } from 'elasticlink';
|
|
555
|
+
|
|
556
|
+
const matterMappings = mappings({
|
|
557
|
+
title: text({ analyzer: 'english' }),
|
|
558
|
+
practice_area: keyword(),
|
|
559
|
+
billing_rate: integer(),
|
|
560
|
+
risk_score: float(),
|
|
561
|
+
opened_at: date(),
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const indexConfig = indexBuilder()
|
|
565
|
+
.mappings(matterMappings)
|
|
567
566
|
.settings({
|
|
568
567
|
number_of_shards: 2,
|
|
569
568
|
number_of_replicas: 1,
|
|
@@ -619,17 +618,18 @@ denseVector({ dims: 384, index: true, similarity: 'cosine' })
|
|
|
619
618
|
|
|
620
619
|
#### Field Types (25+ supported)
|
|
621
620
|
|
|
622
|
-
| Category | Helpers
|
|
623
|
-
| ----------- |
|
|
624
|
-
| Text | `text`, `keyword`, `constantKeyword`
|
|
625
|
-
| Numeric | `long`, `integer`, `short`, `byte`, `double`, `float`, `halfFloat`, `scaledFloat`
|
|
626
|
-
| Date | `date
|
|
627
|
-
| Boolean | `boolean`
|
|
628
|
-
| Range | `integerRange`, `floatRange`, `longRange`, `doubleRange`, `dateRange`
|
|
629
|
-
| Objects | `object`, `nested`, `flattened`
|
|
630
|
-
| Spatial | `geoPoint`, `geoShape`
|
|
631
|
-
|
|
|
632
|
-
|
|
|
621
|
+
| Category | Helpers |
|
|
622
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
623
|
+
| Text | `text`, `keyword`, `constantKeyword`, `matchOnlyText`, `searchAsYouType`, `wildcardField` |
|
|
624
|
+
| Numeric | `long`, `integer`, `short`, `byte`, `double`, `float`, `halfFloat`, `scaledFloat` |
|
|
625
|
+
| Date | `date` |
|
|
626
|
+
| Boolean | `boolean` |
|
|
627
|
+
| Range | `integerRange`, `floatRange`, `longRange`, `doubleRange`, `dateRange` |
|
|
628
|
+
| Objects | `object`, `nested`, `flattened` |
|
|
629
|
+
| Spatial | `geoPoint`, `geoShape` |
|
|
630
|
+
| Vector | `denseVector`, `quantizedDenseVector` |
|
|
631
|
+
| Specialized | `ip`, `binary`, `completion`, `percolator` |
|
|
632
|
+
| Alias | `alias` |
|
|
633
633
|
|
|
634
634
|
#### Mapping Properties
|
|
635
635
|
|
|
@@ -685,6 +685,46 @@ The `.alias()` method accepts an optional `IndicesAlias` object:
|
|
|
685
685
|
| `search_routing` | `string` | Routing value for search operations only |
|
|
686
686
|
| `is_hidden` | `boolean` | Hide alias from wildcard expressions |
|
|
687
687
|
|
|
688
|
+
### Settings Presets
|
|
689
|
+
|
|
690
|
+
Ready-made index settings for common lifecycle stages. Use with `.settings()` on `indexBuilder()` or pass directly to the ES `_settings` API.
|
|
691
|
+
|
|
692
|
+
```typescript
|
|
693
|
+
import { productionSearchSettings, indexSortSettings, fastIngestSettings } from 'elasticlink';
|
|
694
|
+
|
|
695
|
+
// Create index with production settings
|
|
696
|
+
const indexConfig = indexBuilder()
|
|
697
|
+
.mappings(myMappings)
|
|
698
|
+
.settings(productionSearchSettings())
|
|
699
|
+
.build();
|
|
700
|
+
|
|
701
|
+
// Index-time sort for compression and early termination
|
|
702
|
+
const sortedConfig = indexBuilder()
|
|
703
|
+
.mappings(myMappings)
|
|
704
|
+
.settings({
|
|
705
|
+
...productionSearchSettings(),
|
|
706
|
+
index: indexSortSettings({ timestamp: 'desc', status: 'asc' })
|
|
707
|
+
})
|
|
708
|
+
.build();
|
|
709
|
+
|
|
710
|
+
// Before bulk ingest — disables refresh, removes replicas, async translog
|
|
711
|
+
await client.indices.putSettings({ index: 'my-index', body: fastIngestSettings() });
|
|
712
|
+
|
|
713
|
+
// Perform bulk ingest...
|
|
714
|
+
|
|
715
|
+
// Restore production settings afterward
|
|
716
|
+
await client.indices.putSettings({ index: 'my-index', body: productionSearchSettings() });
|
|
717
|
+
await client.indices.refresh({ index: 'my-index' });
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
| Preset | Purpose |
|
|
721
|
+
| ------ | ------- |
|
|
722
|
+
| `productionSearchSettings(overrides?)` | Balanced production defaults — 1 replica, 5s refresh |
|
|
723
|
+
| `indexSortSettings(fields)` | Configure index-time sort order for disk compression and early termination |
|
|
724
|
+
| `fastIngestSettings(overrides?)` | Maximum indexing throughput — async translog, no replicas, refresh disabled |
|
|
725
|
+
|
|
726
|
+
`fastIngestSettings` deep-merges the `translog` key so individual translog overrides don't clobber the other defaults. All presets accept an optional `overrides` argument typed as `Partial<IndicesIndexSettings>`.
|
|
727
|
+
|
|
688
728
|
## Examples
|
|
689
729
|
|
|
690
730
|
More examples available in [src/\_\_tests\_\_/examples.test.ts](src/__tests__/examples.test.ts).
|
|
@@ -694,21 +734,21 @@ More examples available in [src/\_\_tests\_\_/examples.test.ts](src/__tests__/ex
|
|
|
694
734
|
A complete search request: boolean query with must/filter/should, aggregations for facets and price ranges, highlights, `_source` filtering, and pagination.
|
|
695
735
|
|
|
696
736
|
```typescript
|
|
697
|
-
|
|
698
|
-
name:
|
|
699
|
-
description:
|
|
700
|
-
category:
|
|
701
|
-
price:
|
|
702
|
-
tags:
|
|
703
|
-
in_stock: boolean
|
|
704
|
-
};
|
|
737
|
+
const ecommerceMappings = mappings({
|
|
738
|
+
name: text(),
|
|
739
|
+
description: text(),
|
|
740
|
+
category: keyword(),
|
|
741
|
+
price: float(),
|
|
742
|
+
tags: keyword(),
|
|
743
|
+
in_stock: boolean(),
|
|
744
|
+
});
|
|
705
745
|
|
|
706
746
|
const searchTerm = 'gaming laptop';
|
|
707
747
|
const category = 'electronics';
|
|
708
748
|
const minPrice = 800;
|
|
709
749
|
const maxPrice = 2000;
|
|
710
750
|
|
|
711
|
-
const result = query
|
|
751
|
+
const result = query(ecommerceMappings)
|
|
712
752
|
.bool()
|
|
713
753
|
.must(q => q.match('name', searchTerm, { operator: 'and', boost: 2 }))
|
|
714
754
|
.should(q => q.fuzzy('description', searchTerm, { fuzziness: 'AUTO' }))
|
|
@@ -783,16 +823,16 @@ Produces:
|
|
|
783
823
|
Terms + sub-aggregation + date histogram in one request.
|
|
784
824
|
|
|
785
825
|
```typescript
|
|
786
|
-
|
|
787
|
-
name:
|
|
788
|
-
asset_class:
|
|
789
|
-
sector:
|
|
790
|
-
price:
|
|
791
|
-
yield_rate:
|
|
792
|
-
listed_date:
|
|
793
|
-
};
|
|
794
|
-
|
|
795
|
-
const result = query
|
|
826
|
+
const instrumentMappings = mappings({
|
|
827
|
+
name: text(),
|
|
828
|
+
asset_class: keyword(),
|
|
829
|
+
sector: keyword(),
|
|
830
|
+
price: float(),
|
|
831
|
+
yield_rate: float(),
|
|
832
|
+
listed_date: date(),
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
const result = query(instrumentMappings)
|
|
796
836
|
.bool()
|
|
797
837
|
.filter(q => q.term('asset_class', 'fixed-income'))
|
|
798
838
|
.filter(q => q.range('yield_rate', { gte: 3.0 }))
|
|
@@ -846,13 +886,13 @@ Produces:
|
|
|
846
886
|
Batch multiple independent searches into a single HTTP request.
|
|
847
887
|
|
|
848
888
|
```typescript
|
|
849
|
-
|
|
850
|
-
address:
|
|
851
|
-
property_class:
|
|
852
|
-
list_price:
|
|
853
|
-
};
|
|
889
|
+
const listingMappings = mappings({
|
|
890
|
+
address: text(),
|
|
891
|
+
property_class: keyword(),
|
|
892
|
+
list_price: long(),
|
|
893
|
+
});
|
|
854
894
|
|
|
855
|
-
const condoSearch = query
|
|
895
|
+
const condoSearch = query(listingMappings)
|
|
856
896
|
.bool()
|
|
857
897
|
.filter(q => q.term('property_class', 'condo'))
|
|
858
898
|
.filter(q => q.range('list_price', { lte: 2_000_000 }))
|
|
@@ -860,14 +900,14 @@ const condoSearch = query<Listing>()
|
|
|
860
900
|
.size(0)
|
|
861
901
|
.build();
|
|
862
902
|
|
|
863
|
-
const townhouseSearch = query
|
|
903
|
+
const townhouseSearch = query(listingMappings)
|
|
864
904
|
.bool()
|
|
865
905
|
.filter(q => q.term('property_class', 'townhouse'))
|
|
866
906
|
.aggs(agg => agg.avg('avg_price', 'list_price').min('min_price', 'list_price'))
|
|
867
907
|
.size(0)
|
|
868
908
|
.build();
|
|
869
909
|
|
|
870
|
-
const ndjson = msearch
|
|
910
|
+
const ndjson = msearch(listingMappings)
|
|
871
911
|
.addQuery(condoSearch, { index: 'listings' })
|
|
872
912
|
.addQuery(townhouseSearch, { index: 'listings' })
|
|
873
913
|
.build();
|
|
@@ -886,14 +926,14 @@ Produces:
|
|
|
886
926
|
### Suggesters — Autocomplete & Spell Check
|
|
887
927
|
|
|
888
928
|
```typescript
|
|
889
|
-
|
|
890
|
-
name:
|
|
891
|
-
practice_area:
|
|
892
|
-
name_suggest:
|
|
893
|
-
};
|
|
929
|
+
const attorneyMappings = mappings({
|
|
930
|
+
name: text(),
|
|
931
|
+
practice_area: keyword(),
|
|
932
|
+
name_suggest: completion(),
|
|
933
|
+
});
|
|
894
934
|
|
|
895
935
|
// Standalone suggest request
|
|
896
|
-
const suggestions = query
|
|
936
|
+
const suggestions = query(attorneyMappings)
|
|
897
937
|
.suggest(s =>
|
|
898
938
|
s
|
|
899
939
|
.completion('autocomplete', 'kap', { field: 'name_suggest', size: 5 })
|
|
@@ -925,7 +965,7 @@ Produces:
|
|
|
925
965
|
|
|
926
966
|
```typescript
|
|
927
967
|
const buildDynamicQuery = (filters: SearchFilters) => {
|
|
928
|
-
return query
|
|
968
|
+
return query(productMappings)
|
|
929
969
|
.bool()
|
|
930
970
|
.must(q =>
|
|
931
971
|
q.when(filters.searchTerm,
|
|
@@ -951,15 +991,15 @@ const buildDynamicQuery = (filters: SearchFilters) => {
|
|
|
951
991
|
### Geospatial Search
|
|
952
992
|
|
|
953
993
|
```typescript
|
|
954
|
-
|
|
955
|
-
name:
|
|
956
|
-
cuisine:
|
|
957
|
-
location:
|
|
958
|
-
rating:
|
|
959
|
-
};
|
|
960
|
-
|
|
961
|
-
const result = query
|
|
962
|
-
.
|
|
994
|
+
const restaurantMappings = mappings({
|
|
995
|
+
name: text(),
|
|
996
|
+
cuisine: keyword(),
|
|
997
|
+
location: geoPoint(),
|
|
998
|
+
rating: float(),
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
const result = query(restaurantMappings)
|
|
1002
|
+
.term('cuisine', 'italian')
|
|
963
1003
|
.geoDistance(
|
|
964
1004
|
'location',
|
|
965
1005
|
{ lat: 40.7128, lon: -74.006 },
|
|
@@ -972,25 +1012,32 @@ const result = query<Restaurant>()
|
|
|
972
1012
|
|
|
973
1013
|
## TypeScript Support
|
|
974
1014
|
|
|
975
|
-
elasticlink provides
|
|
1015
|
+
elasticlink provides mapping-aware TypeScript safety:
|
|
976
1016
|
|
|
977
|
-
- **Field
|
|
978
|
-
- **
|
|
979
|
-
-
|
|
1017
|
+
- **Field-Type Constraints**: `match()` only accepts text fields, `term()` only keyword fields — enforced at compile time
|
|
1018
|
+
- **Field Autocomplete**: IntelliSense knows your field names and their types
|
|
1019
|
+
- **`Infer<S>`**: Derive TS document types from your mappings schema
|
|
980
1020
|
|
|
981
1021
|
```typescript
|
|
982
|
-
type
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1022
|
+
import { query, mappings, text, keyword, integer, type Infer } from 'elasticlink';
|
|
1023
|
+
|
|
1024
|
+
const userMappings = mappings({
|
|
1025
|
+
name: text(),
|
|
1026
|
+
email: keyword(),
|
|
1027
|
+
age: integer(),
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
type User = Infer<typeof userMappings>;
|
|
1031
|
+
// => { name: string; email: string; age: number }
|
|
1032
|
+
|
|
1033
|
+
// ✅ 'name' is a text field — match() accepts it
|
|
1034
|
+
const q1 = query(userMappings).match('name', 'John').build();
|
|
988
1035
|
|
|
989
|
-
//
|
|
990
|
-
const
|
|
1036
|
+
// ❌ TypeScript error: 'email' is keyword, not text — use term() instead
|
|
1037
|
+
const q2 = query(userMappings).match('email', 'john@example.com').build();
|
|
991
1038
|
|
|
992
|
-
//
|
|
993
|
-
const
|
|
1039
|
+
// ✅ Correct: use term() for keyword fields
|
|
1040
|
+
const q3 = query(userMappings).term('email', 'john@example.com').build();
|
|
994
1041
|
```
|
|
995
1042
|
|
|
996
1043
|
## Testing
|
|
@@ -1035,9 +1082,9 @@ npm run type-check
|
|
|
1035
1082
|
- [x] Integration test suite against live Elasticsearch 9.x
|
|
1036
1083
|
- [x] Types derived from official `@elastic/elasticsearch` for accuracy and completeness
|
|
1037
1084
|
|
|
1038
|
-
##
|
|
1085
|
+
## Development
|
|
1039
1086
|
|
|
1040
|
-
|
|
1087
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and code style.
|
|
1041
1088
|
|
|
1042
1089
|
## License
|
|
1043
1090
|
|
|
@@ -1045,6 +1092,5 @@ MIT © 2026 misterrodger
|
|
|
1045
1092
|
|
|
1046
1093
|
## Support
|
|
1047
1094
|
|
|
1048
|
-
-
|
|
1049
|
-
-
|
|
1050
|
-
- 💬 [Discussions](https://github.com/misterrodger/elasticlink/discussions)
|
|
1095
|
+
- [Documentation](https://github.com/misterrodger/elasticlink#readme)
|
|
1096
|
+
- [Report Issues](https://github.com/misterrodger/elasticlink/issues)
|