@travetto/model-dynamodb 8.0.3 → 8.0.4

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 +3 -3
  2. package/src/service.ts +51 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-dynamodb",
3
- "version": "8.0.3",
3
+ "version": "8.0.4",
4
4
  "description": "DynamoDB backing for the travetto model module.",
5
5
  "keywords": [
6
6
  "dynamodb",
@@ -31,8 +31,8 @@
31
31
  "dependencies": {
32
32
  "@aws-sdk/client-dynamodb": "^3.1131.0",
33
33
  "@travetto/config": "^8.0.3",
34
- "@travetto/model": "^8.0.3",
35
- "@travetto/model-indexed": "^8.0.3"
34
+ "@travetto/model": "^8.0.4",
35
+ "@travetto/model-indexed": "^8.0.4"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@travetto/cli": "^8.0.2"
package/src/service.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import timers from 'node:timers/promises';
2
+
1
3
  import {
2
4
  type AttributeValue,
3
5
  DynamoDB,
@@ -50,6 +52,10 @@ const getKey = <T extends ModelType>(computed: ModelIndexedComputedIndex<T>): At
50
52
  DynamoDBUtil.toValue(computed.getKey() || 'NULL');
51
53
  const getSort = <T extends ModelType>(computed: ModelIndexedComputedIndex<T>): AttributeValue => DynamoDBUtil.toValue(computed.getSort());
52
54
 
55
+ function isNotFoundError(error: unknown): boolean {
56
+ return error instanceof Error && error.name === 'ResourceNotFoundException';
57
+ }
58
+
53
59
  /**
54
60
  * A model service backed by DynamoDB
55
61
  */
@@ -289,6 +295,19 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
289
295
  }
290
296
 
291
297
  // Storage
298
+ async #waitForTableNotExists(table: string): Promise<void> {
299
+ for (let attempt = 0; attempt < 300; attempt += 1) {
300
+ const describeResponse = await ModelStorageUtil.runAndIgnoreNotFound(
301
+ () => this.client.describeTable({ TableName: table }),
302
+ isNotFoundError
303
+ );
304
+ if (!describeResponse?.Table) {
305
+ return;
306
+ }
307
+ await timers.setTimeout(100);
308
+ }
309
+ throw new Error(`Timed out waiting for table ${table} to be deleted`);
310
+ }
292
311
 
293
312
  /**
294
313
  * Add a new model
@@ -299,8 +318,8 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
299
318
  const idx = DynamoDBUtil.computeIndexConfig(cls);
300
319
 
301
320
  const [currentTable, currentTTL] = await Promise.all([
302
- this.client.describeTable({ TableName: table }).catch(() => undefined),
303
- this.client.describeTimeToLive({ TableName: table }).catch(() => ({ TimeToLiveDescription: undefined }))
321
+ ModelStorageUtil.runAndIgnoreNotFound(() => this.client.describeTable({ TableName: table }), isNotFoundError),
322
+ ModelStorageUtil.runAndIgnoreNotFound(() => this.client.describeTimeToLive({ TableName: table }), isNotFoundError)
304
323
  ]);
305
324
 
306
325
  if (!currentTable) {
@@ -328,7 +347,7 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
328
347
  }
329
348
 
330
349
  const ttlRequired = ModelRegistryIndex.getConfig(cls).expiresAt !== undefined;
331
- const ttlEnabled = currentTTL.TimeToLiveDescription?.TimeToLiveStatus === 'ENABLED';
350
+ const ttlEnabled = currentTTL?.TimeToLiveDescription?.TimeToLiveStatus === 'ENABLED';
332
351
  if (ttlEnabled !== ttlRequired) {
333
352
  await this.client.updateTimeToLive({
334
353
  TableName: table,
@@ -343,10 +362,36 @@ export class DynamoDBModelService implements ModelCrudSupport, ModelExpirySuppor
343
362
  */
344
363
  async deleteModel(cls: Class<ModelType>): Promise<void> {
345
364
  const table = this.#resolveTable(cls);
346
- const { Table: verify } = await this.client.describeTable({ TableName: table }).catch(() => ({ Table: undefined }));
347
- if (verify) {
365
+ await ModelStorageUtil.runAndIgnoreNotFound(async () => {
348
366
  await this.client.deleteTable({ TableName: table });
349
- }
367
+ await this.#waitForTableNotExists(table);
368
+ }, isNotFoundError);
369
+ }
370
+
371
+ async truncateModel<T extends ModelType>(cls: Class<T>): Promise<void> {
372
+ const table = this.#resolveTable(cls);
373
+ let startKey: Record<string, AttributeValue> | undefined;
374
+ do {
375
+ const scanResult = await this.client.scan({
376
+ TableName: table,
377
+ ProjectionExpression: 'id',
378
+ ExclusiveStartKey: startKey,
379
+ Limit: 25
380
+ });
381
+ startKey = scanResult.LastEvaluatedKey;
382
+ const items = scanResult.Items ?? [];
383
+ if (items.length > 0) {
384
+ await this.client.batchWriteItem({
385
+ RequestItems: {
386
+ [table]: items.map(item => ({
387
+ DeleteRequest: {
388
+ Key: { id: item.id }
389
+ }
390
+ }))
391
+ }
392
+ });
393
+ }
394
+ } while (startKey);
350
395
  }
351
396
 
352
397
  async createStorage(): Promise<void> {