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.
Files changed (118) hide show
  1. package/README.md +223 -177
  2. package/dist/__tests__/aggregation-builder.test.d.ts +2 -0
  3. package/dist/__tests__/aggregation-builder.test.d.ts.map +1 -0
  4. package/dist/__tests__/aggregation-builder.test.js +622 -0
  5. package/dist/__tests__/bulk.test.d.ts +2 -0
  6. package/dist/__tests__/bulk.test.d.ts.map +1 -0
  7. package/dist/__tests__/bulk.test.js +679 -0
  8. package/dist/__tests__/examples.test.d.ts +2 -0
  9. package/dist/__tests__/examples.test.d.ts.map +1 -0
  10. package/dist/__tests__/examples.test.js +2123 -0
  11. package/dist/__tests__/fixtures/finance.d.ts +58 -0
  12. package/dist/__tests__/fixtures/finance.d.ts.map +1 -0
  13. package/dist/__tests__/fixtures/finance.js +73 -0
  14. package/dist/__tests__/fixtures/legal.d.ts +14 -0
  15. package/dist/__tests__/fixtures/legal.d.ts.map +1 -0
  16. package/dist/__tests__/fixtures/legal.js +27 -0
  17. package/dist/__tests__/fixtures/real-estate.d.ts +31 -0
  18. package/dist/__tests__/fixtures/real-estate.d.ts.map +1 -0
  19. package/dist/__tests__/fixtures/real-estate.js +39 -0
  20. package/dist/__tests__/index-management.test.d.ts +2 -0
  21. package/dist/__tests__/index-management.test.d.ts.map +1 -0
  22. package/dist/__tests__/index-management.test.js +1699 -0
  23. package/dist/__tests__/integration/aggregation.integration.test.d.ts +2 -0
  24. package/dist/__tests__/integration/aggregation.integration.test.d.ts.map +1 -0
  25. package/dist/__tests__/integration/aggregation.integration.test.js +188 -0
  26. package/dist/__tests__/integration/bulk.integration.test.d.ts +2 -0
  27. package/dist/__tests__/integration/bulk.integration.test.d.ts.map +1 -0
  28. package/dist/__tests__/integration/bulk.integration.test.js +90 -0
  29. package/dist/__tests__/integration/fixtures/finance.d.ts +37 -0
  30. package/dist/__tests__/integration/fixtures/finance.d.ts.map +1 -0
  31. package/dist/__tests__/integration/fixtures/finance.js +58 -0
  32. package/dist/__tests__/integration/fixtures/legal.d.ts +38 -0
  33. package/dist/__tests__/integration/fixtures/legal.d.ts.map +1 -0
  34. package/dist/__tests__/integration/fixtures/legal.js +65 -0
  35. package/dist/__tests__/integration/fixtures/real-estate.d.ts +17 -0
  36. package/dist/__tests__/integration/fixtures/real-estate.d.ts.map +1 -0
  37. package/dist/__tests__/integration/fixtures/real-estate.js +28 -0
  38. package/dist/__tests__/integration/helpers.d.ts +15 -0
  39. package/dist/__tests__/integration/helpers.d.ts.map +1 -0
  40. package/dist/__tests__/integration/helpers.js +21 -0
  41. package/dist/__tests__/integration/index-management.integration.test.d.ts +2 -0
  42. package/dist/__tests__/integration/index-management.integration.test.d.ts.map +1 -0
  43. package/dist/__tests__/integration/index-management.integration.test.js +67 -0
  44. package/dist/__tests__/integration/multi-search.integration.test.d.ts +2 -0
  45. package/dist/__tests__/integration/multi-search.integration.test.d.ts.map +1 -0
  46. package/dist/__tests__/integration/multi-search.integration.test.js +49 -0
  47. package/dist/__tests__/integration/query.integration.test.d.ts +2 -0
  48. package/dist/__tests__/integration/query.integration.test.d.ts.map +1 -0
  49. package/dist/__tests__/integration/query.integration.test.js +101 -0
  50. package/dist/__tests__/integration/suggester.integration.test.d.ts +2 -0
  51. package/dist/__tests__/integration/suggester.integration.test.d.ts.map +1 -0
  52. package/dist/__tests__/integration/suggester.integration.test.js +42 -0
  53. package/dist/__tests__/multi-search.test.d.ts +2 -0
  54. package/dist/__tests__/multi-search.test.d.ts.map +1 -0
  55. package/dist/__tests__/multi-search.test.js +325 -0
  56. package/dist/__tests__/query-builder-extensions.test.d.ts +2 -0
  57. package/dist/__tests__/query-builder-extensions.test.d.ts.map +1 -0
  58. package/dist/__tests__/query-builder-extensions.test.js +436 -0
  59. package/dist/__tests__/query-builder.test.d.ts +2 -0
  60. package/dist/__tests__/query-builder.test.d.ts.map +1 -0
  61. package/dist/__tests__/query-builder.test.js +5482 -0
  62. package/dist/__tests__/settings-presets.test.d.ts +2 -0
  63. package/dist/__tests__/settings-presets.test.d.ts.map +1 -0
  64. package/dist/__tests__/settings-presets.test.js +183 -0
  65. package/dist/__tests__/suggester.test.d.ts +2 -0
  66. package/dist/__tests__/suggester.test.d.ts.map +1 -0
  67. package/dist/__tests__/suggester.test.js +1006 -0
  68. package/dist/aggregation.builder.d.ts +4 -2
  69. package/dist/aggregation.builder.d.ts.map +1 -1
  70. package/dist/aggregation.builder.js +102 -95
  71. package/dist/aggregation.types.d.ts +16 -14
  72. package/dist/aggregation.types.d.ts.map +1 -1
  73. package/dist/bulk.builder.d.ts +126 -2
  74. package/dist/bulk.builder.d.ts.map +1 -1
  75. package/dist/bulk.builder.js +11 -12
  76. package/dist/bulk.types.d.ts.map +1 -1
  77. package/dist/field.helpers.d.ts +92 -42
  78. package/dist/field.helpers.d.ts.map +1 -1
  79. package/dist/field.helpers.js +99 -20
  80. package/dist/field.types.d.ts +113 -6
  81. package/dist/field.types.d.ts.map +1 -1
  82. package/dist/field.types.js +3 -3
  83. package/dist/index-management.builder.d.ts +19 -14
  84. package/dist/index-management.builder.d.ts.map +1 -1
  85. package/dist/index-management.builder.js +36 -34
  86. package/dist/index-management.types.d.ts +32 -62
  87. package/dist/index-management.types.d.ts.map +1 -1
  88. package/dist/index-management.types.js +0 -1
  89. package/dist/index.d.ts +18 -13
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +12 -8
  92. package/dist/mapping.builder.d.ts +37 -0
  93. package/dist/mapping.builder.d.ts.map +1 -0
  94. package/dist/mapping.builder.js +38 -0
  95. package/dist/mapping.types.d.ts +105 -0
  96. package/dist/mapping.types.d.ts.map +1 -0
  97. package/dist/mapping.types.js +6 -0
  98. package/dist/multi-search.builder.d.ts +5 -3
  99. package/dist/multi-search.builder.d.ts.map +1 -1
  100. package/dist/multi-search.builder.js +6 -4
  101. package/dist/multi-search.types.d.ts +7 -6
  102. package/dist/multi-search.types.d.ts.map +1 -1
  103. package/dist/query.builder.d.ts +3 -11
  104. package/dist/query.builder.d.ts.map +1 -1
  105. package/dist/query.builder.js +118 -259
  106. package/dist/query.types.d.ts +140 -236
  107. package/dist/query.types.d.ts.map +1 -1
  108. package/dist/query.types.js +2 -1
  109. package/dist/settings.presets.d.ts +98 -0
  110. package/dist/settings.presets.d.ts.map +1 -0
  111. package/dist/settings.presets.js +115 -0
  112. package/dist/suggester.builder.d.ts +5 -3
  113. package/dist/suggester.builder.d.ts.map +1 -1
  114. package/dist/suggester.builder.js +3 -2
  115. package/dist/suggester.types.d.ts +5 -4
  116. package/dist/suggester.types.d.ts.map +1 -1
  117. package/dist/vector.types.d.ts.map +1 -1
  118. 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
- - **Type-Safe**: Full TypeScript generics for field autocomplete and type checking
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 | Elasticsearch |
26
- |-------------|---------|---------------|
27
- | 0.2.0-beta | 20, 22 | 9.x (≥9.0.0) |
28
- | 0.1.0-beta | 20, 22 | 9.x (≥9.0.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`. Open an issue if you need support for a different version.
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
- type Product = {
48
- id: string;
49
- name: string;
50
- price: number;
51
- category: string;
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<Product>()
56
- .match('name', 'laptop', { operator: 'and', boost: 2 })
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 (search-as-you-type)
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<Product>()
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('featured', true)) // OR
120
- .mustNot(q => q.term('discontinued', true)) // NOT
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<Product>()
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<Product>()
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<Product>()
181
- .match('category', 'electronics')
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) - use query(false)
191
- const aggsOnly = query<Product>(false)
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<Product>()
204
- .dateHistogram('sales_timeline', 'created_at', { interval: 'day' })
205
- .subAgg(sub =>
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
- type Product = {
222
- id: string;
223
- name: string;
224
- description: string;
225
- price: number;
226
- category: string;
227
- embedding: number[]; // Vector field
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<Product>()
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<Product>()
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<Product>()
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
- type Product = {
310
- id: string;
311
- name: string;
312
- price: number;
313
- popularity: number;
314
- quality_score: number;
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<Product>()
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<Product>()
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
- type AlertRule = {
353
- query: any;
354
- name: string;
355
- severity: string;
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<AlertRule>()
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
- type Product = {
387
- name: string;
388
- description: string;
389
- suggest_field: string; // Must be type: completion
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<Product>()
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<Product>()
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<Product>()
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<Product>()
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<Product>()
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<Product>()
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<Product>()
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
- type Product = {
493
- id: string;
494
- name: string;
495
- price: number;
496
- category: string;
497
- };
497
+ const productMappings = mappings({
498
+ id: keyword(),
499
+ name: text(),
500
+ price: float(),
501
+ category: keyword(),
502
+ });
498
503
 
499
- const bulkOp = bulk<Product>()
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
- type Matter = {
552
- title: string;
553
- practice_area: string;
554
- billing_rate: number;
555
- risk_score: number;
556
- opened_at: string;
557
- };
558
-
559
- const indexConfig = indexBuilder<Matter>()
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`, `dateNanos` |
627
- | Boolean | `boolean` |
628
- | Range | `integerRange`, `floatRange`, `longRange`, `doubleRange`, `dateRange` |
629
- | Objects | `object`, `nested`, `flattened` |
630
- | Spatial | `geoPoint`, `geoShape` |
631
- | Specialized | `ip`, `completion`, `tokenCount`, `denseVector`, `rankFeature`, `rankFeatures`, `binary`, `percolator` |
632
- | Alias | `alias` |
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
- type Product = {
698
- name: string;
699
- description: string;
700
- category: string;
701
- price: number;
702
- tags: string[];
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<Product>()
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
- type Instrument = {
787
- name: string;
788
- asset_class: string;
789
- sector: string;
790
- price: number;
791
- yield_rate: number;
792
- listed_date: string;
793
- };
794
-
795
- const result = query<Instrument>()
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
- type Listing = {
850
- address: string;
851
- property_class: string;
852
- list_price: number;
853
- };
889
+ const listingMappings = mappings({
890
+ address: text(),
891
+ property_class: keyword(),
892
+ list_price: long(),
893
+ });
854
894
 
855
- const condoSearch = query<Listing>()
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<Listing>()
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<Listing>()
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
- type Attorney = {
890
- name: string;
891
- practice_area: string;
892
- name_suggest: string; // completion field
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<Attorney>()
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<Product>()
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
- type Restaurant = {
955
- name: string;
956
- cuisine: string;
957
- location: { lat: number; lon: number };
958
- rating: number;
959
- };
960
-
961
- const result = query<Restaurant>()
962
- .match('cuisine', 'italian')
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 excellent TypeScript support with:
1015
+ elasticlink provides mapping-aware TypeScript safety:
976
1016
 
977
- - **Field Autocomplete**: Type-safe field names with IntelliSense
978
- - **Type Validation**: Compile-time checking for query structure
979
- - **Generic Parameters**: Full type inference across builder chains
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 User = {
983
- id: string;
984
- name: string;
985
- email: string;
986
- age: number;
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
- // Type-safe field names
990
- const q1 = query<User>().match('name', 'John').build();
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
- // TypeScript error: 'unknown_field' is not a valid field
993
- const q2 = query<User>().match('unknown_field', 'value').build();
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
- ## Contributing
1085
+ ## Development
1039
1086
 
1040
- We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
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
- - 📖 [Documentation](https://github.com/misterrodger/elasticlink#readme)
1049
- - 🐛 [Report Issues](https://github.com/misterrodger/elasticlink/issues)
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)