@travetto/model-elasticsearch 5.0.17 → 5.0.19
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/LICENSE +1 -1
- package/README.md +6 -1
- package/package.json +6 -6
- package/src/config.ts +5 -0
- package/src/index-manager.ts +2 -3
- package/src/internal/query.ts +15 -52
- package/src/internal/schema.ts +9 -9
- package/src/service.ts +67 -46
- package/support/service.elasticsearch.ts +1 -1
package/LICENSE
CHANGED
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#L37) 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)
|
|
@@ -76,6 +76,11 @@ export class ElasticsearchModelConfig {
|
|
|
76
76
|
* Auto-create, disabled in prod by default
|
|
77
77
|
*/
|
|
78
78
|
autoCreate?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Should we store the id as a string in the document
|
|
81
|
+
*/
|
|
82
|
+
storeId?: boolean;
|
|
83
|
+
|
|
79
84
|
/**
|
|
80
85
|
* Base schema config for elasticsearch
|
|
81
86
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-elasticsearch",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.19",
|
|
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,11 +27,11 @@
|
|
|
27
27
|
"directory": "module/model-elasticsearch"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@elastic/elasticsearch": "^8.
|
|
31
|
-
"@travetto/cli": "^5.0.
|
|
32
|
-
"@travetto/config": "^5.0.
|
|
33
|
-
"@travetto/model": "^5.0.
|
|
34
|
-
"@travetto/model-query": "^5.0.
|
|
30
|
+
"@elastic/elasticsearch": "^8.17.0",
|
|
31
|
+
"@travetto/cli": "^5.0.18",
|
|
32
|
+
"@travetto/config": "^5.0.15",
|
|
33
|
+
"@travetto/model": "^5.0.16",
|
|
34
|
+
"@travetto/model-query": "^5.0.16"
|
|
35
35
|
},
|
|
36
36
|
"travetto": {
|
|
37
37
|
"displayName": "Elasticsearch Model Source"
|
package/src/config.ts
CHANGED
|
@@ -29,6 +29,11 @@ export class ElasticsearchModelConfig {
|
|
|
29
29
|
* Auto-create, disabled in prod by default
|
|
30
30
|
*/
|
|
31
31
|
autoCreate?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Should we store the id as a string in the document
|
|
34
|
+
*/
|
|
35
|
+
storeId?: boolean;
|
|
36
|
+
|
|
32
37
|
/**
|
|
33
38
|
* Base schema config for elasticsearch
|
|
34
39
|
*/
|
package/src/index-manager.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Client } from '@elastic/elasticsearch';
|
|
2
|
-
import { ReindexRequest } from '@elastic/elasticsearch/lib/api/types';
|
|
1
|
+
import { Client, estypes } from '@elastic/elasticsearch';
|
|
3
2
|
|
|
4
3
|
import { Class } from '@travetto/runtime';
|
|
5
4
|
import { ModelRegistry, ModelType } from '@travetto/model';
|
|
@@ -164,7 +163,7 @@ export class IndexManager implements ModelStorageSupport {
|
|
|
164
163
|
|
|
165
164
|
const allChange = removes.concat(fieldChanges);
|
|
166
165
|
|
|
167
|
-
const reindexBody: ReindexRequest = {
|
|
166
|
+
const reindexBody: estypes.ReindexRequest = {
|
|
168
167
|
source: { index: curr },
|
|
169
168
|
dest: { index: next },
|
|
170
169
|
script: {
|
package/src/internal/query.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { estypes } from '@elastic/elasticsearch';
|
|
2
2
|
|
|
3
3
|
import { castTo, Class, TypedObject } from '@travetto/runtime';
|
|
4
4
|
import { WhereClause, SelectClause, SortClause, Query } from '@travetto/model-query';
|
|
@@ -54,8 +54,8 @@ export class ElasticsearchQueryUtil {
|
|
|
54
54
|
/**
|
|
55
55
|
* Build sort mechanism
|
|
56
56
|
*/
|
|
57
|
-
static getSort<T extends ModelType>(sort: SortClause<T>[] | IndexConfig<T>['fields']): Sort {
|
|
58
|
-
return sort.map<SortOptions>(x => {
|
|
57
|
+
static getSort<T extends ModelType>(sort: SortClause<T>[] | IndexConfig<T>['fields']): estypes.Sort {
|
|
58
|
+
return sort.map<estypes.SortOptions>(x => {
|
|
59
59
|
const o = this.extractSimple(x);
|
|
60
60
|
const k = Object.keys(o)[0];
|
|
61
61
|
const v: boolean | -1 | 1 = castTo(o[k]);
|
|
@@ -78,6 +78,10 @@ export class ElasticsearchQueryUtil {
|
|
|
78
78
|
((key === 'id' && !path) ? '_id' : `${path}${key}`) :
|
|
79
79
|
`${path}${key}`;
|
|
80
80
|
|
|
81
|
+
const sPathQuery = (val: unknown): {} => (key === 'id' && !path) ?
|
|
82
|
+
{ ids: { values: Array.isArray(val) ? val : [val] } } :
|
|
83
|
+
{ [Array.isArray(val) ? 'terms' : 'term']: { [sPath]: val } };
|
|
84
|
+
|
|
81
85
|
if (DataUtil.isPlainObject(top)) {
|
|
82
86
|
const subKey = Object.keys(top)[0];
|
|
83
87
|
if (!subKey.startsWith('$')) {
|
|
@@ -100,29 +104,19 @@ export class ElasticsearchQueryUtil {
|
|
|
100
104
|
break;
|
|
101
105
|
}
|
|
102
106
|
case '$in': {
|
|
103
|
-
items.push(
|
|
107
|
+
items.push(sPathQuery(Array.isArray(v) ? v : [v]));
|
|
104
108
|
break;
|
|
105
109
|
}
|
|
106
110
|
case '$nin': {
|
|
107
|
-
items.push({
|
|
108
|
-
bool: {
|
|
109
|
-
['must_not']: [{
|
|
110
|
-
terms: {
|
|
111
|
-
[sPath]: Array.isArray(v) ? v : [v]
|
|
112
|
-
}
|
|
113
|
-
}]
|
|
114
|
-
}
|
|
115
|
-
});
|
|
111
|
+
items.push({ bool: { ['must_not']: [sPathQuery(Array.isArray(v) ? v : [v])] } });
|
|
116
112
|
break;
|
|
117
113
|
}
|
|
118
114
|
case '$eq': {
|
|
119
|
-
items.push(
|
|
115
|
+
items.push(sPathQuery(v));
|
|
120
116
|
break;
|
|
121
117
|
}
|
|
122
118
|
case '$ne': {
|
|
123
|
-
items.push({
|
|
124
|
-
bool: { ['must_not']: [{ term: { [sPath]: v } }] }
|
|
125
|
-
});
|
|
119
|
+
items.push({ bool: { ['must_not']: [sPathQuery(v)] } });
|
|
126
120
|
break;
|
|
127
121
|
}
|
|
128
122
|
case '$exists': {
|
|
@@ -183,11 +177,7 @@ export class ElasticsearchQueryUtil {
|
|
|
183
177
|
}
|
|
184
178
|
// Handle operations
|
|
185
179
|
} else {
|
|
186
|
-
items.push(
|
|
187
|
-
[Array.isArray(top) ? 'terms' : 'term']: {
|
|
188
|
-
[(key === 'id' && !path) ? '_id' : `${path}${key}`]: top
|
|
189
|
-
}
|
|
190
|
-
});
|
|
180
|
+
items.push(sPathQuery(top));
|
|
191
181
|
}
|
|
192
182
|
}
|
|
193
183
|
if (items.length === 1) {
|
|
@@ -217,7 +207,7 @@ export class ElasticsearchQueryUtil {
|
|
|
217
207
|
* @param cls
|
|
218
208
|
* @param search
|
|
219
209
|
*/
|
|
220
|
-
static getSearchQuery<T extends ModelType>(cls: Class<T>, search: Record<string, unknown>, checkExpiry = true): QueryDslQueryContainer {
|
|
210
|
+
static getSearchQuery<T extends ModelType>(cls: Class<T>, search: Record<string, unknown>, checkExpiry = true): estypes.QueryDslQueryContainer {
|
|
221
211
|
const clauses = [];
|
|
222
212
|
if (search && Object.keys(search).length) {
|
|
223
213
|
clauses.push(search);
|
|
@@ -250,8 +240,8 @@ export class ElasticsearchQueryUtil {
|
|
|
250
240
|
*/
|
|
251
241
|
static getSearchObject<T extends ModelType>(
|
|
252
242
|
cls: Class<T>, query: Query<T>, config?: EsSchemaConfig, checkExpiry = true
|
|
253
|
-
): SearchRequest & Omit<DeleteByQueryRequest, 'index' | 'sort'> {
|
|
254
|
-
const search: (SearchRequest & Omit<DeleteByQueryRequest, 'index' | 'sort'>) = {
|
|
243
|
+
): estypes.SearchRequest & Omit<estypes.DeleteByQueryRequest, 'index' | 'sort'> {
|
|
244
|
+
const search: (estypes.SearchRequest & Omit<estypes.DeleteByQueryRequest, 'index' | 'sort'>) = {
|
|
255
245
|
query: this.getSearchQuery(cls, this.extractWhereQuery(cls, query.where ?? {}, config), checkExpiry)
|
|
256
246
|
};
|
|
257
247
|
|
|
@@ -281,31 +271,4 @@ export class ElasticsearchQueryUtil {
|
|
|
281
271
|
|
|
282
272
|
return search;
|
|
283
273
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Safely load the data, excluding ids if needed
|
|
288
|
-
*/
|
|
289
|
-
static cleanIdRemoval<T>(req: SearchRequest, results: SearchResponse<T>): T[] {
|
|
290
|
-
const out: T[] = [];
|
|
291
|
-
|
|
292
|
-
const toArr = <V>(x: V | V[] | undefined): V[] => (x ? (Array.isArray(x) ? x : [x]) : []);
|
|
293
|
-
|
|
294
|
-
// determine if id
|
|
295
|
-
const select = [
|
|
296
|
-
toArr(req._source_includes),
|
|
297
|
-
toArr(req._source_excludes)
|
|
298
|
-
];
|
|
299
|
-
const includeId = select[0].includes('_id') || (select[0].length === 0 && !select[1].includes('_id'));
|
|
300
|
-
|
|
301
|
-
for (const r of results.hits.hits) {
|
|
302
|
-
const obj = r._source!;
|
|
303
|
-
if (includeId) {
|
|
304
|
-
castTo<{ _id: string }>(obj)._id = r._id!;
|
|
305
|
-
}
|
|
306
|
-
out.push(obj);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return out;
|
|
310
|
-
}
|
|
311
274
|
}
|
package/src/internal/schema.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { estypes } from '@elastic/elasticsearch';
|
|
2
2
|
|
|
3
3
|
import { Class } from '@travetto/runtime';
|
|
4
4
|
import { ModelRegistry } from '@travetto/model';
|
|
@@ -15,8 +15,8 @@ export class ElasticsearchSchemaUtil {
|
|
|
15
15
|
/**
|
|
16
16
|
* Build the update script for a given object
|
|
17
17
|
*/
|
|
18
|
-
static generateUpdateScript(o: Record<string, unknown>):
|
|
19
|
-
const out:
|
|
18
|
+
static generateUpdateScript(o: Record<string, unknown>): estypes.Script {
|
|
19
|
+
const out: estypes.Script = {
|
|
20
20
|
lang: 'painless',
|
|
21
21
|
source: `
|
|
22
22
|
for (entry in params.body.entrySet()) {
|
|
@@ -42,7 +42,7 @@ export class ElasticsearchSchemaUtil {
|
|
|
42
42
|
* @param o
|
|
43
43
|
* @returns
|
|
44
44
|
*/
|
|
45
|
-
static generateReplaceScript(o: Record<string, unknown>):
|
|
45
|
+
static generateReplaceScript(o: Record<string, unknown>): estypes.Script {
|
|
46
46
|
return {
|
|
47
47
|
lang: 'painless',
|
|
48
48
|
source: 'ctx._source.clear(); ctx._source.putAll(params.body)',
|
|
@@ -53,7 +53,7 @@ export class ElasticsearchSchemaUtil {
|
|
|
53
53
|
/**
|
|
54
54
|
* Build one or more mappings depending on the polymorphic state
|
|
55
55
|
*/
|
|
56
|
-
static generateSchemaMapping(cls: Class, config?: EsSchemaConfig): MappingTypeMapping {
|
|
56
|
+
static generateSchemaMapping(cls: Class, config?: EsSchemaConfig): estypes.MappingTypeMapping {
|
|
57
57
|
return ModelRegistry.get(cls).baseType ?
|
|
58
58
|
this.generateAllMapping(cls, config) :
|
|
59
59
|
this.generateSingleMapping(cls, config);
|
|
@@ -62,9 +62,9 @@ export class ElasticsearchSchemaUtil {
|
|
|
62
62
|
/**
|
|
63
63
|
* Generate all mappings
|
|
64
64
|
*/
|
|
65
|
-
static generateAllMapping(cls: Class, config?: EsSchemaConfig): MappingTypeMapping {
|
|
65
|
+
static generateAllMapping(cls: Class, config?: EsSchemaConfig): estypes.MappingTypeMapping {
|
|
66
66
|
const allTypes = ModelRegistry.getClassesByBaseType(cls);
|
|
67
|
-
return allTypes.reduce<MappingTypeMapping>((acc, schemaCls) => {
|
|
67
|
+
return allTypes.reduce<estypes.MappingTypeMapping>((acc, schemaCls) => {
|
|
68
68
|
DataUtil.deepAssign(acc, this.generateSingleMapping(schemaCls, config));
|
|
69
69
|
return acc;
|
|
70
70
|
}, { properties: {}, dynamic: false });
|
|
@@ -73,10 +73,10 @@ export class ElasticsearchSchemaUtil {
|
|
|
73
73
|
/**
|
|
74
74
|
* Build a mapping for a given class
|
|
75
75
|
*/
|
|
76
|
-
static generateSingleMapping<T>(cls: Class<T>, config?: EsSchemaConfig): MappingTypeMapping {
|
|
76
|
+
static generateSingleMapping<T>(cls: Class<T>, config?: EsSchemaConfig): estypes.MappingTypeMapping {
|
|
77
77
|
const schema = SchemaRegistry.getViewSchema(cls);
|
|
78
78
|
|
|
79
|
-
const props: Record<string, MappingProperty> = {};
|
|
79
|
+
const props: Record<string, estypes.MappingProperty> = {};
|
|
80
80
|
|
|
81
81
|
for (const field of schema.fields) {
|
|
82
82
|
const conf = schema.schema[field];
|
package/src/service.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { Client, errors } from '@elastic/elasticsearch';
|
|
2
|
-
import { AggregationsStringTermsAggregate, SearchRequest, SearchResponse } from '@elastic/elasticsearch/lib/api/types';
|
|
1
|
+
import { Client, errors, estypes } from '@elastic/elasticsearch';
|
|
3
2
|
|
|
4
3
|
import {
|
|
5
4
|
ModelCrudSupport, BulkOp, BulkResponse, ModelBulkSupport, ModelExpirySupport,
|
|
6
5
|
ModelIndexedSupport, ModelType, ModelStorageSupport, NotFoundError, ModelRegistry,
|
|
7
6
|
OptionalId
|
|
8
7
|
} from '@travetto/model';
|
|
9
|
-
import { ShutdownManager, type DeepPartial, type Class,
|
|
8
|
+
import { ShutdownManager, type DeepPartial, type Class, castTo, asFull, TypedObject, asConstructable } from '@travetto/runtime';
|
|
10
9
|
import { SchemaChange, BindUtil } from '@travetto/schema';
|
|
11
10
|
import { Injectable } from '@travetto/di';
|
|
12
11
|
import {
|
|
@@ -31,10 +30,6 @@ import { ElasticsearchQueryUtil } from './internal/query';
|
|
|
31
30
|
import { ElasticsearchSchemaUtil } from './internal/schema';
|
|
32
31
|
import { IndexManager } from './index-manager';
|
|
33
32
|
|
|
34
|
-
type WithId<T> = T & { _id?: string };
|
|
35
|
-
|
|
36
|
-
const isWithId = <T extends ModelType>(o: T): o is WithId<T> => !o && '_id' in o;
|
|
37
|
-
|
|
38
33
|
/**
|
|
39
34
|
* Elasticsearch model source.
|
|
40
35
|
*/
|
|
@@ -55,7 +50,7 @@ export class ElasticsearchModelService implements
|
|
|
55
50
|
/**
|
|
56
51
|
* Directly run the search
|
|
57
52
|
*/
|
|
58
|
-
async execSearch<T extends ModelType>(cls: Class<T>, search: SearchRequest): Promise<SearchResponse<T>> {
|
|
53
|
+
async execSearch<T extends ModelType>(cls: Class<T>, search: estypes.SearchRequest): Promise<estypes.SearchResponse<T>> {
|
|
59
54
|
let query = search.query;
|
|
60
55
|
if (query && Object.keys(query).length === 0) {
|
|
61
56
|
query = undefined;
|
|
@@ -75,13 +70,34 @@ export class ElasticsearchModelService implements
|
|
|
75
70
|
}
|
|
76
71
|
}
|
|
77
72
|
|
|
73
|
+
preUpdate(o: { id: string }): string;
|
|
74
|
+
preUpdate(o: {}): undefined;
|
|
75
|
+
preUpdate(o: { id?: string }): string | undefined {
|
|
76
|
+
if ('id' in o && typeof o.id === 'string') {
|
|
77
|
+
const id = o.id;
|
|
78
|
+
if (!this.config.storeId) {
|
|
79
|
+
delete o.id;
|
|
80
|
+
}
|
|
81
|
+
return id;
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
postUpdate<T extends ModelType>(o: T, id?: string): T {
|
|
87
|
+
if (!this.config.storeId) {
|
|
88
|
+
o.id = id!;
|
|
89
|
+
}
|
|
90
|
+
return o;
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
/**
|
|
79
94
|
* Convert _id to id
|
|
80
95
|
*/
|
|
81
|
-
async postLoad<T extends ModelType>(cls: Class<T>,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
async postLoad<T extends ModelType>(cls: Class<T>, inp: estypes.SearchHit<T> | estypes.GetGetResult<T>): Promise<T> {
|
|
97
|
+
let item = {
|
|
98
|
+
...(inp._id ? { id: inp._id } : {}),
|
|
99
|
+
...inp._source!,
|
|
100
|
+
};
|
|
85
101
|
|
|
86
102
|
item = await ModelCrudUtil.load(cls, item);
|
|
87
103
|
|
|
@@ -121,8 +137,8 @@ export class ElasticsearchModelService implements
|
|
|
121
137
|
|
|
122
138
|
async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
|
|
123
139
|
try {
|
|
124
|
-
const res = await this.client.get({ ...this.manager.getIdentity(cls), id });
|
|
125
|
-
return this.postLoad(cls,
|
|
140
|
+
const res = await this.client.get<T>({ ...this.manager.getIdentity(cls), id });
|
|
141
|
+
return this.postLoad(cls, res);
|
|
126
142
|
} catch {
|
|
127
143
|
throw new NotFoundError(cls, id);
|
|
128
144
|
}
|
|
@@ -151,7 +167,7 @@ export class ElasticsearchModelService implements
|
|
|
151
167
|
async create<T extends ModelType>(cls: Class<T>, o: OptionalId<T>): Promise<T> {
|
|
152
168
|
try {
|
|
153
169
|
const clean = await ModelCrudUtil.preStore(cls, o, this);
|
|
154
|
-
const id = clean
|
|
170
|
+
const id = this.preUpdate(clean);
|
|
155
171
|
|
|
156
172
|
await this.client.index({
|
|
157
173
|
...this.manager.getIdentity(cls),
|
|
@@ -160,7 +176,7 @@ export class ElasticsearchModelService implements
|
|
|
160
176
|
body: clean
|
|
161
177
|
});
|
|
162
178
|
|
|
163
|
-
return clean;
|
|
179
|
+
return this.postUpdate(clean, id);
|
|
164
180
|
} catch (err) {
|
|
165
181
|
console.error(err);
|
|
166
182
|
throw err;
|
|
@@ -172,7 +188,7 @@ export class ElasticsearchModelService implements
|
|
|
172
188
|
|
|
173
189
|
o = await ModelCrudUtil.preStore(cls, o, this);
|
|
174
190
|
|
|
175
|
-
const id = o
|
|
191
|
+
const id = this.preUpdate(o);
|
|
176
192
|
|
|
177
193
|
if (ModelRegistry.get(cls).expiresAt) {
|
|
178
194
|
await this.get(cls, id);
|
|
@@ -186,18 +202,18 @@ export class ElasticsearchModelService implements
|
|
|
186
202
|
body: o
|
|
187
203
|
});
|
|
188
204
|
|
|
189
|
-
o
|
|
190
|
-
return o;
|
|
205
|
+
return this.postUpdate(o, id);
|
|
191
206
|
}
|
|
192
207
|
|
|
193
208
|
async upsert<T extends ModelType>(cls: Class<T>, o: OptionalId<T>): Promise<T> {
|
|
194
209
|
ModelCrudUtil.ensureNotSubType(cls);
|
|
195
210
|
|
|
196
211
|
const item = await ModelCrudUtil.preStore(cls, o, this);
|
|
212
|
+
const id = this.preUpdate(item);
|
|
197
213
|
|
|
198
214
|
await this.client.update({
|
|
199
215
|
...this.manager.getIdentity(cls),
|
|
200
|
-
id
|
|
216
|
+
id,
|
|
201
217
|
refresh: true,
|
|
202
218
|
body: {
|
|
203
219
|
doc: item,
|
|
@@ -205,18 +221,15 @@ export class ElasticsearchModelService implements
|
|
|
205
221
|
}
|
|
206
222
|
});
|
|
207
223
|
|
|
208
|
-
return item;
|
|
224
|
+
return this.postUpdate(item, id);
|
|
209
225
|
}
|
|
210
226
|
|
|
211
227
|
async updatePartial<T extends ModelType>(cls: Class<T>, data: Partial<T> & { id: string }, view?: string): Promise<T> {
|
|
212
228
|
ModelCrudUtil.ensureNotSubType(cls);
|
|
213
229
|
|
|
214
|
-
const
|
|
215
|
-
|
|
230
|
+
const id = data.id;
|
|
231
|
+
const item = castTo<typeof data>(await ModelCrudUtil.prePartialUpdate(cls, data, view));
|
|
216
232
|
const script = ElasticsearchSchemaUtil.generateUpdateScript(item);
|
|
217
|
-
const id = item.id!;
|
|
218
|
-
|
|
219
|
-
console.debug('Partial Script', { script });
|
|
220
233
|
|
|
221
234
|
try {
|
|
222
235
|
await this.client.update({
|
|
@@ -238,7 +251,7 @@ export class ElasticsearchModelService implements
|
|
|
238
251
|
}
|
|
239
252
|
|
|
240
253
|
async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
|
|
241
|
-
let search: SearchResponse<T> = await this.execSearch<T>(cls, {
|
|
254
|
+
let search: estypes.SearchResponse<T> = await this.execSearch<T>(cls, {
|
|
242
255
|
scroll: '2m',
|
|
243
256
|
size: 100,
|
|
244
257
|
query: ElasticsearchQueryUtil.getSearchQuery(cls, {})
|
|
@@ -247,7 +260,7 @@ export class ElasticsearchModelService implements
|
|
|
247
260
|
while (search.hits.hits.length > 0) {
|
|
248
261
|
for (const el of search.hits.hits) {
|
|
249
262
|
try {
|
|
250
|
-
yield this.postLoad(cls, el
|
|
263
|
+
yield this.postLoad(cls, el);
|
|
251
264
|
} catch (err) {
|
|
252
265
|
if (!(err instanceof NotFoundError)) {
|
|
253
266
|
throw err;
|
|
@@ -273,11 +286,14 @@ export class ElasticsearchModelService implements
|
|
|
273
286
|
if (op.delete) {
|
|
274
287
|
acc.push({ delete: { ...ident, _id: op.delete.id } });
|
|
275
288
|
} else if (op.insert) {
|
|
276
|
-
|
|
289
|
+
const id = this.preUpdate(op.insert);
|
|
290
|
+
acc.push({ create: { ...ident, _id: id } }, castTo(op.insert));
|
|
277
291
|
} else if (op.upsert) {
|
|
278
|
-
|
|
292
|
+
const id = this.preUpdate(op.upsert);
|
|
293
|
+
acc.push({ index: { ...ident, _id: id } }, castTo(op.upsert));
|
|
279
294
|
} else if (op.update) {
|
|
280
|
-
|
|
295
|
+
const id = this.preUpdate(op.update);
|
|
296
|
+
acc.push({ update: { ...ident, _id: id } }, { doc: op.update });
|
|
281
297
|
}
|
|
282
298
|
return acc;
|
|
283
299
|
}, []);
|
|
@@ -342,7 +358,7 @@ export class ElasticsearchModelService implements
|
|
|
342
358
|
// Indexed
|
|
343
359
|
async getByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<T> {
|
|
344
360
|
const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, body);
|
|
345
|
-
const res
|
|
361
|
+
const res = await this.execSearch<T>(cls, {
|
|
346
362
|
query: ElasticsearchQueryUtil.getSearchQuery(cls,
|
|
347
363
|
ElasticsearchQueryUtil.extractWhereTermQuery(cls,
|
|
348
364
|
ModelIndexedUtil.projectIndex(cls, idx, body))
|
|
@@ -351,7 +367,7 @@ export class ElasticsearchModelService implements
|
|
|
351
367
|
if (!res.hits.hits.length) {
|
|
352
368
|
throw new NotFoundError(`${cls.name}: ${idx}`, key);
|
|
353
369
|
}
|
|
354
|
-
return this.postLoad(cls, res.hits.hits[0]
|
|
370
|
+
return this.postLoad(cls, res.hits.hits[0]);
|
|
355
371
|
}
|
|
356
372
|
|
|
357
373
|
async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> {
|
|
@@ -389,7 +405,7 @@ export class ElasticsearchModelService implements
|
|
|
389
405
|
while (search.hits.hits.length > 0) {
|
|
390
406
|
for (const el of search.hits.hits) {
|
|
391
407
|
try {
|
|
392
|
-
yield this.postLoad(cls, el
|
|
408
|
+
yield this.postLoad(cls, el);
|
|
393
409
|
} catch (err) {
|
|
394
410
|
if (!(err instanceof NotFoundError)) {
|
|
395
411
|
throw err;
|
|
@@ -409,8 +425,13 @@ export class ElasticsearchModelService implements
|
|
|
409
425
|
|
|
410
426
|
const req = ElasticsearchQueryUtil.getSearchObject(cls, query, this.config.schemaConfig);
|
|
411
427
|
const results = await this.execSearch(cls, req);
|
|
412
|
-
const
|
|
413
|
-
return Promise.all(
|
|
428
|
+
const shouldRemoveIds = query.select && 'id' in query.select && !query.select.id;
|
|
429
|
+
return Promise.all(results.hits.hits.map(m => this.postLoad(cls, m).then(v => {
|
|
430
|
+
if (shouldRemoveIds) {
|
|
431
|
+
delete castTo<OptionalId<T>>(v).id;
|
|
432
|
+
}
|
|
433
|
+
return v;
|
|
434
|
+
})));
|
|
414
435
|
}
|
|
415
436
|
|
|
416
437
|
async queryOne<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>, failOnMany: boolean = true): Promise<T> {
|
|
@@ -432,10 +453,12 @@ export class ElasticsearchModelService implements
|
|
|
432
453
|
await QueryVerifier.verify(cls, query);
|
|
433
454
|
|
|
434
455
|
const item = await ModelCrudUtil.preStore(cls, data, this);
|
|
435
|
-
const id = item
|
|
456
|
+
const id = this.preUpdate(item);
|
|
436
457
|
|
|
437
458
|
const where = ModelQueryUtil.getWhereClause(cls, query.where);
|
|
438
|
-
|
|
459
|
+
if (id) {
|
|
460
|
+
where.id = id;
|
|
461
|
+
}
|
|
439
462
|
query.where = where;
|
|
440
463
|
|
|
441
464
|
if (ModelRegistry.get(cls).expiresAt) {
|
|
@@ -466,7 +489,7 @@ export class ElasticsearchModelService implements
|
|
|
466
489
|
}
|
|
467
490
|
}
|
|
468
491
|
|
|
469
|
-
return item;
|
|
492
|
+
return this.postUpdate(item, id);
|
|
470
493
|
}
|
|
471
494
|
|
|
472
495
|
async deleteByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T> = {}): Promise<number> {
|
|
@@ -485,7 +508,6 @@ export class ElasticsearchModelService implements
|
|
|
485
508
|
await QueryVerifier.verify(cls, query);
|
|
486
509
|
|
|
487
510
|
const item = await ModelCrudUtil.prePartialUpdate(cls, data);
|
|
488
|
-
|
|
489
511
|
const script = ElasticsearchSchemaUtil.generateUpdateScript(item);
|
|
490
512
|
|
|
491
513
|
const search = ElasticsearchQueryUtil.getSearchObject(cls, query, this.config.schemaConfig);
|
|
@@ -506,9 +528,8 @@ export class ElasticsearchModelService implements
|
|
|
506
528
|
const q = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, query);
|
|
507
529
|
const search = ElasticsearchQueryUtil.getSearchObject(cls, q);
|
|
508
530
|
const res = await this.execSearch(cls, search);
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
return Promise.all(combined.map(m => this.postLoad(cls, m)));
|
|
531
|
+
const all = await Promise.all(res.hits.hits.map(x => this.postLoad(cls, x)));
|
|
532
|
+
return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, all, (x, v) => v, query && query.limit);
|
|
512
533
|
}
|
|
513
534
|
|
|
514
535
|
async suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
|
|
@@ -520,8 +541,8 @@ export class ElasticsearchModelService implements
|
|
|
520
541
|
});
|
|
521
542
|
const search = ElasticsearchQueryUtil.getSearchObject(cls, q);
|
|
522
543
|
const res = await this.execSearch(cls, search);
|
|
523
|
-
const
|
|
524
|
-
return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix,
|
|
544
|
+
const all = await Promise.all(res.hits.hits.map(x => castTo<T>(({ [field]: field === 'id' ? x._id : x._source![field] }))));
|
|
545
|
+
return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, all, x => x, query && query.limit);
|
|
525
546
|
}
|
|
526
547
|
|
|
527
548
|
// Facet
|
|
@@ -539,7 +560,7 @@ export class ElasticsearchModelService implements
|
|
|
539
560
|
};
|
|
540
561
|
|
|
541
562
|
const res = await this.execSearch(cls, search);
|
|
542
|
-
const { buckets } = castTo<AggregationsStringTermsAggregate>('buckets' in res.aggregations![field] ? res.aggregations![field] : { buckets: [] });
|
|
563
|
+
const { buckets } = castTo<estypes.AggregationsStringTermsAggregate>('buckets' in res.aggregations![field] ? res.aggregations![field] : { buckets: [] });
|
|
543
564
|
const out = Array.isArray(buckets) ? buckets.map(b => ({ key: b.key, count: b.doc_count })) : [];
|
|
544
565
|
return out;
|
|
545
566
|
}
|