@travetto/model-dynamodb 8.0.0-alpha.11 → 8.0.0-alpha.13

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 (2) hide show
  1. package/package.json +6 -6
  2. 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.11",
3
+ "version": "8.0.0-alpha.13",
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.1019.0",
30
- "@travetto/config": "^8.0.0-alpha.10",
31
- "@travetto/model": "^8.0.0-alpha.10",
32
- "@travetto/model-indexed": "^8.0.0-alpha.11"
29
+ "@aws-sdk/client-dynamodb": "^3.1027.0",
30
+ "@travetto/config": "^8.0.0-alpha.12",
31
+ "@travetto/model": "^8.0.0-alpha.12",
32
+ "@travetto/model-indexed": "^8.0.0-alpha.13"
33
33
  },
34
34
  "peerDependencies": {
35
- "@travetto/cli": "^8.0.0-alpha.15"
35
+ "@travetto/cli": "^8.0.0-alpha.17"
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 ListPageOptions, type ListPageResult, type ModelIndexedSupport, type SingleItemIndex,
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 * #scanIndex<
46
- T extends ModelType,
47
- K extends KeyedIndexSelection<T>,
48
- S extends SortedIndexSelection<T>
49
- >(
50
- cls: Class,
51
- idx: SortedIndex<T, K, S>,
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 this.client.query({
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
- if (produced > limit) {
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>): AsyncIterable<T> {
379
- let done = false;
380
- let token: Record<string, AttributeValue> | undefined;
381
- while (!done) {
382
- const batch = await this.client.scan({
383
- TableName: this.#resolveTable(cls),
384
- ExclusiveStartKey: token
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?: ListPageOptions,
463
- ): Promise<ListPageResult<T>> {
464
- const items: T[] = [];
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 batch of this.#scanIndex(cls, idx, body, { ...options, offset })) {
467
- for (const item of batch.Items ?? []) {
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 (items.length) {
483
- const last: T = items.at(-1)!;
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
- ): AsyncIterable<T> {
505
- for await (const batch of this.#scanIndex(cls, idx, body, { limit: Number.MAX_SAFE_INTEGER })) {
506
- for (const item of batch.Items ?? []) {
507
- try {
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
  }