@travetto/model-elasticsearch 6.0.1 → 7.0.0-rc.0
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 +2 -4
- package/package.json +6 -6
- package/src/config.ts +0 -3
- package/src/index-manager.ts +7 -8
- package/src/internal/query.ts +13 -11
- package/src/internal/schema.ts +6 -9
- package/src/service.ts +15 -22
- package/support/service.elasticsearch.ts +1 -1
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#L29) 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/types/crud.ts#L11)
|
|
@@ -42,7 +42,7 @@ export class Init {
|
|
|
42
42
|
}
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
where the [ElasticsearchModelConfig](https://github.com/travetto/travetto/tree/main/module/model-elasticsearch/src/config.ts#
|
|
45
|
+
where the [ElasticsearchModelConfig](https://github.com/travetto/travetto/tree/main/module/model-elasticsearch/src/config.ts#L10) is defined by:
|
|
46
46
|
|
|
47
47
|
**Code: Structure of ElasticsearchModelConfig**
|
|
48
48
|
```typescript
|
|
@@ -76,7 +76,6 @@ export class ElasticsearchModelConfig {
|
|
|
76
76
|
/**
|
|
77
77
|
* Base schema config for elasticsearch
|
|
78
78
|
*/
|
|
79
|
-
@Field(Object)
|
|
80
79
|
schemaConfig: EsSchemaConfig = {
|
|
81
80
|
caseSensitive: false
|
|
82
81
|
};
|
|
@@ -84,7 +83,6 @@ export class ElasticsearchModelConfig {
|
|
|
84
83
|
/**
|
|
85
84
|
* Base index create settings
|
|
86
85
|
*/
|
|
87
|
-
@Field(Object)
|
|
88
86
|
indexCreate = {
|
|
89
87
|
['number_of_replicas']: 0,
|
|
90
88
|
['number_of_shards']: 1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-elasticsearch",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-rc.0",
|
|
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": "^9.
|
|
31
|
-
"@travetto/cli": "^
|
|
32
|
-
"@travetto/config": "^
|
|
33
|
-
"@travetto/model": "^
|
|
34
|
-
"@travetto/model-query": "^
|
|
30
|
+
"@elastic/elasticsearch": "^9.2.0",
|
|
31
|
+
"@travetto/cli": "^7.0.0-rc.0",
|
|
32
|
+
"@travetto/config": "^7.0.0-rc.0",
|
|
33
|
+
"@travetto/model": "^7.0.0-rc.0",
|
|
34
|
+
"@travetto/model-query": "^7.0.0-rc.0"
|
|
35
35
|
},
|
|
36
36
|
"travetto": {
|
|
37
37
|
"displayName": "Elasticsearch Model Source"
|
package/src/config.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { TimeSpan } from '@travetto/runtime';
|
|
2
2
|
import { Config } from '@travetto/config';
|
|
3
|
-
import { Field } from '@travetto/schema';
|
|
4
3
|
|
|
5
4
|
import { EsSchemaConfig } from './internal/types.ts';
|
|
6
5
|
|
|
@@ -37,7 +36,6 @@ export class ElasticsearchModelConfig {
|
|
|
37
36
|
/**
|
|
38
37
|
* Base schema config for elasticsearch
|
|
39
38
|
*/
|
|
40
|
-
@Field(Object)
|
|
41
39
|
schemaConfig: EsSchemaConfig = {
|
|
42
40
|
caseSensitive: false
|
|
43
41
|
};
|
|
@@ -45,7 +43,6 @@ export class ElasticsearchModelConfig {
|
|
|
45
43
|
/**
|
|
46
44
|
* Base index create settings
|
|
47
45
|
*/
|
|
48
|
-
@Field(Object)
|
|
49
46
|
indexCreate = {
|
|
50
47
|
['number_of_replicas']: 0,
|
|
51
48
|
['number_of_shards']: 1
|
package/src/index-manager.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Client, estypes } from '@elastic/elasticsearch';
|
|
2
2
|
|
|
3
3
|
import { Class } from '@travetto/runtime';
|
|
4
|
-
import {
|
|
5
|
-
import { SchemaChange } from '@travetto/schema';
|
|
4
|
+
import { ModelRegistryIndex, ModelType, ModelStorageSupport } from '@travetto/model';
|
|
5
|
+
import { SchemaChange, SchemaRegistryIndex } from '@travetto/schema';
|
|
6
6
|
|
|
7
7
|
import { ElasticsearchModelConfig } from './config.ts';
|
|
8
8
|
import { ElasticsearchSchemaUtil } from './internal/schema.ts';
|
|
@@ -24,7 +24,7 @@ export class IndexManager implements ModelStorageSupport {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
getStore(cls: Class): string {
|
|
27
|
-
return
|
|
27
|
+
return ModelRegistryIndex.getStoreName(cls).toLowerCase().replace(/[^A-Za-z0-9_]+/g, '_');
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -98,13 +98,12 @@ export class IndexManager implements ModelStorageSupport {
|
|
|
98
98
|
* Build an index if missing
|
|
99
99
|
*/
|
|
100
100
|
async createIndexIfMissing(cls: Class): Promise<void> {
|
|
101
|
-
|
|
102
|
-
const ident = this.getIdentity(
|
|
101
|
+
const baseCls = SchemaRegistryIndex.getBaseClass(cls);
|
|
102
|
+
const ident = this.getIdentity(baseCls);
|
|
103
103
|
try {
|
|
104
104
|
await this.#client.search(ident);
|
|
105
|
-
console.debug('Index already exists, not creating', ident);
|
|
106
105
|
} catch {
|
|
107
|
-
await this.createIndex(
|
|
106
|
+
await this.createIndex(baseCls);
|
|
108
107
|
}
|
|
109
108
|
}
|
|
110
109
|
|
|
@@ -186,7 +185,7 @@ export class IndexManager implements ModelStorageSupport {
|
|
|
186
185
|
|
|
187
186
|
await this.#client.indices.putMapping({
|
|
188
187
|
index,
|
|
189
|
-
|
|
188
|
+
...schema,
|
|
190
189
|
});
|
|
191
190
|
}
|
|
192
191
|
}
|
package/src/internal/query.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { estypes } from '@elastic/elasticsearch';
|
|
|
2
2
|
|
|
3
3
|
import { castTo, Class, TypedObject } from '@travetto/runtime';
|
|
4
4
|
import { WhereClause, SelectClause, SortClause, Query, ModelQueryUtil } from '@travetto/model-query';
|
|
5
|
-
import { IndexConfig, ModelType,
|
|
6
|
-
import { DataUtil,
|
|
5
|
+
import { IndexConfig, ModelType, ModelRegistryIndex } from '@travetto/model';
|
|
6
|
+
import { DataUtil, SchemaRegistryIndex } from '@travetto/schema';
|
|
7
7
|
|
|
8
8
|
import { EsSchemaConfig } from './types.ts';
|
|
9
9
|
|
|
@@ -65,11 +65,11 @@ export class ElasticsearchQueryUtil {
|
|
|
65
65
|
*/
|
|
66
66
|
static extractWhereTermQuery<T>(cls: Class<T>, o: Record<string, unknown>, config?: EsSchemaConfig, path: string = ''): Record<string, unknown> {
|
|
67
67
|
const items = [];
|
|
68
|
-
const
|
|
68
|
+
const fields = SchemaRegistryIndex.getFieldMap(cls);
|
|
69
69
|
|
|
70
70
|
for (const key of TypedObject.keys(o)) {
|
|
71
71
|
const top = o[key];
|
|
72
|
-
const declaredSchema =
|
|
72
|
+
const declaredSchema = fields[key];
|
|
73
73
|
const declaredType = declaredSchema.type;
|
|
74
74
|
const sPath = declaredType === String ?
|
|
75
75
|
((key === 'id' && !path) ? '_id' : `${path}${key}`) :
|
|
@@ -205,11 +205,11 @@ export class ElasticsearchQueryUtil {
|
|
|
205
205
|
* @param search
|
|
206
206
|
*/
|
|
207
207
|
static getSearchQuery<T extends ModelType>(cls: Class<T>, search: Record<string, unknown>, checkExpiry = true): estypes.QueryDslQueryContainer {
|
|
208
|
-
const clauses = [];
|
|
208
|
+
const clauses: estypes.QueryDslQueryContainer[] = [];
|
|
209
209
|
if (search && Object.keys(search).length) {
|
|
210
210
|
clauses.push(search);
|
|
211
211
|
}
|
|
212
|
-
const { expiresAt
|
|
212
|
+
const { expiresAt } = ModelRegistryIndex.getConfig(cls);
|
|
213
213
|
if (checkExpiry && expiresAt) {
|
|
214
214
|
clauses.push({
|
|
215
215
|
bool: {
|
|
@@ -221,11 +221,13 @@ export class ElasticsearchQueryUtil {
|
|
|
221
221
|
},
|
|
222
222
|
});
|
|
223
223
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
224
|
+
const polymorphicConfig = SchemaRegistryIndex.getDiscriminatedConfig(cls);
|
|
225
|
+
if (polymorphicConfig) {
|
|
226
|
+
if (polymorphicConfig.discriminatedBase) {
|
|
227
|
+
clauses.push({ terms: { [polymorphicConfig.discriminatedField]: SchemaRegistryIndex.getDiscriminatedTypes(cls)! } });
|
|
228
|
+
} else {
|
|
229
|
+
clauses.push({ term: { [polymorphicConfig.discriminatedField]: { value: polymorphicConfig.discriminatedType } } });
|
|
230
|
+
}
|
|
229
231
|
}
|
|
230
232
|
return clauses.length === 0 ? {} :
|
|
231
233
|
clauses.length === 1 ? clauses[0] :
|
package/src/internal/schema.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { estypes } from '@elastic/elasticsearch';
|
|
2
2
|
|
|
3
3
|
import { Class, toConcrete } from '@travetto/runtime';
|
|
4
|
-
import {
|
|
5
|
-
import { Point, DataUtil, SchemaRegistry } from '@travetto/schema';
|
|
4
|
+
import { Point, DataUtil, SchemaRegistryIndex } from '@travetto/schema';
|
|
6
5
|
|
|
7
6
|
import { EsSchemaConfig } from './types.ts';
|
|
8
7
|
|
|
@@ -55,7 +54,7 @@ export class ElasticsearchSchemaUtil {
|
|
|
55
54
|
* Build one or more mappings depending on the polymorphic state
|
|
56
55
|
*/
|
|
57
56
|
static generateSchemaMapping(cls: Class, config?: EsSchemaConfig): estypes.MappingTypeMapping {
|
|
58
|
-
return
|
|
57
|
+
return SchemaRegistryIndex.getConfig(cls).discriminatedBase ?
|
|
59
58
|
this.generateAllMapping(cls, config) :
|
|
60
59
|
this.generateSingleMapping(cls, config);
|
|
61
60
|
}
|
|
@@ -64,7 +63,7 @@ export class ElasticsearchSchemaUtil {
|
|
|
64
63
|
* Generate all mappings
|
|
65
64
|
*/
|
|
66
65
|
static generateAllMapping(cls: Class, config?: EsSchemaConfig): estypes.MappingTypeMapping {
|
|
67
|
-
const allTypes =
|
|
66
|
+
const allTypes = SchemaRegistryIndex.getDiscriminatedClasses(cls);
|
|
68
67
|
return allTypes.reduce<estypes.MappingTypeMapping>((acc, schemaCls) => {
|
|
69
68
|
DataUtil.deepAssign(acc, this.generateSingleMapping(schemaCls, config));
|
|
70
69
|
return acc;
|
|
@@ -75,13 +74,11 @@ export class ElasticsearchSchemaUtil {
|
|
|
75
74
|
* Build a mapping for a given class
|
|
76
75
|
*/
|
|
77
76
|
static generateSingleMapping<T>(cls: Class<T>, config?: EsSchemaConfig): estypes.MappingTypeMapping {
|
|
78
|
-
const
|
|
77
|
+
const fields = SchemaRegistryIndex.getFieldMap(cls);
|
|
79
78
|
|
|
80
79
|
const props: Record<string, estypes.MappingProperty> = {};
|
|
81
80
|
|
|
82
|
-
for (const field of
|
|
83
|
-
const conf = schema.schema[field];
|
|
84
|
-
|
|
81
|
+
for (const [field, conf] of Object.entries(fields)) {
|
|
85
82
|
if (conf.type === PointImpl) {
|
|
86
83
|
props[field] = { type: 'geo_point' };
|
|
87
84
|
} else if (conf.type === Number) {
|
|
@@ -136,7 +133,7 @@ export class ElasticsearchSchemaUtil {
|
|
|
136
133
|
props[field] = { type: 'keyword', ...text };
|
|
137
134
|
} else if (conf.type === Object) {
|
|
138
135
|
props[field] = { type: 'object', dynamic: true };
|
|
139
|
-
} else if (
|
|
136
|
+
} else if (SchemaRegistryIndex.has(conf.type)) {
|
|
140
137
|
props[field] = {
|
|
141
138
|
type: conf.array ? 'nested' : 'object',
|
|
142
139
|
...this.generateSingleMapping(conf.type, config)
|
package/src/service.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { Client, errors, estypes } from '@elastic/elasticsearch';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
ModelCrudSupport, BulkOp, BulkResponse, ModelBulkSupport, ModelExpirySupport,
|
|
5
|
-
ModelIndexedSupport, ModelType, ModelStorageSupport, NotFoundError,
|
|
6
|
-
ModelCrudUtil, ModelIndexedUtil, ModelStorageUtil, ModelExpiryUtil, ModelBulkUtil
|
|
5
|
+
ModelIndexedSupport, ModelType, ModelStorageSupport, NotFoundError, ModelRegistryIndex, OptionalId,
|
|
6
|
+
ModelCrudUtil, ModelIndexedUtil, ModelStorageUtil, ModelExpiryUtil, ModelBulkUtil,
|
|
7
7
|
} from '@travetto/model';
|
|
8
8
|
import { ShutdownManager, type DeepPartial, type Class, castTo, asFull, TypedObject, asConstructable } from '@travetto/runtime';
|
|
9
9
|
import { SchemaChange, BindUtil } from '@travetto/schema';
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
ModelQueryFacet,
|
|
17
17
|
} from '@travetto/model-query';
|
|
18
18
|
|
|
19
|
-
|
|
20
19
|
import { ElasticsearchModelConfig } from './config.ts';
|
|
21
20
|
import { EsBulkError } from './internal/types.ts';
|
|
22
21
|
import { ElasticsearchQueryUtil } from './internal/query.ts';
|
|
@@ -95,7 +94,7 @@ export class ElasticsearchModelService implements
|
|
|
95
94
|
|
|
96
95
|
item = await ModelCrudUtil.load(cls, item);
|
|
97
96
|
|
|
98
|
-
const { expiresAt } =
|
|
97
|
+
const { expiresAt } = ModelRegistryIndex.getConfig(cls);
|
|
99
98
|
|
|
100
99
|
if (expiresAt) {
|
|
101
100
|
const expiry = ModelExpiryUtil.getExpiryState(cls, item);
|
|
@@ -167,7 +166,7 @@ export class ElasticsearchModelService implements
|
|
|
167
166
|
...this.manager.getIdentity(cls),
|
|
168
167
|
id,
|
|
169
168
|
refresh: true,
|
|
170
|
-
body: clean
|
|
169
|
+
body: castTo<T & { id: never }>(clean)
|
|
171
170
|
});
|
|
172
171
|
|
|
173
172
|
return this.postUpdate(clean, id);
|
|
@@ -184,7 +183,7 @@ export class ElasticsearchModelService implements
|
|
|
184
183
|
|
|
185
184
|
const id = this.preUpdate(o);
|
|
186
185
|
|
|
187
|
-
if (
|
|
186
|
+
if (ModelRegistryIndex.getConfig(cls).expiresAt) {
|
|
188
187
|
await this.get(cls, id);
|
|
189
188
|
}
|
|
190
189
|
|
|
@@ -193,7 +192,7 @@ export class ElasticsearchModelService implements
|
|
|
193
192
|
id,
|
|
194
193
|
op_type: 'index',
|
|
195
194
|
refresh: true,
|
|
196
|
-
body: o
|
|
195
|
+
body: castTo<T & { id: never }>(o)
|
|
197
196
|
});
|
|
198
197
|
|
|
199
198
|
return this.postUpdate(o, id);
|
|
@@ -209,10 +208,8 @@ export class ElasticsearchModelService implements
|
|
|
209
208
|
...this.manager.getIdentity(cls),
|
|
210
209
|
id,
|
|
211
210
|
refresh: true,
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
doc_as_upsert: true
|
|
215
|
-
}
|
|
211
|
+
doc: item,
|
|
212
|
+
doc_as_upsert: true
|
|
216
213
|
});
|
|
217
214
|
|
|
218
215
|
return this.postUpdate(item, id);
|
|
@@ -230,9 +227,7 @@ export class ElasticsearchModelService implements
|
|
|
230
227
|
...this.manager.getIdentity(cls),
|
|
231
228
|
id,
|
|
232
229
|
refresh: true,
|
|
233
|
-
|
|
234
|
-
script
|
|
235
|
-
}
|
|
230
|
+
script,
|
|
236
231
|
});
|
|
237
232
|
} catch (err) {
|
|
238
233
|
if (err instanceof Error && /document_missing_exception/.test(err.message)) {
|
|
@@ -385,7 +380,7 @@ export class ElasticsearchModelService implements
|
|
|
385
380
|
}
|
|
386
381
|
|
|
387
382
|
async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>): AsyncIterable<T> {
|
|
388
|
-
const cfg =
|
|
383
|
+
const cfg = ModelRegistryIndex.getIndex(cls, idx, ['sorted', 'unsorted']);
|
|
389
384
|
let search = await this.execSearch<T>(cls, {
|
|
390
385
|
scroll: '2m',
|
|
391
386
|
size: 100,
|
|
@@ -455,7 +450,7 @@ export class ElasticsearchModelService implements
|
|
|
455
450
|
}
|
|
456
451
|
query.where = where;
|
|
457
452
|
|
|
458
|
-
if (
|
|
453
|
+
if (ModelRegistryIndex.getConfig(cls).expiresAt) {
|
|
459
454
|
await this.get(cls, id);
|
|
460
455
|
}
|
|
461
456
|
|
|
@@ -545,17 +540,15 @@ export class ElasticsearchModelService implements
|
|
|
545
540
|
|
|
546
541
|
const q = ElasticsearchQueryUtil.getSearchObject(cls, query ?? {}, this.config.schemaConfig);
|
|
547
542
|
|
|
548
|
-
const search = {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
aggs: { [field]: { terms: { field, size: 100 } } }
|
|
552
|
-
},
|
|
543
|
+
const search: estypes.SearchRequest = {
|
|
544
|
+
query: q.query ?? { ['match_all']: {} },
|
|
545
|
+
aggs: { [field]: { terms: { field, size: 100 } } },
|
|
553
546
|
size: 0
|
|
554
547
|
};
|
|
555
548
|
|
|
556
549
|
const result = await this.execSearch(cls, search);
|
|
557
550
|
const { buckets } = castTo<estypes.AggregationsStringTermsAggregate>('buckets' in result.aggregations![field] ? result.aggregations![field] : { buckets: [] });
|
|
558
|
-
const out = Array.isArray(buckets) ? buckets.map(b => ({ key: b.key, count: b.doc_count })) : [];
|
|
551
|
+
const out = Array.isArray(buckets) ? buckets.map(b => ({ key: b.key!.toString(), count: b.doc_count })) : [];
|
|
559
552
|
return out;
|
|
560
553
|
}
|
|
561
554
|
}
|