elasticlink 0.2.1-beta → 0.3.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +162 -163
  2. package/dist/aggregation.builder.d.ts +4 -2
  3. package/dist/aggregation.builder.d.ts.map +1 -1
  4. package/dist/aggregation.builder.js +1 -1
  5. package/dist/aggregation.types.d.ts +15 -14
  6. package/dist/aggregation.types.d.ts.map +1 -1
  7. package/dist/bulk.builder.d.ts +115 -1
  8. package/dist/bulk.builder.d.ts.map +1 -1
  9. package/dist/bulk.builder.js +1 -1
  10. package/dist/field.helpers.d.ts +39 -44
  11. package/dist/field.helpers.d.ts.map +1 -1
  12. package/dist/field.helpers.js +15 -19
  13. package/dist/field.types.d.ts +35 -5
  14. package/dist/field.types.d.ts.map +1 -1
  15. package/dist/field.types.js +3 -3
  16. package/dist/index-management.builder.d.ts +5 -12
  17. package/dist/index-management.builder.d.ts.map +1 -1
  18. package/dist/index-management.builder.js +10 -32
  19. package/dist/index-management.types.d.ts +10 -64
  20. package/dist/index-management.types.d.ts.map +1 -1
  21. package/dist/index-management.types.js +0 -1
  22. package/dist/index.d.ts +16 -12
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +10 -8
  25. package/dist/mapping.builder.d.ts +24 -0
  26. package/dist/mapping.builder.d.ts.map +1 -0
  27. package/dist/mapping.builder.js +23 -0
  28. package/dist/mapping.types.d.ts +82 -0
  29. package/dist/mapping.types.d.ts.map +1 -0
  30. package/dist/mapping.types.js +6 -0
  31. package/dist/multi-search.builder.d.ts +5 -3
  32. package/dist/multi-search.builder.d.ts.map +1 -1
  33. package/dist/multi-search.builder.js +2 -2
  34. package/dist/multi-search.types.d.ts +7 -6
  35. package/dist/multi-search.types.d.ts.map +1 -1
  36. package/dist/query.builder.d.ts +3 -7
  37. package/dist/query.builder.d.ts.map +1 -1
  38. package/dist/query.builder.js +30 -92
  39. package/dist/query.types.d.ts +85 -235
  40. package/dist/query.types.d.ts.map +1 -1
  41. package/dist/query.types.js +2 -1
  42. package/dist/suggester.builder.d.ts +5 -3
  43. package/dist/suggester.builder.d.ts.map +1 -1
  44. package/dist/suggester.builder.js +2 -2
  45. package/dist/suggester.types.d.ts +5 -4
  46. package/dist/suggester.types.d.ts.map +1 -1
  47. package/package.json +7 -7
  48. package/dist/__tests__/aggregation-builder.test.d.ts +0 -2
  49. package/dist/__tests__/aggregation-builder.test.d.ts.map +0 -1
  50. package/dist/__tests__/aggregation-builder.test.js +0 -662
  51. package/dist/__tests__/bulk.test.d.ts +0 -2
  52. package/dist/__tests__/bulk.test.d.ts.map +0 -1
  53. package/dist/__tests__/bulk.test.js +0 -684
  54. package/dist/__tests__/examples.test.d.ts +0 -2
  55. package/dist/__tests__/examples.test.d.ts.map +0 -1
  56. package/dist/__tests__/examples.test.js +0 -2006
  57. package/dist/__tests__/fixtures/finance.d.ts +0 -26
  58. package/dist/__tests__/fixtures/finance.d.ts.map +0 -1
  59. package/dist/__tests__/fixtures/finance.js +0 -32
  60. package/dist/__tests__/fixtures/legal.d.ts +0 -8
  61. package/dist/__tests__/fixtures/legal.d.ts.map +0 -1
  62. package/dist/__tests__/fixtures/legal.js +0 -20
  63. package/dist/__tests__/fixtures/real-estate.d.ts +0 -19
  64. package/dist/__tests__/fixtures/real-estate.d.ts.map +0 -1
  65. package/dist/__tests__/fixtures/real-estate.js +0 -20
  66. package/dist/__tests__/index-management.test.d.ts +0 -2
  67. package/dist/__tests__/index-management.test.d.ts.map +0 -1
  68. package/dist/__tests__/index-management.test.js +0 -1148
  69. package/dist/__tests__/integration/aggregation.integration.test.d.ts +0 -2
  70. package/dist/__tests__/integration/aggregation.integration.test.d.ts.map +0 -1
  71. package/dist/__tests__/integration/aggregation.integration.test.js +0 -200
  72. package/dist/__tests__/integration/bulk.integration.test.d.ts +0 -2
  73. package/dist/__tests__/integration/bulk.integration.test.d.ts.map +0 -1
  74. package/dist/__tests__/integration/bulk.integration.test.js +0 -93
  75. package/dist/__tests__/integration/fixtures/finance.d.ts +0 -17
  76. package/dist/__tests__/integration/fixtures/finance.d.ts.map +0 -1
  77. package/dist/__tests__/integration/fixtures/finance.js +0 -42
  78. package/dist/__tests__/integration/fixtures/legal.d.ts +0 -21
  79. package/dist/__tests__/integration/fixtures/legal.d.ts.map +0 -1
  80. package/dist/__tests__/integration/fixtures/legal.js +0 -52
  81. package/dist/__tests__/integration/fixtures/real-estate.d.ts +0 -7
  82. package/dist/__tests__/integration/fixtures/real-estate.d.ts.map +0 -1
  83. package/dist/__tests__/integration/fixtures/real-estate.js +0 -22
  84. package/dist/__tests__/integration/helpers.d.ts +0 -15
  85. package/dist/__tests__/integration/helpers.d.ts.map +0 -1
  86. package/dist/__tests__/integration/helpers.js +0 -21
  87. package/dist/__tests__/integration/index-management.integration.test.d.ts +0 -2
  88. package/dist/__tests__/integration/index-management.integration.test.d.ts.map +0 -1
  89. package/dist/__tests__/integration/index-management.integration.test.js +0 -79
  90. package/dist/__tests__/integration/multi-search.integration.test.d.ts +0 -2
  91. package/dist/__tests__/integration/multi-search.integration.test.d.ts.map +0 -1
  92. package/dist/__tests__/integration/multi-search.integration.test.js +0 -55
  93. package/dist/__tests__/integration/query.integration.test.d.ts +0 -2
  94. package/dist/__tests__/integration/query.integration.test.d.ts.map +0 -1
  95. package/dist/__tests__/integration/query.integration.test.js +0 -118
  96. package/dist/__tests__/integration/suggester.integration.test.d.ts +0 -2
  97. package/dist/__tests__/integration/suggester.integration.test.d.ts.map +0 -1
  98. package/dist/__tests__/integration/suggester.integration.test.js +0 -48
  99. package/dist/__tests__/multi-search.test.d.ts +0 -2
  100. package/dist/__tests__/multi-search.test.d.ts.map +0 -1
  101. package/dist/__tests__/multi-search.test.js +0 -336
  102. package/dist/__tests__/query-builder.test.d.ts +0 -2
  103. package/dist/__tests__/query-builder.test.d.ts.map +0 -1
  104. package/dist/__tests__/query-builder.test.js +0 -5624
  105. package/dist/__tests__/suggester.test.d.ts +0 -2
  106. package/dist/__tests__/suggester.test.d.ts.map +0 -1
  107. package/dist/__tests__/suggester.test.js +0 -1001
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,13 @@ 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.3.0-beta | 20, 22, 24 | 9.x (≥9.0.0) |
28
+ | 0.2.0-beta | 20, 22 | 9.x (≥9.0.0) |
29
+ | 0.1.0-beta | 20, 22 | 9.x (≥9.0.0) |
29
30
 
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.
31
+ Tested against the versions listed. Peer dependency is `@elastic/elasticsearch >=9.0.0`.
31
32
 
32
33
  ## Installation
33
34
 
@@ -42,18 +43,21 @@ Requires Node.js 20+ and `@elastic/elasticsearch` 9.x as a peer dependency.
42
43
  ## Quick Start
43
44
 
44
45
  ```typescript
45
- import { query } from 'elasticlink';
46
+ import { query, mappings, text, keyword, float, type Infer } from 'elasticlink';
46
47
 
47
- type Product = {
48
- id: string;
49
- name: string;
50
- price: number;
51
- category: string;
52
- };
48
+ // Define field types once — this is the single source of truth
49
+ const productMappings = mappings({
50
+ name: text(),
51
+ price: float(),
52
+ category: keyword(),
53
+ });
54
+
55
+ // Derive a TS type from mappings (optional, for use elsewhere)
56
+ type Product = Infer<typeof productMappings>;
53
57
 
54
- // Build a type-safe query
55
- const q = query<Product>()
56
- .match('name', 'laptop', { operator: 'and', boost: 2 })
58
+ // Build a type-safe query — field constraints are enforced at compile time
59
+ const q = query(productMappings)
60
+ .match('name', 'laptop') // 'name' is a text field
57
61
  .range('price', { gte: 500, lte: 2000 })
58
62
  .from(0)
59
63
  .size(20)
@@ -112,12 +116,12 @@ const response = await client.search({ index: 'products', ...q });
112
116
  ### Boolean Logic
113
117
 
114
118
  ```typescript
115
- query<Product>()
119
+ query(productMappings)
116
120
  .bool()
117
121
  .must(q => q.match('name', 'laptop')) // AND
118
122
  .filter(q => q.range('price', { gte: 500 }))
119
- .should(q => q.term('featured', true)) // OR
120
- .mustNot(q => q.term('discontinued', true)) // NOT
123
+ .should(q => q.term('category', 'featured')) // OR
124
+ .mustNot(q => q.term('category', 'discontinued')) // NOT
121
125
  .minimumShouldMatch(1)
122
126
  .build();
123
127
  ```
@@ -130,7 +134,7 @@ Build queries dynamically based on runtime values:
130
134
  const searchTerm = getUserInput();
131
135
  const minPrice = getMinPrice();
132
136
 
133
- query<Product>()
137
+ query(productMappings)
134
138
  .bool()
135
139
  .must(q =>
136
140
  q.when(searchTerm, q => q.match('name', searchTerm)) || q.matchAll()
@@ -144,7 +148,7 @@ query<Product>()
144
148
  ### Query Parameters
145
149
 
146
150
  ```typescript
147
- query<Product>()
151
+ query(productMappings)
148
152
  .match('name', 'laptop')
149
153
  .from(0) // Pagination offset
150
154
  .size(20) // Results per page
@@ -176,9 +180,9 @@ Aggregations can be combined with queries or used standalone:
176
180
  ```typescript
177
181
  import { query, aggregations } from 'elasticlink';
178
182
 
179
- // Combined query + aggregations
180
- const result = query<Product>()
181
- .match('category', 'electronics')
183
+ // Combined query + aggregations (inline aggs auto-thread mappings)
184
+ const result = query(productMappings)
185
+ .term('category', 'electronics')
182
186
  .aggs(agg =>
183
187
  agg
184
188
  .terms('by_category', 'category', { size: 10 })
@@ -187,8 +191,8 @@ const result = query<Product>()
187
191
  .size(20)
188
192
  .build();
189
193
 
190
- // Standalone aggregations (no query) - use query(false)
191
- const aggsOnly = query<Product>(false)
194
+ // Standalone aggregations (no query) use query(mappings, false)
195
+ const aggsOnly = query(productMappings, false)
192
196
  .aggs(agg =>
193
197
  agg
194
198
  .terms('by_category', 'category')
@@ -200,12 +204,9 @@ const aggsOnly = query<Product>(false)
200
204
  .build();
201
205
 
202
206
  // 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
- )
207
+ const standaloneAgg = aggregations(productMappings)
208
+ .avg('avg_price', 'price')
209
+ .terms('by_category', 'category', { size: 10 })
209
210
  .build();
210
211
  ```
211
212
 
@@ -216,21 +217,20 @@ const standaloneAgg = aggregations<Product>()
216
217
  KNN (k-nearest neighbors) queries enable semantic search using vector embeddings from machine learning models.
217
218
 
218
219
  ```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
- };
220
+ import { query, mappings, text, keyword, float, denseVector } from 'elasticlink';
221
+
222
+ const productWithEmbeddingMappings = mappings({
223
+ name: text(),
224
+ description: text(),
225
+ price: float(),
226
+ category: keyword(),
227
+ embedding: denseVector({ dims: 384 }),
228
+ });
229
229
 
230
230
  // Basic semantic search
231
231
  const searchEmbedding = [0.23, 0.45, 0.67, 0.12, 0.89]; // From your ML model
232
232
 
233
- const result = query<Product>()
233
+ const result = query(productWithEmbeddingMappings)
234
234
  .knn('embedding', searchEmbedding, {
235
235
  k: 10, // Return top 10 nearest neighbors
236
236
  num_candidates: 100 // Consider 100 candidates per shard
@@ -239,7 +239,7 @@ const result = query<Product>()
239
239
  .build();
240
240
 
241
241
  // Semantic search with filters
242
- const filtered = query<Product>()
242
+ const filtered = query(productWithEmbeddingMappings)
243
243
  .knn('embedding', searchEmbedding, {
244
244
  k: 20,
245
245
  num_candidates: 200,
@@ -256,7 +256,7 @@ const filtered = query<Product>()
256
256
  .build();
257
257
 
258
258
  // Hybrid search with aggregations
259
- const hybridSearch = query<Product>()
259
+ const hybridSearch = query(productWithEmbeddingMappings)
260
260
  .knn('embedding', searchEmbedding, {
261
261
  k: 100,
262
262
  num_candidates: 1000,
@@ -304,18 +304,17 @@ const mapping: DenseVectorOptions = {
304
304
  **Note:** Scripts must be enabled in Elasticsearch configuration. Use with caution as they can impact performance.
305
305
 
306
306
  ```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
- };
307
+ import { query, mappings, text, keyword, float, long } from 'elasticlink';
308
+
309
+ const scoredProductMappings = mappings({
310
+ name: text(),
311
+ price: float(),
312
+ popularity: long(),
313
+ quality_score: float(),
314
+ });
316
315
 
317
316
  // Script-based filtering
318
- const filtered = query<Product>()
317
+ const filtered = query(scoredProductMappings)
319
318
  .bool()
320
319
  .must((q) => q.match('name', 'laptop'))
321
320
  .filter((q) =>
@@ -327,7 +326,7 @@ const filtered = query<Product>()
327
326
  .build();
328
327
 
329
328
  // Custom scoring with script_score
330
- const customScored = query<Product>()
329
+ const customScored = query(scoredProductMappings)
331
330
  .scriptScore(
332
331
  (q) => q.match('name', 'smartphone'),
333
332
  {
@@ -349,14 +348,14 @@ const customScored = query<Product>()
349
348
  Percolate queries enable reverse search - match documents against stored queries. Perfect for alerting, content classification, and saved searches.
350
349
 
351
350
  ```typescript
352
- type AlertRule = {
353
- query: any;
354
- name: string;
355
- severity: string;
356
- };
351
+ const alertRuleMappings = mappings({
352
+ query: percolator(),
353
+ name: keyword(),
354
+ severity: keyword(),
355
+ });
357
356
 
358
357
  // Match document against stored queries
359
- const alerts = query<AlertRule>()
358
+ const alerts = query(alertRuleMappings)
360
359
  .percolate({
361
360
  field: 'query',
362
361
  document: {
@@ -381,16 +380,16 @@ const alerts = query<AlertRule>()
381
380
  Elasticsearch Suggesters provide spell-checking, phrase correction, and autocomplete functionality. Perfect for search-as-you-type experiences and fixing user typos.
382
381
 
383
382
  ```typescript
384
- import { query, suggest } from 'elasticlink';
383
+ import { query, suggest, mappings, text, keyword, completion } from 'elasticlink';
385
384
 
386
- type Product = {
387
- name: string;
388
- description: string;
389
- suggest_field: string; // Must be type: completion
390
- };
385
+ const searchableMappings = mappings({
386
+ name: text(),
387
+ description: text(),
388
+ suggest_field: completion(), // Must be type: completion
389
+ });
391
390
 
392
391
  // Term suggester - Fix typos in individual terms
393
- const termSuggestions = suggest<Product>()
392
+ const termSuggestions = suggest(searchableMappings)
394
393
  .term('name-suggestions', 'laptpo', {
395
394
  field: 'name',
396
395
  size: 5,
@@ -401,7 +400,7 @@ const termSuggestions = suggest<Product>()
401
400
  .build();
402
401
 
403
402
  // Completion suggester - Fast autocomplete
404
- const autocomplete = suggest<Product>()
403
+ const autocomplete = suggest(searchableMappings)
405
404
  .completion('autocomplete', 'lap', {
406
405
  field: 'suggest_field',
407
406
  size: 10,
@@ -416,7 +415,7 @@ const autocomplete = suggest<Product>()
416
415
  .build();
417
416
 
418
417
  // Combine with query - Search with autocomplete
419
- const searchWithSuggestions = query<Product>()
418
+ const searchWithSuggestions = query(searchableMappings)
420
419
  .match('name', 'laptpo')
421
420
  .suggest((s) =>
422
421
  s.term('spelling-correction', 'laptpo', {
@@ -442,24 +441,24 @@ Batch multiple search requests in a single API call using the NDJSON format.
442
441
  ```typescript
443
442
  import { query, msearch } from 'elasticlink';
444
443
 
445
- const laptopQuery = query<Product>()
444
+ const laptopQuery = query(productMappings)
446
445
  .match('name', 'laptop')
447
446
  .range('price', { gte: 500, lte: 2000 })
448
447
  .build();
449
448
 
450
- const phoneQuery = query<Product>()
449
+ const phoneQuery = query(productMappings)
451
450
  .match('name', 'smartphone')
452
451
  .range('price', { gte: 300, lte: 1000 })
453
452
  .build();
454
453
 
455
454
  // Build as NDJSON string for Elasticsearch API
456
- const ndjson = msearch<Product>()
455
+ const ndjson = msearch(productMappings)
457
456
  .addQuery(laptopQuery, { index: 'products', preference: '_local' })
458
457
  .addQuery(phoneQuery, { index: 'products', preference: '_local' })
459
458
  .build();
460
459
 
461
460
  // Or build as array of objects
462
- const array = msearch<Product>()
461
+ const array = msearch(productMappings)
463
462
  .addQuery(laptopQuery, { index: 'products' })
464
463
  .addQuery(phoneQuery, { index: 'products' })
465
464
  .buildArray();
@@ -487,16 +486,16 @@ const array = msearch<Product>()
487
486
  Batch create, index, update, and delete operations efficiently.
488
487
 
489
488
  ```typescript
490
- import { bulk } from 'elasticlink';
489
+ import { bulk, mappings, keyword, text, float } from 'elasticlink';
491
490
 
492
- type Product = {
493
- id: string;
494
- name: string;
495
- price: number;
496
- category: string;
497
- };
491
+ const productMappings = mappings({
492
+ id: keyword(),
493
+ name: text(),
494
+ price: float(),
495
+ category: keyword(),
496
+ });
498
497
 
499
- const bulkOp = bulk<Product>()
498
+ const bulkOp = bulk(productMappings)
500
499
  // Index (create or replace)
501
500
  .index(
502
501
  { id: '1', name: 'Laptop Pro', price: 1299, category: 'electronics' },
@@ -546,24 +545,18 @@ const bulkOp = bulk<Product>()
546
545
  Configure index mappings, settings, and aliases declaratively.
547
546
 
548
547
  ```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
- })
548
+ import { indexBuilder, mappings, keyword, integer, float, date, text } from 'elasticlink';
549
+
550
+ const matterMappings = mappings({
551
+ title: text({ analyzer: 'english' }),
552
+ practice_area: keyword(),
553
+ billing_rate: integer(),
554
+ risk_score: float(),
555
+ opened_at: date(),
556
+ });
557
+
558
+ const indexConfig = indexBuilder()
559
+ .mappings(matterMappings)
567
560
  .settings({
568
561
  number_of_shards: 2,
569
562
  number_of_replicas: 1,
@@ -694,21 +687,21 @@ More examples available in [src/\_\_tests\_\_/examples.test.ts](src/__tests__/ex
694
687
  A complete search request: boolean query with must/filter/should, aggregations for facets and price ranges, highlights, `_source` filtering, and pagination.
695
688
 
696
689
  ```typescript
697
- type Product = {
698
- name: string;
699
- description: string;
700
- category: string;
701
- price: number;
702
- tags: string[];
703
- in_stock: boolean;
704
- };
690
+ const ecommerceMappings = mappings({
691
+ name: text(),
692
+ description: text(),
693
+ category: keyword(),
694
+ price: float(),
695
+ tags: keyword(),
696
+ in_stock: boolean(),
697
+ });
705
698
 
706
699
  const searchTerm = 'gaming laptop';
707
700
  const category = 'electronics';
708
701
  const minPrice = 800;
709
702
  const maxPrice = 2000;
710
703
 
711
- const result = query<Product>()
704
+ const result = query(ecommerceMappings)
712
705
  .bool()
713
706
  .must(q => q.match('name', searchTerm, { operator: 'and', boost: 2 }))
714
707
  .should(q => q.fuzzy('description', searchTerm, { fuzziness: 'AUTO' }))
@@ -783,16 +776,16 @@ Produces:
783
776
  Terms + sub-aggregation + date histogram in one request.
784
777
 
785
778
  ```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>()
779
+ const instrumentMappings = mappings({
780
+ name: text(),
781
+ asset_class: keyword(),
782
+ sector: keyword(),
783
+ price: float(),
784
+ yield_rate: float(),
785
+ listed_date: date(),
786
+ });
787
+
788
+ const result = query(instrumentMappings)
796
789
  .bool()
797
790
  .filter(q => q.term('asset_class', 'fixed-income'))
798
791
  .filter(q => q.range('yield_rate', { gte: 3.0 }))
@@ -846,13 +839,13 @@ Produces:
846
839
  Batch multiple independent searches into a single HTTP request.
847
840
 
848
841
  ```typescript
849
- type Listing = {
850
- address: string;
851
- property_class: string;
852
- list_price: number;
853
- };
842
+ const listingMappings = mappings({
843
+ address: text(),
844
+ property_class: keyword(),
845
+ list_price: long(),
846
+ });
854
847
 
855
- const condoSearch = query<Listing>()
848
+ const condoSearch = query(listingMappings)
856
849
  .bool()
857
850
  .filter(q => q.term('property_class', 'condo'))
858
851
  .filter(q => q.range('list_price', { lte: 2_000_000 }))
@@ -860,14 +853,14 @@ const condoSearch = query<Listing>()
860
853
  .size(0)
861
854
  .build();
862
855
 
863
- const townhouseSearch = query<Listing>()
856
+ const townhouseSearch = query(listingMappings)
864
857
  .bool()
865
858
  .filter(q => q.term('property_class', 'townhouse'))
866
859
  .aggs(agg => agg.avg('avg_price', 'list_price').min('min_price', 'list_price'))
867
860
  .size(0)
868
861
  .build();
869
862
 
870
- const ndjson = msearch<Listing>()
863
+ const ndjson = msearch(listingMappings)
871
864
  .addQuery(condoSearch, { index: 'listings' })
872
865
  .addQuery(townhouseSearch, { index: 'listings' })
873
866
  .build();
@@ -886,14 +879,14 @@ Produces:
886
879
  ### Suggesters — Autocomplete & Spell Check
887
880
 
888
881
  ```typescript
889
- type Attorney = {
890
- name: string;
891
- practice_area: string;
892
- name_suggest: string; // completion field
893
- };
882
+ const attorneyMappings = mappings({
883
+ name: text(),
884
+ practice_area: keyword(),
885
+ name_suggest: completion(),
886
+ });
894
887
 
895
888
  // Standalone suggest request
896
- const suggestions = query<Attorney>()
889
+ const suggestions = query(attorneyMappings)
897
890
  .suggest(s =>
898
891
  s
899
892
  .completion('autocomplete', 'kap', { field: 'name_suggest', size: 5 })
@@ -925,7 +918,7 @@ Produces:
925
918
 
926
919
  ```typescript
927
920
  const buildDynamicQuery = (filters: SearchFilters) => {
928
- return query<Product>()
921
+ return query(productMappings)
929
922
  .bool()
930
923
  .must(q =>
931
924
  q.when(filters.searchTerm,
@@ -951,15 +944,15 @@ const buildDynamicQuery = (filters: SearchFilters) => {
951
944
  ### Geospatial Search
952
945
 
953
946
  ```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')
947
+ const restaurantMappings = mappings({
948
+ name: text(),
949
+ cuisine: keyword(),
950
+ location: geoPoint(),
951
+ rating: float(),
952
+ });
953
+
954
+ const result = query(restaurantMappings)
955
+ .term('cuisine', 'italian')
963
956
  .geoDistance(
964
957
  'location',
965
958
  { lat: 40.7128, lon: -74.006 },
@@ -972,25 +965,32 @@ const result = query<Restaurant>()
972
965
 
973
966
  ## TypeScript Support
974
967
 
975
- elasticlink provides excellent TypeScript support with:
968
+ elasticlink provides mapping-aware TypeScript safety:
976
969
 
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
970
+ - **Field-Type Constraints**: `match()` only accepts text fields, `term()` only keyword fields — enforced at compile time
971
+ - **Field Autocomplete**: IntelliSense knows your field names and their types
972
+ - **`Infer<S>`**: Derive TS document types from your mappings schema
980
973
 
981
974
  ```typescript
982
- type User = {
983
- id: string;
984
- name: string;
985
- email: string;
986
- age: number;
987
- };
975
+ import { query, mappings, text, keyword, integer, type Infer } from 'elasticlink';
976
+
977
+ const userMappings = mappings({
978
+ name: text(),
979
+ email: keyword(),
980
+ age: integer(),
981
+ });
982
+
983
+ type User = Infer<typeof userMappings>;
984
+ // => { name: string; email: string; age: number }
985
+
986
+ // ✅ 'name' is a text field — match() accepts it
987
+ const q1 = query(userMappings).match('name', 'John').build();
988
988
 
989
- // Type-safe field names
990
- const q1 = query<User>().match('name', 'John').build();
989
+ // TypeScript error: 'email' is keyword, not text — use term() instead
990
+ const q2 = query(userMappings).match('email', 'john@example.com').build();
991
991
 
992
- // TypeScript error: 'unknown_field' is not a valid field
993
- const q2 = query<User>().match('unknown_field', 'value').build();
992
+ // Correct: use term() for keyword fields
993
+ const q3 = query(userMappings).term('email', 'john@example.com').build();
994
994
  ```
995
995
 
996
996
  ## Testing
@@ -1035,9 +1035,9 @@ npm run type-check
1035
1035
  - [x] Integration test suite against live Elasticsearch 9.x
1036
1036
  - [x] Types derived from official `@elastic/elasticsearch` for accuracy and completeness
1037
1037
 
1038
- ## Contributing
1038
+ ## Development
1039
1039
 
1040
- We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
1040
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and code style.
1041
1041
 
1042
1042
  ## License
1043
1043
 
@@ -1045,6 +1045,5 @@ MIT © 2026 misterrodger
1045
1045
 
1046
1046
  ## Support
1047
1047
 
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)
1048
+ - [Documentation](https://github.com/misterrodger/elasticlink#readme)
1049
+ - [Report Issues](https://github.com/misterrodger/elasticlink/issues)
@@ -1,4 +1,6 @@
1
+ import type { FieldTypeString } from './index-management.types.js';
2
+ import type { MappingsSchema } from './mapping.types.js';
1
3
  import { AggregationBuilder, AggregationState } from './aggregation.types.js';
2
- export declare const createAggregationBuilder: <T>(state?: AggregationState) => AggregationBuilder<T>;
3
- export declare const aggregations: <T>() => AggregationBuilder<T>;
4
+ export declare const createAggregationBuilder: <M extends Record<string, FieldTypeString>>(state?: AggregationState) => AggregationBuilder<M>;
5
+ export declare const aggregations: <M extends Record<string, FieldTypeString>>(_schema: MappingsSchema<M>) => AggregationBuilder<M>;
4
6
  //# sourceMappingURL=aggregation.builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"aggregation.builder.d.ts","sourceRoot":"","sources":["../src/aggregation.builder.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAajB,MAAM,wBAAwB,CAAC;AAEhC,eAAO,MAAM,wBAAwB,GAAI,CAAC,EACxC,QAAO,gBAAqB,KAC3B,kBAAkB,CAAC,CAAC,CAiMrB,CAAC;AAGH,eAAO,MAAM,YAAY,GAAI,CAAC,4BAAoC,CAAC"}
1
+ {"version":3,"file":"aggregation.builder.d.ts","sourceRoot":"","sources":["../src/aggregation.builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAajB,MAAM,wBAAwB,CAAC;AAEhC,eAAO,MAAM,wBAAwB,GACnC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EAEzC,QAAO,gBAAqB,KAC3B,kBAAkB,CAAC,CAAC,CAiNrB,CAAC;AAGH,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EACpE,SAAS,cAAc,CAAC,CAAC,CAAC,0BACM,CAAC"}
@@ -145,4 +145,4 @@ export const createAggregationBuilder = (state = {}) => ({
145
145
  build: () => state
146
146
  });
147
147
  // Helper function to create aggregations without needing to import the builder
148
- export const aggregations = () => createAggregationBuilder();
148
+ export const aggregations = (_schema) => createAggregationBuilder();
@@ -4,6 +4,7 @@
4
4
  * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
5
5
  */
6
6
  import type { AggregationsTermsAggregation, AggregationsDateHistogramAggregation, AggregationsRangeAggregation, AggregationsHistogramAggregation, AggregationsAverageAggregation, AggregationsSumAggregation, AggregationsMinAggregation, AggregationsMaxAggregation, AggregationsCardinalityAggregation, AggregationsPercentilesAggregation, AggregationsStatsAggregation, AggregationsValueCountAggregation, AggregationsCalendarInterval } from '@elastic/elasticsearch/lib/api/types';
7
+ import type { FieldTypeString } from './index-management.types.js';
7
8
  /**
8
9
  * Options for terms aggregation (excludes 'field' which is handled by the builder)
9
10
  * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html
@@ -78,33 +79,33 @@ export type AggregationState = {
78
79
  /**
79
80
  * Aggregation builder interface
80
81
  */
81
- export type AggregationBuilder<T> = {
82
+ export type AggregationBuilder<M extends Record<string, FieldTypeString>> = {
82
83
  /** Terms aggregation - group by field values */
83
- terms: <K extends keyof T>(name: string, field: K, options?: TermsAggOptions) => AggregationBuilder<T>;
84
+ terms: <K extends string & keyof M>(name: string, field: K, options?: TermsAggOptions) => AggregationBuilder<M>;
84
85
  /** Date histogram aggregation - group by time intervals */
85
- dateHistogram: <K extends keyof T>(name: string, field: K, options: DateHistogramAggOptions) => AggregationBuilder<T>;
86
+ dateHistogram: <K extends string & keyof M>(name: string, field: K, options: DateHistogramAggOptions) => AggregationBuilder<M>;
86
87
  /** Range aggregation - group by numeric/date ranges */
87
- range: <K extends keyof T>(name: string, field: K, options: RangeAggOptions) => AggregationBuilder<T>;
88
+ range: <K extends string & keyof M>(name: string, field: K, options: RangeAggOptions) => AggregationBuilder<M>;
88
89
  /** Histogram aggregation - group by numeric intervals */
89
- histogram: <K extends keyof T>(name: string, field: K, options: HistogramAggOptions) => AggregationBuilder<T>;
90
+ histogram: <K extends string & keyof M>(name: string, field: K, options: HistogramAggOptions) => AggregationBuilder<M>;
90
91
  /** Average aggregation */
91
- avg: <K extends keyof T>(name: string, field: K, options?: AvgAggOptions) => AggregationBuilder<T>;
92
+ avg: <K extends string & keyof M>(name: string, field: K, options?: AvgAggOptions) => AggregationBuilder<M>;
92
93
  /** Sum aggregation */
93
- sum: <K extends keyof T>(name: string, field: K, options?: SumAggOptions) => AggregationBuilder<T>;
94
+ sum: <K extends string & keyof M>(name: string, field: K, options?: SumAggOptions) => AggregationBuilder<M>;
94
95
  /** Minimum value aggregation */
95
- min: <K extends keyof T>(name: string, field: K, options?: MinAggOptions) => AggregationBuilder<T>;
96
+ min: <K extends string & keyof M>(name: string, field: K, options?: MinAggOptions) => AggregationBuilder<M>;
96
97
  /** Maximum value aggregation */
97
- max: <K extends keyof T>(name: string, field: K, options?: MaxAggOptions) => AggregationBuilder<T>;
98
+ max: <K extends string & keyof M>(name: string, field: K, options?: MaxAggOptions) => AggregationBuilder<M>;
98
99
  /** Cardinality aggregation - count unique values */
99
- cardinality: <K extends keyof T>(name: string, field: K, options?: CardinalityAggOptions) => AggregationBuilder<T>;
100
+ cardinality: <K extends string & keyof M>(name: string, field: K, options?: CardinalityAggOptions) => AggregationBuilder<M>;
100
101
  /** Percentiles aggregation */
101
- percentiles: <K extends keyof T>(name: string, field: K, options?: PercentilesAggOptions) => AggregationBuilder<T>;
102
+ percentiles: <K extends string & keyof M>(name: string, field: K, options?: PercentilesAggOptions) => AggregationBuilder<M>;
102
103
  /** Statistics aggregation (count, min, max, avg, sum) */
103
- stats: <K extends keyof T>(name: string, field: K, options?: StatsAggOptions) => AggregationBuilder<T>;
104
+ stats: <K extends string & keyof M>(name: string, field: K, options?: StatsAggOptions) => AggregationBuilder<M>;
104
105
  /** Value count aggregation */
105
- valueCount: <K extends keyof T>(name: string, field: K, options?: ValueCountAggOptions) => AggregationBuilder<T>;
106
+ valueCount: <K extends string & keyof M>(name: string, field: K, options?: ValueCountAggOptions) => AggregationBuilder<M>;
106
107
  /** Add sub-aggregation to parent bucket aggregation */
107
- subAgg: (fn: (agg: AggregationBuilder<T>) => AggregationBuilder<T>) => AggregationBuilder<T>;
108
+ subAgg: (fn: (agg: AggregationBuilder<M>) => AggregationBuilder<M>) => AggregationBuilder<M>;
108
109
  /** Build aggregation DSL */
109
110
  build: () => AggregationState;
110
111
  };