@travetto/model-elasticsearch 3.1.3 → 3.1.4
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/package.json +3 -3
- package/src/internal/schema.ts +30 -26
- package/src/service.ts +45 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-elasticsearch",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.4",
|
|
4
4
|
"description": "Elasticsearch backing for the travetto model module, with real-time modeling support for Elasticsearch mappings.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"elasticsearch",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@elastic/elasticsearch": "^8.6.0",
|
|
31
31
|
"@travetto/config": "^3.1.2",
|
|
32
|
-
"@travetto/model": "^3.1.
|
|
33
|
-
"@travetto/model-query": "^3.1.
|
|
32
|
+
"@travetto/model": "^3.1.6",
|
|
33
|
+
"@travetto/model-query": "^3.1.4"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@travetto/command": "^3.1.1"
|
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, DataUtil
|
|
3
|
+
import { Class, DataUtil } from '@travetto/base';
|
|
4
4
|
import { ModelRegistry } from '@travetto/model';
|
|
5
5
|
import { PointImpl } from '@travetto/model-query/src/internal/model/point';
|
|
6
6
|
import { SchemaRegistry } from '@travetto/schema';
|
|
@@ -15,37 +15,41 @@ 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 ops: string[] = [];
|
|
18
|
+
static generateUpdateScript(o: Record<string, unknown>): InlineScript {
|
|
20
19
|
const out: InlineScript = {
|
|
21
|
-
params: {},
|
|
22
20
|
lang: 'painless',
|
|
23
|
-
source:
|
|
21
|
+
source: `
|
|
22
|
+
for (entry in params.body.entrySet()) {
|
|
23
|
+
def key = entry.getKey();
|
|
24
|
+
def value = entry.getValue();
|
|
25
|
+
if (key ==~ /^_?id$/) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (value == null) {
|
|
29
|
+
ctx._source.remove(key);
|
|
30
|
+
} else {
|
|
31
|
+
ctx._source[key] = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
`,
|
|
35
|
+
params: { body: o },
|
|
24
36
|
};
|
|
25
|
-
for (const x of Object.keys(o ?? {})) {
|
|
26
|
-
if (!path && (x === '_id' || x === 'id')) {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
const prop = arr ? `${path}[${x}]` : `${path}${path ? '.' : ''}${x}`;
|
|
30
|
-
if (o[x] === undefined || o[x] === null) {
|
|
31
|
-
ops.push(`ctx._source.${path}${path ? '.' : ''}remove("${x}")`);
|
|
32
|
-
} else if (ObjectUtil.isPrimitive(o[x]) || Array.isArray(o[x])) {
|
|
33
|
-
const param = prop.toLowerCase().replace(/[^a-z0-9_$]/g, '_');
|
|
34
|
-
ops.push(`ctx._source.${prop} = params.${param}`);
|
|
35
|
-
out.params![param] = o[x];
|
|
36
|
-
} else {
|
|
37
|
-
ops.push(`ctx._source.${prop} = ctx._source.${prop} == null ? [:] : ctx._source.${prop}`);
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
39
|
-
const sub = this.generateUpdateScript(o[x] as Record<string, unknown>, prop);
|
|
40
|
-
ops.push(sub.source);
|
|
41
|
-
Object.assign(out.params!, sub.params);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
out.source = ops.join(';');
|
|
45
|
-
|
|
46
37
|
return out;
|
|
47
38
|
}
|
|
48
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Generate replace script
|
|
42
|
+
* @param o
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
static generateReplaceScript(o: Record<string, unknown>): InlineScript {
|
|
46
|
+
return {
|
|
47
|
+
lang: 'painless',
|
|
48
|
+
source: 'ctx._source.clear(); ctx._source.putAll(params.body)',
|
|
49
|
+
params: { body: o }
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
/**
|
|
50
54
|
* Build one or more mappings depending on the polymorphic state
|
|
51
55
|
*/
|
package/src/service.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import es from '@elastic/elasticsearch';
|
|
2
2
|
import {
|
|
3
|
-
AggregationsStringTermsAggregate, AggregationsStringTermsBucket, DeleteByQueryRequest, SearchRequest, SearchResponse
|
|
3
|
+
AggregationsStringTermsAggregate, AggregationsStringTermsBucket, DeleteByQueryRequest, SearchRequest, SearchResponse, UpdateByQueryResponse
|
|
4
4
|
} from '@elastic/elasticsearch/lib/api/types';
|
|
5
5
|
|
|
6
6
|
import {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
OptionalId
|
|
10
10
|
} from '@travetto/model';
|
|
11
11
|
import { ShutdownManager, type Class, AppError } from '@travetto/base';
|
|
12
|
-
import { SchemaChange, DeepPartial } from '@travetto/schema';
|
|
12
|
+
import { SchemaChange, DeepPartial, BindUtil } from '@travetto/schema';
|
|
13
13
|
import { Injectable } from '@travetto/di';
|
|
14
14
|
import {
|
|
15
15
|
ModelQuery, ModelQueryCrudSupport, ModelQueryFacetSupport,
|
|
@@ -96,7 +96,7 @@ export class ElasticsearchModelService implements
|
|
|
96
96
|
async postConstruct(this: ElasticsearchModelService): Promise<void> {
|
|
97
97
|
this.client = new es.Client({
|
|
98
98
|
nodes: this.config.hosts,
|
|
99
|
-
...(this.config.options || {})
|
|
99
|
+
...(this.config.options || {}),
|
|
100
100
|
});
|
|
101
101
|
await this.client.cluster.health({});
|
|
102
102
|
this.manager = new IndexManager(this.config, this.client);
|
|
@@ -132,7 +132,7 @@ export class ElasticsearchModelService implements
|
|
|
132
132
|
const res = await this.client.delete({
|
|
133
133
|
...this.manager.getIdentity(cls),
|
|
134
134
|
id,
|
|
135
|
-
refresh: true
|
|
135
|
+
refresh: true,
|
|
136
136
|
});
|
|
137
137
|
if (res.result === 'not_found') {
|
|
138
138
|
throw new NotFoundError(cls, id);
|
|
@@ -421,6 +421,47 @@ export class ElasticsearchModelService implements
|
|
|
421
421
|
}
|
|
422
422
|
|
|
423
423
|
// Query Crud
|
|
424
|
+
async updateOneWithQuery<T extends ModelType>(cls: Class<T>, data: T, query: ModelQuery<T>): Promise<T> {
|
|
425
|
+
ModelCrudUtil.ensureNotSubType(cls);
|
|
426
|
+
|
|
427
|
+
const item = await ModelCrudUtil.preStore(cls, data, this);
|
|
428
|
+
const id = item.id;
|
|
429
|
+
|
|
430
|
+
query = ModelQueryUtil.getQueryWithId(cls, data, query);
|
|
431
|
+
|
|
432
|
+
if (ModelRegistry.get(cls).expiresAt) {
|
|
433
|
+
await this.get(cls, id);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const search = ElasticsearchQueryUtil.getSearchObject(cls, query, this.config.schemaConfig);
|
|
437
|
+
|
|
438
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
439
|
+
const copy = BindUtil.bindSchemaToObject(cls, {} as T, item);
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
const res = await this.client.updateByQuery({
|
|
443
|
+
...this.manager.getIdentity(cls),
|
|
444
|
+
refresh: true,
|
|
445
|
+
query: search.query,
|
|
446
|
+
max_docs: 1,
|
|
447
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
448
|
+
script: ElasticsearchSchemaUtil.generateReplaceScript(copy as {})
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
if (res.version_conflicts || res.updated === undefined || res.updated === 0) {
|
|
452
|
+
throw new NotFoundError(cls, id);
|
|
453
|
+
}
|
|
454
|
+
} catch (err) {
|
|
455
|
+
if (err instanceof es.errors.ResponseError && (err.body as UpdateByQueryResponse).version_conflicts) {
|
|
456
|
+
throw new NotFoundError(cls, id);
|
|
457
|
+
} else {
|
|
458
|
+
throw err;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return item;
|
|
463
|
+
}
|
|
464
|
+
|
|
424
465
|
async deleteByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T> = {}): Promise<number> {
|
|
425
466
|
const res = await this.client.deleteByQuery({
|
|
426
467
|
...this.manager.getIdentity(cls),
|