@twin.org/entity-storage-connector-dynamodb 0.0.2-next.9 → 0.0.3-next.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.
@@ -1,39 +1,44 @@
1
- import { waitUntilTableExists, DynamoDB, QueryCommand } from '@aws-sdk/client-dynamodb';
2
- import { GetCommand, PutCommand, DeleteCommand, DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
3
- import { unmarshall } from '@aws-sdk/util-dynamodb';
4
- import { Guards, Is, ComponentFactory, BaseError, GeneralError, Coerce, ObjectHelper, Converter } from '@twin.org/core';
5
- import { EntitySchemaFactory, EntitySchemaHelper, ComparisonOperator, LogicalOperator, SortDirection } from '@twin.org/entity';
6
-
7
1
  // Copyright 2024 IOTA Stiftung.
8
2
  // SPDX-License-Identifier: Apache-2.0.
3
+ import { DynamoDB, QueryCommand, waitUntilTableExists } from "@aws-sdk/client-dynamodb";
4
+ import { BatchWriteCommand, DeleteCommand, DynamoDBDocumentClient, GetCommand, PutCommand, ScanCommand } from "@aws-sdk/lib-dynamodb";
5
+ import { unmarshall } from "@aws-sdk/util-dynamodb";
6
+ import { ContextIdHelper, ContextIdStore } from "@twin.org/context";
7
+ import { BaseError, Coerce, ComponentFactory, Converter, GeneralError, Guards, HealthStatus, Is, ObjectHelper } from "@twin.org/core";
8
+ import { ComparisonOperator, EntitySchemaFactory, EntitySchemaHelper, LogicalOperator, SortDirection } from "@twin.org/entity";
9
9
  /**
10
10
  * Class for performing entity storage operations using Dynamo DB.
11
11
  */
12
- class DynamoDbEntityStorageConnector {
12
+ export class DynamoDbEntityStorageConnector {
13
+ /**
14
+ * Runtime name for the class.
15
+ */
16
+ static CLASS_NAME = "DynamoDbEntityStorageConnector";
13
17
  /**
14
18
  * Limit the number of entities when finding.
15
19
  * @internal
16
20
  */
17
- static _PAGE_SIZE = 40;
21
+ static _DEFAULT_LIMIT = 40;
18
22
  /**
19
23
  * Partition id field name.
20
24
  * @internal
21
25
  */
22
- static _PARTITION_ID_NAME = "partitionId";
26
+ static _PARTITION_KEY = "partitionId";
23
27
  /**
24
28
  * Partition id field value.
25
29
  * @internal
26
30
  */
27
- static _PARTITION_ID_VALUE = "1";
28
- /**
29
- * Runtime name for the class.
30
- */
31
- CLASS_NAME = "DynamoDbEntityStorageConnector";
31
+ static _PARTITION_KEY_VALUE = "root";
32
32
  /**
33
33
  * The schema for the entity.
34
34
  * @internal
35
35
  */
36
36
  _entitySchema;
37
+ /**
38
+ * The keys to use from the context ids to create partitions.
39
+ * @internal
40
+ */
41
+ _partitionContextIds;
37
42
  /**
38
43
  * The primary key.
39
44
  * @internal
@@ -49,23 +54,65 @@ class DynamoDbEntityStorageConnector {
49
54
  * @param options The options for the connector.
50
55
  */
51
56
  constructor(options) {
52
- Guards.object(this.CLASS_NAME, "options", options);
53
- Guards.stringValue(this.CLASS_NAME, "options.entitySchema", options.entitySchema);
54
- Guards.object(this.CLASS_NAME, "options.config", options.config);
57
+ Guards.object(DynamoDbEntityStorageConnector.CLASS_NAME, "options", options);
58
+ Guards.stringValue(DynamoDbEntityStorageConnector.CLASS_NAME, "options.entitySchema", options.entitySchema);
59
+ Guards.object(DynamoDbEntityStorageConnector.CLASS_NAME, "options.config", options.config);
55
60
  options.config.authMode ??= "credentials";
56
61
  if (options.config.authMode === "credentials") {
57
- Guards.stringValue(this.CLASS_NAME, "options.config.accessKeyId", options.config.accessKeyId);
58
- Guards.stringValue(this.CLASS_NAME, "options.config.secretAccessKey", options.config.secretAccessKey);
62
+ Guards.stringValue(DynamoDbEntityStorageConnector.CLASS_NAME, "options.config.accessKeyId", options.config.accessKeyId);
63
+ Guards.stringValue(DynamoDbEntityStorageConnector.CLASS_NAME, "options.config.secretAccessKey", options.config.secretAccessKey);
59
64
  }
60
- Guards.stringValue(this.CLASS_NAME, "options.config.region", options.config.region);
61
- Guards.stringValue(this.CLASS_NAME, "options.config.tableName", options.config.tableName);
65
+ Guards.stringValue(DynamoDbEntityStorageConnector.CLASS_NAME, "options.config.region", options.config.region);
66
+ Guards.stringValue(DynamoDbEntityStorageConnector.CLASS_NAME, "options.config.tableName", options.config.tableName);
62
67
  this._entitySchema = EntitySchemaFactory.get(options.entitySchema);
68
+ this._partitionContextIds = options.partitionContextIds;
63
69
  this._primaryKey = EntitySchemaHelper.getPrimaryKey(this._entitySchema);
64
70
  this._config = options.config;
65
71
  this._config.endpoint = Is.stringValue(this._config.endpoint)
66
72
  ? this._config.endpoint
67
73
  : undefined;
68
74
  }
75
+ /**
76
+ * Returns the class name of the component.
77
+ * @returns The class name of the component.
78
+ */
79
+ className() {
80
+ return DynamoDbEntityStorageConnector.CLASS_NAME;
81
+ }
82
+ /**
83
+ * Returns the health status of the component.
84
+ * @returns The health status of the component.
85
+ */
86
+ async health() {
87
+ try {
88
+ const dbConnection = this.createConnection();
89
+ await dbConnection.describeTable({ TableName: this._config.tableName });
90
+ return [
91
+ {
92
+ source: DynamoDbEntityStorageConnector.CLASS_NAME,
93
+ status: HealthStatus.Ok,
94
+ description: "healthDescription"
95
+ }
96
+ ];
97
+ }
98
+ catch {
99
+ return [
100
+ {
101
+ source: DynamoDbEntityStorageConnector.CLASS_NAME,
102
+ status: HealthStatus.Error,
103
+ description: "healthDescription",
104
+ message: "connectionFailed"
105
+ }
106
+ ];
107
+ }
108
+ }
109
+ /**
110
+ * Get the schema for the entities.
111
+ * @returns The schema for the entities.
112
+ */
113
+ getSchema() {
114
+ return this._entitySchema;
115
+ }
69
116
  /**
70
117
  * Bootstrap the component by creating and initializing any resources it needs.
71
118
  * @param nodeLoggingComponentType The node logging component type.
@@ -76,7 +123,7 @@ class DynamoDbEntityStorageConnector {
76
123
  if (!(await this.tableExists(this._config.tableName))) {
77
124
  await nodeLogging?.log({
78
125
  level: "info",
79
- source: this.CLASS_NAME,
126
+ source: DynamoDbEntityStorageConnector.CLASS_NAME,
80
127
  ts: Date.now(),
81
128
  message: "tableCreating",
82
129
  data: {
@@ -97,11 +144,11 @@ class DynamoDbEntityStorageConnector {
97
144
  // We always add a partition key to the table as a non optional hash key
98
145
  // is always required when querying using sort parameters
99
146
  tableParams.AttributeDefinitions?.push({
100
- AttributeName: DynamoDbEntityStorageConnector._PARTITION_ID_NAME,
147
+ AttributeName: DynamoDbEntityStorageConnector._PARTITION_KEY,
101
148
  AttributeType: "S"
102
149
  });
103
150
  tableParams.KeySchema?.push({
104
- AttributeName: DynamoDbEntityStorageConnector._PARTITION_ID_NAME,
151
+ AttributeName: DynamoDbEntityStorageConnector._PARTITION_KEY,
105
152
  KeyType: "HASH"
106
153
  });
107
154
  const gsi = [];
@@ -128,7 +175,7 @@ class DynamoDbEntityStorageConnector {
128
175
  IndexName: `${prop.property}Index`,
129
176
  KeySchema: [
130
177
  {
131
- AttributeName: DynamoDbEntityStorageConnector._PARTITION_ID_NAME,
178
+ AttributeName: DynamoDbEntityStorageConnector._PARTITION_KEY,
132
179
  KeyType: "HASH"
133
180
  },
134
181
  {
@@ -160,7 +207,7 @@ class DynamoDbEntityStorageConnector {
160
207
  });
161
208
  await nodeLogging?.log({
162
209
  level: "info",
163
- source: this.CLASS_NAME,
210
+ source: DynamoDbEntityStorageConnector.CLASS_NAME,
164
211
  ts: Date.now(),
165
212
  message: "tableCreated",
166
213
  data: {
@@ -172,7 +219,7 @@ class DynamoDbEntityStorageConnector {
172
219
  if (BaseError.isErrorCode(err, "ResourceInUseException")) {
173
220
  await nodeLogging?.log({
174
221
  level: "info",
175
- source: this.CLASS_NAME,
222
+ source: DynamoDbEntityStorageConnector.CLASS_NAME,
176
223
  ts: Date.now(),
177
224
  message: "tableExists",
178
225
  data: {
@@ -183,7 +230,7 @@ class DynamoDbEntityStorageConnector {
183
230
  else {
184
231
  await nodeLogging?.log({
185
232
  level: "error",
186
- source: this.CLASS_NAME,
233
+ source: DynamoDbEntityStorageConnector.CLASS_NAME,
187
234
  ts: Date.now(),
188
235
  message: "tableCreateFailed",
189
236
  error: BaseError.fromError(err),
@@ -198,7 +245,7 @@ class DynamoDbEntityStorageConnector {
198
245
  else {
199
246
  await nodeLogging?.log({
200
247
  level: "info",
201
- source: this.CLASS_NAME,
248
+ source: DynamoDbEntityStorageConnector.CLASS_NAME,
202
249
  ts: Date.now(),
203
250
  message: "tableExists",
204
251
  data: {
@@ -208,13 +255,6 @@ class DynamoDbEntityStorageConnector {
208
255
  }
209
256
  return true;
210
257
  }
211
- /**
212
- * Get the schema for the entities.
213
- * @returns The schema for the entities.
214
- */
215
- getSchema() {
216
- return this._entitySchema;
217
- }
218
258
  /**
219
259
  * Get an entity.
220
260
  * @param id The id of the entity to get, or the index value if secondaryIndex is set.
@@ -223,19 +263,21 @@ class DynamoDbEntityStorageConnector {
223
263
  * @returns The object if it can be found or undefined.
224
264
  */
225
265
  async get(id, secondaryIndex, conditions) {
226
- Guards.stringValue(this.CLASS_NAME, "id", id);
266
+ Guards.stringValue(DynamoDbEntityStorageConnector.CLASS_NAME, "id", id);
267
+ const contextIds = await ContextIdStore.getContextIds();
268
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
227
269
  try {
228
270
  const docClient = this.createDocClient();
229
271
  if (Is.empty(secondaryIndex) && Is.empty(conditions)) {
230
272
  const getCommand = new GetCommand({
231
273
  TableName: this._config.tableName,
232
274
  Key: {
233
- [DynamoDbEntityStorageConnector._PARTITION_ID_NAME]: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE,
275
+ [DynamoDbEntityStorageConnector._PARTITION_KEY]: partitionKey ?? DynamoDbEntityStorageConnector._PARTITION_KEY_VALUE,
234
276
  [this._primaryKey.property]: id
235
277
  }
236
278
  });
237
279
  const response = await docClient.send(getCommand);
238
- delete response.Item?.[DynamoDbEntityStorageConnector._PARTITION_ID_NAME];
280
+ delete response.Item?.[DynamoDbEntityStorageConnector._PARTITION_KEY];
239
281
  return response.Item;
240
282
  }
241
283
  const finalConditions = {
@@ -257,16 +299,16 @@ class DynamoDbEntityStorageConnector {
257
299
  });
258
300
  }
259
301
  }
260
- const queryResult = await this.internalQuery(finalConditions, undefined, undefined, undefined, 1, secondaryIndex);
302
+ const queryResult = await this.internalQuery(finalConditions, undefined, undefined, undefined, 1, secondaryIndex, partitionKey);
261
303
  return queryResult.entities[0];
262
304
  }
263
305
  catch (err) {
264
306
  if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
265
- throw new GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
266
- table: this._config.tableName
307
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "tableDoesNotExist", {
308
+ tableName: this._config.tableName
267
309
  }, err);
268
310
  }
269
- throw new GeneralError(this.CLASS_NAME, "getFailed", {
311
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "getFailed", {
270
312
  id
271
313
  }, err);
272
314
  }
@@ -278,7 +320,9 @@ class DynamoDbEntityStorageConnector {
278
320
  * @returns The id of the entity.
279
321
  */
280
322
  async set(entity, conditions) {
281
- Guards.object(this.CLASS_NAME, "entity", entity);
323
+ Guards.object(DynamoDbEntityStorageConnector.CLASS_NAME, "entity", entity);
324
+ const contextIds = await ContextIdStore.getContextIds();
325
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
282
326
  EntitySchemaHelper.validateEntity(entity, this.getSchema());
283
327
  const id = entity[this._primaryKey.property];
284
328
  try {
@@ -287,7 +331,7 @@ class DynamoDbEntityStorageConnector {
287
331
  const putCommand = new PutCommand({
288
332
  TableName: this._config.tableName,
289
333
  Item: {
290
- [DynamoDbEntityStorageConnector._PARTITION_ID_NAME]: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE,
334
+ [DynamoDbEntityStorageConnector._PARTITION_KEY]: partitionKey ?? DynamoDbEntityStorageConnector._PARTITION_KEY_VALUE,
291
335
  ...entity
292
336
  },
293
337
  // Only set the condition expression if we have conditions to match
@@ -305,15 +349,100 @@ class DynamoDbEntityStorageConnector {
305
349
  return;
306
350
  }
307
351
  if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
308
- throw new GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
352
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "tableDoesNotExist", {
309
353
  tableName: this._config.tableName
310
354
  }, err);
311
355
  }
312
- throw new GeneralError(this.CLASS_NAME, "setFailed", {
356
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "setFailed", {
313
357
  id
314
358
  }, err);
315
359
  }
316
360
  }
361
+ /**
362
+ * Set multiple entities in a batch.
363
+ * @param entities The entities to set.
364
+ * @returns Nothing.
365
+ */
366
+ async setBatch(entities) {
367
+ Guards.arrayValue(DynamoDbEntityStorageConnector.CLASS_NAME, "entities", entities);
368
+ const contextIds = await ContextIdStore.getContextIds();
369
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
370
+ for (const entity of entities) {
371
+ EntitySchemaHelper.validateEntity(entity, this.getSchema());
372
+ }
373
+ try {
374
+ const docClient = this.createDocClient();
375
+ const chunkSize = 25;
376
+ for (let i = 0; i < entities.length; i += chunkSize) {
377
+ const chunk = entities.slice(i, i + chunkSize);
378
+ await docClient.send(new BatchWriteCommand({
379
+ RequestItems: {
380
+ [this._config.tableName]: chunk.map(entity => ({
381
+ PutRequest: {
382
+ Item: {
383
+ [DynamoDbEntityStorageConnector._PARTITION_KEY]: partitionKey ?? DynamoDbEntityStorageConnector._PARTITION_KEY_VALUE,
384
+ ...entity
385
+ }
386
+ }
387
+ }))
388
+ }
389
+ }));
390
+ }
391
+ }
392
+ catch (err) {
393
+ if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
394
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "tableDoesNotExist", { tableName: this._config.tableName }, err);
395
+ }
396
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "setBatchFailed", undefined, err);
397
+ }
398
+ }
399
+ /**
400
+ * Empty the entity storage.
401
+ * @returns Nothing.
402
+ */
403
+ async empty() {
404
+ try {
405
+ const contextIds = await ContextIdStore.getContextIds();
406
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
407
+ const pKey = partitionKey ?? DynamoDbEntityStorageConnector._PARTITION_KEY_VALUE;
408
+ const docClient = this.createDocClient();
409
+ const chunkSize = 25;
410
+ let exclusiveStartKey;
411
+ do {
412
+ const scanResult = await docClient.send(new ScanCommand({
413
+ TableName: this._config.tableName,
414
+ FilterExpression: "#partitionId = :partitionId",
415
+ ExpressionAttributeNames: {
416
+ "#partitionId": DynamoDbEntityStorageConnector._PARTITION_KEY
417
+ },
418
+ ExpressionAttributeValues: {
419
+ ":partitionId": pKey
420
+ },
421
+ ExclusiveStartKey: exclusiveStartKey
422
+ }));
423
+ const items = scanResult.Items ?? [];
424
+ for (let i = 0; i < items.length; i += chunkSize) {
425
+ const chunk = items.slice(i, i + chunkSize);
426
+ await docClient.send(new BatchWriteCommand({
427
+ RequestItems: {
428
+ [this._config.tableName]: chunk.map((item) => ({
429
+ DeleteRequest: {
430
+ Key: {
431
+ [DynamoDbEntityStorageConnector._PARTITION_KEY]: item[DynamoDbEntityStorageConnector._PARTITION_KEY],
432
+ [this._primaryKey.property]: item[this._primaryKey.property]
433
+ }
434
+ }
435
+ }))
436
+ }
437
+ }));
438
+ }
439
+ exclusiveStartKey = scanResult.LastEvaluatedKey;
440
+ } while (exclusiveStartKey);
441
+ }
442
+ catch (err) {
443
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "emptyFailed", undefined, err);
444
+ }
445
+ }
317
446
  /**
318
447
  * Remove the entity.
319
448
  * @param id The id of the entity to remove.
@@ -321,14 +450,16 @@ class DynamoDbEntityStorageConnector {
321
450
  * @returns Nothing.
322
451
  */
323
452
  async remove(id, conditions) {
324
- Guards.stringValue(this.CLASS_NAME, "id", id);
453
+ Guards.stringValue(DynamoDbEntityStorageConnector.CLASS_NAME, "id", id);
454
+ const contextIds = await ContextIdStore.getContextIds();
455
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
325
456
  try {
326
457
  const docClient = this.createDocClient();
327
458
  const { conditionExpression, attributeNames, attributeValues } = this.buildConditionExpression(conditions);
328
459
  const deleteCommand = new DeleteCommand({
329
460
  TableName: this._config.tableName,
330
461
  Key: {
331
- [DynamoDbEntityStorageConnector._PARTITION_ID_NAME]: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE,
462
+ [DynamoDbEntityStorageConnector._PARTITION_KEY]: partitionKey ?? DynamoDbEntityStorageConnector._PARTITION_KEY_VALUE,
332
463
  [this._primaryKey.property]: id
333
464
  },
334
465
  ConditionExpression: conditionExpression,
@@ -342,38 +473,134 @@ class DynamoDbEntityStorageConnector {
342
473
  return;
343
474
  }
344
475
  if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
345
- throw new GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
346
- table: this._config.tableName
476
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "tableDoesNotExist", {
477
+ tableName: this._config.tableName
347
478
  }, err);
348
479
  }
349
- throw new GeneralError(this.CLASS_NAME, "removeFailed", {
480
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "removeFailed", {
350
481
  id
351
482
  }, err);
352
483
  }
353
484
  }
485
+ /**
486
+ * Remove multiple entities by their IDs in a batch.
487
+ * @param ids The ids of the entities to remove.
488
+ * @returns Nothing.
489
+ */
490
+ async removeBatch(ids) {
491
+ Guards.arrayValue(DynamoDbEntityStorageConnector.CLASS_NAME, "ids", ids);
492
+ const contextIds = await ContextIdStore.getContextIds();
493
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
494
+ try {
495
+ const docClient = this.createDocClient();
496
+ const chunkSize = 25;
497
+ const primaryKeyProperty = this._primaryKey.property;
498
+ for (let i = 0; i < ids.length; i += chunkSize) {
499
+ const chunk = ids.slice(i, i + chunkSize);
500
+ await docClient.send(new BatchWriteCommand({
501
+ RequestItems: {
502
+ [this._config.tableName]: chunk.map(id => ({
503
+ DeleteRequest: {
504
+ Key: {
505
+ [primaryKeyProperty]: id,
506
+ [DynamoDbEntityStorageConnector._PARTITION_KEY]: partitionKey ?? DynamoDbEntityStorageConnector._PARTITION_KEY_VALUE
507
+ }
508
+ }
509
+ }))
510
+ }
511
+ }));
512
+ }
513
+ }
514
+ catch (err) {
515
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "removeBatchFailed", undefined, err);
516
+ }
517
+ }
518
+ /**
519
+ * Teardown the entity storage by deleting the underlying table.
520
+ * @param nodeLoggingComponentType The node logging component type.
521
+ * @returns True if the teardown process was successful.
522
+ */
523
+ async teardown(nodeLoggingComponentType) {
524
+ const nodeLogging = ComponentFactory.getIfExists(nodeLoggingComponentType);
525
+ await nodeLogging?.log({
526
+ level: "info",
527
+ source: DynamoDbEntityStorageConnector.CLASS_NAME,
528
+ ts: Date.now(),
529
+ message: "tableDeleting",
530
+ data: { tableName: this._config.tableName }
531
+ });
532
+ try {
533
+ const dbConnection = this.createConnection();
534
+ await dbConnection.deleteTable({ TableName: this._config.tableName });
535
+ await nodeLogging?.log({
536
+ level: "info",
537
+ source: DynamoDbEntityStorageConnector.CLASS_NAME,
538
+ ts: Date.now(),
539
+ message: "tableDeleted",
540
+ data: { tableName: this._config.tableName }
541
+ });
542
+ return true;
543
+ }
544
+ catch (err) {
545
+ await nodeLogging?.log({
546
+ level: "error",
547
+ source: DynamoDbEntityStorageConnector.CLASS_NAME,
548
+ ts: Date.now(),
549
+ message: "teardownFailed",
550
+ error: BaseError.fromError(err)
551
+ });
552
+ return false;
553
+ }
554
+ }
354
555
  /**
355
556
  * Find all the entities which match the conditions.
356
557
  * @param conditions The conditions to match for the entities.
357
558
  * @param sortProperties The optional sort order.
358
559
  * @param properties The optional properties to return, defaults to all.
359
- * @param cursor The cursor to request the next page of entities.
360
- * @param pageSize The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
560
+ * @param cursor The cursor to request the next chunk of entities.
561
+ * @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
361
562
  * @returns All the entities for the storage matching the conditions,
362
563
  * and a cursor which can be used to request more entities.
363
564
  */
364
- async query(conditions, sortProperties, properties, cursor, pageSize) {
365
- return this.internalQuery(conditions, sortProperties, properties, cursor, pageSize);
565
+ async query(conditions, sortProperties, properties, cursor, limit) {
566
+ const contextIds = await ContextIdStore.getContextIds();
567
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
568
+ return this.internalQuery(conditions, sortProperties, properties, cursor, limit, undefined, partitionKey);
366
569
  }
367
570
  /**
368
- * Delete the table.
369
- * @returns Nothing.
571
+ * Count all the entities which match the conditions.
572
+ * @returns The total count of entities in the storage.
370
573
  */
371
- async tableDelete() {
574
+ async count() {
372
575
  try {
576
+ const contextIds = await ContextIdStore.getContextIds();
577
+ const partitionKey = ContextIdHelper.combinedContextKey(contextIds, this._partitionContextIds);
373
578
  const dbConnection = this.createConnection();
374
- await dbConnection.deleteTable({ TableName: this._config.tableName });
579
+ let total = 0;
580
+ let exclusiveStartKey;
581
+ do {
582
+ const result = await dbConnection.send(new QueryCommand({
583
+ TableName: this._config.tableName,
584
+ Select: "COUNT",
585
+ KeyConditionExpression: "#partitionId = :partitionId",
586
+ ExpressionAttributeNames: {
587
+ "#partitionId": DynamoDbEntityStorageConnector._PARTITION_KEY
588
+ },
589
+ ExpressionAttributeValues: {
590
+ ":partitionId": {
591
+ S: partitionKey ?? DynamoDbEntityStorageConnector._PARTITION_KEY_VALUE
592
+ }
593
+ },
594
+ ExclusiveStartKey: exclusiveStartKey
595
+ }));
596
+ total += result.Count ?? 0;
597
+ exclusiveStartKey = result.LastEvaluatedKey;
598
+ } while (exclusiveStartKey);
599
+ return total;
600
+ }
601
+ catch (err) {
602
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "countFailed", undefined, err);
375
603
  }
376
- catch { }
377
604
  }
378
605
  /**
379
606
  * Create the parameters for a query.
@@ -492,7 +719,7 @@ class DynamoDbEntityStorageConnector {
492
719
  else if (comparator.comparison === ComparisonOperator.In) {
493
720
  return `${propName} IN ${attributeName}`;
494
721
  }
495
- throw new GeneralError(this.CLASS_NAME, "comparisonNotSupported", {
722
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "comparisonNotSupported", {
496
723
  comparison: comparator.comparison
497
724
  });
498
725
  }
@@ -529,7 +756,9 @@ class DynamoDbEntityStorageConnector {
529
756
  else if (operator === LogicalOperator.Or) {
530
757
  return "OR";
531
758
  }
532
- throw new GeneralError(this.CLASS_NAME, "conditionalNotSupported", { operator });
759
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "conditionalNotSupported", {
760
+ operator
761
+ });
533
762
  }
534
763
  /**
535
764
  * Format a value to insert into DB.
@@ -585,6 +814,9 @@ class DynamoDbEntityStorageConnector {
585
814
  * @internal
586
815
  */
587
816
  createConnectionConfig() {
817
+ const requestHandler = {
818
+ requestTimeout: this._config.connectionTimeoutMs
819
+ };
588
820
  if (Is.stringValue(this._config.secretAccessKey) &&
589
821
  Is.stringValue(this._config.accessKeyId) &&
590
822
  this._config.authMode === "credentials") {
@@ -594,12 +826,14 @@ class DynamoDbEntityStorageConnector {
594
826
  secretAccessKey: this._config.secretAccessKey
595
827
  },
596
828
  endpoint: this._config.endpoint,
597
- region: this._config.region
829
+ region: this._config.region,
830
+ requestHandler
598
831
  };
599
832
  }
600
833
  return {
601
834
  endpoint: this._config.endpoint,
602
- region: this._config.region
835
+ region: this._config.region,
836
+ requestHandler
603
837
  };
604
838
  }
605
839
  /**
@@ -623,16 +857,17 @@ class DynamoDbEntityStorageConnector {
623
857
  * @param conditions The conditions to match for the entities.
624
858
  * @param sortProperties The optional sort order.
625
859
  * @param properties The optional properties to return, defaults to all.
626
- * @param cursor The cursor to request the next page of entities.
627
- * @param pageSize The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
860
+ * @param cursor The cursor to request the next chunk of entities.
861
+ * @param limit The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
628
862
  * @param secondaryIndex The secondary index to use for the query.
863
+ * @param partitionKey The partition key to use for the query.
629
864
  * @returns All the entities for the storage matching the conditions,
630
865
  * and a cursor which can be used to request more entities.
631
866
  * @internal
632
867
  */
633
- async internalQuery(conditions, sortProperties, properties, cursor, pageSize, secondaryIndex) {
868
+ async internalQuery(conditions, sortProperties, properties, cursor, limit, secondaryIndex, partitionKey) {
634
869
  try {
635
- const returnSize = pageSize ?? DynamoDbEntityStorageConnector._PAGE_SIZE;
870
+ const returnSize = limit ?? DynamoDbEntityStorageConnector._DEFAULT_LIMIT;
636
871
  let indexName = Is.stringValue(secondaryIndex)
637
872
  ? `${secondaryIndex}Index`
638
873
  : undefined;
@@ -641,7 +876,7 @@ class DynamoDbEntityStorageConnector {
641
876
  let scanAscending = true;
642
877
  if (Is.arrayValue(sortProperties)) {
643
878
  if (sortProperties.length > 1) {
644
- throw new GeneralError(this.CLASS_NAME, "sortSingle");
879
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "sortSingle");
645
880
  }
646
881
  for (const sortProperty of sortProperties) {
647
882
  const propertySchema = this._entitySchema.properties?.find(e => e.property === sortProperty.property);
@@ -649,7 +884,7 @@ class DynamoDbEntityStorageConnector {
649
884
  (!propertySchema.isPrimary &&
650
885
  !propertySchema.isSecondary &&
651
886
  Is.empty(propertySchema.sortDirection))) {
652
- throw new GeneralError(this.CLASS_NAME, "sortNotIndexed", {
887
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "sortNotIndexed", {
653
888
  property: sortProperty.property
654
889
  });
655
890
  }
@@ -661,8 +896,8 @@ class DynamoDbEntityStorageConnector {
661
896
  }
662
897
  const attributeNames = { "#partitionId": "partitionId" };
663
898
  const attributeValues = {
664
- [`:${DynamoDbEntityStorageConnector._PARTITION_ID_NAME}`]: {
665
- S: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE
899
+ [`:${DynamoDbEntityStorageConnector._PARTITION_KEY}`]: {
900
+ S: partitionKey ?? DynamoDbEntityStorageConnector._PARTITION_KEY_VALUE
666
901
  }
667
902
  };
668
903
  const expressions = this.buildQueryParameters("", conditions, attributeNames, attributeValues, secondaryIndex);
@@ -692,7 +927,7 @@ class DynamoDbEntityStorageConnector {
692
927
  if (Is.arrayValue(results.Items)) {
693
928
  entities = results.Items.map(item => {
694
929
  const unmarshalled = unmarshall(item);
695
- delete unmarshalled[DynamoDbEntityStorageConnector._PARTITION_ID_NAME];
930
+ delete unmarshalled[DynamoDbEntityStorageConnector._PARTITION_KEY];
696
931
  return unmarshalled;
697
932
  });
698
933
  }
@@ -705,11 +940,11 @@ class DynamoDbEntityStorageConnector {
705
940
  }
706
941
  catch (err) {
707
942
  if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
708
- throw new GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
709
- table: this._config.tableName
943
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "tableDoesNotExist", {
944
+ tableName: this._config.tableName
710
945
  }, err);
711
946
  }
712
- throw new GeneralError(this.CLASS_NAME, "queryFailed", undefined, err);
947
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "queryFailed", undefined, err);
713
948
  }
714
949
  }
715
950
  /**
@@ -728,7 +963,7 @@ class DynamoDbEntityStorageConnector {
728
963
  for (const c of conditions) {
729
964
  const schemaProp = this._entitySchema.properties?.find(p => p.property === c.property);
730
965
  if (Is.undefined(schemaProp)) {
731
- throw new GeneralError(this.CLASS_NAME, "propertyNotFound", {
966
+ throw new GeneralError(DynamoDbEntityStorageConnector.CLASS_NAME, "propertyNotFound", {
732
967
  property: c.property
733
968
  });
734
969
  }
@@ -745,5 +980,4 @@ class DynamoDbEntityStorageConnector {
745
980
  return { conditionExpression, attributeNames, attributeValues };
746
981
  }
747
982
  }
748
-
749
- export { DynamoDbEntityStorageConnector };
983
+ //# sourceMappingURL=dynamoDbEntityStorageConnector.js.map