@travetto/model-elasticsearch 8.0.0-alpha.16 → 8.0.0-alpha.17

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 CHANGED
@@ -13,13 +13,13 @@ npm install @travetto/model-elasticsearch
13
13
  yarn add @travetto/model-elasticsearch
14
14
  ```
15
15
 
16
- This module provides an [elasticsearch](https://elastic.co)-based implementation of the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations."). This source allows the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module to read, write and query against [elasticsearch](https://elastic.co). In development mode, [ElasticsearchModelService](https://github.com/travetto/travetto/tree/main/module/model-elasticsearch/src/service.ts#L40) will also modify the [elasticsearch](https://elastic.co) schema in real time to minimize impact to development.
16
+ This module provides an [elasticsearch](https://elastic.co)-based implementation of the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations."). This source allows the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") module to read, write and query against [elasticsearch](https://elastic.co). In development mode, [ElasticsearchModelService](https://github.com/travetto/travetto/tree/main/module/model-elasticsearch/src/service.ts#L41) will also modify the [elasticsearch](https://elastic.co) schema in real time to minimize impact to development.
17
17
 
18
18
  Supported features:
19
19
  * [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L11)
20
20
  * [Bulk](https://github.com/travetto/travetto/tree/main/module/model/src/types/bulk.ts#L64)
21
21
  * [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10)
22
- * [Indexed](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#L15)
22
+ * [Indexed](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#L16)
23
23
  * [Query Crud](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/crud.ts#L11)
24
24
  * [Facet](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/facet.ts#L14)
25
25
  * [Query](https://github.com/travetto/travetto/tree/main/module/model-query/src/types/query.ts#L10)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-elasticsearch",
3
- "version": "8.0.0-alpha.16",
3
+ "version": "8.0.0-alpha.17",
4
4
  "type": "module",
5
5
  "description": "Elasticsearch backing for the travetto model module, with real-time modeling support for Elasticsearch mappings.",
6
6
  "keywords": [
@@ -29,13 +29,13 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@elastic/elasticsearch": "^9.3.4",
32
- "@travetto/config": "^8.0.0-alpha.14",
33
- "@travetto/model": "^8.0.0-alpha.14",
34
- "@travetto/model-indexed": "^8.0.0-alpha.16",
35
- "@travetto/model-query": "^8.0.0-alpha.15"
32
+ "@travetto/config": "^8.0.0-alpha.15",
33
+ "@travetto/model": "^8.0.0-alpha.15",
34
+ "@travetto/model-indexed": "^8.0.0-alpha.17",
35
+ "@travetto/model-query": "^8.0.0-alpha.16"
36
36
  },
37
37
  "peerDependencies": {
38
- "@travetto/cli": "^8.0.0-alpha.19"
38
+ "@travetto/cli": "^8.0.0-alpha.20"
39
39
  },
40
40
  "peerDependenciesMeta": {
41
41
  "@travetto/cli": {
@@ -144,8 +144,8 @@ export class IndexManager implements ModelStorageSupport {
144
144
 
145
145
  async deleteStorage(): Promise<void> {
146
146
  console.debug('Deleting storage', { idx: this.getNamespacedIndex('*') });
147
- await this.#client.indices.delete({
148
- index: this.getNamespacedIndex('*')
149
- });
147
+ // await this.#client.indices.delete({
148
+ // index: this.getNamespacedIndex('*')
149
+ // });
150
150
  }
151
151
  }
@@ -141,7 +141,13 @@ export class ElasticsearchQueryUtil {
141
141
  }
142
142
  case '$regex': {
143
143
  const pattern = DataUtil.toRegex(castTo(value));
144
- if (pattern.source.startsWith('\\b') && pattern.source.endsWith('.*')) {
144
+ if (pattern.source.startsWith('^')) { // We have a prefix query
145
+ if (/^\^[A-Za-z0-9_\-]+/.test(pattern.source)) {
146
+ items.push({ prefix: { [subPath]: pattern.source.substring(1) } });
147
+ } else {
148
+ items.push({ regexp: { [subPath]: pattern.source.substring(1) } });
149
+ }
150
+ } else if (pattern.source.startsWith('\\b') && pattern.source.endsWith('.*') && declaredSchema.specifiers?.includes('text')) {
145
151
  const textField = !pattern.flags.includes('i') && config && config.caseSensitive ?
146
152
  `${subPath}.text_cs` :
147
153
  `${subPath}.text`;
@@ -198,7 +204,7 @@ export class ElasticsearchQueryUtil {
198
204
  if (ModelQueryUtil.has$And(clause)) {
199
205
  return { bool: { must: clause.$and.map(item => this.extractWhereQuery<T>(cls, item, config)) } };
200
206
  } else if (ModelQueryUtil.has$Or(clause)) {
201
- return { bool: { should: clause.$or.map(item => this.extractWhereQuery<T>(cls, item, config)), ['minimum_should_match']: 1 } };
207
+ return { bool: { should: clause.$or.map(item => this.extractWhereQuery<T>(cls, item, config)), minimum_should_match: 1 } };
202
208
  } else if (ModelQueryUtil.has$Not(clause)) {
203
209
  return { bool: { ['must_not']: this.extractWhereQuery<T>(cls, clause.$not, config) } };
204
210
  } else {
package/src/service.ts CHANGED
@@ -14,11 +14,12 @@ import {
14
14
  type ModelQuery, type ModelQueryCrudSupport, type ModelQueryFacetSupport, type ModelQuerySupport, type PageableModelQuery,
15
15
  type Query, type ValidStringFields, QueryVerifier, type ModelQuerySuggestSupport, ModelQueryUtil, ModelQuerySuggestUtil,
16
16
  ModelQueryCrudUtil, type ModelQueryFacet,
17
+ type WhereClause,
17
18
  } from '@travetto/model-query';
18
19
  import {
19
20
  type ModelIndexedSupport, type KeyedIndexSelection, type KeyedIndexBody, type ModelPageOptions, ModelIndexedUtil,
20
21
  type SingleItemIndex, type SortedIndexSelection, type ModelPageResult, type SortedIndex, type FullKeyedIndexBody,
21
- type FullKeyedIndexWithPartialBody, ModelIndexedComputedIndex
22
+ type FullKeyedIndexWithPartialBody, ModelIndexedComputedIndex, type ModelIndexedSearchOptions, type SortedIndexSelectionType
22
23
  } from '@travetto/model-indexed';
23
24
 
24
25
  import type { ElasticsearchModelConfig } from './config.ts';
@@ -119,27 +120,32 @@ export class ElasticsearchModelService implements
119
120
  }
120
121
  }
121
122
 
122
- async * #scrollIndex<
123
- T extends ModelType,
124
- K extends KeyedIndexSelection<T>,
125
- S extends SortedIndexSelection<T>
126
- >(
123
+ async * #scrollIndex<T extends ModelType>(
127
124
  cls: Class<T>,
128
- idx: SortedIndex<T, K, S>,
129
- body: KeyedIndexBody<T, K>,
130
- options?: ModelPageOptions<estypes.SortResults> & ModelListOptions
125
+ idx: SortedIndex<T>,
126
+ body: KeyedIndexBody<T>,
127
+ options?: ModelPageOptions<estypes.SortResults> & ModelListOptions,
128
+ transformWhere?: (where: WhereClause<T>) => WhereClause<T>
131
129
  ): AsyncIterable<{
132
130
  items: T[];
133
131
  nextOffset?: estypes.SortResults | undefined;
134
132
  }> {
135
133
  const computed = ModelIndexedComputedIndex.get(idx, body).validate();
136
- yield* this.#scrollCollection(cls, (offset) => ({
137
- query: ElasticsearchQueryUtil.getSearchQuery(cls,
138
- ElasticsearchQueryUtil.extractWhereTermQuery(cls, computed.project())
139
- ),
140
- search_after: offset,
141
- sort: ElasticsearchQueryUtil.getSort(idx)
142
- }), options);
134
+ let whereClause: WhereClause<T> = castTo(computed.project());
135
+ if (transformWhere) {
136
+ whereClause = transformWhere(whereClause);
137
+ }
138
+
139
+ yield* this.#scrollCollection(cls, (offset) => {
140
+ const result = {
141
+ query: ElasticsearchQueryUtil.getSearchQuery(cls,
142
+ ElasticsearchQueryUtil.extractWhereQuery(cls, whereClause)
143
+ ),
144
+ ...(offset ? { search_after: offset } : {}),
145
+ sort: ElasticsearchQueryUtil.getSort(idx)
146
+ };
147
+ return result;
148
+ }, options);
143
149
  }
144
150
 
145
151
  @PostConstruct()
@@ -415,19 +421,19 @@ export class ElasticsearchModelService implements
415
421
  S extends SortedIndexSelection<T>
416
422
  >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<T> {
417
423
  const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
424
+ const projected = computed.project({
425
+ sort: true,
426
+ includeId: true,
427
+ emptyValue: { $exists: true }
428
+ });
418
429
 
419
430
  const result = await this.execSearch<T>(cls, {
420
431
  query: ElasticsearchQueryUtil.getSearchQuery(cls,
421
- ElasticsearchQueryUtil.extractWhereTermQuery(cls, computed.project({
422
- sort: true,
423
- includeId: true,
424
- emptyValue: { $exists: true }
425
- }))
426
-
432
+ ElasticsearchQueryUtil.extractWhereTermQuery(cls, projected)
427
433
  )
428
434
  });
429
435
  if (!result.hits.hits.length) {
430
- throw new NotFoundError(`${cls.name}: ${idx}`, computed.getKey({ sort: true }));
436
+ throw new NotFoundError(`${cls.name}: ${idx.name}`, computed.getKey({ sort: true }));
431
437
  }
432
438
  return this.#postLoad(cls, result.hits.hits[0]);
433
439
 
@@ -515,6 +521,25 @@ export class ElasticsearchModelService implements
515
521
  }
516
522
  }
517
523
 
524
+ async suggestByIndex<
525
+ T extends ModelType,
526
+ S extends SortedIndexSelection<T>,
527
+ K extends KeyedIndexSelection<T>,
528
+ B extends SortedIndexSelectionType<T, S> & string
529
+ >(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>, prefix: B, options?: ModelIndexedSearchOptions): Promise<T[]> {
530
+ const items: T[] = [];
531
+ for await (const { items: fetched } of this.#scrollIndex(cls, idx, body, { limit: 10, ...options },
532
+ where => castTo({
533
+ $and: [where, {
534
+ [idx.sortTemplate[0].path.join('.')]: { $regex: ModelIndexedUtil.getSuggestRegex(prefix) }
535
+ }]
536
+ })
537
+ )) {
538
+ items.push(...fetched);
539
+ }
540
+ return items;
541
+ }
542
+
518
543
  // Query
519
544
  async query<T extends ModelType>(cls: Class<T>, query: PageableModelQuery<T>): Promise<T[]> {
520
545
  await QueryVerifier.verify(cls, query);
@@ -618,7 +643,7 @@ export class ElasticsearchModelService implements
618
643
  }
619
644
 
620
645
  // Query Facet
621
- async suggest<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {
646
+ async suggestByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {
622
647
  await QueryVerifier.verify(cls, query);
623
648
 
624
649
  const resolvedQuery = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, query);
@@ -628,7 +653,7 @@ export class ElasticsearchModelService implements
628
653
  return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, all, (_, value) => value, query && query.limit);
629
654
  }
630
655
 
631
- async suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
656
+ async suggestValuesByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
632
657
  await QueryVerifier.verify(cls, query);
633
658
 
634
659
  const resolvedQuery = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, {
@@ -642,7 +667,7 @@ export class ElasticsearchModelService implements
642
667
  }
643
668
 
644
669
  // Facet
645
- async facet<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<ModelQueryFacet[]> {
670
+ async facetByQuery<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<ModelQueryFacet[]> {
646
671
  await QueryVerifier.verify(cls, query);
647
672
 
648
673
  const resolvedSearch = ElasticsearchQueryUtil.getSearchObject(cls, query ?? {}, this.config.schemaConfig);