@travetto/model-memory 5.0.0-rc.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 +20 -0
- package/__index__.ts +1 -0
- package/package.json +51 -0
- package/src/service.ts +342 -0
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!-- This file was generated by @travetto/doc and should not be modified directly -->
|
|
2
|
+
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/model-memory/DOC.tsx and execute "npx trv doc" to rebuild -->
|
|
3
|
+
# Memory Model Support
|
|
4
|
+
|
|
5
|
+
## Memory backing for the travetto model module.
|
|
6
|
+
|
|
7
|
+
**Install: @travetto/model-memory**
|
|
8
|
+
```bash
|
|
9
|
+
npm install @travetto/model-memory
|
|
10
|
+
|
|
11
|
+
# or
|
|
12
|
+
|
|
13
|
+
yarn add @travetto/model-memory
|
|
14
|
+
```
|
|
15
|
+
|
|
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/service/crud.ts#L11)
|
|
18
|
+
* [Expiry](https://github.com/travetto/travetto/tree/main/module/model/src/service/expiry.ts#L11)
|
|
19
|
+
* [Indexed](https://github.com/travetto/travetto/tree/main/module/model/src/service/indexed.ts#L12)
|
|
20
|
+
* [Blob](https://github.com/travetto/travetto/tree/main/module/model/src/service/blob.ts#L8)
|
package/__index__.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './src/service';
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@travetto/model-memory",
|
|
3
|
+
"version": "5.0.0-rc.11",
|
|
4
|
+
"description": "Memory backing for the travetto model module.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"datastore",
|
|
7
|
+
"memory",
|
|
8
|
+
"schema",
|
|
9
|
+
"typescript",
|
|
10
|
+
"travetto"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://travetto.io",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": {
|
|
15
|
+
"email": "travetto.framework@gmail.com",
|
|
16
|
+
"name": "Travetto Framework"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"__index__.ts",
|
|
20
|
+
"src"
|
|
21
|
+
],
|
|
22
|
+
"main": "__index__.ts",
|
|
23
|
+
"repository": {
|
|
24
|
+
"url": "https://github.com/travetto/travetto.git",
|
|
25
|
+
"directory": "module/model-memory"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@travetto/config": "^5.0.0-rc.11",
|
|
29
|
+
"@travetto/di": "^5.0.0-rc.10",
|
|
30
|
+
"@travetto/model": "^5.0.0-rc.11",
|
|
31
|
+
"@travetto/schema": "^5.0.0-rc.11"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@travetto/cli": "^5.0.0-rc.11",
|
|
35
|
+
"@travetto/test": "^5.0.0-rc.10"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"@travetto/cli": {
|
|
39
|
+
"optional": true
|
|
40
|
+
},
|
|
41
|
+
"@travetto/test": {
|
|
42
|
+
"optional": true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"travetto": {
|
|
46
|
+
"displayName": "Memory Model Support"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/service.ts
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import { buffer as toBuffer } from 'node:stream/consumers';
|
|
3
|
+
|
|
4
|
+
import { Class, TimeSpan, DeepPartial, castTo, BlobMeta, ByteRange, BinaryInput, BinaryUtil } from '@travetto/runtime';
|
|
5
|
+
import { Injectable } from '@travetto/di';
|
|
6
|
+
import { Config } from '@travetto/config';
|
|
7
|
+
import {
|
|
8
|
+
ModelType, IndexConfig, ModelCrudSupport, ModelExpirySupport, ModelStorageSupport, ModelIndexedSupport,
|
|
9
|
+
ModelRegistry, NotFoundError, ExistsError, OptionalId, ModelBlobSupport, ModelBlobUtil
|
|
10
|
+
} from '@travetto/model';
|
|
11
|
+
|
|
12
|
+
import { ModelCrudUtil } from '@travetto/model/src/internal/service/crud';
|
|
13
|
+
import { ModelExpiryUtil } from '@travetto/model/src/internal/service/expiry';
|
|
14
|
+
import { ModelIndexedUtil } from '@travetto/model/src/internal/service/indexed';
|
|
15
|
+
import { ModelStorageUtil } from '@travetto/model/src/internal/service/storage';
|
|
16
|
+
import { MODEL_BLOB, ModelBlobNamespace } from '@travetto/model/src/internal/service/blob';
|
|
17
|
+
|
|
18
|
+
const ModelBlobMetaNamespace = `${ModelBlobNamespace}_meta`;
|
|
19
|
+
|
|
20
|
+
type StoreType = Map<string, Buffer>;
|
|
21
|
+
|
|
22
|
+
@Config('model.memory')
|
|
23
|
+
export class MemoryModelConfig {
|
|
24
|
+
autoCreate?: boolean = true;
|
|
25
|
+
namespace?: string;
|
|
26
|
+
cullRate?: number | TimeSpan;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function indexName<T extends ModelType>(cls: Class<T>, idx: IndexConfig<T> | string, suffix?: string): string {
|
|
30
|
+
return [cls.Ⲑid, typeof idx === 'string' ? idx : idx.name, suffix].filter(x => !!x).join(':');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getFirstId(data: Map<string, unknown> | Set<string>, value?: string | number): string | undefined {
|
|
34
|
+
let id: string | undefined;
|
|
35
|
+
if (data instanceof Set) {
|
|
36
|
+
id = data.values().next().value;
|
|
37
|
+
} else {
|
|
38
|
+
id = [...data.entries()].find(([k, v]) => value === undefined || v === value)?.[0];
|
|
39
|
+
}
|
|
40
|
+
return id;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Standard in-memory support
|
|
45
|
+
*/
|
|
46
|
+
@Injectable()
|
|
47
|
+
export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, ModelExpirySupport, ModelStorageSupport, ModelIndexedSupport {
|
|
48
|
+
|
|
49
|
+
#store = new Map<string, StoreType>();
|
|
50
|
+
#indices = {
|
|
51
|
+
sorted: new Map<string, Map<string, Map<string, number>>>(),
|
|
52
|
+
unsorted: new Map<string, Map<string, Set<string>>>()
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
idSource = ModelCrudUtil.uuidSource();
|
|
56
|
+
get client(): Map<string, StoreType> { return this.#store; }
|
|
57
|
+
|
|
58
|
+
constructor(public readonly config: MemoryModelConfig) { }
|
|
59
|
+
|
|
60
|
+
#getStore<T extends ModelType>(cls: Class<T> | string): StoreType {
|
|
61
|
+
const key = typeof cls === 'string' ? cls : ModelRegistry.getStore(cls);
|
|
62
|
+
if (!this.#store.has(key)) {
|
|
63
|
+
this.#store.set(key, new Map());
|
|
64
|
+
}
|
|
65
|
+
return this.#store.get(key)!;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#find<T extends ModelType>(cls: Class<T> | string, id?: string, errorState?: 'data' | 'notfound'): StoreType {
|
|
69
|
+
const store = this.#getStore(cls);
|
|
70
|
+
|
|
71
|
+
if (id && errorState && (errorState === 'notfound' ? !store.has(id) : store.has(id))) {
|
|
72
|
+
throw errorState === 'notfound' ? new NotFoundError(cls, id) : new ExistsError(cls, id);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return store;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async #removeIndices<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
|
|
79
|
+
try {
|
|
80
|
+
const item = await this.get(cls, id);
|
|
81
|
+
for (const idx of ModelRegistry.getIndices(cls, ['sorted', 'unsorted'])) {
|
|
82
|
+
const idxName = indexName(cls, idx);
|
|
83
|
+
const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, castTo(item));
|
|
84
|
+
this.#indices[idx.type].get(idxName)?.get(key)?.delete(id);
|
|
85
|
+
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if (!(err instanceof NotFoundError)) {
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async #writeIndices<T extends ModelType>(cls: Class<T>, item: T): Promise<void> {
|
|
94
|
+
for (const idx of ModelRegistry.getIndices(cls, ['sorted', 'unsorted'])) {
|
|
95
|
+
const idxName = indexName(cls, idx);
|
|
96
|
+
const { key, sort } = ModelIndexedUtil.computeIndexKey(cls, idx, castTo(item));
|
|
97
|
+
let index = this.#indices[idx.type].get(idxName)?.get(key);
|
|
98
|
+
|
|
99
|
+
if (!index) {
|
|
100
|
+
if (!this.#indices[idx.type].has(idxName)) {
|
|
101
|
+
this.#indices[idx.type].set(idxName, new Map());
|
|
102
|
+
}
|
|
103
|
+
if (idx.type === 'sorted') {
|
|
104
|
+
this.#indices[idx.type].get(idxName)!.set(key, index = new Map());
|
|
105
|
+
} else {
|
|
106
|
+
this.#indices[idx.type].get(idxName)!.set(key, index = new Set());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (index instanceof Map) {
|
|
111
|
+
index?.set(item.id, +sort!);
|
|
112
|
+
} else {
|
|
113
|
+
index?.add(item.id);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async #persist<T extends ModelType>(cls: Class<T>, item: T, action: 'remove'): Promise<void>;
|
|
119
|
+
async #persist<T extends ModelType>(cls: Class<T>, item: T, action: 'write'): Promise<T>;
|
|
120
|
+
async #persist<T extends ModelType>(cls: Class<T>, item: T, action: 'write' | 'remove'): Promise<T | void> {
|
|
121
|
+
const store = this.#getStore(cls);
|
|
122
|
+
await this.#removeIndices(cls, item.id);
|
|
123
|
+
if (action === 'write') {
|
|
124
|
+
store.set(item.id, Buffer.from(JSON.stringify(item)));
|
|
125
|
+
await this.#writeIndices(cls, item);
|
|
126
|
+
return item;
|
|
127
|
+
} else {
|
|
128
|
+
store.delete(item.id);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async #getIdByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<string> {
|
|
133
|
+
const config = ModelRegistry.getIndex(cls, idx, ['sorted', 'unsorted']);
|
|
134
|
+
const { key, sort } = ModelIndexedUtil.computeIndexKey(cls, config, body);
|
|
135
|
+
const index = this.#indices[config.type].get(indexName(cls, idx))?.get(key);
|
|
136
|
+
let id: string | undefined;
|
|
137
|
+
if (index) {
|
|
138
|
+
if (index instanceof Map) {
|
|
139
|
+
id = getFirstId(index, +sort!); // Grab first id
|
|
140
|
+
} else {
|
|
141
|
+
id = getFirstId(index); // Grab first id
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (id) {
|
|
145
|
+
return id;
|
|
146
|
+
}
|
|
147
|
+
throw new NotFoundError(cls, key);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async postConstruct(): Promise<void> {
|
|
151
|
+
await ModelStorageUtil.registerModelChangeListener(this);
|
|
152
|
+
ModelExpiryUtil.registerCull(this);
|
|
153
|
+
|
|
154
|
+
for (const el of ModelRegistry.getClasses()) {
|
|
155
|
+
for (const idx of ModelRegistry.get(el).indices ?? []) {
|
|
156
|
+
switch (idx.type) {
|
|
157
|
+
case 'unique': {
|
|
158
|
+
console.error('Unique indices are not supported for', { cls: el.Ⲑid, idx: idx.name });
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// CRUD Support
|
|
167
|
+
async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
|
|
168
|
+
const store = this.#getStore(cls);
|
|
169
|
+
if (store.has(id)) {
|
|
170
|
+
const res = await ModelCrudUtil.load(cls, store.get(id)!);
|
|
171
|
+
if (res) {
|
|
172
|
+
if (ModelRegistry.get(cls).expiresAt) {
|
|
173
|
+
if (!ModelExpiryUtil.getExpiryState(cls, res).expired) {
|
|
174
|
+
return res;
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
return res;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
throw new NotFoundError(cls, id);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async create<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
|
|
185
|
+
if (!item.id) {
|
|
186
|
+
item.id = this.idSource.create();
|
|
187
|
+
}
|
|
188
|
+
this.#find(cls, item.id, 'data');
|
|
189
|
+
return await this.upsert(cls, item);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async update<T extends ModelType>(cls: Class<T>, item: T): Promise<T> {
|
|
193
|
+
await this.get(cls, item.id);
|
|
194
|
+
return await this.upsert(cls, item);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async upsert<T extends ModelType>(cls: Class<T>, item: OptionalId<T>): Promise<T> {
|
|
198
|
+
const store = this.#getStore(cls);
|
|
199
|
+
if (item.id && store.has(item.id)) {
|
|
200
|
+
await ModelCrudUtil.load(cls, store.get(item.id)!, 'exists');
|
|
201
|
+
}
|
|
202
|
+
const prepped = await ModelCrudUtil.preStore(cls, item, this);
|
|
203
|
+
return await this.#persist(cls, prepped, 'write');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async updatePartial<T extends ModelType>(cls: Class<T>, item: Partial<T> & { id: string }, view?: string): Promise<T> {
|
|
207
|
+
const id = item.id;
|
|
208
|
+
const clean = await ModelCrudUtil.naivePartialUpdate(cls, item, view, () => this.get(cls, id));
|
|
209
|
+
return await this.#persist(cls, clean, 'write');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async delete<T extends ModelType>(cls: Class<T>, id: string): Promise<void> {
|
|
213
|
+
const store = this.#getStore(cls);
|
|
214
|
+
if (!store.has(id)) {
|
|
215
|
+
throw new NotFoundError(cls, id);
|
|
216
|
+
}
|
|
217
|
+
await ModelCrudUtil.load(cls, store.get(id)!);
|
|
218
|
+
const where: ModelType = { id };
|
|
219
|
+
await this.#persist(cls, where, 'remove');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async * list<T extends ModelType>(cls: Class<T>): AsyncIterable<T> {
|
|
223
|
+
for (const id of this.#getStore(cls).keys()) {
|
|
224
|
+
try {
|
|
225
|
+
yield await this.get(cls, id);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
if (!(err instanceof NotFoundError)) {
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Blob Support
|
|
235
|
+
async insertBlob(location: string, input: BinaryInput, meta?: BlobMeta, errorIfExisting = false): Promise<void> {
|
|
236
|
+
await this.describeBlob(location);
|
|
237
|
+
if (errorIfExisting) {
|
|
238
|
+
throw new ExistsError(ModelBlobNamespace, location);
|
|
239
|
+
}
|
|
240
|
+
return this.upsertBlob(location, input, meta);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async upsertBlob(location: string, input: BinaryInput, meta?: BlobMeta): Promise<void> {
|
|
244
|
+
const [stream, blobMeta] = await ModelBlobUtil.getInput(input, meta);
|
|
245
|
+
const blobs = this.#getStore(ModelBlobNamespace);
|
|
246
|
+
const metaContent = this.#getStore(ModelBlobMetaNamespace);
|
|
247
|
+
metaContent.set(location, Buffer.from(JSON.stringify(blobMeta)));
|
|
248
|
+
blobs.set(location, await toBuffer(stream));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async getBlob(location: string, range?: ByteRange): Promise<Blob> {
|
|
252
|
+
const blobs = this.#find(ModelBlobNamespace, location, 'notfound');
|
|
253
|
+
let buffer = blobs.get(location)!;
|
|
254
|
+
const final = range ? ModelBlobUtil.enforceRange(range, buffer.length) : undefined;
|
|
255
|
+
if (final) {
|
|
256
|
+
buffer = Buffer.from(buffer.subarray(final.start, final.end + 1));
|
|
257
|
+
}
|
|
258
|
+
const meta = await this.describeBlob(location);
|
|
259
|
+
return BinaryUtil.readableBlob(() => Readable.from(buffer), { ...meta, range: final });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async describeBlob(location: string): Promise<BlobMeta> {
|
|
263
|
+
const metaContent = this.#find(ModelBlobMetaNamespace, location, 'notfound');
|
|
264
|
+
const meta: BlobMeta = JSON.parse(metaContent.get(location)!.toString('utf8'));
|
|
265
|
+
return meta;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async deleteBlob(location: string): Promise<void> {
|
|
269
|
+
const blobs = this.#getStore(ModelBlobNamespace);
|
|
270
|
+
const metaContent = this.#getStore(ModelBlobMetaNamespace);
|
|
271
|
+
if (blobs.has(location)) {
|
|
272
|
+
blobs.delete(location);
|
|
273
|
+
metaContent.delete(location);
|
|
274
|
+
} else {
|
|
275
|
+
throw new NotFoundError(ModelBlobNamespace, location);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Expiry
|
|
280
|
+
async deleteExpired<T extends ModelType>(cls: Class<T>): Promise<number> {
|
|
281
|
+
return ModelExpiryUtil.naiveDeleteExpired(this, cls);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Storage Support
|
|
285
|
+
async createStorage(): Promise<void> {
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async deleteStorage(): Promise<void> {
|
|
289
|
+
this.#store.clear();
|
|
290
|
+
this.#indices.sorted.clear();
|
|
291
|
+
this.#indices.unsorted.clear();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
async createModel<T extends ModelType>(cls: Class<T>): Promise<void> {
|
|
296
|
+
for (const idx of ModelRegistry.get(cls).indices ?? []) {
|
|
297
|
+
if (idx.type === 'sorted' || idx.type === 'unsorted') {
|
|
298
|
+
this.#indices[idx.type].set(indexName(cls, idx), new Map());
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async truncateModel<T extends ModelType>(cls: Class<T>): Promise<void> {
|
|
304
|
+
if (cls === MODEL_BLOB) {
|
|
305
|
+
this.#getStore(ModelBlobNamespace).clear();
|
|
306
|
+
this.#getStore(ModelBlobMetaNamespace).clear();
|
|
307
|
+
} else {
|
|
308
|
+
this.#getStore(cls).clear();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Indexed
|
|
313
|
+
async getByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<T> {
|
|
314
|
+
return this.get(cls, await this.#getIdByIndex(cls, idx, body));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> {
|
|
318
|
+
await this.delete(cls, await this.#getIdByIndex(cls, idx, body));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
upsertByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: OptionalId<T>): Promise<T> {
|
|
322
|
+
return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>): AsyncIterable<T> {
|
|
326
|
+
const config = ModelRegistry.getIndex(cls, idx, ['sorted', 'unsorted']);
|
|
327
|
+
const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, body, { emptySortValue: null });
|
|
328
|
+
const index = this.#indices[config.type].get(indexName(cls, idx))?.get(key);
|
|
329
|
+
|
|
330
|
+
if (index) {
|
|
331
|
+
if (index instanceof Set) {
|
|
332
|
+
for (const id of index) {
|
|
333
|
+
yield this.get(cls, id);
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
for (const id of [...index.entries()].sort((a, b) => +a[1] - +b[1]).map(([a, b]) => a)) {
|
|
337
|
+
yield this.get(cls, id);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|