@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 +2 -2
- package/package.json +6 -6
- package/src/config.ts +1 -1
- package/src/index-manager.ts +1 -1
- package/src/internal/query.ts +21 -41
- package/src/internal/schema.ts +1 -1
- package/src/service.ts +46 -43
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#
|
|
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/
|
|
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.
|
|
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.
|
|
31
|
-
"@travetto/config": "^5.0.0-rc.
|
|
32
|
-
"@travetto/model": "^5.0.0-rc.
|
|
33
|
-
"@travetto/model-query": "^5.0.0-rc.
|
|
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.
|
|
36
|
+
"@travetto/command": "^5.0.0-rc.10"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@travetto/command": {
|
package/src/config.ts
CHANGED
package/src/index-manager.ts
CHANGED
|
@@ -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/
|
|
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';
|
package/src/internal/query.ts
CHANGED
|
@@ -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/
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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] =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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>(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/internal/schema.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InlineScript, MappingProperty, MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types';
|
|
2
2
|
|
|
3
|
-
import { Class } from '@travetto/
|
|
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/
|
|
12
|
-
import { SchemaChange,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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] =
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
324
|
-
sk = 'insert';
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
540
|
-
const
|
|
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
|
}
|