@travetto/model-memory 8.0.0-alpha.0 → 8.0.0-alpha.10

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/package.json +8 -7
  3. package/src/service.ts +142 -57
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#L23)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-memory",
3
- "version": "8.0.0-alpha.0",
3
+ "version": "8.0.0-alpha.10",
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.0",
30
- "@travetto/di": "^8.0.0-alpha.0",
31
- "@travetto/model": "^8.0.0-alpha.0",
32
- "@travetto/schema": "^8.0.0-alpha.0"
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.10",
33
+ "@travetto/schema": "^8.0.0-alpha.10"
33
34
  },
34
35
  "peerDependencies": {
35
- "@travetto/cli": "^8.0.0-alpha.0",
36
- "@travetto/test": "^8.0.0-alpha.0"
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, type DeepPartial, castTo, type BinaryMetadata,
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
- import { Injectable } from '@travetto/di';
5
+ import { Injectable, PostConstruct } from '@travetto/di';
6
6
  import { Config } from '@travetto/config';
7
7
  import {
8
- type ModelType, type IndexConfig, type ModelCrudSupport, type ModelExpirySupport, type ModelStorageSupport, type ModelIndexedSupport,
9
- ModelRegistryIndex, NotFoundError, ExistsError, type OptionalId, type ModelBlobSupport,
10
- ModelCrudUtil, ModelExpiryUtil, ModelIndexedUtil, ModelStorageUtil
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: IndexConfig<T> | string, suffix?: string): string {
26
- return [cls.Ⲑid, typeof idx === 'string' ? idx : idx.name, suffix].filter(part => !!part).join(':');
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 ModelCrudSupport, ModelBlobSupport, ModelExpirySupport, ModelStorageSupport, ModelIndexedSupport {
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
- unsorted: new Map<string, Map<string, Set<string>>>()
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, ['sorted', 'unsorted'])) {
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 { key } = ModelIndexedUtil.computeIndexKey(cls, idx, castTo(item));
79
- this.#indices[idx.type].get(idxName)?.get(key)?.delete(id);
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, ['sorted', 'unsorted'])) {
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 { key, sort } = ModelIndexedUtil.computeIndexKey(cls, idx, castTo(item));
92
-
93
- if (idx.type === 'sorted') {
94
- this.#indices[idx.type].getOrInsert(idxName, new Map()).getOrInsert(key, new Map()).set(item.id, +sort!);
95
- } else {
96
- this.#indices[idx.type].getOrInsert(idxName, new Map()).getOrInsert(key, new Set()).add(item.id);
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,35 +140,58 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
112
140
  }
113
141
  }
114
142
 
115
- async #getIdByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<string> {
116
- const config = ModelRegistryIndex.getIndex(cls, idx, ['sorted', 'unsorted']);
117
- const { key, sort } = ModelIndexedUtil.computeIndexKey(cls, config, body);
118
- const index = this.#indices[config.type].get(indexName(cls, idx))?.get(key);
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
153
  if (index instanceof Map) {
122
- id = getFirstId(index, +sort!); // Grab first id
123
- } else {
154
+ id = getFirstId(index, computed.getSort()); // Grab first id
155
+ } else if (index instanceof Set) {
124
156
  id = getFirstId(index); // Grab first id
125
157
  }
126
158
  }
127
159
  if (id) {
128
160
  return id;
129
161
  }
130
- throw new NotFoundError(cls, key);
162
+ throw new NotFoundError(cls, computed.getKey({ sort: true }));
131
163
  }
132
164
 
133
- async postConstruct(): Promise<void> {
165
+ #getIndexIds<
166
+ T extends ModelType,
167
+ K extends KeyedIndexSelection<T>,
168
+ S extends SortedIndexSelection<T>
169
+ >(cls: Class<T>, idx: AllIndexes<T, K, S>, body: KeyedIndexBody<T, K>): string[] {
170
+ const computed = ModelIndexedComputedIndex.get(idx, body).validate();
171
+ if (!isModelIndexedIndex(idx)) {
172
+ throw new IndexNotSupported(cls, idx, 'Only ModelIndexed indices can be used with MemoryModelService');
173
+ }
174
+
175
+ const base = this.#indices[idx.type].get(indexName(cls, idx));
176
+ const index = base?.get(computed.getKey());
177
+ if (!index) {
178
+ return [];
179
+ } else if (index instanceof Map) {
180
+ return [...index.entries()].toSorted((a, b) => a[1] - b[1]).map(([id,]) => id);
181
+ } else {
182
+ return [...index];
183
+ }
184
+ }
185
+
186
+ @PostConstruct()
187
+ async initializeClient(): Promise<void> {
134
188
  await ModelStorageUtil.storageInitialization(this);
135
189
  ModelExpiryUtil.registerCull(this);
136
190
 
137
191
  for (const cls of ModelRegistryIndex.getClasses()) {
138
- for (const idx of ModelRegistryIndex.getConfig(cls).indices ?? []) {
139
- switch (idx.type) {
140
- case 'unique': {
141
- console.error('Unique indices are not supported for', { cls: cls.Ⲑid, idx: idx.name });
142
- break;
143
- }
192
+ for (const idx of Object.values(ModelRegistryIndex.getConfig(cls).indices ?? {})) {
193
+ if (!isModelIndexedIndex(idx)) {
194
+ console.error(`Indices of type ${idx.type} are not supported for`, { cls: cls.Ⲑid, name: idx.name, type: idx.type });
144
195
  }
145
196
  }
146
197
  }
@@ -284,13 +335,14 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
284
335
 
285
336
  async deleteStorage(): Promise<void> {
286
337
  this.#store.clear();
287
- this.#indices.sorted.clear();
288
- this.#indices.unsorted.clear();
338
+ for (const value of Object.values(this.#indices)) {
339
+ value.clear();
340
+ }
289
341
  }
290
342
 
291
343
  async upsertModel<T extends ModelType>(cls: Class<T>): Promise<void> {
292
- for (const idx of ModelRegistryIndex.getConfig(cls).indices ?? []) {
293
- if (idx.type === 'sorted' || idx.type === 'unsorted') {
344
+ for (const idx of ModelRegistryIndex.getIndices(cls)) {
345
+ if (isModelIndexedIndex(idx)) {
294
346
  this.#indices[idx.type].set(indexName(cls, idx), new Map());
295
347
  }
296
348
  }
@@ -306,33 +358,66 @@ export class MemoryModelService implements ModelCrudSupport, ModelBlobSupport, M
306
358
  }
307
359
 
308
360
  // Indexed
309
- async getByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<T> {
361
+ async getByIndex<
362
+ T extends ModelType,
363
+ K extends KeyedIndexSelection<T>,
364
+ S extends SortedIndexSelection<T>
365
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<T> {
310
366
  return this.get(cls, await this.#getIdByIndex(cls, idx, body));
367
+
311
368
  }
312
369
 
313
- async deleteByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: DeepPartial<T>): Promise<void> {
370
+ async deleteByIndex<
371
+ T extends ModelType,
372
+ K extends KeyedIndexSelection<T>,
373
+ S extends SortedIndexSelection<T>
374
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexBody<T, K, S>): Promise<void> {
314
375
  await this.delete(cls, await this.#getIdByIndex(cls, idx, body));
315
376
  }
316
377
 
317
- upsertByIndex<T extends ModelType>(cls: Class<T>, idx: string, body: OptionalId<T>): Promise<T> {
378
+ upsertByIndex<
379
+ T extends ModelType,
380
+ K extends KeyedIndexSelection<T>,
381
+ S extends SortedIndexSelection<T>
382
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: OptionalId<T>): Promise<T> {
318
383
  return ModelIndexedUtil.naiveUpsert(this, cls, idx, body);
319
384
  }
320
385
 
321
- async * listByIndex<T extends ModelType>(cls: Class<T>, idx: string, body?: DeepPartial<T>): AsyncIterable<T> {
322
- const config = ModelRegistryIndex.getIndex(cls, idx, ['sorted', 'unsorted']);
323
- const { key } = ModelIndexedUtil.computeIndexKey(cls, idx, body, { emptySortValue: null });
324
- const index = this.#indices[config.type].get(indexName(cls, idx))?.get(key);
386
+ updateByIndex<
387
+ T extends ModelType,
388
+ K extends KeyedIndexSelection<T>,
389
+ S extends SortedIndexSelection<T>
390
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: T): Promise<T> {
391
+ return ModelIndexedUtil.naiveUpdate(this, cls, idx, body);
392
+ }
325
393
 
326
- if (index) {
327
- if (index instanceof Set) {
328
- for (const id of index) {
329
- yield this.get(cls, id);
330
- }
331
- } else {
332
- for (const id of [...index.entries()].toSorted((a, b) => +a[1] - +b[1]).map(([a,]) => a)) {
333
- yield this.get(cls, id);
334
- }
335
- }
394
+ async updatePartialByIndex<
395
+ T extends ModelType,
396
+ K extends KeyedIndexSelection<T>,
397
+ S extends SortedIndexSelection<T>
398
+ >(cls: Class<T>, idx: SingleItemIndex<T, K, S>, body: FullKeyedIndexWithPartialBody<T, K, S>): Promise<T> {
399
+ const item = await ModelCrudUtil.naivePartialUpdate(cls, () => this.getByIndex(cls, idx, castTo(body)), castTo(body));
400
+ return this.update(cls, item);
401
+ }
402
+
403
+ async listByIndex<
404
+ T extends ModelType,
405
+ K extends KeyedIndexSelection<T>,
406
+ S extends SortedIndexSelection<T>
407
+ >(
408
+ cls: Class<T>,
409
+ idx: SortedIndex<T, K, S>,
410
+ body: KeyedIndexBody<T, K>,
411
+ options?: ListPageOptions
412
+ ): Promise<ListPageResult<T>> {
413
+ const ids = this.#getIndexIds(cls, idx, body);
414
+ const offset = options?.offset ? JSONUtil.fromBase64<number>(options.offset) : 0;
415
+ const limit = options?.limit ?? 100;
416
+
417
+ const items: T[] = [];
418
+ for (const id of ids.slice(offset, offset + limit)) {
419
+ items.push(await this.get(cls, id));
336
420
  }
421
+ return { items, nextOffset: items.length ? JSONUtil.toBase64(offset + items.length) : undefined };
337
422
  }
338
423
  }