@travetto/model-memory 8.0.0-alpha.3 → 8.0.0-alpha.30
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 +3 -3
- package/__index__.ts +1 -1
- package/package.json +19 -18
- package/src/service.ts +253 -77
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ yarn add @travetto/model-memory
|
|
|
14
14
|
```
|
|
15
15
|
|
|
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
|
-
* [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L11)
|
|
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
17
|
* [Blob](https://github.com/travetto/travetto/tree/main/module/model/src/types/blob.ts#L8)
|
|
18
|
+
* [CRUD](https://github.com/travetto/travetto/tree/main/module/model/src/types/crud.ts#L10)
|
|
19
|
+
* [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/types/expiry.ts#L10)
|
|
20
|
+
* [Indexed](https://github.com/travetto/travetto/tree/main/module/model-indexed/src/types/service.ts#L21)
|
package/__index__.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './src/service.ts';
|
|
1
|
+
export * from './src/service.ts';
|
package/package.json
CHANGED
|
@@ -1,39 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-memory",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "8.0.0-alpha.30",
|
|
5
4
|
"description": "Memory backing for the travetto model module.",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"datastore",
|
|
8
7
|
"memory",
|
|
9
8
|
"schema",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
9
|
+
"travetto",
|
|
10
|
+
"typescript"
|
|
12
11
|
],
|
|
13
12
|
"homepage": "https://travetto.io",
|
|
14
13
|
"license": "MIT",
|
|
15
14
|
"author": {
|
|
16
|
-
"
|
|
17
|
-
"
|
|
15
|
+
"name": "Travetto Framework",
|
|
16
|
+
"email": "travetto.framework@gmail.com"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"url": "git+https://github.com/travetto/travetto.git",
|
|
20
|
+
"directory": "module/model-memory"
|
|
18
21
|
},
|
|
19
22
|
"files": [
|
|
20
23
|
"__index__.ts",
|
|
21
24
|
"src"
|
|
22
25
|
],
|
|
26
|
+
"type": "module",
|
|
23
27
|
"main": "__index__.ts",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"directory": "module/model-memory"
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
27
30
|
},
|
|
28
31
|
"dependencies": {
|
|
29
|
-
"@travetto/config": "^8.0.0-alpha.
|
|
30
|
-
"@travetto/di": "^8.0.0-alpha.
|
|
31
|
-
"@travetto/model": "^8.0.0-alpha.
|
|
32
|
-
"@travetto/
|
|
32
|
+
"@travetto/config": "^8.0.0-alpha.27",
|
|
33
|
+
"@travetto/di": "^8.0.0-alpha.24",
|
|
34
|
+
"@travetto/model": "^8.0.0-alpha.28",
|
|
35
|
+
"@travetto/model-indexed": "^8.0.0-alpha.30",
|
|
36
|
+
"@travetto/schema": "^8.0.0-alpha.27"
|
|
33
37
|
},
|
|
34
38
|
"peerDependencies": {
|
|
35
|
-
"@travetto/cli": "^8.0.0-alpha.
|
|
36
|
-
"@travetto/test": "^8.0.0-alpha.
|
|
39
|
+
"@travetto/cli": "^8.0.0-alpha.33",
|
|
40
|
+
"@travetto/test": "^8.0.0-alpha.26"
|
|
37
41
|
},
|
|
38
42
|
"peerDependenciesMeta": {
|
|
39
43
|
"@travetto/cli": {
|
|
@@ -45,8 +49,5 @@
|
|
|
45
49
|
},
|
|
46
50
|
"travetto": {
|
|
47
51
|
"displayName": "Memory Model Support"
|
|
48
|
-
},
|
|
49
|
-
"publishConfig": {
|
|
50
|
-
"access": "public"
|
|
51
52
|
}
|
|
52
53
|
}
|
package/src/service.ts
CHANGED
|
@@ -1,20 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Class, type TimeSpan, type DeepPartial, castTo, type BinaryMetadata,
|
|
3
|
-
type ByteRange, type BinaryType, BinaryUtil, type BinaryArray, JSONUtil, BinaryMetadataUtil
|
|
4
|
-
} from '@travetto/runtime';
|
|
5
|
-
import { Injectable, PostConstruct } from '@travetto/di';
|
|
6
1
|
import { Config } from '@travetto/config';
|
|
2
|
+
import { Injectable, PostConstruct } from '@travetto/di';
|
|
7
3
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
ExistsError,
|
|
5
|
+
IndexNotSupported,
|
|
6
|
+
type ModelBlobSupport,
|
|
7
|
+
type ModelCrudSupport,
|
|
8
|
+
ModelCrudUtil,
|
|
9
|
+
type ModelExpirySupport,
|
|
10
|
+
ModelExpiryUtil,
|
|
11
|
+
type ModelListOptions,
|
|
12
|
+
ModelRegistryIndex,
|
|
13
|
+
type ModelStorageSupport,
|
|
14
|
+
ModelStorageUtil,
|
|
15
|
+
type ModelType,
|
|
16
|
+
NotFoundError,
|
|
17
|
+
type OptionalId,
|
|
18
|
+
UniqueError
|
|
11
19
|
} from '@travetto/model';
|
|
20
|
+
import {
|
|
21
|
+
type AllIndexes,
|
|
22
|
+
type FullKeyedIndexBody,
|
|
23
|
+
type FullKeyedIndexWithPartialBody,
|
|
24
|
+
isModelIndexedIndex,
|
|
25
|
+
type KeyedIndexBody,
|
|
26
|
+
type KeyedIndexSelection,
|
|
27
|
+
ModelIndexedComputedIndex,
|
|
28
|
+
type ModelIndexedSearchOptions,
|
|
29
|
+
type ModelIndexedSupport,
|
|
30
|
+
ModelIndexedUtil,
|
|
31
|
+
type ModelPageOptions,
|
|
32
|
+
type ModelPageResult,
|
|
33
|
+
type SingleItemIndex,
|
|
34
|
+
type SortedIndex,
|
|
35
|
+
type SortedIndexSelection,
|
|
36
|
+
type SortedIndexSelectionType
|
|
37
|
+
} from '@travetto/model-indexed';
|
|
38
|
+
import {
|
|
39
|
+
type BinaryArray,
|
|
40
|
+
type BinaryMetadata,
|
|
41
|
+
BinaryMetadataUtil,
|
|
42
|
+
type BinaryType,
|
|
43
|
+
BinaryUtil,
|
|
44
|
+
type ByteRange,
|
|
45
|
+
type Class,
|
|
46
|
+
castTo,
|
|
47
|
+
JSONUtil,
|
|
48
|
+
type TimeSpan
|
|
49
|
+
} from '@travetto/runtime';
|
|
12
50
|
|
|
13
51
|
const ModelBlobNamespace = '__blobs';
|
|
14
52
|
const ModelBlobMetaNamespace = `${ModelBlobNamespace}_meta`;
|
|
15
53
|
|
|
16
54
|
type StoreType = Map<string, BinaryArray>;
|
|
17
55
|
|
|
56
|
+
const sortValue = (a: string | number, b: string | number): number =>
|
|
57
|
+
typeof a === 'number' && typeof b === 'number' ? a - b : typeof a === 'string' && typeof b === 'string' ? a.localeCompare(b) : 0;
|
|
58
|
+
|
|
18
59
|
@Config('model.memory')
|
|
19
60
|
export class MemoryModelConfig {
|
|
20
61
|
modifyStorage?: boolean = true;
|
|
@@ -22,8 +63,8 @@ export class MemoryModelConfig {
|
|
|
22
63
|
cullRate?: number | TimeSpan;
|
|
23
64
|
}
|
|
24
65
|
|
|
25
|
-
function indexName<T extends ModelType>(cls: Class<T>, idx:
|
|
26
|
-
return [cls.Ⲑid,
|
|
66
|
+
function indexName<T extends ModelType>(cls: Class<T>, idx: AllIndexes<T>, suffix?: string): string {
|
|
67
|
+
return [cls.Ⲑid, idx.name, suffix].filter(part => !!part).join(':');
|
|
27
68
|
}
|
|
28
69
|
|
|
29
70
|
function getFirstId(data: Map<string, unknown> | Set<string>, value?: string | number): string | undefined {
|
|
@@ -40,20 +81,25 @@ function getFirstId(data: Map<string, unknown> | Set<string>, value?: string | n
|
|
|
40
81
|
* Standard in-memory support
|
|
41
82
|
*/
|
|
42
83
|
@Injectable()
|
|
43
|
-
export class MemoryModelService
|
|
44
|
-
|
|
84
|
+
export class MemoryModelService
|
|
85
|
+
implements ModelCrudSupport, ModelBlobSupport, ModelExpirySupport, ModelStorageSupport, ModelIndexedSupport
|
|
86
|
+
{
|
|
45
87
|
#store = new Map<string, StoreType>();
|
|
46
88
|
#indices = {
|
|
47
|
-
sorted: new Map<string, Map<string, Map<string, number>>>(),
|
|
48
|
-
|
|
49
|
-
};
|
|
89
|
+
'indexed:sorted': new Map<string, Map<string, Map<string, number | string>>>(),
|
|
90
|
+
'indexed:keyed': new Map<string, Map<string, Set<string>>>()
|
|
91
|
+
} as const;
|
|
50
92
|
|
|
51
93
|
idSource = ModelCrudUtil.uuidSource();
|
|
52
94
|
config: MemoryModelConfig;
|
|
53
95
|
|
|
54
|
-
constructor(config: MemoryModelConfig) {
|
|
96
|
+
constructor(config: MemoryModelConfig) {
|
|
97
|
+
this.config = config;
|
|
98
|
+
}
|
|
55
99
|
|
|
56
|
-
get client(): Map<string, StoreType> {
|
|
100
|
+
get client(): Map<string, StoreType> {
|
|
101
|
+
return this.#store;
|
|
102
|
+
}
|
|
57
103
|
|
|
58
104
|
#getStore<T extends ModelType>(cls: Class<T> | string): StoreType {
|
|
59
105
|
const key = typeof cls === 'string' ? cls : ModelRegistryIndex.getStoreName(cls);
|
|
@@ -73,10 +119,18 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
73
119
|
async #removeIndices<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
|
|
74
120
|
try {
|
|
75
121
|
const item = await this.get(cls, id);
|
|
76
|
-
for (const idx of ModelRegistryIndex.getIndices(cls
|
|
122
|
+
for (const idx of ModelRegistryIndex.getIndices(cls)) {
|
|
123
|
+
if (!isModelIndexedIndex(idx)) {
|
|
124
|
+
continue; // Only support ModelIndexed indices
|
|
125
|
+
}
|
|
77
126
|
const idxName = indexName(cls, idx);
|
|
78
|
-
const
|
|
79
|
-
|
|
127
|
+
const computed = ModelIndexedComputedIndex.get(idx, item).validate({ sort: true });
|
|
128
|
+
switch (idx.type) {
|
|
129
|
+
case 'indexed:sorted':
|
|
130
|
+
case 'indexed:keyed':
|
|
131
|
+
this.#indices[idx.type].get(idxName)?.get(computed.getKey())?.delete(id);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
80
134
|
}
|
|
81
135
|
} catch (error) {
|
|
82
136
|
if (!(error instanceof NotFoundError)) {
|
|
@@ -86,14 +140,28 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
86
140
|
}
|
|
87
141
|
|
|
88
142
|
async #writeIndices<T extends ModelType>(cls: Class<T>, item: T): Promise<void> {
|
|
89
|
-
for (const idx of ModelRegistryIndex.getIndices(cls
|
|
143
|
+
for (const idx of ModelRegistryIndex.getIndices(cls)) {
|
|
144
|
+
if (!isModelIndexedIndex(idx)) {
|
|
145
|
+
continue; // Only support ModelIndexed indices
|
|
146
|
+
}
|
|
90
147
|
const idxName = indexName(cls, idx);
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
148
|
+
const computed = ModelIndexedComputedIndex.get(idx, item).validate({ sort: true });
|
|
149
|
+
const key = computed.getKey();
|
|
150
|
+
switch (idx.type) {
|
|
151
|
+
case 'indexed:keyed': {
|
|
152
|
+
if (idx.unique) {
|
|
153
|
+
const existing = this.#indices[idx.type].get(idxName)?.get(key);
|
|
154
|
+
if (existing && existing.size > 0 && !existing.has(item.id)) {
|
|
155
|
+
throw new UniqueError(cls, key);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
this.#indices[idx.type].getOrInsert(idxName, new Map()).getOrInsert(key, new Set()).add(item.id);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case 'indexed:sorted': {
|
|
162
|
+
this.#indices[idx.type].getOrInsert(idxName, new Map()).getOrInsert(key, new Map()).set(item.id, computed.getSort());
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
97
165
|
}
|
|
98
166
|
}
|
|
99
167
|
}
|
|
@@ -112,22 +180,82 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
112
180
|
}
|
|
113
181
|
}
|
|
114
182
|
|
|
115
|
-
async #getIdByIndex<T extends ModelType
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
183
|
+
async #getIdByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
184
|
+
cls: Class<T>,
|
|
185
|
+
idx: SingleItemIndex<T, K, S>,
|
|
186
|
+
body: FullKeyedIndexBody<T, K, S>
|
|
187
|
+
): Promise<string> {
|
|
188
|
+
const computed = ModelIndexedComputedIndex.get(idx, body).validate({ sort: true });
|
|
189
|
+
|
|
190
|
+
const index = this.#indices[idx.type].get(indexName(cls, idx))?.get(computed.getKey());
|
|
119
191
|
let id: string | undefined;
|
|
120
192
|
if (index) {
|
|
121
|
-
if (
|
|
122
|
-
|
|
193
|
+
if (computed.idPart) {
|
|
194
|
+
if (index.has(computed.idPart.value)) {
|
|
195
|
+
id = computed.idPart.value;
|
|
196
|
+
} else {
|
|
197
|
+
throw new NotFoundError(cls, computed.getKey({ sort: true }));
|
|
198
|
+
}
|
|
123
199
|
} else {
|
|
124
|
-
|
|
200
|
+
if (index instanceof Map) {
|
|
201
|
+
id = getFirstId(index, computed.getSort()); // Grab first id
|
|
202
|
+
} else if (index instanceof Set) {
|
|
203
|
+
id = getFirstId(index); // Grab first id
|
|
204
|
+
}
|
|
125
205
|
}
|
|
126
206
|
}
|
|
127
207
|
if (id) {
|
|
128
208
|
return id;
|
|
129
209
|
}
|
|
130
|
-
throw new NotFoundError(cls,
|
|
210
|
+
throw new NotFoundError(cls, computed.getKey({ sort: true }));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async *#iterateIds(ids: string[], options?: ModelListOptions & ModelPageOptions<number>): AsyncIterable<string[]> {
|
|
214
|
+
let offset = options && 'offset' in options ? (options.offset ?? 0) : 0;
|
|
215
|
+
const batchSize = options?.batchSizeHint ?? 100;
|
|
216
|
+
let produced = 0;
|
|
217
|
+
const maxCount = options?.limit ?? Number.MAX_SAFE_INTEGER;
|
|
218
|
+
while (offset < ids.length && produced < maxCount) {
|
|
219
|
+
if (options?.abort?.aborted) {
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
const end = Math.min(offset + batchSize, ids.length, maxCount - produced + offset);
|
|
223
|
+
const batch = ids.slice(offset, end);
|
|
224
|
+
if (batch.length) {
|
|
225
|
+
yield batch;
|
|
226
|
+
}
|
|
227
|
+
offset += batchSize;
|
|
228
|
+
produced += batch.length;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async *#getIndexIds<T extends ModelType>(
|
|
233
|
+
cls: Class<T>,
|
|
234
|
+
idx: AllIndexes<T>,
|
|
235
|
+
body: KeyedIndexBody<T>,
|
|
236
|
+
options?: ModelListOptions & ModelPageOptions<number>,
|
|
237
|
+
filterIndex?: (indexValue: string | number) => boolean
|
|
238
|
+
): AsyncIterable<string[]> {
|
|
239
|
+
const computed = ModelIndexedComputedIndex.get(idx, body).validate();
|
|
240
|
+
if (!isModelIndexedIndex(idx)) {
|
|
241
|
+
throw new IndexNotSupported(cls, idx, 'Only ModelIndexed indices can be used with MemoryModelService');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const base = this.#indices[idx.type].get(indexName(cls, idx));
|
|
245
|
+
const index = base?.get(computed.getKey());
|
|
246
|
+
let ids: string[];
|
|
247
|
+
if (!index) {
|
|
248
|
+
ids = [];
|
|
249
|
+
} else if (index instanceof Map) {
|
|
250
|
+
ids = [...index.entries()]
|
|
251
|
+
.filter(([, sort]) => (filterIndex ? filterIndex(sort) : true))
|
|
252
|
+
.sort((a, b) => sortValue(a[1], b[1]))
|
|
253
|
+
.map(([id]) => id);
|
|
254
|
+
} else {
|
|
255
|
+
ids = [...index];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
yield* this.#iterateIds(ids, options);
|
|
131
259
|
}
|
|
132
260
|
|
|
133
261
|
@PostConstruct()
|
|
@@ -136,12 +264,9 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
136
264
|
ModelExpiryUtil.registerCull(this);
|
|
137
265
|
|
|
138
266
|
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
|
-
}
|
|
267
|
+
for (const idx of Object.values(ModelRegistryIndex.getConfig(cls).indices ?? {})) {
|
|
268
|
+
if (!isModelIndexedIndex(idx)) {
|
|
269
|
+
console.error(`Indices of type ${idx.type} are not supported for`, { cls: cls.Ⲑid, name: idx.name, type: idx.type });
|
|
145
270
|
}
|
|
146
271
|
}
|
|
147
272
|
}
|
|
@@ -203,21 +328,21 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
203
328
|
await this.#persist(cls, where, 'remove');
|
|
204
329
|
}
|
|
205
330
|
|
|
206
|
-
async *
|
|
207
|
-
for (const
|
|
208
|
-
|
|
209
|
-
yield await this.get(cls, id);
|
|
210
|
-
} catch (error) {
|
|
211
|
-
if (!(error instanceof NotFoundError)) {
|
|
212
|
-
throw error;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
331
|
+
async *list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]> {
|
|
332
|
+
for await (const batch of this.#iterateIds([...this.#getStore(cls).keys()], options)) {
|
|
333
|
+
yield ModelCrudUtil.filterOutNotFound(batch.map(id => this.get(cls, id)));
|
|
215
334
|
}
|
|
216
335
|
}
|
|
217
336
|
|
|
218
337
|
// Blob Support
|
|
219
338
|
async upsertBlob(location: string, input: BinaryType, metadata?: BinaryMetadata, overwrite = true): Promise<void> {
|
|
220
|
-
if (
|
|
339
|
+
if (
|
|
340
|
+
!overwrite &&
|
|
341
|
+
(await this.getBlobMetadata(location).then(
|
|
342
|
+
() => true,
|
|
343
|
+
() => false
|
|
344
|
+
))
|
|
345
|
+
) {
|
|
221
346
|
return;
|
|
222
347
|
}
|
|
223
348
|
const resolved = await BinaryMetadataUtil.compute(input, metadata);
|
|
@@ -228,7 +353,6 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
228
353
|
}
|
|
229
354
|
|
|
230
355
|
async getBlob(location: string, range?: ByteRange): Promise<Blob> {
|
|
231
|
-
|
|
232
356
|
const blobs = this.#find(ModelBlobNamespace, location, 'notfound');
|
|
233
357
|
|
|
234
358
|
let data = blobs.get(location)!;
|
|
@@ -274,24 +398,24 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
274
398
|
store.delete(key);
|
|
275
399
|
deleted += 1;
|
|
276
400
|
}
|
|
277
|
-
} catch {
|
|
401
|
+
} catch {} // Do not let a single error stop the process
|
|
278
402
|
}
|
|
279
403
|
return deleted;
|
|
280
404
|
}
|
|
281
405
|
|
|
282
406
|
// Storage Support
|
|
283
|
-
async createStorage(): Promise<void> {
|
|
284
|
-
}
|
|
407
|
+
async createStorage(): Promise<void> {}
|
|
285
408
|
|
|
286
409
|
async deleteStorage(): Promise<void> {
|
|
287
410
|
this.#store.clear();
|
|
288
|
-
this.#indices
|
|
289
|
-
|
|
411
|
+
for (const value of Object.values(this.#indices)) {
|
|
412
|
+
value.clear();
|
|
413
|
+
}
|
|
290
414
|
}
|
|
291
415
|
|
|
292
416
|
async upsertModel<T extends ModelType>(cls: Class<T>): Promise<void> {
|
|
293
|
-
for (const idx of ModelRegistryIndex.
|
|
294
|
-
if (idx
|
|
417
|
+
for (const idx of ModelRegistryIndex.getIndices(cls)) {
|
|
418
|
+
if (isModelIndexedIndex(idx)) {
|
|
295
419
|
this.#indices[idx.type].set(indexName(cls, idx), new Map());
|
|
296
420
|
}
|
|
297
421
|
}
|
|
@@ -307,33 +431,85 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
|
|
|
307
431
|
}
|
|
308
432
|
|
|
309
433
|
// Indexed
|
|
310
|
-
async getByIndex<T extends ModelType
|
|
434
|
+
async getByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
435
|
+
cls: Class<T>,
|
|
436
|
+
idx: SingleItemIndex<T, K, S>,
|
|
437
|
+
body: FullKeyedIndexBody<T, K, S>
|
|
438
|
+
): Promise<T> {
|
|
311
439
|
return this.get(cls, await this.#getIdByIndex(cls, idx, body));
|
|
312
440
|
}
|
|
313
441
|
|
|
314
|
-
async deleteByIndex<T extends ModelType
|
|
442
|
+
async deleteByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
443
|
+
cls: Class<T>,
|
|
444
|
+
idx: SingleItemIndex<T, K, S>,
|
|
445
|
+
body: FullKeyedIndexBody<T, K, S>
|
|
446
|
+
): Promise<void> {
|
|
315
447
|
await this.delete(cls, await this.#getIdByIndex(cls, idx, body));
|
|
316
448
|
}
|
|
317
449
|
|
|
318
|
-
upsertByIndex<T extends ModelType
|
|
450
|
+
upsertByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
451
|
+
cls: Class<T>,
|
|
452
|
+
idx: SingleItemIndex<T, K, S>,
|
|
453
|
+
body: OptionalId<T>
|
|
454
|
+
): Promise<T> {
|
|
319
455
|
return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
|
|
320
456
|
}
|
|
321
457
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
458
|
+
updateByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
459
|
+
cls: Class<T>,
|
|
460
|
+
idx: SingleItemIndex<T, K, S>,
|
|
461
|
+
body: T
|
|
462
|
+
): Promise<T> {
|
|
463
|
+
return ModelIndexedUtil.naiveUpdate(this, cls, idx, body);
|
|
464
|
+
}
|
|
326
465
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
466
|
+
async updatePartialByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
467
|
+
cls: Class<T>,
|
|
468
|
+
idx: SingleItemIndex<T, K, S>,
|
|
469
|
+
body: FullKeyedIndexWithPartialBody<T, K, S>
|
|
470
|
+
): Promise<T> {
|
|
471
|
+
const item = await ModelCrudUtil.naivePartialUpdate(cls, () => this.getByIndex(cls, idx, castTo(body)), castTo(body));
|
|
472
|
+
return this.update(cls, item);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async pageByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
476
|
+
cls: Class<T>,
|
|
477
|
+
idx: SortedIndex<T, K, S>,
|
|
478
|
+
body: KeyedIndexBody<T, K>,
|
|
479
|
+
options?: ModelPageOptions
|
|
480
|
+
): Promise<ModelPageResult<T>> {
|
|
481
|
+
const offset = options?.offset ? JSONUtil.fromBase64<number>(options.offset) : 0;
|
|
482
|
+
let produced = 0;
|
|
483
|
+
|
|
484
|
+
const items: T[] = [];
|
|
485
|
+
for await (const batch of this.#getIndexIds(cls, idx, body, { limit: 100, ...options, offset })) {
|
|
486
|
+
produced += batch.length;
|
|
487
|
+
items.push(...(await ModelCrudUtil.filterOutNotFound(batch.map(id => this.get(cls, id)))));
|
|
337
488
|
}
|
|
489
|
+
return { items, nextOffset: items.length ? JSONUtil.toBase64(offset + produced) : undefined };
|
|
338
490
|
}
|
|
339
|
-
|
|
491
|
+
|
|
492
|
+
async *listByIndex<T extends ModelType, K extends KeyedIndexSelection<T>, S extends SortedIndexSelection<T>>(
|
|
493
|
+
cls: Class<T>,
|
|
494
|
+
idx: SortedIndex<T, K, S>,
|
|
495
|
+
body: KeyedIndexBody<T, K>,
|
|
496
|
+
options?: ModelListOptions
|
|
497
|
+
): AsyncIterable<T[]> {
|
|
498
|
+
for await (const batch of this.#getIndexIds(cls, idx, body, options)) {
|
|
499
|
+
yield ModelCrudUtil.filterOutNotFound(batch.map(id => this.get(cls, id)));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async suggestByIndex<
|
|
504
|
+
T extends ModelType,
|
|
505
|
+
S extends SortedIndexSelection<T>,
|
|
506
|
+
K extends KeyedIndexSelection<T>,
|
|
507
|
+
B extends SortedIndexSelectionType<T, S> & string
|
|
508
|
+
>(cls: Class<T>, idx: SortedIndex<T, K, S>, body: KeyedIndexBody<T, K>, prefix: B, options?: ModelIndexedSearchOptions): Promise<T[]> {
|
|
509
|
+
const items: T[] = [];
|
|
510
|
+
for await (const batch of this.#getIndexIds(cls, idx, body, options, id => typeof id === 'string' && id.startsWith(prefix))) {
|
|
511
|
+
items.push(...(await ModelCrudUtil.filterOutNotFound(batch.map(id => this.get(cls, id)))));
|
|
512
|
+
}
|
|
513
|
+
return items;
|
|
514
|
+
}
|
|
515
|
+
}
|