@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 ArcSine Technologies
3
+ Copyright (c) 2020 ArcSine Technologies
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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#L42) 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#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.17",
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.15.1",
31
- "@travetto/cli": "^5.0.16",
32
- "@travetto/config": "^5.0.13",
33
- "@travetto/model": "^5.0.14",
34
- "@travetto/model-query": "^5.0.14"
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
  */
@@ -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: {
@@ -1,4 +1,4 @@
1
- import { DeleteByQueryRequest, QueryDslQueryContainer, SearchRequest, SearchResponse, Sort, SortOptions } from '@elastic/elasticsearch/lib/api/types';
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({ terms: { [sPath]: Array.isArray(v) ? v : [v] } });
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({ term: { [sPath]: v } });
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
  }
@@ -1,4 +1,4 @@
1
- import { InlineScript, MappingProperty, MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types';
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>): InlineScript {
19
- const out: InlineScript = {
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>): InlineScript {
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, AppError, castTo, asFull, TypedObject, asConstructable } from '@travetto/runtime';
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>, item: T): Promise<T> {
82
- if (isWithId(item)) {
83
- delete item._id;
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, castTo(res._source));
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.id;
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.id;
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.id = id;
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: item.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 item = await ModelCrudUtil.prePartialUpdate(cls, data, view);
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._source!);
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
- acc.push({ create: { ...ident, _id: op.insert.id } }, castTo(op.insert));
289
+ const id = this.preUpdate(op.insert);
290
+ acc.push({ create: { ...ident, _id: id } }, castTo(op.insert));
277
291
  } else if (op.upsert) {
278
- acc.push({ index: { ...ident, _id: op.upsert.id } }, castTo(op.upsert));
292
+ const id = this.preUpdate(op.upsert);
293
+ acc.push({ index: { ...ident, _id: id } }, castTo(op.upsert));
279
294
  } else if (op.update) {
280
- acc.push({ update: { ...ident, _id: op.update.id } }, { doc: op.update });
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: SearchResponse<T> = await this.execSearch<T>(cls, {
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]._source!);
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._source!);
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 items = ElasticsearchQueryUtil.cleanIdRemoval(req, results);
413
- return Promise.all(items.map(m => this.postLoad(cls, m)));
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.id;
456
+ const id = this.preUpdate(item);
436
457
 
437
458
  const where = ModelQueryUtil.getWhereClause(cls, query.where);
438
- where.id = item.id;
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 safe = ElasticsearchQueryUtil.cleanIdRemoval<T>(search, res);
510
- const combined = ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, safe, (x, v) => v, query && query.limit);
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 safe = ElasticsearchQueryUtil.cleanIdRemoval(search, res);
524
- return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, safe, x => x, query && query.limit);
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
  }
@@ -1,6 +1,6 @@
1
1
  import type { ServiceDescriptor } from '@travetto/cli';
2
2
 
3
- const version = '8.9.1';
3
+ const version = '8.17.0';
4
4
 
5
5
  const port = 9200;
6
6