@travetto/model-dynamodb 8.0.0-alpha.11 → 8.0.0-alpha.12
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/package.json +6 -6
- package/src/service.ts +62 -90
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-dynamodb",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DynamoDB backing for the travetto model module.",
|
|
6
6
|
"keywords": [
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
"directory": "module/model-dynamodb"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@aws-sdk/client-dynamodb": "^3.
|
|
30
|
-
"@travetto/config": "^8.0.0-alpha.
|
|
31
|
-
"@travetto/model": "^8.0.0-alpha.
|
|
32
|
-
"@travetto/model-indexed": "^8.0.0-alpha.
|
|
29
|
+
"@aws-sdk/client-dynamodb": "^3.1024.0",
|
|
30
|
+
"@travetto/config": "^8.0.0-alpha.11",
|
|
31
|
+
"@travetto/model": "^8.0.0-alpha.11",
|
|
32
|
+
"@travetto/model-indexed": "^8.0.0-alpha.12"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@travetto/cli": "^8.0.0-alpha.
|
|
35
|
+
"@travetto/cli": "^8.0.0-alpha.16"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"@travetto/cli": {
|
package/src/service.ts
CHANGED
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
type ModelCrudSupport, type ModelExpirySupport, ModelRegistryIndex, type ModelStorageSupport,
|
|
7
7
|
type ModelType, NotFoundError, ExistsError, type OptionalId, ModelCrudUtil,
|
|
8
8
|
ModelExpiryUtil, ModelStorageUtil,
|
|
9
|
+
type ModelListOptions,
|
|
9
10
|
} from '@travetto/model';
|
|
10
11
|
import {
|
|
11
12
|
isModelIndexedIndex, ModelIndexedUtil, type KeyedIndexBody, type KeyedIndexSelection,
|
|
12
|
-
type
|
|
13
|
+
type ModelPageOptions, type ModelPageResult, type ModelIndexedSupport, type SingleItemIndex,
|
|
13
14
|
type FullKeyedIndexBody, type FullKeyedIndexWithPartialBody, type SortedIndex, type SortedIndexSelection,
|
|
14
15
|
ModelIndexedComputedIndex
|
|
15
16
|
} from '@travetto/model-indexed';
|
|
@@ -42,51 +43,59 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
42
43
|
return table;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
async * #
|
|
46
|
-
T
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
body: KeyedIndexBody<T, K>,
|
|
53
|
-
options?: ListPageOptions<Record<string, AttributeValue>>
|
|
54
|
-
): AsyncIterable<QueryCommandOutput & { LastEvaluatedOffset?: string }> {
|
|
55
|
-
ModelCrudUtil.ensureNotSubType(cls);
|
|
56
|
-
const computed = ModelIndexedComputedIndex.get(idx, body).validate();
|
|
57
|
-
const safeName = DynamoDBUtil.toSafeName(idx.name);
|
|
58
|
-
const expression = { [`:${safeName}`]: getKey(computed) };
|
|
59
|
-
const limit = options?.limit ?? 100;
|
|
60
|
-
|
|
46
|
+
async * #scanCollection<T extends ModelType>(
|
|
47
|
+
cls: Class<T>,
|
|
48
|
+
query: (batchSize: number, lastKey: Record<string, AttributeValue> | undefined) => Promise<QueryCommandOutput>,
|
|
49
|
+
options?: ModelListOptions & ModelPageOptions<Record<string, AttributeValue>>,
|
|
50
|
+
): AsyncIterable<{ items: T[], lastKey?: Record<string, AttributeValue> }> {
|
|
51
|
+
const batchSize = options?.batchSizeHint ?? 100;
|
|
52
|
+
const limit = options?.limit ?? Number.MAX_SAFE_INTEGER;
|
|
61
53
|
let startKey = options?.offset ?? undefined;
|
|
62
54
|
let produced = 0;
|
|
63
|
-
|
|
64
55
|
do {
|
|
65
56
|
const remaining = limit - produced;
|
|
66
|
-
const batch = await
|
|
67
|
-
TableName: this.#resolveTable(cls),
|
|
68
|
-
IndexName: safeName,
|
|
69
|
-
ProjectionExpression: 'body',
|
|
70
|
-
KeyConditionExpression: `${safeName}__ = :${safeName}`,
|
|
71
|
-
ExpressionAttributeValues: expression,
|
|
72
|
-
Limit: Math.min(remaining, 100),
|
|
73
|
-
ExclusiveStartKey: startKey,
|
|
74
|
-
});
|
|
57
|
+
const batch = await query(Math.min(remaining, batchSize), startKey);
|
|
75
58
|
|
|
76
59
|
if (batch.Count && batch.Items) {
|
|
77
60
|
produced += batch.Count;
|
|
78
61
|
|
|
79
|
-
|
|
80
|
-
const items = batch.Items.slice(0, remaining);
|
|
81
|
-
yield { ...batch, Items: items, };
|
|
82
|
-
} else {
|
|
83
|
-
yield batch;
|
|
84
|
-
}
|
|
62
|
+
const items = (produced > limit) ? batch.Items.slice(0, remaining) : batch.Items;
|
|
85
63
|
startKey = batch.LastEvaluatedKey;
|
|
64
|
+
yield {
|
|
65
|
+
items: await ModelCrudUtil.filterOutNotFound(
|
|
66
|
+
items.map(item => DynamoDBUtil.loadAndCheckExpiry(cls, item.body.S!))),
|
|
67
|
+
lastKey: startKey
|
|
68
|
+
};
|
|
86
69
|
} else {
|
|
87
70
|
startKey = undefined;
|
|
88
71
|
}
|
|
89
|
-
} while (startKey && produced < limit);
|
|
72
|
+
} while (startKey && produced < limit && !(options?.abort?.aborted));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async * #scanIndex<
|
|
76
|
+
T extends ModelType,
|
|
77
|
+
K extends KeyedIndexSelection<T>,
|
|
78
|
+
S extends SortedIndexSelection<T>
|
|
79
|
+
>(
|
|
80
|
+
cls: Class<T>,
|
|
81
|
+
idx: SortedIndex<T, K, S>,
|
|
82
|
+
body: KeyedIndexBody<T, K>,
|
|
83
|
+
options?: ModelPageOptions<Record<string, AttributeValue>> & ModelListOptions
|
|
84
|
+
): AsyncIterable<{ items: T[], lastKey?: Record<string, AttributeValue> }> {
|
|
85
|
+
ModelCrudUtil.ensureNotSubType(cls);
|
|
86
|
+
const computed = ModelIndexedComputedIndex.get(idx, body).validate();
|
|
87
|
+
const safeName = DynamoDBUtil.toSafeName(idx.name);
|
|
88
|
+
const expression = { [`:${safeName}`]: getKey(computed) };
|
|
89
|
+
|
|
90
|
+
yield* this.#scanCollection(cls, (batchSize, lastKey) => this.client.query({
|
|
91
|
+
TableName: this.#resolveTable(cls),
|
|
92
|
+
IndexName: safeName,
|
|
93
|
+
ProjectionExpression: 'body',
|
|
94
|
+
KeyConditionExpression: `${safeName}__ = :${safeName}`,
|
|
95
|
+
ExpressionAttributeValues: expression,
|
|
96
|
+
Limit: batchSize,
|
|
97
|
+
ExclusiveStartKey: lastKey,
|
|
98
|
+
}), options);
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
async #getIdByIndex<
|
|
@@ -375,32 +384,13 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
375
384
|
}
|
|
376
385
|
}
|
|
377
386
|
|
|
378
|
-
async * list<T extends ModelType>(cls: Class<T
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
if (batch.Count && batch.Items) {
|
|
388
|
-
for (const item of batch.Items) {
|
|
389
|
-
try {
|
|
390
|
-
yield await DynamoDBUtil.loadAndCheckExpiry(cls, item.body.S!);
|
|
391
|
-
} catch (error) {
|
|
392
|
-
if (!(error instanceof NotFoundError)) {
|
|
393
|
-
throw error;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
if (!batch.Count || !batch.LastEvaluatedKey) {
|
|
400
|
-
done = true;
|
|
401
|
-
} else {
|
|
402
|
-
token = batch.LastEvaluatedKey;
|
|
403
|
-
}
|
|
387
|
+
async * list<T extends ModelType>(cls: Class<T>, options?: ModelListOptions): AsyncIterable<T[]> {
|
|
388
|
+
for await (const { items } of this.#scanCollection(cls, (batchSize, lastKey) => this.client.scan({
|
|
389
|
+
TableName: this.#resolveTable(cls),
|
|
390
|
+
ExclusiveStartKey: lastKey,
|
|
391
|
+
Limit: batchSize
|
|
392
|
+
}), options)) {
|
|
393
|
+
yield items;
|
|
404
394
|
}
|
|
405
395
|
}
|
|
406
396
|
|
|
@@ -459,28 +449,17 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
459
449
|
cls: Class<T>,
|
|
460
450
|
idx: SortedIndex<T, K, S>,
|
|
461
451
|
body: KeyedIndexBody<T, K>,
|
|
462
|
-
options?:
|
|
463
|
-
): Promise<
|
|
464
|
-
const
|
|
452
|
+
options?: ModelPageOptions,
|
|
453
|
+
): Promise<ModelPageResult<T>> {
|
|
454
|
+
const output: T[] = [];
|
|
465
455
|
const offset = options?.offset ? JSONUtil.fromBase64<Record<string, AttributeValue>>(options.offset) : undefined;
|
|
466
|
-
for await (const
|
|
467
|
-
|
|
468
|
-
try {
|
|
469
|
-
items.push(await DynamoDBUtil.loadAndCheckExpiry(cls, item.body.S!));
|
|
470
|
-
if (options?.limit && items.length >= options.limit) {
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
473
|
-
} catch (error) {
|
|
474
|
-
if (!(error instanceof NotFoundError)) {
|
|
475
|
-
throw error;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
456
|
+
for await (const { items } of this.#scanIndex(cls, idx, body, { limit: 100, ...options, offset })) {
|
|
457
|
+
output.push(...items);
|
|
479
458
|
}
|
|
480
459
|
|
|
481
460
|
let nextOffset;
|
|
482
|
-
if (
|
|
483
|
-
const last: T =
|
|
461
|
+
if (output.length) {
|
|
462
|
+
const last: T = output.at(-1)!;
|
|
484
463
|
const computed = ModelIndexedComputedIndex.get(idx, last).validate();
|
|
485
464
|
const safeName = DynamoDBUtil.toSafeName(idx.name);
|
|
486
465
|
nextOffset = JSONUtil.toBase64({
|
|
@@ -490,7 +469,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
490
469
|
});
|
|
491
470
|
}
|
|
492
471
|
|
|
493
|
-
return { items, nextOffset };
|
|
472
|
+
return { items: output, nextOffset };
|
|
494
473
|
}
|
|
495
474
|
|
|
496
475
|
async * listByIndex<
|
|
@@ -501,17 +480,10 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
|
|
|
501
480
|
cls: Class<T>,
|
|
502
481
|
idx: SortedIndex<T, K, S>,
|
|
503
482
|
body: KeyedIndexBody<T, K>,
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
yield await DynamoDBUtil.loadAndCheckExpiry(cls, item.body.S!);
|
|
509
|
-
} catch (error) {
|
|
510
|
-
if (!(error instanceof NotFoundError)) {
|
|
511
|
-
throw error;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
}
|
|
483
|
+
options?: ModelListOptions
|
|
484
|
+
): AsyncIterable<T[]> {
|
|
485
|
+
for await (const { items } of this.#scanIndex(cls, idx, body, options)) {
|
|
486
|
+
yield items;
|
|
515
487
|
}
|
|
516
488
|
}
|
|
517
489
|
}
|