@travetto/model-elasticsearch 3.1.2 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-elasticsearch",
3
- "version": "3.1.2",
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",
@@ -28,9 +28,9 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@elastic/elasticsearch": "^8.6.0",
31
- "@travetto/config": "^3.1.1",
32
- "@travetto/model": "^3.1.2",
33
- "@travetto/model-query": "^3.1.2"
31
+ "@travetto/config": "^3.1.2",
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"
@@ -1,6 +1,6 @@
1
1
  import { InlineScript, MappingProperty, MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types';
2
2
 
3
- import { Class, DataUtil, ObjectUtil } from '@travetto/base';
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>, path: string = '', arr = false): InlineScript {
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),