@travetto/model-elasticsearch 7.0.0-rc.1 → 7.0.0-rc.2

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 CHANGED
@@ -36,8 +36,8 @@ export class Init {
36
36
  @InjectableFactory({
37
37
  primary: true
38
38
  })
39
- static getModelSource(conf: ElasticsearchModelConfig) {
40
- return new ElasticsearchModelService(conf);
39
+ static getModelSource(config: ElasticsearchModelConfig) {
40
+ return new ElasticsearchModelService(config);
41
41
  }
42
42
  }
43
43
  ```
@@ -99,8 +99,8 @@ export class ElasticsearchModelConfig {
99
99
  postConstruct(): void {
100
100
  console.debug('Constructed', { config: this });
101
101
  this.hosts = this.hosts
102
- .map(x => x.includes(':') ? x : `${x}:${this.port}`)
103
- .map(x => x.startsWith('http') ? x : `http://${x}`);
102
+ .map(host => host.includes(':') ? host : `${host}:${this.port}`)
103
+ .map(host => host.startsWith('http') ? host : `http://${host}`);
104
104
  }
105
105
  }
106
106
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-elasticsearch",
3
- "version": "7.0.0-rc.1",
3
+ "version": "7.0.0-rc.2",
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,10 +28,10 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@elastic/elasticsearch": "^9.2.0",
31
- "@travetto/cli": "^7.0.0-rc.1",
32
- "@travetto/config": "^7.0.0-rc.1",
33
- "@travetto/model": "^7.0.0-rc.1",
34
- "@travetto/model-query": "^7.0.0-rc.1"
31
+ "@travetto/cli": "^7.0.0-rc.2",
32
+ "@travetto/config": "^7.0.0-rc.2",
33
+ "@travetto/model": "^7.0.0-rc.2",
34
+ "@travetto/model-query": "^7.0.0-rc.2"
35
35
  },
36
36
  "travetto": {
37
37
  "displayName": "Elasticsearch Model Source"
package/src/config.ts CHANGED
@@ -59,7 +59,7 @@ export class ElasticsearchModelConfig {
59
59
  postConstruct(): void {
60
60
  console.debug('Constructed', { config: this });
61
61
  this.hosts = this.hosts
62
- .map(x => x.includes(':') ? x : `${x}:${this.port}`)
63
- .map(x => x.startsWith('http') ? x : `http://${x}`);
62
+ .map(host => host.includes(':') ? host : `${host}:${this.port}`)
63
+ .map(host => host.startsWith('http') ? host : `http://${host}`);
64
64
  }
65
65
  }
@@ -74,22 +74,22 @@ export class IndexManager implements ModelStorageSupport {
74
74
  */
75
75
  async createIndex(cls: Class, alias = true): Promise<string> {
76
76
  const mapping = ElasticsearchSchemaUtil.generateSchemaMapping(cls, this.config.schemaConfig);
77
- const ident = this.getIdentity(cls); // Already namespaced
78
- const concreteIndex = `${ident.index}_${Date.now()}`;
77
+ const { index } = this.getIdentity(cls); // Already namespaced
78
+ const concreteIndex = `${index}_${Date.now()}`;
79
79
  try {
80
80
  await this.#client.indices.create({
81
81
  index: concreteIndex,
82
82
  mappings: mapping,
83
83
  settings: this.config.indexCreate,
84
- ...(alias ? { aliases: { [ident.index]: {} } } : {})
84
+ ...(alias ? { aliases: { [index]: {} } } : {})
85
85
  });
86
- console.debug('Index created', { index: ident.index, concrete: concreteIndex });
86
+ console.debug('Index created', { index, concrete: concreteIndex });
87
87
  console.debug('Index Config', {
88
88
  mappings: mapping,
89
89
  settings: this.config.indexCreate
90
90
  });
91
- } catch (err) {
92
- console.warn('Index already created', { index: ident.index, error: err });
91
+ } catch (error) {
92
+ console.warn('Index already created', { index, error });
93
93
  }
94
94
  return concreteIndex;
95
95
  }
@@ -99,9 +99,9 @@ export class IndexManager implements ModelStorageSupport {
99
99
  */
100
100
  async createIndexIfMissing(cls: Class): Promise<void> {
101
101
  const baseCls = SchemaRegistryIndex.getBaseClass(cls);
102
- const ident = this.getIdentity(baseCls);
102
+ const identity = this.getIdentity(baseCls);
103
103
  try {
104
- await this.#client.search(ident);
104
+ await this.#client.search(identity);
105
105
  } catch {
106
106
  await this.createIndex(baseCls);
107
107
  }
@@ -114,8 +114,8 @@ export class IndexManager implements ModelStorageSupport {
114
114
 
115
115
  async exportModel(cls: Class<ModelType>): Promise<string> {
116
116
  const schema = ElasticsearchSchemaUtil.generateSchemaMapping(cls, this.config.schemaConfig);
117
- const ident = this.getIdentity(cls); // Already namespaced
118
- return `curl -XPOST $ES_HOST/${ident.index} -d '${JSON.stringify({
117
+ const { index } = this.getIdentity(cls); // Already namespaced
118
+ return `curl -XPOST $ES_HOST/${index} -d '${JSON.stringify({
119
119
  mappings: schema,
120
120
  settings: this.config.indexCreate
121
121
  })}'`;
@@ -136,20 +136,20 @@ export class IndexManager implements ModelStorageSupport {
136
136
  */
137
137
  async changeSchema(cls: Class, change: SchemaChange): Promise<void> {
138
138
  // Find which fields are gone
139
- const removes = change.subs.reduce<string[]>((acc, v) => {
140
- acc.push(...v.fields
141
- .filter(ev => ev.type === 'removing')
142
- .map(ev => [...v.path.map(f => f.name), ev.prev!.name].join('.')));
143
- return acc;
139
+ const removes = change.subs.reduce<string[]>((toRemove, subChange) => {
140
+ toRemove.push(...subChange.fields
141
+ .filter(event => event.type === 'removing')
142
+ .map(event => [...subChange.path.map(field => field.name), event.previous!.name].join('.')));
143
+ return toRemove;
144
144
  }, []);
145
145
 
146
146
  // Find which types have changed
147
- const fieldChanges = change.subs.reduce<string[]>((acc, v) => {
148
- acc.push(...v.fields
149
- .filter(ev => ev.type === 'changed')
150
- .filter(ev => ev.prev?.type !== ev.curr?.type)
151
- .map(ev => [...v.path.map(f => f.name), ev.prev!.name].join('.')));
152
- return acc;
147
+ const fieldChanges = change.subs.reduce<string[]>((toChange, subChange) => {
148
+ toChange.push(...subChange.fields
149
+ .filter(event => event.type === 'changed')
150
+ .filter(event => event.previous?.type !== event.current?.type)
151
+ .map(event => [...subChange.path.map(field => field.name), event.previous!.name].join('.')));
152
+ return toChange;
153
153
  }, []);
154
154
 
155
155
  const { index } = this.getIdentity(cls);
@@ -159,16 +159,16 @@ export class IndexManager implements ModelStorageSupport {
159
159
  const next = await this.createIndex(cls, false);
160
160
 
161
161
  const aliases = (await this.#client.indices.getAlias({ index })).body;
162
- const curr = Object.keys(aliases)[0];
162
+ const current = Object.keys(aliases)[0];
163
163
 
164
164
  const allChange = removes.concat(fieldChanges);
165
165
 
166
166
  const reindexBody: estypes.ReindexRequest = {
167
- source: { index: curr },
167
+ source: { index: current },
168
168
  dest: { index: next },
169
169
  script: {
170
170
  lang: 'painless',
171
- source: allChange.map(x => `ctx._source.remove("${x}");`).join(' ') // Removing
171
+ source: allChange.map(part => `ctx._source.remove("${part}");`).join(' ') // Removing
172
172
  },
173
173
  wait_for_completion: true
174
174
  };
@@ -177,7 +177,7 @@ export class IndexManager implements ModelStorageSupport {
177
177
  await this.#client.reindex(reindexBody);
178
178
 
179
179
  await Promise.all(Object.keys(aliases)
180
- .map(x => this.#client.indices.delete({ index: x })));
180
+ .map(alias => this.#client.indices.delete({ index: alias })));
181
181
 
182
182
  await this.#client.indices.putAlias({ index: next, name: index });
183
183
  } else { // Only update the schema
@@ -15,15 +15,15 @@ export class ElasticsearchQueryUtil {
15
15
  /**
16
16
  * Convert `a.b.c` to `a : { b : { c : ... }}`
17
17
  */
18
- static extractSimple<T>(o: T, path: string = ''): Record<string, unknown> {
18
+ static extractSimple<T>(input: T, path: string = ''): Record<string, unknown> {
19
19
  const out: Record<string, unknown> = {};
20
- const keys = TypedObject.keys(o);
20
+ const keys = TypedObject.keys(input);
21
21
  for (const key of keys) {
22
22
  const subPath = `${path}${key}`;
23
- if (DataUtil.isPlainObject(o[key]) && !Object.keys(o[key])[0].startsWith('$')) {
24
- Object.assign(out, this.extractSimple(o[key], `${subPath}.`));
23
+ if (DataUtil.isPlainObject(input[key]) && !Object.keys(input[key])[0].startsWith('$')) {
24
+ Object.assign(out, this.extractSimple(input[key], `${subPath}.`));
25
25
  } else {
26
- out[subPath] = o[key];
26
+ out[subPath] = input[key];
27
27
  }
28
28
  }
29
29
  return out;
@@ -36,13 +36,13 @@ export class ElasticsearchQueryUtil {
36
36
  const simp = this.extractSimple(clause);
37
37
  const include: string[] = [];
38
38
  const exclude: string[] = [];
39
- for (const k of Object.keys(simp)) {
40
- const nk = k === 'id' ? '_id' : k;
41
- const v: 1 | 0 | boolean = castTo(simp[k]);
42
- if (v === 0 || v === false) {
43
- exclude.push(nk);
39
+ for (const key of Object.keys(simp)) {
40
+ const translatedKey = key === 'id' ? '_id' : key;
41
+ const value: 1 | 0 | boolean = castTo(simp[key]);
42
+ if (value === 0 || value === false) {
43
+ exclude.push(translatedKey);
44
44
  } else {
45
- include.push(nk);
45
+ include.push(translatedKey);
46
46
  }
47
47
  }
48
48
  return [include, exclude];
@@ -52,73 +52,73 @@ export class ElasticsearchQueryUtil {
52
52
  * Build sort mechanism
53
53
  */
54
54
  static getSort<T extends ModelType>(sort: SortClause<T>[] | IndexConfig<T>['fields']): estypes.Sort {
55
- return sort.map<estypes.SortOptions>(x => {
56
- const o = this.extractSimple(x);
57
- const k = Object.keys(o)[0];
58
- const v: boolean | -1 | 1 = castTo(o[k]);
59
- return { [k]: { order: v === 1 || v === true ? 'asc' : 'desc' } };
55
+ return sort.map<estypes.SortOptions>(option => {
56
+ const item = this.extractSimple(option);
57
+ const key = Object.keys(item)[0];
58
+ const value: boolean | -1 | 1 = castTo(item[key]);
59
+ return { [key]: { order: value === 1 || value === true ? 'asc' : 'desc' } };
60
60
  });
61
61
  }
62
62
 
63
63
  /**
64
64
  * Extract specific term for a class, and a given field
65
65
  */
66
- static extractWhereTermQuery<T>(cls: Class<T>, o: Record<string, unknown>, config?: EsSchemaConfig, path: string = ''): Record<string, unknown> {
66
+ static extractWhereTermQuery<T>(cls: Class<T>, item: Record<string, unknown>, config?: EsSchemaConfig, path: string = ''): Record<string, unknown> {
67
67
  const items = [];
68
68
  const fields = SchemaRegistryIndex.get(cls).getFields();
69
69
 
70
- for (const key of TypedObject.keys(o)) {
71
- const top = o[key];
72
- const declaredSchema = fields[key];
70
+ for (const property of TypedObject.keys(item)) {
71
+ const top = item[property];
72
+ const declaredSchema = fields[property];
73
73
  const declaredType = declaredSchema.type;
74
- const sPath = declaredType === String ?
75
- ((key === 'id' && !path) ? '_id' : `${path}${key}`) :
76
- `${path}${key}`;
74
+ const subPath = declaredType === String ?
75
+ ((property === 'id' && !path) ? '_id' : `${path}${property}`) :
76
+ `${path}${property}`;
77
77
 
78
- const sPathQuery = (val: unknown): {} => (key === 'id' && !path) ?
79
- { ids: { values: Array.isArray(val) ? val : [val] } } :
80
- { [Array.isArray(val) ? 'terms' : 'term']: { [sPath]: val } };
78
+ const subPathQuery = (value: unknown): {} => (property === 'id' && !path) ?
79
+ { ids: { values: Array.isArray(value) ? value : [value] } } :
80
+ { [Array.isArray(value) ? 'terms' : 'term']: { [subPath]: value } };
81
81
 
82
82
  if (DataUtil.isPlainObject(top)) {
83
83
  const subKey = Object.keys(top)[0];
84
84
  if (!subKey.startsWith('$')) {
85
- const inner = this.extractWhereTermQuery(declaredType, top, config, `${sPath}.`);
85
+ const inner = this.extractWhereTermQuery(declaredType, top, config, `${subPath}.`);
86
86
  items.push(declaredSchema.array ?
87
- { nested: { path: sPath, query: inner } } :
87
+ { nested: { path: subPath, query: inner } } :
88
88
  inner
89
89
  );
90
90
  } else {
91
- const v = top[subKey];
91
+ const value = top[subKey];
92
92
 
93
93
  switch (subKey) {
94
94
  case '$all': {
95
- const arr = Array.isArray(v) ? v : [v];
95
+ const values = Array.isArray(value) ? value : [value];
96
96
  items.push({
97
97
  bool: {
98
- must: arr.map(x => ({ term: { [sPath]: x } }))
98
+ must: values.map(term => ({ term: { [subPath]: term } }))
99
99
  }
100
100
  });
101
101
  break;
102
102
  }
103
103
  case '$in': {
104
- items.push(sPathQuery(Array.isArray(v) ? v : [v]));
104
+ items.push(subPathQuery(Array.isArray(value) ? value : [value]));
105
105
  break;
106
106
  }
107
107
  case '$nin': {
108
- items.push({ bool: { ['must_not']: [sPathQuery(Array.isArray(v) ? v : [v])] } });
108
+ items.push({ bool: { ['must_not']: [subPathQuery(Array.isArray(value) ? value : [value])] } });
109
109
  break;
110
110
  }
111
111
  case '$eq': {
112
- items.push(sPathQuery(v));
112
+ items.push(subPathQuery(value));
113
113
  break;
114
114
  }
115
115
  case '$ne': {
116
- items.push({ bool: { ['must_not']: [sPathQuery(v)] } });
116
+ items.push({ bool: { ['must_not']: [subPathQuery(value)] } });
117
117
  break;
118
118
  }
119
119
  case '$exists': {
120
- const q = { exists: { field: sPath } };
121
- items.push(v ? q : { bool: { ['must_not']: q } });
120
+ const clause = { exists: { field: subPath } };
121
+ items.push(value ? clause : { bool: { ['must_not']: clause } });
122
122
  break;
123
123
  }
124
124
  case '$lt':
@@ -126,18 +126,18 @@ export class ElasticsearchQueryUtil {
126
126
  case '$gte':
127
127
  case '$lte': {
128
128
  const out: Record<string, unknown> = {};
129
- for (const k of Object.keys(top)) {
130
- out[k.replace(/^[$]/, '')] = ModelQueryUtil.resolveComparator(top[k]);
129
+ for (const key of Object.keys(top)) {
130
+ out[key.replace(/^[$]/, '')] = ModelQueryUtil.resolveComparator(top[key]);
131
131
  }
132
- items.push({ range: { [sPath]: out } });
132
+ items.push({ range: { [subPath]: out } });
133
133
  break;
134
134
  }
135
135
  case '$regex': {
136
- const pattern = DataUtil.toRegex(castTo(v));
136
+ const pattern = DataUtil.toRegex(castTo(value));
137
137
  if (pattern.source.startsWith('\\b') && pattern.source.endsWith('.*')) {
138
138
  const textField = !pattern.flags.includes('i') && config && config.caseSensitive ?
139
- `${sPath}.text_cs` :
140
- `${sPath}.text`;
139
+ `${subPath}.text_cs` :
140
+ `${subPath}.text`;
141
141
  const query = pattern.source.substring(2, pattern.source.length - 2);
142
142
  items.push({
143
143
  ['match_phrase_prefix']: {
@@ -145,12 +145,12 @@ export class ElasticsearchQueryUtil {
145
145
  }
146
146
  });
147
147
  } else {
148
- items.push({ regexp: { [sPath]: pattern.source } });
148
+ items.push({ regexp: { [subPath]: pattern.source } });
149
149
  }
150
150
  break;
151
151
  }
152
152
  case '$geoWithin': {
153
- items.push({ ['geo_polygon']: { [sPath]: { points: v } } });
153
+ items.push({ ['geo_polygon']: { [subPath]: { points: value } } });
154
154
  break;
155
155
  }
156
156
  case '$unit':
@@ -165,7 +165,7 @@ export class ElasticsearchQueryUtil {
165
165
  items.push({
166
166
  ['geo_distance']: {
167
167
  distance: `${dist}${unit}`,
168
- [sPath]: top.$near
168
+ [subPath]: top.$near
169
169
  }
170
170
  });
171
171
  break;
@@ -174,7 +174,7 @@ export class ElasticsearchQueryUtil {
174
174
  }
175
175
  // Handle operations
176
176
  } else {
177
- items.push(sPathQuery(top));
177
+ items.push(subPathQuery(top));
178
178
  }
179
179
  }
180
180
  if (items.length === 1) {
@@ -187,15 +187,15 @@ export class ElasticsearchQueryUtil {
187
187
  /**
188
188
  * Build query from the where clause
189
189
  */
190
- static extractWhereQuery<T>(cls: Class<T>, o: WhereClause<T>, config?: EsSchemaConfig): Record<string, unknown> {
191
- if (ModelQueryUtil.has$And(o)) {
192
- return { bool: { must: o.$and.map(x => this.extractWhereQuery<T>(cls, x, config)) } };
193
- } else if (ModelQueryUtil.has$Or(o)) {
194
- return { bool: { should: o.$or.map(x => this.extractWhereQuery<T>(cls, x, config)), ['minimum_should_match']: 1 } };
195
- } else if (ModelQueryUtil.has$Not(o)) {
196
- return { bool: { ['must_not']: this.extractWhereQuery<T>(cls, o.$not, config) } };
190
+ static extractWhereQuery<T>(cls: Class<T>, clause: WhereClause<T>, config?: EsSchemaConfig): Record<string, unknown> {
191
+ if (ModelQueryUtil.has$And(clause)) {
192
+ return { bool: { must: clause.$and.map(item => this.extractWhereQuery<T>(cls, item, config)) } };
193
+ } else if (ModelQueryUtil.has$Or(clause)) {
194
+ return { bool: { should: clause.$or.map(item => this.extractWhereQuery<T>(cls, item, config)), ['minimum_should_match']: 1 } };
195
+ } else if (ModelQueryUtil.has$Not(clause)) {
196
+ return { bool: { ['must_not']: this.extractWhereQuery<T>(cls, clause.$not, config) } };
197
197
  } else {
198
- return this.extractWhereTermQuery(cls, o, config);
198
+ return this.extractWhereTermQuery(cls, clause, config);
199
199
  }
200
200
  }
201
201
 
@@ -5,7 +5,7 @@ import { Point, DataUtil, SchemaRegistryIndex } from '@travetto/schema';
5
5
 
6
6
  import { EsSchemaConfig } from './types.ts';
7
7
 
8
- const PointImpl = toConcrete<Point>();
8
+ const PointConcrete = toConcrete<Point>();
9
9
 
10
10
  /**
11
11
  * Utils for ES Schema management
@@ -15,7 +15,7 @@ export class ElasticsearchSchemaUtil {
15
15
  /**
16
16
  * Build the update script for a given object
17
17
  */
18
- static generateUpdateScript(o: Record<string, unknown>): estypes.Script {
18
+ static generateUpdateScript(item: Record<string, unknown>): estypes.Script {
19
19
  const out: estypes.Script = {
20
20
  lang: 'painless',
21
21
  source: `
@@ -32,21 +32,21 @@ export class ElasticsearchSchemaUtil {
32
32
  }
33
33
  }
34
34
  `,
35
- params: { body: o },
35
+ params: { body: item },
36
36
  };
37
37
  return out;
38
38
  }
39
39
 
40
40
  /**
41
41
  * Generate replace script
42
- * @param o
42
+ * @param item
43
43
  * @returns
44
44
  */
45
- static generateReplaceScript(o: Record<string, unknown>): estypes.Script {
45
+ static generateReplaceScript(item: Record<string, unknown>): estypes.Script {
46
46
  return {
47
47
  lang: 'painless',
48
48
  source: 'ctx._source.clear(); ctx._source.putAll(params.body)',
49
- params: { body: o }
49
+ params: { body: item }
50
50
  };
51
51
  }
52
52
 
@@ -64,65 +64,65 @@ export class ElasticsearchSchemaUtil {
64
64
  */
65
65
  static generateAllMapping(cls: Class, config?: EsSchemaConfig): estypes.MappingTypeMapping {
66
66
  const allTypes = SchemaRegistryIndex.getDiscriminatedClasses(cls);
67
- return allTypes.reduce<estypes.MappingTypeMapping>((acc, schemaCls) => {
68
- DataUtil.deepAssign(acc, this.generateSingleMapping(schemaCls, config));
69
- return acc;
67
+ return allTypes.reduce<estypes.MappingTypeMapping>((mapping, schemaCls) => {
68
+ DataUtil.deepAssign(mapping, this.generateSingleMapping(schemaCls, config));
69
+ return mapping;
70
70
  }, { properties: {}, dynamic: false });
71
71
  }
72
72
 
73
73
  /**
74
74
  * Build a mapping for a given class
75
75
  */
76
- static generateSingleMapping<T>(cls: Class<T>, config?: EsSchemaConfig): estypes.MappingTypeMapping {
76
+ static generateSingleMapping<T>(cls: Class<T>, esSchema?: EsSchemaConfig): estypes.MappingTypeMapping {
77
77
  const fields = SchemaRegistryIndex.get(cls).getFields();
78
78
 
79
- const props: Record<string, estypes.MappingProperty> = {};
79
+ const properties: Record<string, estypes.MappingProperty> = {};
80
80
 
81
- for (const [field, conf] of Object.entries(fields)) {
82
- if (conf.type === PointImpl) {
83
- props[field] = { type: 'geo_point' };
84
- } else if (conf.type === Number) {
85
- let prop: Record<string, unknown> = { type: 'integer' };
86
- if (conf.precision) {
87
- const [digits, decimals] = conf.precision;
81
+ for (const [field, config] of Object.entries(fields)) {
82
+ if (config.type === PointConcrete) {
83
+ properties[field] = { type: 'geo_point' };
84
+ } else if (config.type === Number) {
85
+ let property: Record<string, unknown> = { type: 'integer' };
86
+ if (config.precision) {
87
+ const [digits, decimals] = config.precision;
88
88
  if (decimals) {
89
89
  if ((decimals + digits) < 16) {
90
- prop = { type: 'scaled_float', ['scaling_factor']: decimals };
90
+ property = { type: 'scaled_float', ['scaling_factor']: decimals };
91
91
  } else {
92
92
  if (digits < 6 && decimals < 9) {
93
- prop = { type: 'half_float' };
93
+ property = { type: 'half_float' };
94
94
  } else if (digits > 20) {
95
- prop = { type: 'double' };
95
+ property = { type: 'double' };
96
96
  } else {
97
- prop = { type: 'float' };
97
+ property = { type: 'float' };
98
98
  }
99
99
  }
100
100
  } else if (digits) {
101
101
  if (digits <= 2) {
102
- prop = { type: 'byte' };
102
+ property = { type: 'byte' };
103
103
  } else if (digits <= 4) {
104
- prop = { type: 'short' };
104
+ property = { type: 'short' };
105
105
  } else if (digits <= 9) {
106
- prop = { type: 'integer' };
106
+ property = { type: 'integer' };
107
107
  } else {
108
- prop = { type: 'long' };
108
+ property = { type: 'long' };
109
109
  }
110
110
  }
111
111
  }
112
- props[field] = prop;
113
- } else if (conf.type === Date) {
114
- props[field] = { type: 'date', format: 'date_optional_time' };
115
- } else if (conf.type === Boolean) {
116
- props[field] = { type: 'boolean' };
117
- } else if (conf.type === String) {
112
+ properties[field] = property;
113
+ } else if (config.type === Date) {
114
+ properties[field] = { type: 'date', format: 'date_optional_time' };
115
+ } else if (config.type === Boolean) {
116
+ properties[field] = { type: 'boolean' };
117
+ } else if (config.type === String) {
118
118
  let text = {};
119
- if (conf.specifiers?.includes('text')) {
119
+ if (config.specifiers?.includes('text')) {
120
120
  text = {
121
121
  fields: {
122
122
  text: { type: 'text' }
123
123
  }
124
124
  };
125
- if (config && config.caseSensitive) {
125
+ if (esSchema && esSchema.caseSensitive) {
126
126
  DataUtil.deepAssign(text, {
127
127
  fields: {
128
128
  ['text_cs']: { type: 'text', analyzer: 'whitespace' }
@@ -130,17 +130,17 @@ export class ElasticsearchSchemaUtil {
130
130
  });
131
131
  }
132
132
  }
133
- props[field] = { type: 'keyword', ...text };
134
- } else if (conf.type === Object) {
135
- props[field] = { type: 'object', dynamic: true };
136
- } else if (SchemaRegistryIndex.has(conf.type)) {
137
- props[field] = {
138
- type: conf.array ? 'nested' : 'object',
139
- ...this.generateSingleMapping(conf.type, config)
133
+ properties[field] = { type: 'keyword', ...text };
134
+ } else if (config.type === Object) {
135
+ properties[field] = { type: 'object', dynamic: true };
136
+ } else if (SchemaRegistryIndex.has(config.type)) {
137
+ properties[field] = {
138
+ type: config.array ? 'nested' : 'object',
139
+ ...this.generateSingleMapping(config.type, esSchema)
140
140
  };
141
141
  }
142
142
  }
143
143
 
144
- return { properties: props, dynamic: false };
144
+ return { properties, dynamic: false };
145
145
  }
146
146
  }
package/src/service.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Client, errors, estypes } from '@elastic/elasticsearch';
2
2
 
3
3
  import {
4
- ModelCrudSupport, BulkOp, BulkResponse, ModelBulkSupport, ModelExpirySupport,
4
+ ModelCrudSupport, BulkOperation, BulkResponse, ModelBulkSupport, ModelExpirySupport,
5
5
  ModelIndexedSupport, ModelType, ModelStorageSupport, NotFoundError, ModelRegistryIndex, OptionalId,
6
6
  ModelCrudUtil, ModelIndexedUtil, ModelStorageUtil, ModelExpiryUtil, ModelBulkUtil,
7
7
  } from '@travetto/model';
@@ -55,41 +55,41 @@ export class ElasticsearchModelService implements
55
55
  query
56
56
  });
57
57
  return result;
58
- } catch (err) {
59
- if (err instanceof errors.ResponseError && err.meta.body && typeof err.meta.body === 'object' && 'error' in err.meta.body) {
60
- console.error(err.meta.body.error);
58
+ } catch (error) {
59
+ if (error instanceof errors.ResponseError && error.meta.body && typeof error.meta.body === 'object' && 'error' in error.meta.body) {
60
+ console.error(error.meta.body.error);
61
61
  }
62
- throw err;
62
+ throw error;
63
63
  }
64
64
  }
65
65
 
66
- preUpdate(o: { id: string }): string;
67
- preUpdate(o: {}): undefined;
68
- preUpdate(o: { id?: string }): string | undefined {
69
- if ('id' in o && typeof o.id === 'string') {
70
- const id = o.id;
66
+ preUpdate(item: { id: string }): string;
67
+ preUpdate(item: {}): undefined;
68
+ preUpdate(item: { id?: string }): string | undefined {
69
+ if ('id' in item && typeof item.id === 'string') {
70
+ const id = item.id;
71
71
  if (!this.config.storeId) {
72
- delete o.id;
72
+ delete item.id;
73
73
  }
74
74
  return id;
75
75
  }
76
76
  return;
77
77
  }
78
78
 
79
- postUpdate<T extends ModelType>(o: T, id?: string): T {
79
+ postUpdate<T extends ModelType>(item: T, id?: string): T {
80
80
  if (!this.config.storeId) {
81
- o.id = id!;
81
+ item.id = id!;
82
82
  }
83
- return o;
83
+ return item;
84
84
  }
85
85
 
86
86
  /**
87
87
  * Convert _id to id
88
88
  */
89
- async postLoad<T extends ModelType>(cls: Class<T>, inp: estypes.SearchHit<T> | estypes.GetGetResult<T>): Promise<T> {
89
+ async postLoad<T extends ModelType>(cls: Class<T>, input: estypes.SearchHit<T> | estypes.GetGetResult<T>): Promise<T> {
90
90
  let item = {
91
- ...(inp._id ? { id: inp._id } : {}),
92
- ...inp._source!,
91
+ ...(input._id ? { id: input._id } : {}),
92
+ ...input._source!,
93
93
  };
94
94
 
95
95
  item = await ModelCrudUtil.load(cls, item);
@@ -149,17 +149,17 @@ export class ElasticsearchModelService implements
149
149
  if (result.result === 'not_found') {
150
150
  throw new NotFoundError(cls, id);
151
151
  }
152
- } catch (err) {
153
- if (err && err instanceof errors.ResponseError && err.body && err.body.result === 'not_found') {
152
+ } catch (error) {
153
+ if (error && error instanceof errors.ResponseError && error.body && error.body.result === 'not_found') {
154
154
  throw new NotFoundError(cls, id);
155
155
  }
156
- throw err;
156
+ throw error;
157
157
  }
158
158
  }
159
159
 
160
- async create<T extends ModelType>(cls: Class<T>, o: OptionalId<T>): Promise<T> {
160
+ async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
161
161
  try {
162
- const clean = await ModelCrudUtil.preStore(cls, o, this);
162
+ const clean = await ModelCrudUtil.preStore(cls, item, this);
163
163
  const id = this.preUpdate(clean);
164
164
 
165
165
  await this.client.index({
@@ -170,18 +170,18 @@ export class ElasticsearchModelService implements
170
170
  });
171
171
 
172
172
  return this.postUpdate(clean, id);
173
- } catch (err) {
174
- console.error(err);
175
- throw err;
173
+ } catch (error) {
174
+ console.error(error);
175
+ throw error;
176
176
  }
177
177
  }
178
178
 
179
- async update<T extends ModelType>(cls: Class<T>, o: T): Promise<T> {
179
+ async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
180
180
  ModelCrudUtil.ensureNotSubType(cls);
181
181
 
182
- o = await ModelCrudUtil.preStore(cls, o, this);
182
+ item = await ModelCrudUtil.preStore(cls, item, this);
183
183
 
184
- const id = this.preUpdate(o);
184
+ const id = this.preUpdate(item);
185
185
 
186
186
  if (ModelRegistryIndex.getConfig(cls).expiresAt) {
187
187
  await this.get(cls, id);
@@ -192,16 +192,16 @@ export class ElasticsearchModelService implements
192
192
  id,
193
193
  op_type: 'index',
194
194
  refresh: true,
195
- body: castTo<T & { id: never }>(o)
195
+ body: castTo<T & { id: never }>(item)
196
196
  });
197
197
 
198
- return this.postUpdate(o, id);
198
+ return this.postUpdate(item, id);
199
199
  }
200
200
 
201
- async upsert<T extends ModelType>(cls: Class<T>, o: OptionalId<T>): Promise<T> {
201
+ async upsert<T extends ModelType>(cls: Class<T>, input: OptionalId<T>): Promise<T> {
202
202
  ModelCrudUtil.ensureNotSubType(cls);
203
203
 
204
- const item = await ModelCrudUtil.preStore(cls, o, this);
204
+ const item = await ModelCrudUtil.preStore(cls, input, this);
205
205
  const id = this.preUpdate(item);
206
206
 
207
207
  await this.client.update({
@@ -229,11 +229,11 @@ export class ElasticsearchModelService implements
229
229
  refresh: true,
230
230
  script,
231
231
  });
232
- } catch (err) {
233
- if (err instanceof Error && /document_missing_exception/.test(err.message)) {
232
+ } catch (error) {
233
+ if (error instanceof Error && /document_missing_exception/.test(error.message)) {
234
234
  throw new NotFoundError(cls, id);
235
235
  }
236
- throw err;
236
+ throw error;
237
237
  }
238
238
 
239
239
  return this.get(cls, id);
@@ -247,12 +247,12 @@ export class ElasticsearchModelService implements
247
247
  });
248
248
 
249
249
  while (search.hits.hits.length > 0) {
250
- for (const el of search.hits.hits) {
250
+ for (const hit of search.hits.hits) {
251
251
  try {
252
- yield this.postLoad(cls, el);
253
- } catch (err) {
254
- if (!(err instanceof NotFoundError)) {
255
- throw err;
252
+ yield this.postLoad(cls, hit);
253
+ } catch (error) {
254
+ if (!(error instanceof NotFoundError)) {
255
+ throw error;
256
256
  }
257
257
  }
258
258
  search = await this.client.scroll({
@@ -263,28 +263,30 @@ export class ElasticsearchModelService implements
263
263
  }
264
264
  }
265
265
 
266
- async processBulk<T extends ModelType>(cls: Class<T>, operations: BulkOp<T>[]): Promise<BulkResponse<EsBulkError>> {
266
+ async processBulk<T extends ModelType>(cls: Class<T>, operations: BulkOperation<T>[]): Promise<BulkResponse<EsBulkError>> {
267
267
 
268
268
  await ModelBulkUtil.preStore(cls, operations, this);
269
269
 
270
- const body = operations.reduce<(T | Partial<Record<'delete' | 'create' | 'index' | 'update', { _index: string, _id?: string }>> | { doc: T })[]>((acc, op) => {
271
-
272
- const esIdent = this.manager.getIdentity(asConstructable<T>((op.upsert ?? op.delete ?? op.insert ?? op.update ?? { constructor: cls })).constructor);
273
- const ident: { _index: string, _type?: unknown } = { _index: esIdent.index };
274
-
275
- if (op.delete) {
276
- acc.push({ delete: { ...ident, _id: op.delete.id } });
277
- } else if (op.insert) {
278
- const id = this.preUpdate(op.insert);
279
- acc.push({ create: { ...ident, _id: id } }, castTo(op.insert));
280
- } else if (op.upsert) {
281
- const id = this.preUpdate(op.upsert);
282
- acc.push({ index: { ...ident, _id: id } }, castTo(op.upsert));
283
- } else if (op.update) {
284
- const id = this.preUpdate(op.update);
285
- acc.push({ update: { ...ident, _id: id } }, { doc: op.update });
270
+ type BulkDoc = Partial<Record<'delete' | 'create' | 'index' | 'update', { _index: string, _id?: string }>>;
271
+ const body = operations.reduce<(T | BulkDoc | { doc: T })[]>((toRun, operation) => {
272
+
273
+ const core = (operation.upsert ?? operation.delete ?? operation.insert ?? operation.update ?? { constructor: cls });
274
+ const { index } = this.manager.getIdentity(asConstructable<T>(core).constructor);
275
+ const identity: { _index: string, _type?: unknown } = { _index: index };
276
+
277
+ if (operation.delete) {
278
+ toRun.push({ delete: { ...identity, _id: operation.delete.id } });
279
+ } else if (operation.insert) {
280
+ const id = this.preUpdate(operation.insert);
281
+ toRun.push({ create: { ...identity, _id: id } }, castTo(operation.insert));
282
+ } else if (operation.upsert) {
283
+ const id = this.preUpdate(operation.upsert);
284
+ toRun.push({ index: { ...identity, _id: id } }, castTo(operation.upsert));
285
+ } else if (operation.update) {
286
+ const id = this.preUpdate(operation.update);
287
+ toRun.push({ update: { ...identity, _id: id } }, { doc: operation.update });
286
288
  }
287
- return acc;
289
+ return toRun;
288
290
  }, []);
289
291
 
290
292
  const result = await this.client.bulk({
@@ -304,35 +306,35 @@ export class ElasticsearchModelService implements
304
306
  errors: []
305
307
  };
306
308
 
307
- type Count = keyof typeof out['counts'];
309
+ type CountProperty = keyof typeof out['counts'];
308
310
 
309
311
  for (let i = 0; i < result.items.length; i++) {
310
312
  const item = result.items[i];
311
- const [k] = TypedObject.keys(item);
312
- const v = item[k]!;
313
- if (v.error) {
313
+ const [key] = TypedObject.keys(item);
314
+ const responseItem = item[key]!;
315
+ if (responseItem.error) {
314
316
  out.errors.push({
315
- reason: v.error!.reason!,
316
- type: v.error!.type
317
+ reason: responseItem.error!.reason!,
318
+ type: responseItem.error!.type
317
319
  });
318
320
  out.counts.error += 1;
319
321
  } else {
320
- let sk: Count;
321
- switch (k) {
322
- case 'create': sk = 'insert'; break;
323
- case 'index': sk = operations[i].insert ? 'insert' : 'upsert'; break;
324
- case 'delete': case 'update': sk = k; break;
322
+ let property: CountProperty;
323
+ switch (key) {
324
+ case 'create': property = 'insert'; break;
325
+ case 'index': property = operations[i].insert ? 'insert' : 'upsert'; break;
326
+ case 'delete': case 'update': property = key; break;
325
327
  default: {
326
- throw new Error(`Unknown response key: ${k}`);
328
+ throw new Error(`Unknown response key: ${key}`);
327
329
  }
328
330
  }
329
331
 
330
- if (v.result === 'created') {
331
- out.insertedIds.set(i, v._id!);
332
- (operations[i].insert ?? operations[i].upsert)!.id = v._id!;
332
+ if (responseItem.result === 'created') {
333
+ out.insertedIds.set(i, responseItem._id!);
334
+ (operations[i].insert ?? operations[i].upsert)!.id = responseItem._id!;
333
335
  }
334
336
 
335
- out.counts[sk] += 1;
337
+ out.counts[property] += 1;
336
338
  }
337
339
  }
338
340
 
@@ -380,7 +382,7 @@ export class ElasticsearchModelService implements
380
382
  }
381
383
 
382
384
  async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>): AsyncIterable<T> {
383
- const cfg = ModelRegistryIndex.getIndex(cls, idx, ['sorted', 'unsorted']);
385
+ const config = ModelRegistryIndex.getIndex(cls, idx, ['sorted', 'unsorted']);
384
386
  let search = await this.execSearch<T>(cls, {
385
387
  scroll: '2m',
386
388
  size: 100,
@@ -388,16 +390,16 @@ export class ElasticsearchModelService implements
388
390
  ElasticsearchQueryUtil.extractWhereTermQuery(cls,
389
391
  ModelIndexedUtil.projectIndex(cls, idx, body, { emptySortValue: { $exists: true } }))
390
392
  ),
391
- sort: ElasticsearchQueryUtil.getSort(cfg.fields)
393
+ sort: ElasticsearchQueryUtil.getSort(config.fields)
392
394
  });
393
395
 
394
396
  while (search.hits.hits.length > 0) {
395
- for (const el of search.hits.hits) {
397
+ for (const hit of search.hits.hits) {
396
398
  try {
397
- yield this.postLoad(cls, el);
398
- } catch (err) {
399
- if (!(err instanceof NotFoundError)) {
400
- throw err;
399
+ yield this.postLoad(cls, hit);
400
+ } catch (error) {
401
+ if (!(error instanceof NotFoundError)) {
402
+ throw error;
401
403
  }
402
404
  }
403
405
  search = await this.client.scroll({
@@ -412,14 +414,14 @@ export class ElasticsearchModelService implements
412
414
  async query<T extends ModelType>(cls: Class<T>, query: PageableModelQuery<T>): Promise<T[]> {
413
415
  await QueryVerifier.verify(cls, query);
414
416
 
415
- const req = ElasticsearchQueryUtil.getSearchObject(cls, query, this.config.schemaConfig);
416
- const results = await this.execSearch(cls, req);
417
+ const search = ElasticsearchQueryUtil.getSearchObject(cls, query, this.config.schemaConfig);
418
+ const results = await this.execSearch(cls, search);
417
419
  const shouldRemoveIds = query.select && 'id' in query.select && !query.select.id;
418
- return Promise.all(results.hits.hits.map(m => this.postLoad(cls, m).then(v => {
420
+ return Promise.all(results.hits.hits.map(hit => this.postLoad(cls, hit).then(item => {
419
421
  if (shouldRemoveIds) {
420
- delete castTo<OptionalId<T>>(v).id;
422
+ delete castTo<OptionalId<T>>(item).id;
421
423
  }
422
- return v;
424
+ return item;
423
425
  })));
424
426
  }
425
427
 
@@ -431,8 +433,8 @@ export class ElasticsearchModelService implements
431
433
  async queryCount<T extends ModelType>(cls: Class<T>, query: Query<T>): Promise<number> {
432
434
  await QueryVerifier.verify(cls, query);
433
435
 
434
- const req = ElasticsearchQueryUtil.getSearchObject(cls, { ...query, limit: 0 }, this.config.schemaConfig);
435
- const result: number | { value: number } = (await this.execSearch(cls, req)).hits.total || { value: 0 };
436
+ const search = ElasticsearchQueryUtil.getSearchObject(cls, { ...query, limit: 0 }, this.config.schemaConfig);
437
+ const result: number | { value: number } = (await this.execSearch(cls, search)).hits.total || { value: 0 };
436
438
  return typeof result !== 'number' ? result.value : result;
437
439
  }
438
440
 
@@ -470,11 +472,11 @@ export class ElasticsearchModelService implements
470
472
  if (result.version_conflicts || result.updated === undefined || result.updated === 0) {
471
473
  throw new NotFoundError(cls, id);
472
474
  }
473
- } catch (err) {
474
- if (err instanceof errors.ResponseError && 'version_conflicts' in err.body) {
475
+ } catch (error) {
476
+ if (error instanceof errors.ResponseError && 'version_conflicts' in error.body) {
475
477
  throw new NotFoundError(cls, id);
476
478
  } else {
477
- throw err;
479
+ throw error;
478
480
  }
479
481
  }
480
482
 
@@ -484,10 +486,10 @@ export class ElasticsearchModelService implements
484
486
  async deleteByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T> = {}): Promise<number> {
485
487
  await QueryVerifier.verify(cls, query);
486
488
 
487
- const { sort: _, ...q } = ElasticsearchQueryUtil.getSearchObject(cls, query, this.config.schemaConfig, false);
489
+ const { sort: _, ...rest } = ElasticsearchQueryUtil.getSearchObject(cls, query, this.config.schemaConfig, false);
488
490
  const result = await this.client.deleteByQuery({
489
491
  ...this.manager.getIdentity(cls),
490
- ...q,
492
+ ...rest,
491
493
  refresh: true,
492
494
  });
493
495
  return result.deleted ?? 0;
@@ -514,34 +516,34 @@ export class ElasticsearchModelService implements
514
516
  async suggest<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {
515
517
  await QueryVerifier.verify(cls, query);
516
518
 
517
- const q = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, query);
518
- const search = ElasticsearchQueryUtil.getSearchObject(cls, q);
519
+ const resolvedQuery = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, query);
520
+ const search = ElasticsearchQueryUtil.getSearchObject(cls, resolvedQuery);
519
521
  const result = await this.execSearch(cls, search);
520
- const all = await Promise.all(result.hits.hits.map(x => this.postLoad(cls, x)));
521
- return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, all, (x, v) => v, query && query.limit);
522
+ const all = await Promise.all(result.hits.hits.map(hit => this.postLoad(cls, hit)));
523
+ return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, all, (_, value) => value, query && query.limit);
522
524
  }
523
525
 
524
526
  async suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
525
527
  await QueryVerifier.verify(cls, query);
526
528
 
527
- const q = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, {
529
+ const resolvedQuery = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, {
528
530
  select: castTo({ [field]: 1 }),
529
531
  ...query
530
532
  });
531
- const search = ElasticsearchQueryUtil.getSearchObject(cls, q);
533
+ const search = ElasticsearchQueryUtil.getSearchObject(cls, resolvedQuery);
532
534
  const result = await this.execSearch(cls, search);
533
- const all = await Promise.all(result.hits.hits.map(x => castTo<T>(({ [field]: field === 'id' ? x._id : x._source![field] }))));
534
- return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, all, x => x, query && query.limit);
535
+ const all = await Promise.all(result.hits.hits.map(hit => castTo<T>(({ [field]: field === 'id' ? hit._id : hit._source![field] }))));
536
+ return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, all, item => item, query && query.limit);
535
537
  }
536
538
 
537
539
  // Facet
538
540
  async facet<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<ModelQueryFacet[]> {
539
541
  await QueryVerifier.verify(cls, query);
540
542
 
541
- const q = ElasticsearchQueryUtil.getSearchObject(cls, query ?? {}, this.config.schemaConfig);
543
+ const resolvedSearch = ElasticsearchQueryUtil.getSearchObject(cls, query ?? {}, this.config.schemaConfig);
542
544
 
543
545
  const search: estypes.SearchRequest = {
544
- query: q.query ?? { ['match_all']: {} },
546
+ query: resolvedSearch.query ?? { ['match_all']: {} },
545
547
  aggs: { [field]: { terms: { field, size: 100 } } },
546
548
  size: 0
547
549
  };