@travetto/model-elasticsearch 5.0.0-rc.1 → 5.0.0-rc.11

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,7 +13,7 @@ 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#L43) 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#L42) 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/service/crud.ts#L11)
@@ -45,7 +45,7 @@ where the [ElasticsearchModelConfig](https://github.com/travetto/travetto/tree/m
45
45
 
46
46
  **Code: Structure of ElasticsearchModelConfig**
47
47
  ```typescript
48
- import { TimeSpan } from '@travetto/base';
48
+ import { TimeSpan } from '@travetto/runtime';
49
49
  import { Config } from '@travetto/config';
50
50
  import { Field } from '@travetto/schema';
51
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-elasticsearch",
3
- "version": "5.0.0-rc.1",
3
+ "version": "5.0.0-rc.11",
4
4
  "description": "Elasticsearch backing for the travetto model module, with real-time modeling support for Elasticsearch mappings.",
5
5
  "keywords": [
6
6
  "elasticsearch",
@@ -27,13 +27,13 @@
27
27
  "directory": "module/model-elasticsearch"
28
28
  },
29
29
  "dependencies": {
30
- "@elastic/elasticsearch": "^8.14.0",
31
- "@travetto/config": "^5.0.0-rc.1",
32
- "@travetto/model": "^5.0.0-rc.1",
33
- "@travetto/model-query": "^5.0.0-rc.1"
30
+ "@elastic/elasticsearch": "^8.15.0",
31
+ "@travetto/config": "^5.0.0-rc.11",
32
+ "@travetto/model": "^5.0.0-rc.11",
33
+ "@travetto/model-query": "^5.0.0-rc.11"
34
34
  },
35
35
  "peerDependencies": {
36
- "@travetto/command": "^5.0.0-rc.1"
36
+ "@travetto/command": "^5.0.0-rc.10"
37
37
  },
38
38
  "peerDependenciesMeta": {
39
39
  "@travetto/command": {
package/src/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { TimeSpan } from '@travetto/base';
1
+ import { TimeSpan } from '@travetto/runtime';
2
2
  import { Config } from '@travetto/config';
3
3
  import { Field } from '@travetto/schema';
4
4
 
@@ -1,7 +1,7 @@
1
1
  import { Client } from '@elastic/elasticsearch';
2
2
  import { ReindexRequest } from '@elastic/elasticsearch/lib/api/types';
3
3
 
4
- import { Class } from '@travetto/base';
4
+ import { Class } from '@travetto/runtime';
5
5
  import { ModelRegistry, ModelType } from '@travetto/model';
6
6
  import { ModelStorageSupport } from '@travetto/model/src/service/storage';
7
7
  import { SchemaChange } from '@travetto/schema';
@@ -1,9 +1,7 @@
1
- import { QueryDslQueryContainer, SearchRequest, SearchResponse, Sort, SortOptions } from '@elastic/elasticsearch/lib/api/types';
1
+ import { DeleteByQueryRequest, QueryDslQueryContainer, SearchRequest, SearchResponse, Sort, SortOptions } from '@elastic/elasticsearch/lib/api/types';
2
2
 
3
- import { Class } from '@travetto/base';
3
+ import { castTo, Class, TypedObject } from '@travetto/runtime';
4
4
  import { WhereClause, SelectClause, SortClause, Query } from '@travetto/model-query';
5
- import { QueryLanguageParser } from '@travetto/model-query/src/internal/query/parser';
6
- import { QueryVerifier } from '@travetto/model-query/src/internal/query/verifier';
7
5
  import { ModelQueryUtil } from '@travetto/model-query/src/internal/service/query';
8
6
  import { ModelRegistry } from '@travetto/model/src/registry/model';
9
7
  import { IndexConfig } from '@travetto/model/src/registry/types';
@@ -12,13 +10,6 @@ import { DataUtil, SchemaRegistry } from '@travetto/schema';
12
10
 
13
11
  import { EsSchemaConfig } from './types';
14
12
 
15
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
16
- const has$And = (o: unknown): o is ({ $and: WhereClause<unknown>[] }) => !!o && '$and' in (o as object);
17
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
18
- const has$Or = (o: unknown): o is ({ $or: WhereClause<unknown>[] }) => !!o && '$or' in (o as object);
19
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
20
- const has$Not = (o: unknown): o is ({ $not: WhereClause<unknown> }) => !!o && '$not' in (o as object);
21
-
22
13
  /**
23
14
  * Support tools for dealing with elasticsearch specific requirements
24
15
  */
@@ -29,16 +20,13 @@ export class ElasticsearchQueryUtil {
29
20
  */
30
21
  static extractSimple<T>(o: T, path: string = ''): Record<string, unknown> {
31
22
  const out: Record<string, unknown> = {};
32
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
33
- const sub = o as Record<string, unknown>;
34
- const keys = Object.keys(sub);
23
+ const keys = TypedObject.keys(o);
35
24
  for (const key of keys) {
36
25
  const subPath = `${path}${key}`;
37
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
38
- if (DataUtil.isPlainObject(sub[key]) && !Object.keys(sub[key] as Record<string, unknown>)[0].startsWith('$')) {
39
- Object.assign(out, this.extractSimple(sub[key], `${subPath}.`));
26
+ if (DataUtil.isPlainObject(o[key]) && !Object.keys(o[key])[0].startsWith('$')) {
27
+ Object.assign(out, this.extractSimple(o[key], `${subPath}.`));
40
28
  } else {
41
- out[subPath] = sub[key];
29
+ out[subPath] = o[key];
42
30
  }
43
31
  }
44
32
  return out;
@@ -53,8 +41,7 @@ export class ElasticsearchQueryUtil {
53
41
  const exclude: string[] = [];
54
42
  for (const k of Object.keys(simp)) {
55
43
  const nk = k === 'id' ? '_id' : k;
56
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
57
- const v = simp[k] as (1 | 0 | boolean);
44
+ const v: 1 | 0 | boolean = castTo(simp[k]);
58
45
  if (v === 0 || v === false) {
59
46
  exclude.push(nk);
60
47
  } else {
@@ -71,8 +58,7 @@ export class ElasticsearchQueryUtil {
71
58
  return sort.map<SortOptions>(x => {
72
59
  const o = this.extractSimple(x);
73
60
  const k = Object.keys(o)[0];
74
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
75
- const v = o[k] as (boolean | -1 | 1);
61
+ const v: boolean | -1 | 1 = castTo(o[k]);
76
62
  return { [k]: { order: v === 1 || v === true ? 'asc' : 'desc' } };
77
63
  });
78
64
  }
@@ -84,8 +70,7 @@ export class ElasticsearchQueryUtil {
84
70
  const items = [];
85
71
  const schema = SchemaRegistry.getViewSchema(cls).schema;
86
72
 
87
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
88
- for (const key of Object.keys(o) as (keyof typeof o)[]) {
73
+ for (const key of TypedObject.keys(o)) {
89
74
  const top = o[key];
90
75
  const declaredSchema = schema[key];
91
76
  const declaredType = declaredSchema.type;
@@ -157,8 +142,7 @@ export class ElasticsearchQueryUtil {
157
142
  break;
158
143
  }
159
144
  case '$regex': {
160
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
161
- const pattern = DataUtil.toRegex(v as string);
145
+ const pattern = DataUtil.toRegex(castTo(v));
162
146
  if (pattern.source.startsWith('\\b') && pattern.source.endsWith('.*')) {
163
147
  const textField = !pattern.flags.includes('i') && config && config.caseSensitive ?
164
148
  `${sPath}.text_cs` :
@@ -183,9 +167,8 @@ export class ElasticsearchQueryUtil {
183
167
  case '$near': {
184
168
  let dist = top.$maxDistance;
185
169
  let unit = top.$unit ?? 'm';
186
- if (unit === 'rad') {
187
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
188
- dist = 6378.1 * (dist as number);
170
+ if (unit === 'rad' && typeof dist === 'number') {
171
+ dist = 6378.1 * dist;
189
172
  unit = 'km';
190
173
  }
191
174
  items.push({
@@ -218,11 +201,11 @@ export class ElasticsearchQueryUtil {
218
201
  * Build query from the where clause
219
202
  */
220
203
  static extractWhereQuery<T>(cls: Class<T>, o: WhereClause<T>, config?: EsSchemaConfig): Record<string, unknown> {
221
- if (has$And(o)) {
204
+ if (ModelQueryUtil.has$And(o)) {
222
205
  return { bool: { must: o.$and.map(x => this.extractWhereQuery<T>(cls, x, config)) } };
223
- } else if (has$Or(o)) {
206
+ } else if (ModelQueryUtil.has$Or(o)) {
224
207
  return { bool: { should: o.$or.map(x => this.extractWhereQuery<T>(cls, x, config)), ['minimum_should_match']: 1 } };
225
- } else if (has$Not(o)) {
208
+ } else if (ModelQueryUtil.has$Not(o)) {
226
209
  return { bool: { ['must_not']: this.extractWhereQuery<T>(cls, o.$not, config) } };
227
210
  } else {
228
211
  return this.extractWhereTermQuery(cls, o, config);
@@ -265,13 +248,11 @@ export class ElasticsearchQueryUtil {
265
248
  /**
266
249
  * Build a base search object from a class and a query
267
250
  */
268
- static getSearchObject<T extends ModelType>(cls: Class<T>, query: Query<T>, config?: EsSchemaConfig, checkExpiry = true): SearchRequest {
269
- query.where = query.where ? (typeof query.where === 'string' ? QueryLanguageParser.parseToQuery(query.where) : query.where) : {};
270
- QueryVerifier.verify(cls, query); // Verify
271
-
272
- const search: SearchRequest = {
273
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
274
- query: this.getSearchQuery(cls, this.extractWhereQuery(cls, query.where as WhereClause<T>, config), checkExpiry)
251
+ static getSearchObject<T extends ModelType>(
252
+ cls: Class<T>, query: Query<T>, config?: EsSchemaConfig, checkExpiry = true
253
+ ): SearchRequest & Omit<DeleteByQueryRequest, 'index' | 'sort'> {
254
+ const search: (SearchRequest & Omit<DeleteByQueryRequest, 'index' | 'sort'>) = {
255
+ query: this.getSearchQuery(cls, this.extractWhereQuery(cls, query.where ?? {}, config), checkExpiry)
275
256
  };
276
257
 
277
258
  const sort = query.sort;
@@ -320,8 +301,7 @@ export class ElasticsearchQueryUtil {
320
301
  for (const r of results.hits.hits) {
321
302
  const obj = r._source!;
322
303
  if (includeId) {
323
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
324
- (obj as unknown as { _id: string })._id = r._id!;
304
+ castTo<{ _id: string }>(obj)._id = r._id!;
325
305
  }
326
306
  out.push(obj);
327
307
  }
@@ -1,6 +1,6 @@
1
1
  import { InlineScript, MappingProperty, MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types';
2
2
 
3
- import { Class } from '@travetto/base';
3
+ import { Class } from '@travetto/runtime';
4
4
  import { ModelRegistry } from '@travetto/model';
5
5
  import { PointImpl } from '@travetto/model-query/src/internal/model/point';
6
6
  import { DataUtil, SchemaRegistry } from '@travetto/schema';
package/src/service.ts CHANGED
@@ -1,19 +1,18 @@
1
1
  import { Client, errors } from '@elastic/elasticsearch';
2
- import {
3
- AggregationsStringTermsAggregate, AggregationsStringTermsBucket, DeleteByQueryRequest, SearchRequest, SearchResponse, UpdateByQueryResponse
4
- } from '@elastic/elasticsearch/lib/api/types';
2
+ import { AggregationsStringTermsAggregate, SearchRequest, SearchResponse } from '@elastic/elasticsearch/lib/api/types';
5
3
 
6
4
  import {
7
5
  ModelCrudSupport, BulkOp, BulkResponse, ModelBulkSupport, ModelExpirySupport,
8
6
  ModelIndexedSupport, ModelType, ModelStorageSupport, NotFoundError, ModelRegistry,
9
7
  OptionalId
10
8
  } from '@travetto/model';
11
- import { ShutdownManager, type Class, AppError } from '@travetto/base';
12
- import { SchemaChange, DeepPartial, BindUtil } from '@travetto/schema';
9
+ import { ShutdownManager, type DeepPartial, type Class, AppError, castTo, asFull, TypedObject, asConstructable } from '@travetto/runtime';
10
+ import { SchemaChange, BindUtil } from '@travetto/schema';
13
11
  import { Injectable } from '@travetto/di';
14
12
  import {
15
13
  ModelQuery, ModelQueryCrudSupport, ModelQueryFacetSupport,
16
- ModelQuerySupport, PageableModelQuery, Query, SelectClause, ValidStringFields
14
+ ModelQuerySupport, PageableModelQuery, Query, ValidStringFields,
15
+ QueryVerifier
17
16
  } from '@travetto/model-query';
18
17
 
19
18
  import { ModelCrudUtil } from '@travetto/model/src/internal/service/crud';
@@ -107,8 +106,7 @@ export class ElasticsearchModelService implements
107
106
  await this.client.cluster.health({});
108
107
  this.manager = new IndexManager(this.config, this.client);
109
108
 
110
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
111
- await ModelStorageUtil.registerModelChangeListener(this.manager, this.constructor as Class);
109
+ await ModelStorageUtil.registerModelChangeListener(this.manager);
112
110
  ShutdownManager.onGracefulShutdown(() => this.client.close(), this);
113
111
  ModelExpiryUtil.registerCull(this);
114
112
  }
@@ -124,8 +122,7 @@ export class ElasticsearchModelService implements
124
122
  async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
125
123
  try {
126
124
  const res = await this.client.get({ ...this.manager.getIdentity(cls), id });
127
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
128
- return this.postLoad(cls, res._source as T);
125
+ return this.postLoad(cls, castTo(res._source));
129
126
  } catch {
130
127
  throw new NotFoundError(cls, id);
131
128
  }
@@ -270,18 +267,15 @@ export class ElasticsearchModelService implements
270
267
 
271
268
  const body = operations.reduce<(T | Partial<Record<'delete' | 'create' | 'index' | 'update', { _index: string, _id?: string }>> | { doc: T })[]>((acc, op) => {
272
269
 
273
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
274
- const esIdent = this.manager.getIdentity((op.upsert ?? op.delete ?? op.insert ?? op.update ?? { constructor: cls }).constructor as Class);
270
+ const esIdent = this.manager.getIdentity(asConstructable<T>((op.upsert ?? op.delete ?? op.insert ?? op.update ?? { constructor: cls })).constructor);
275
271
  const ident: { _index: string, _type?: unknown } = { _index: esIdent.index };
276
272
 
277
273
  if (op.delete) {
278
274
  acc.push({ delete: { ...ident, _id: op.delete.id } });
279
275
  } else if (op.insert) {
280
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
281
- acc.push({ create: { ...ident, _id: op.insert.id } }, op.insert as T);
276
+ acc.push({ create: { ...ident, _id: op.insert.id } }, castTo(op.insert));
282
277
  } else if (op.upsert) {
283
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
284
- acc.push({ index: { ...ident, _id: op.upsert.id } }, op.upsert as T);
278
+ acc.push({ index: { ...ident, _id: op.upsert.id } }, castTo(op.upsert));
285
279
  } else if (op.update) {
286
280
  acc.push({ update: { ...ident, _id: op.update.id } }, { doc: op.update });
287
281
  }
@@ -309,10 +303,9 @@ export class ElasticsearchModelService implements
309
303
 
310
304
  for (let i = 0; i < res.items.length; i++) {
311
305
  const item = res.items[i];
312
- const [k] = Object.keys(item);
313
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
314
- const v = item[k as keyof typeof item]!;
315
- if (k === 'error') {
306
+ const [k] = TypedObject.keys(item);
307
+ const v = item[k]!;
308
+ if (v.error) {
316
309
  out.errors.push({
317
310
  reason: v.error!.reason!,
318
311
  type: v.error!.type
@@ -320,13 +313,13 @@ export class ElasticsearchModelService implements
320
313
  out.counts.error += 1;
321
314
  } else {
322
315
  let sk: Count;
323
- if (k === 'create') {
324
- sk = 'insert';
325
- } else if (k === 'index') {
326
- sk = operations[i].insert ? 'insert' : 'upsert';
327
- } else {
328
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
329
- sk = k as Count;
316
+ switch (k) {
317
+ case 'create': sk = 'insert'; break;
318
+ case 'index': sk = operations[i].insert ? 'insert' : 'upsert'; break;
319
+ case 'delete': case 'update': sk = k; break;
320
+ default: {
321
+ throw new Error(`Unknown response key: ${k}`);
322
+ }
330
323
  }
331
324
 
332
325
  if (v.result === 'created') {
@@ -415,6 +408,8 @@ export class ElasticsearchModelService implements
415
408
 
416
409
  // Query
417
410
  async query<T extends ModelType>(cls: Class<T>, query: PageableModelQuery<T>): Promise<T[]> {
411
+ await QueryVerifier.verify(cls, query);
412
+
418
413
  const req = ElasticsearchQueryUtil.getSearchObject(cls, query, this.config.schemaConfig);
419
414
  const results = await this.execSearch(cls, req);
420
415
  const items = ElasticsearchQueryUtil.cleanIdRemoval(req, results);
@@ -422,10 +417,14 @@ export class ElasticsearchModelService implements
422
417
  }
423
418
 
424
419
  async queryOne<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>, failOnMany?: boolean): Promise<T> {
420
+ await QueryVerifier.verify(cls, query);
421
+
425
422
  return ModelQueryUtil.verifyGetSingleCounts(cls, await this.query<T>(cls, { ...query, limit: failOnMany ? 2 : 1 }));
426
423
  }
427
424
 
428
425
  async queryCount<T extends ModelType>(cls: Class<T>, query: Query<T>): Promise<number> {
426
+ await QueryVerifier.verify(cls, query);
427
+
429
428
  const req = ElasticsearchQueryUtil.getSearchObject(cls, { ...query, limit: 0 }, this.config.schemaConfig);
430
429
  const res: number | { value: number } = (await this.execSearch(cls, req)).hits.total || { value: 0 };
431
430
  return typeof res !== 'number' ? res.value : res;
@@ -434,11 +433,14 @@ export class ElasticsearchModelService implements
434
433
  // Query Crud
435
434
  async updateOneWithQuery<T extends ModelType>(cls: Class<T>, data: T, query: ModelQuery<T>): Promise<T> {
436
435
  ModelCrudUtil.ensureNotSubType(cls);
436
+ await QueryVerifier.verify(cls, query);
437
437
 
438
438
  const item = await ModelCrudUtil.preStore(cls, data, this);
439
439
  const id = item.id;
440
440
 
441
- query = ModelQueryUtil.getQueryWithId(cls, data, query);
441
+ const where = ModelQueryUtil.getWhereClause(cls, query.where);
442
+ where.id = item.id;
443
+ query.where = where;
442
444
 
443
445
  if (ModelRegistry.get(cls).expiresAt) {
444
446
  await this.get(cls, id);
@@ -446,8 +448,7 @@ export class ElasticsearchModelService implements
446
448
 
447
449
  const search = ElasticsearchQueryUtil.getSearchObject(cls, query, this.config.schemaConfig);
448
450
 
449
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
450
- const copy = BindUtil.bindSchemaToObject(cls, {} as T, item);
451
+ const copy = BindUtil.bindSchemaToObject(cls, asFull<T>({}), item);
451
452
 
452
453
  try {
453
454
  const res = await this.client.updateByQuery({
@@ -455,16 +456,14 @@ export class ElasticsearchModelService implements
455
456
  refresh: true,
456
457
  query: search.query,
457
458
  max_docs: 1,
458
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
459
- script: ElasticsearchSchemaUtil.generateReplaceScript(copy as {})
459
+ script: ElasticsearchSchemaUtil.generateReplaceScript(castTo(copy))
460
460
  });
461
461
 
462
462
  if (res.version_conflicts || res.updated === undefined || res.updated === 0) {
463
463
  throw new NotFoundError(cls, id);
464
464
  }
465
465
  } catch (err) {
466
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
467
- if (err instanceof errors.ResponseError && (err.body as UpdateByQueryResponse).version_conflicts) {
466
+ if (err instanceof errors.ResponseError && 'version_conflicts' in err.body) {
468
467
  throw new NotFoundError(cls, id);
469
468
  } else {
470
469
  throw err;
@@ -475,16 +474,19 @@ export class ElasticsearchModelService implements
475
474
  }
476
475
 
477
476
  async deleteByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T> = {}): Promise<number> {
477
+ await QueryVerifier.verify(cls, query);
478
+
479
+ const { sort: _, ...q } = ElasticsearchQueryUtil.getSearchObject(cls, query, this.config.schemaConfig, false);
478
480
  const res = await this.client.deleteByQuery({
479
481
  ...this.manager.getIdentity(cls),
480
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
481
- ...ElasticsearchQueryUtil.getSearchObject(cls, query, this.config.schemaConfig, false) as DeleteByQueryRequest,
482
+ ...q,
482
483
  refresh: true,
483
484
  });
484
485
  return res.deleted ?? 0;
485
486
  }
486
487
 
487
488
  async updateByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>, data: Partial<T>): Promise<number> {
489
+ await QueryVerifier.verify(cls, query);
488
490
 
489
491
  const script = ElasticsearchSchemaUtil.generateUpdateScript(data);
490
492
 
@@ -501,6 +503,8 @@ export class ElasticsearchModelService implements
501
503
 
502
504
  // Query Facet
503
505
  async suggest<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {
506
+ await QueryVerifier.verify(cls, query);
507
+
504
508
  const q = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, query);
505
509
  const search = ElasticsearchQueryUtil.getSearchObject(cls, q);
506
510
  const res = await this.execSearch(cls, search);
@@ -510,11 +514,10 @@ export class ElasticsearchModelService implements
510
514
  }
511
515
 
512
516
  async suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
513
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
514
- const select: SelectClause<T> = { [field]: 1 } as SelectClause<T>;
517
+ await QueryVerifier.verify(cls, query);
515
518
 
516
519
  const q = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, {
517
- select,
520
+ select: castTo({ [field]: 1 }),
518
521
  ...query
519
522
  });
520
523
  const search = ElasticsearchQueryUtil.getSearchObject(cls, q);
@@ -525,6 +528,8 @@ export class ElasticsearchModelService implements
525
528
 
526
529
  // Facet
527
530
  async facet<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<{ key: string, count: number }[]> {
531
+ await QueryVerifier.verify(cls, query);
532
+
528
533
  const q = ElasticsearchQueryUtil.getSearchObject(cls, query ?? {}, this.config.schemaConfig);
529
534
 
530
535
  const search = {
@@ -536,10 +541,8 @@ export class ElasticsearchModelService implements
536
541
  };
537
542
 
538
543
  const res = await this.execSearch(cls, search);
539
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
540
- const { buckets } = res.aggregations![field] as AggregationsStringTermsAggregate;
541
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
542
- const out = (buckets as AggregationsStringTermsBucket[]).map(b => ({ key: b.key, count: b.doc_count }));
544
+ const { buckets } = castTo<AggregationsStringTermsAggregate>('buckets' in res.aggregations![field] ? res.aggregations![field] : { buckets: [] });
545
+ const out = Array.isArray(buckets) ? buckets.map(b => ({ key: b.key, count: b.doc_count })) : [];
543
546
  return out;
544
547
  }
545
548
  }