@travetto/model-memory 8.0.0-alpha.1 → 8.0.0-alpha.11
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 +1 -1
- package/package.json +8 -7
- package/src/service.ts +163 -56
package/README.md
CHANGED
|
@@ -16,5 +16,5 @@ yarn add @travetto/model-memory
|
|
|
16
16
|
This module provides a memory-based implementation for the [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations."). Supported features:
|
|
17
17
|
* [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L11)
|
|
18
18
|
* [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10)
|
|
19
|
-
* [Indexed](https://github.com/travetto/travetto/tree/main/module/model/src/types/indexed.ts#L11)
|
|
20
19
|
* [Blob](https://github.com/travetto/travetto/tree/main/module/model/src/types/blob.ts#L8)
|
|
20
|
+
* [Indexed](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#L15)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-memory",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Memory backing for the travetto model module.",
|
|
6
6
|
"keywords": [
|
|
@@ -26,14 +26,15 @@
|
|
|
26
26
|
"directory": "module/model-memory"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@travetto/config": "^8.0.0-alpha.
|
|
30
|
-
"@travetto/di": "^8.0.0-alpha.
|
|
31
|
-
"@travetto/model": "^8.0.0-alpha.
|
|
32
|
-
"@travetto/
|
|
29
|
+
"@travetto/config": "^8.0.0-alpha.10",
|
|
30
|
+
"@travetto/di": "^8.0.0-alpha.10",
|
|
31
|
+
"@travetto/model": "^8.0.0-alpha.10",
|
|
32
|
+
"@travetto/model-indexed": "^8.0.0-alpha.11",
|
|
33
|
+
"@travetto/schema": "^8.0.0-alpha.10"
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
|
-
"@travetto/cli": "^8.0.0-alpha.
|
|
36
|
-
"@travetto/test": "^8.0.0-alpha.
|
|
36
|
+
"@travetto/cli": "^8.0.0-alpha.15",
|
|
37
|
+
"@travetto/test": "^8.0.0-alpha.10"
|
|
37
38
|
},
|
|
38
39
|
"peerDependenciesMeta": {
|
|
39
40
|
"@travetto/cli": {
|
package/src/service.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type Class, type TimeSpan,
|
|
3
|
-
type ByteRange, type BinaryType, BinaryUtil, type BinaryArray, JSONUtil, BinaryMetadataUtil
|
|
2
|
+
type Class, type TimeSpan, castTo, type BinaryMetadata,
|
|
3
|
+
type ByteRange, type BinaryType, BinaryUtil, type BinaryArray, JSONUtil, BinaryMetadataUtil,
|
|
4
4
|
} from '@travetto/runtime';
|
|
5
5
|
import { Injectable, PostConstruct } from '@travetto/di';
|
|
6
6
|
import { Config } from '@travetto/config';
|
|
7
7
|
import {
|
|
8
|
-
type ModelType, type
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
type ModelType, type ModelCrudSupport, type ModelExpirySupport, type ModelStorageSupport, ModelRegistryIndex,
|
|
9
|
+
NotFoundError, ExistsError, type OptionalId, type ModelBlobSupport, ModelCrudUtil, ModelExpiryUtil, ModelStorageUtil,
|
|
10
|
+
IndexNotSupported,
|
|
11
11
|
} from '@travetto/model';
|
|
12
|
+
import {
|
|
13
|
+
type ModelIndexedSupport, type KeyedIndexSelection, type KeyedIndexBody, type ListPageOptions, ModelIndexedUtil,
|
|
14
|
+
type SingleItemIndex, type SortedIndexSelection, type ListPageResult, type SortedIndex,
|
|
15
|
+
type AllIndexes, isModelIndexedIndex, type FullKeyedIndexBody, type FullKeyedIndexWithPartialBody, ModelIndexedComputedIndex,
|
|
16
|
+
} from '@travetto/model-indexed';
|
|
12
17
|
|
|
13
18
|
const ModelBlobNamespace = '__blobs';
|
|
14
19
|
const ModelBlobMetaNamespace = `${ModelBlobNamespace}_meta`;
|
|
@@ -22,8 +27,8 @@ export class MemoryModelConfig {
|
|
|
22
27
|
cullRate?: number | TimeSpan;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
function indexName<T extends ModelType>(cls: Class<T>, idx:
|
|
26
|
-
return [cls.Ⲑid,
|
|
30
|
+
function indexName<T extends ModelType>(cls: Class<T>, idx: AllIndexes<T>, suffix?: string): string {
|
|
31
|
+
return [cls.Ⲑid, idx.name, suffix].filter(part => !!part).join(':');
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
function getFirstId(data: Map<string, unknown> | Set<string>, value?: string | number): string | undefined {
|
|
@@ -40,13 +45,16 @@ function getFirstId(data: Map<string, unknown> | Set<string>, value?: string | n
|
|
|
40
45
|
* Standard in-memory support
|
|
41
46
|
*/
|
|
42
47
|
@Injectable()
|
|
43
|
-
export class MemoryModelService implements
|
|
48
|
+
export class MemoryModelService implements
|
|
49
|
+
ModelCrudSupport, ModelBlobSupport,
|
|
50
|
+
ModelExpirySupport, ModelStorageSupport,
|
|
51
|
+
ModelIndexedSupport {
|
|
44
52
|
|
|
45
53
|
#store = new Map<string, StoreType>();
|
|
46
54
|
#indices = {
|
|
47
|
-
sorted: new Map<string, Map<string, Map<string, number>>>(),
|
|
48
|
-
|
|
49
|
-
};
|
|
55
|
+
'indexed:sorted': new Map<string, Map<string, Map<string, number>>>(),
|
|
56
|
+
'indexed:keyed': new Map<string, Map<string, Set<string>>>(),
|
|
57
|
+
} as const;
|
|
50
58
|
|
|
51
59
|
idSource = ModelCrudUtil.uuidSource();
|
|
52
60
|
config: MemoryModelConfig;
|
|
@@ -73,10 +81,16 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
73
81
|
async #removeIndices<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
|
|
74
82
|
try {
|
|
75
83
|
const item = await this.get(cls, id);
|
|
76
|
-
for (const idx of ModelRegistryIndex.getIndices(cls
|
|
84
|
+
for (const idx of ModelRegistryIndex.getIndices(cls)) {
|
|
85
|
+
if (!isModelIndexedIndex(idx)) {
|
|
86
|
+
continue; // Only support ModelIndexed indices
|
|
87
|
+
}
|
|
77
88
|
const idxName = indexName(cls, idx);
|
|
78
|
-
const
|
|
79
|
-
|
|
89
|
+
const computed = ModelIndexedComputedIndex.get(idx, item).validate({ sort: true });
|
|
90
|
+
switch (idx.type) {
|
|
91
|
+
case 'indexed:sorted':
|
|
92
|
+
case 'indexed:keyed': this.#indices[idx.type].get(idxName)?.get(computed.getKey())?.delete(id); break;
|
|
93
|
+
}
|
|
80
94
|
}
|
|
81
95
|
} catch (error) {
|
|
82
96
|
if (!(error instanceof NotFoundError)) {
|
|
@@ -86,14 +100,28 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
async #writeIndices<T extends ModelType>(cls: Class<T>, item: T): Promise<void> {
|
|
89
|
-
for (const idx of ModelRegistryIndex.getIndices(cls
|
|
103
|
+
for (const idx of ModelRegistryIndex.getIndices(cls)) {
|
|
104
|
+
if (!isModelIndexedIndex(idx)) {
|
|
105
|
+
continue; // Only support ModelIndexed indices
|
|
106
|
+
}
|
|
90
107
|
const idxName = indexName(cls, idx);
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
const computed = ModelIndexedComputedIndex.get(idx, item).validate({ sort: true });
|
|
109
|
+
const key = computed.getKey();
|
|
110
|
+
switch (idx.type) {
|
|
111
|
+
case 'indexed:keyed': {
|
|
112
|
+
if (idx.unique) {
|
|
113
|
+
const existing = this.#indices[idx.type].get(idxName)?.get(key);
|
|
114
|
+
if (existing && existing.size > 0 && !existing.has(item.id)) {
|
|
115
|
+
throw new ExistsError(cls, key);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
this.#indices[idx.type].getOrInsert(idxName, new Map()).getOrInsert(key, new Set()).add(item.id);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case 'indexed:sorted': {
|
|
122
|
+
this.#indices[idx.type].getOrInsert(idxName, new Map()).getOrInsert(key, new Map()).set(item.id, computed.getSort());
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
97
125
|
}
|
|
98
126
|
}
|
|
99
127
|
}
|
|
@@ -112,22 +140,55 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
112
140
|
}
|
|
113
141
|
}
|
|
114
142
|
|
|
115
|
-
async #getIdByIndex<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
143
|
+
async #getIdByIndex<
|
|
144
|
+
T extends ModelType,
|
|
145
|
+
K extends KeyedIndexSelection<T>,
|
|
146
|
+
S extends SortedIndexSelection<T>
|
|
147
|
+
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<string> {
|
|
148
|
+
const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
|
|
149
|
+
|
|
150
|
+
const index = this.#indices[idx.type].get(indexName(cls, idx))?.get(computed.getKey());
|
|
119
151
|
let id: string | undefined;
|
|
120
152
|
if (index) {
|
|
121
|
-
if (
|
|
122
|
-
|
|
153
|
+
if (computed.idPart) {
|
|
154
|
+
if (index.has(computed.idPart.value)) {
|
|
155
|
+
id = computed.idPart.value;
|
|
156
|
+
} else {
|
|
157
|
+
throw new NotFoundError(cls, computed.getKey({ sort: true }));
|
|
158
|
+
}
|
|
123
159
|
} else {
|
|
124
|
-
|
|
160
|
+
if (index instanceof Map) {
|
|
161
|
+
id = getFirstId(index, computed.getSort()); // Grab first id
|
|
162
|
+
} else if (index instanceof Set) {
|
|
163
|
+
id = getFirstId(index); // Grab first id
|
|
164
|
+
}
|
|
125
165
|
}
|
|
126
166
|
}
|
|
127
167
|
if (id) {
|
|
128
168
|
return id;
|
|
129
169
|
}
|
|
130
|
-
throw new NotFoundError(cls,
|
|
170
|
+
throw new NotFoundError(cls, computed.getKey({ sort: true }));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#getIndexIds<
|
|
174
|
+
T extends ModelType,
|
|
175
|
+
K extends KeyedIndexSelection<T>,
|
|
176
|
+
S extends SortedIndexSelection<T>
|
|
177
|
+
>(cls: Class<T>, idx: AllIndexes<T, K, S>, body: KeyedIndexBody<T, K>): string[] {
|
|
178
|
+
const computed = ModelIndexedComputedIndex.get(idx, body).validate();
|
|
179
|
+
if (!isModelIndexedIndex(idx)) {
|
|
180
|
+
throw new IndexNotSupported(cls, idx, 'Only ModelIndexed indices can be used with MemoryModelService');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const base = this.#indices[idx.type].get(indexName(cls, idx));
|
|
184
|
+
const index = base?.get(computed.getKey());
|
|
185
|
+
if (!index) {
|
|
186
|
+
return [];
|
|
187
|
+
} else if (index instanceof Map) {
|
|
188
|
+
return [...index.entries()].toSorted((a, b) => a[1] - b[1]).map(([id,]) => id);
|
|
189
|
+
} else {
|
|
190
|
+
return [...index];
|
|
191
|
+
}
|
|
131
192
|
}
|
|
132
193
|
|
|
133
194
|
@PostConstruct()
|
|
@@ -136,12 +197,9 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
136
197
|
ModelExpiryUtil.registerCull(this);
|
|
137
198
|
|
|
138
199
|
for (const cls of ModelRegistryIndex.getClasses()) {
|
|
139
|
-
for (const idx of ModelRegistryIndex.getConfig(cls).indices ??
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
console.error('Unique indices are not supported for', { cls: cls.Ⲑid, idx: idx.name });
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
200
|
+
for (const idx of Object.values(ModelRegistryIndex.getConfig(cls).indices ?? {})) {
|
|
201
|
+
if (!isModelIndexedIndex(idx)) {
|
|
202
|
+
console.error(`Indices of type ${idx.type} are not supported for`, { cls: cls.Ⲑid, name: idx.name, type: idx.type });
|
|
145
203
|
}
|
|
146
204
|
}
|
|
147
205
|
}
|
|
@@ -285,13 +343,14 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
285
343
|
|
|
286
344
|
async deleteStorage(): Promise<void> {
|
|
287
345
|
this.#store.clear();
|
|
288
|
-
this.#indices
|
|
289
|
-
|
|
346
|
+
for (const value of Object.values(this.#indices)) {
|
|
347
|
+
value.clear();
|
|
348
|
+
}
|
|
290
349
|
}
|
|
291
350
|
|
|
292
351
|
async upsertModel<T extends ModelType>(cls: Class<T>): Promise<void> {
|
|
293
|
-
for (const idx of ModelRegistryIndex.
|
|
294
|
-
if (idx
|
|
352
|
+
for (const idx of ModelRegistryIndex.getIndices(cls)) {
|
|
353
|
+
if (isModelIndexedIndex(idx)) {
|
|
295
354
|
this.#indices[idx.type].set(indexName(cls, idx), new Map());
|
|
296
355
|
}
|
|
297
356
|
}
|
|
@@ -307,33 +366,81 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
307
366
|
}
|
|
308
367
|
|
|
309
368
|
// Indexed
|
|
310
|
-
async getByIndex<
|
|
369
|
+
async getByIndex<
|
|
370
|
+
T extends ModelType,
|
|
371
|
+
K extends KeyedIndexSelection<T>,
|
|
372
|
+
S extends SortedIndexSelection<T>
|
|
373
|
+
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<T> {
|
|
311
374
|
return this.get(cls, await this.#getIdByIndex(cls, idx, body));
|
|
375
|
+
|
|
312
376
|
}
|
|
313
377
|
|
|
314
|
-
async deleteByIndex<
|
|
378
|
+
async deleteByIndex<
|
|
379
|
+
T extends ModelType,
|
|
380
|
+
K extends KeyedIndexSelection<T>,
|
|
381
|
+
S extends SortedIndexSelection<T>
|
|
382
|
+
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<void> {
|
|
315
383
|
await this.delete(cls, await this.#getIdByIndex(cls, idx, body));
|
|
316
384
|
}
|
|
317
385
|
|
|
318
|
-
upsertByIndex<
|
|
386
|
+
upsertByIndex<
|
|
387
|
+
T extends ModelType,
|
|
388
|
+
K extends KeyedIndexSelection<T>,
|
|
389
|
+
S extends SortedIndexSelection<T>
|
|
390
|
+
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: OptionalId<T>): Promise<T> {
|
|
319
391
|
return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
|
|
320
392
|
}
|
|
321
393
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
394
|
+
updateByIndex<
|
|
395
|
+
T extends ModelType,
|
|
396
|
+
K extends KeyedIndexSelection<T>,
|
|
397
|
+
S extends SortedIndexSelection<T>
|
|
398
|
+
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: T): Promise<T> {
|
|
399
|
+
return ModelIndexedUtil.naiveUpdate(this, cls, idx, body);
|
|
400
|
+
}
|
|
326
401
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
402
|
+
async updatePartialByIndex<
|
|
403
|
+
T extends ModelType,
|
|
404
|
+
K extends KeyedIndexSelection<T>,
|
|
405
|
+
S extends SortedIndexSelection<T>
|
|
406
|
+
>(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexWithPartialBody<T, K, S>): Promise<T> {
|
|
407
|
+
const item = await ModelCrudUtil.naivePartialUpdate(cls, () => this.getByIndex(cls, idx, castTo(body)), castTo(body));
|
|
408
|
+
return this.update(cls, item);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async pageByIndex<
|
|
412
|
+
T extends ModelType,
|
|
413
|
+
K extends KeyedIndexSelection<T>,
|
|
414
|
+
S extends SortedIndexSelection<T>
|
|
415
|
+
>(
|
|
416
|
+
cls: Class<T>,
|
|
417
|
+
idx: SortedIndex<T, K, S>,
|
|
418
|
+
body: KeyedIndexBody<T, K>,
|
|
419
|
+
options?: ListPageOptions
|
|
420
|
+
): Promise<ListPageResult<T>> {
|
|
421
|
+
const ids = this.#getIndexIds(cls, idx, body);
|
|
422
|
+
const offset = options?.offset ? JSONUtil.fromBase64<number>(options.offset) : 0;
|
|
423
|
+
const limit = options?.limit ?? 100;
|
|
424
|
+
|
|
425
|
+
const items: T[] = [];
|
|
426
|
+
for (const id of ids.slice(offset, offset + limit)) {
|
|
427
|
+
items.push(await this.get(cls, id));
|
|
428
|
+
}
|
|
429
|
+
return { items, nextOffset: items.length ? JSONUtil.toBase64(offset + items.length) : undefined };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async * listByIndex<
|
|
433
|
+
T extends ModelType,
|
|
434
|
+
K extends KeyedIndexSelection<T>,
|
|
435
|
+
S extends SortedIndexSelection<T>
|
|
436
|
+
>(
|
|
437
|
+
cls: Class<T>,
|
|
438
|
+
idx: SortedIndex<T, K, S>,
|
|
439
|
+
body: KeyedIndexBody<T, K>,
|
|
440
|
+
): AsyncIterable<T> {
|
|
441
|
+
const ids = this.#getIndexIds(cls, idx, body);
|
|
442
|
+
for (const id of ids) {
|
|
443
|
+
yield await this.get(cls, id);
|
|
337
444
|
}
|
|
338
445
|
}
|
|
339
446
|
}
|