@twin.org/entity-storage-connector-dynamodb 0.0.1-next.8 → 0.0.1
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/dist/cjs/index.cjs +192 -109
- package/dist/esm/index.mjs +192 -109
- package/dist/types/dynamoDbEntityStorageConnector.d.ts +18 -13
- package/dist/types/index.d.ts +1 -0
- package/dist/types/models/IDynamoDbEntityStorageConnectorConstructorOptions.d.ts +18 -0
- package/docs/changelog.md +106 -1
- package/docs/reference/classes/DynamoDbEntityStorageConnector.md +65 -47
- package/docs/reference/index.md +1 -0
- package/docs/reference/interfaces/IDynamoDbEntityStorageConnectorConstructorOptions.md +27 -0
- package/package.json +12 -12
package/dist/esm/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { waitUntilTableExists,
|
|
1
|
+
import { waitUntilTableExists, DynamoDB, QueryCommand } from '@aws-sdk/client-dynamodb';
|
|
2
2
|
import { GetCommand, PutCommand, DeleteCommand, DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
3
3
|
import { unmarshall } from '@aws-sdk/util-dynamodb';
|
|
4
|
-
import { Guards, Is, BaseError, GeneralError, ObjectHelper, Converter
|
|
5
|
-
import { EntitySchemaFactory, EntitySchemaHelper, ComparisonOperator, LogicalOperator } from '@twin.org/entity';
|
|
4
|
+
import { Guards, Is, BaseError, GeneralError, Coerce, ObjectHelper, Converter } from '@twin.org/core';
|
|
5
|
+
import { EntitySchemaFactory, EntitySchemaHelper, ComparisonOperator, LogicalOperator, SortDirection } from '@twin.org/entity';
|
|
6
6
|
import { LoggingConnectorFactory } from '@twin.org/logging-models';
|
|
7
7
|
|
|
8
8
|
// Copyright 2024 IOTA Stiftung.
|
|
@@ -48,9 +48,6 @@ class DynamoDbEntityStorageConnector {
|
|
|
48
48
|
/**
|
|
49
49
|
* Create a new instance of DynamoDbEntityStorageConnector.
|
|
50
50
|
* @param options The options for the connector.
|
|
51
|
-
* @param options.entitySchema The schema for the entity.
|
|
52
|
-
* @param options.loggingConnectorType The type of logging connector to use, defaults to no logging.
|
|
53
|
-
* @param options.config The configuration for the connector.
|
|
54
51
|
*/
|
|
55
52
|
constructor(options) {
|
|
56
53
|
Guards.object(this.CLASS_NAME, "options", options);
|
|
@@ -223,13 +220,14 @@ class DynamoDbEntityStorageConnector {
|
|
|
223
220
|
* Get an entity.
|
|
224
221
|
* @param id The id of the entity to get, or the index value if secondaryIndex is set.
|
|
225
222
|
* @param secondaryIndex Get the item using a secondary index.
|
|
223
|
+
* @param conditions The optional conditions to match for the entities.
|
|
226
224
|
* @returns The object if it can be found or undefined.
|
|
227
225
|
*/
|
|
228
|
-
async get(id, secondaryIndex) {
|
|
226
|
+
async get(id, secondaryIndex, conditions) {
|
|
229
227
|
Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
230
228
|
try {
|
|
231
229
|
const docClient = this.createDocClient();
|
|
232
|
-
if (Is.
|
|
230
|
+
if (Is.empty(secondaryIndex) && Is.empty(conditions)) {
|
|
233
231
|
const getCommand = new GetCommand({
|
|
234
232
|
TableName: this._config.tableName,
|
|
235
233
|
Key: {
|
|
@@ -241,27 +239,27 @@ class DynamoDbEntityStorageConnector {
|
|
|
241
239
|
delete response.Item?.[DynamoDbEntityStorageConnector._PARTITION_ID_NAME];
|
|
242
240
|
return response.Item;
|
|
243
241
|
}
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
242
|
+
const finalConditions = {
|
|
243
|
+
conditions: []
|
|
244
|
+
};
|
|
245
|
+
if (Is.stringValue(secondaryIndex)) {
|
|
246
|
+
finalConditions.conditions.push({
|
|
247
|
+
property: secondaryIndex,
|
|
248
|
+
comparison: ComparisonOperator.Equals,
|
|
249
|
+
value: id
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (Is.arrayValue(conditions)) {
|
|
253
|
+
for (const c of conditions) {
|
|
254
|
+
finalConditions.conditions.push({
|
|
255
|
+
property: c.property,
|
|
256
|
+
comparison: ComparisonOperator.Equals,
|
|
257
|
+
value: c.value
|
|
258
|
+
});
|
|
259
259
|
}
|
|
260
|
-
});
|
|
261
|
-
const response = await docClient.send(queryCommand);
|
|
262
|
-
if (response.Items?.length === 1) {
|
|
263
|
-
return unmarshall(response.Items[0]);
|
|
264
260
|
}
|
|
261
|
+
const queryResult = await this.internalQuery(finalConditions, undefined, undefined, undefined, 1, secondaryIndex);
|
|
262
|
+
return queryResult.entities[0];
|
|
265
263
|
}
|
|
266
264
|
catch (err) {
|
|
267
265
|
if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
@@ -273,28 +271,40 @@ class DynamoDbEntityStorageConnector {
|
|
|
273
271
|
id
|
|
274
272
|
}, err);
|
|
275
273
|
}
|
|
276
|
-
return undefined;
|
|
277
274
|
}
|
|
278
275
|
/**
|
|
279
276
|
* Set an entity.
|
|
280
277
|
* @param entity The entity to set.
|
|
278
|
+
* @param conditions The optional conditions to match for the entities.
|
|
281
279
|
* @returns The id of the entity.
|
|
282
280
|
*/
|
|
283
|
-
async set(entity) {
|
|
281
|
+
async set(entity, conditions) {
|
|
284
282
|
Guards.object(this.CLASS_NAME, "entity", entity);
|
|
283
|
+
EntitySchemaHelper.validateEntity(entity, this.getSchema());
|
|
285
284
|
const id = entity[this._primaryKey.property];
|
|
286
285
|
try {
|
|
287
286
|
const docClient = this.createDocClient();
|
|
287
|
+
const { conditionExpression, attributeNames, attributeValues } = this.buildConditionExpression(conditions);
|
|
288
288
|
const putCommand = new PutCommand({
|
|
289
289
|
TableName: this._config.tableName,
|
|
290
290
|
Item: {
|
|
291
291
|
[DynamoDbEntityStorageConnector._PARTITION_ID_NAME]: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE,
|
|
292
292
|
...entity
|
|
293
|
-
}
|
|
293
|
+
},
|
|
294
|
+
// Only set the condition expression if we have conditions to match
|
|
295
|
+
// and the primary key exists, otherwise we are creating a new object
|
|
296
|
+
ConditionExpression: Is.stringValue(conditionExpression)
|
|
297
|
+
? `(attribute_exists(${this._primaryKey.property}) AND ${conditionExpression}) OR attribute_not_exists(${this._primaryKey.property})`
|
|
298
|
+
: undefined,
|
|
299
|
+
ExpressionAttributeNames: attributeNames,
|
|
300
|
+
ExpressionAttributeValues: attributeValues
|
|
294
301
|
});
|
|
295
302
|
await docClient.send(putCommand);
|
|
296
303
|
}
|
|
297
304
|
catch (err) {
|
|
305
|
+
if (BaseError.isErrorName(err, "ConditionalCheckFailedException")) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
298
308
|
if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
299
309
|
throw new GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
300
310
|
tableName: this._config.tableName
|
|
@@ -308,22 +318,30 @@ class DynamoDbEntityStorageConnector {
|
|
|
308
318
|
/**
|
|
309
319
|
* Remove the entity.
|
|
310
320
|
* @param id The id of the entity to remove.
|
|
321
|
+
* @param conditions The optional conditions to match for the entities.
|
|
311
322
|
* @returns Nothing.
|
|
312
323
|
*/
|
|
313
|
-
async remove(id) {
|
|
324
|
+
async remove(id, conditions) {
|
|
314
325
|
Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
315
326
|
try {
|
|
316
327
|
const docClient = this.createDocClient();
|
|
328
|
+
const { conditionExpression, attributeNames, attributeValues } = this.buildConditionExpression(conditions);
|
|
317
329
|
const deleteCommand = new DeleteCommand({
|
|
318
330
|
TableName: this._config.tableName,
|
|
319
331
|
Key: {
|
|
320
332
|
[DynamoDbEntityStorageConnector._PARTITION_ID_NAME]: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE,
|
|
321
333
|
[this._primaryKey.property]: id
|
|
322
|
-
}
|
|
334
|
+
},
|
|
335
|
+
ConditionExpression: conditionExpression,
|
|
336
|
+
ExpressionAttributeNames: attributeNames,
|
|
337
|
+
ExpressionAttributeValues: attributeValues
|
|
323
338
|
});
|
|
324
339
|
await docClient.send(deleteCommand);
|
|
325
340
|
}
|
|
326
341
|
catch (err) {
|
|
342
|
+
if (BaseError.isErrorName(err, "ConditionalCheckFailedException")) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
327
345
|
if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
328
346
|
throw new GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
329
347
|
table: this._config.tableName
|
|
@@ -345,77 +363,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
345
363
|
* and a cursor which can be used to request more entities.
|
|
346
364
|
*/
|
|
347
365
|
async query(conditions, sortProperties, properties, cursor, pageSize) {
|
|
348
|
-
|
|
349
|
-
const returnSize = pageSize ?? DynamoDbEntityStorageConnector._PAGE_SIZE;
|
|
350
|
-
let indexName;
|
|
351
|
-
// If we have a sortable property defined in the descriptor then we must use
|
|
352
|
-
// the secondary index for the query
|
|
353
|
-
if (Is.arrayValue(sortProperties)) {
|
|
354
|
-
if (sortProperties.length > 1) {
|
|
355
|
-
throw new GeneralError(this.CLASS_NAME, "sortSingle");
|
|
356
|
-
}
|
|
357
|
-
for (const sortProperty of sortProperties) {
|
|
358
|
-
const propertySchema = this._entitySchema.properties?.find(e => e.property === sortProperty.property);
|
|
359
|
-
if (Is.undefined(propertySchema) ||
|
|
360
|
-
(!propertySchema.isPrimary &&
|
|
361
|
-
!propertySchema.isSecondary &&
|
|
362
|
-
Is.empty(propertySchema.sortDirection))) {
|
|
363
|
-
throw new GeneralError(this.CLASS_NAME, "sortNotIndexed", {
|
|
364
|
-
property: sortProperty.property
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
indexName = propertySchema.isPrimary
|
|
368
|
-
? undefined
|
|
369
|
-
: `${sortProperty.property}Index`;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
const attributeNames = { "#partitionId": "partitionId" };
|
|
373
|
-
const attributeValues = {
|
|
374
|
-
[`:${DynamoDbEntityStorageConnector._PARTITION_ID_NAME}`]: {
|
|
375
|
-
S: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE
|
|
376
|
-
}
|
|
377
|
-
};
|
|
378
|
-
const expressions = this.buildQueryParameters("", conditions, attributeNames, attributeValues);
|
|
379
|
-
let keyExpression = "#partitionId = :partitionId";
|
|
380
|
-
if (expressions.keyCondition.length > 0) {
|
|
381
|
-
keyExpression += ` AND ${expressions.keyCondition}`;
|
|
382
|
-
}
|
|
383
|
-
const query = new QueryCommand({
|
|
384
|
-
TableName: this._config.tableName,
|
|
385
|
-
IndexName: indexName,
|
|
386
|
-
KeyConditionExpression: keyExpression,
|
|
387
|
-
FilterExpression: Is.stringValue(expressions.filterCondition)
|
|
388
|
-
? expressions.filterCondition
|
|
389
|
-
: undefined,
|
|
390
|
-
ExpressionAttributeNames: attributeNames,
|
|
391
|
-
ExpressionAttributeValues: attributeValues,
|
|
392
|
-
ProjectionExpression: properties?.map(p => p).join(", "),
|
|
393
|
-
Limit: returnSize,
|
|
394
|
-
ExclusiveStartKey: Is.empty(cursor)
|
|
395
|
-
? undefined
|
|
396
|
-
: ObjectHelper.fromBytes(Converter.base64ToBytes(cursor))
|
|
397
|
-
});
|
|
398
|
-
const connection = this.createDocClient();
|
|
399
|
-
const results = await connection.send(query);
|
|
400
|
-
let entities = [];
|
|
401
|
-
if (Is.arrayValue(results.Items)) {
|
|
402
|
-
entities = results.Items.map(item => unmarshall(item));
|
|
403
|
-
}
|
|
404
|
-
return {
|
|
405
|
-
entities,
|
|
406
|
-
cursor: Is.empty(results.LastEvaluatedKey)
|
|
407
|
-
? undefined
|
|
408
|
-
: Converter.bytesToBase64(ObjectHelper.toBytes(results.LastEvaluatedKey))
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
catch (err) {
|
|
412
|
-
if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
413
|
-
throw new GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
414
|
-
table: this._config.tableName
|
|
415
|
-
}, err);
|
|
416
|
-
}
|
|
417
|
-
throw new GeneralError(this.CLASS_NAME, "queryFailed", undefined, err);
|
|
418
|
-
}
|
|
366
|
+
return this.internalQuery(conditions, sortProperties, properties, cursor, pageSize);
|
|
419
367
|
}
|
|
420
368
|
/**
|
|
421
369
|
* Delete the table.
|
|
@@ -437,7 +385,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
437
385
|
* @returns The condition clause.
|
|
438
386
|
* @internal
|
|
439
387
|
*/
|
|
440
|
-
buildQueryParameters(objectPath, condition, attributeNames, attributeValues) {
|
|
388
|
+
buildQueryParameters(objectPath, condition, attributeNames, attributeValues, secondaryIndex) {
|
|
441
389
|
// If no conditions are defined then return empty string
|
|
442
390
|
if (Is.undefined(condition)) {
|
|
443
391
|
return {
|
|
@@ -453,7 +401,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
453
401
|
};
|
|
454
402
|
}
|
|
455
403
|
// It's a group of comparisons, so check the individual items and combine with the logical operator
|
|
456
|
-
const joinConditions = condition.conditions.map(c => this.buildQueryParameters(objectPath, c, attributeNames, attributeValues));
|
|
404
|
+
const joinConditions = condition.conditions.map(c => this.buildQueryParameters(objectPath, c, attributeNames, attributeValues, secondaryIndex));
|
|
457
405
|
const logicalOperator = this.mapConditionalOperator(condition.logicalOperator);
|
|
458
406
|
const keyCondition = joinConditions
|
|
459
407
|
.filter(j => j.keyCondition.length > 0)
|
|
@@ -471,9 +419,10 @@ class DynamoDbEntityStorageConnector {
|
|
|
471
419
|
const schemaProp = this._entitySchema.properties?.find(p => p.property === condition.property);
|
|
472
420
|
// It's a single value so just create the property comparison for the condition
|
|
473
421
|
const comparison = this.mapComparisonOperator(objectPath, condition, schemaProp?.type, attributeNames, attributeValues);
|
|
422
|
+
const isKey = schemaProp?.isPrimary || (schemaProp?.isSecondary && schemaProp?.property === secondaryIndex);
|
|
474
423
|
return {
|
|
475
|
-
keyCondition:
|
|
476
|
-
filterCondition:
|
|
424
|
+
keyCondition: isKey ? comparison : "",
|
|
425
|
+
filterCondition: !isKey ? comparison : ""
|
|
477
426
|
};
|
|
478
427
|
}
|
|
479
428
|
/**
|
|
@@ -494,6 +443,14 @@ class DynamoDbEntityStorageConnector {
|
|
|
494
443
|
}
|
|
495
444
|
prop += comparator.property;
|
|
496
445
|
let attributeName = this.populateAttributeNames(prop, attributeNames);
|
|
446
|
+
if (Is.empty(comparator.value)) {
|
|
447
|
+
if (comparator.comparison === ComparisonOperator.Equals) {
|
|
448
|
+
return `attribute_not_exists(${attributeName})`;
|
|
449
|
+
}
|
|
450
|
+
else if (comparator.comparison === ComparisonOperator.NotEquals) {
|
|
451
|
+
return `attribute_exists(${attributeName})`;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
497
454
|
let propName = `:${attributeName.replace(/\./g, "").replace(/#/g, "")}`;
|
|
498
455
|
if (Is.array(comparator.value)) {
|
|
499
456
|
const dbValues = comparator.value.map(v => this.propertyToDbValue(v, type));
|
|
@@ -617,7 +574,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
617
574
|
}
|
|
618
575
|
/**
|
|
619
576
|
* Create a new DB connection.
|
|
620
|
-
* @returns The
|
|
577
|
+
* @returns The Dynamo DB connection.
|
|
621
578
|
* @internal
|
|
622
579
|
*/
|
|
623
580
|
createConnection() {
|
|
@@ -625,7 +582,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
625
582
|
}
|
|
626
583
|
/**
|
|
627
584
|
* Create a new DB connection configuration.
|
|
628
|
-
* @returns The
|
|
585
|
+
* @returns The Dynamo DB connection configuration.
|
|
629
586
|
* @internal
|
|
630
587
|
*/
|
|
631
588
|
createConnectionConfig() {
|
|
@@ -654,6 +611,132 @@ class DynamoDbEntityStorageConnector {
|
|
|
654
611
|
return false;
|
|
655
612
|
}
|
|
656
613
|
}
|
|
614
|
+
/**
|
|
615
|
+
* Find all the entities which match the conditions.
|
|
616
|
+
* @param conditions The conditions to match for the entities.
|
|
617
|
+
* @param sortProperties The optional sort order.
|
|
618
|
+
* @param properties The optional properties to return, defaults to all.
|
|
619
|
+
* @param cursor The cursor to request the next page of entities.
|
|
620
|
+
* @param pageSize The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
|
|
621
|
+
* @param secondaryIndex The secondary index to use for the query.
|
|
622
|
+
* @returns All the entities for the storage matching the conditions,
|
|
623
|
+
* and a cursor which can be used to request more entities.
|
|
624
|
+
* @internal
|
|
625
|
+
*/
|
|
626
|
+
async internalQuery(conditions, sortProperties, properties, cursor, pageSize, secondaryIndex) {
|
|
627
|
+
try {
|
|
628
|
+
const returnSize = pageSize ?? DynamoDbEntityStorageConnector._PAGE_SIZE;
|
|
629
|
+
let indexName = Is.stringValue(secondaryIndex)
|
|
630
|
+
? `${secondaryIndex}Index`
|
|
631
|
+
: undefined;
|
|
632
|
+
// If we have a sortable property defined in the descriptor then we must use
|
|
633
|
+
// the secondary index for the query
|
|
634
|
+
let scanAscending = true;
|
|
635
|
+
if (Is.arrayValue(sortProperties)) {
|
|
636
|
+
if (sortProperties.length > 1) {
|
|
637
|
+
throw new GeneralError(this.CLASS_NAME, "sortSingle");
|
|
638
|
+
}
|
|
639
|
+
for (const sortProperty of sortProperties) {
|
|
640
|
+
const propertySchema = this._entitySchema.properties?.find(e => e.property === sortProperty.property);
|
|
641
|
+
if (Is.undefined(propertySchema) ||
|
|
642
|
+
(!propertySchema.isPrimary &&
|
|
643
|
+
!propertySchema.isSecondary &&
|
|
644
|
+
Is.empty(propertySchema.sortDirection))) {
|
|
645
|
+
throw new GeneralError(this.CLASS_NAME, "sortNotIndexed", {
|
|
646
|
+
property: sortProperty.property
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
indexName = propertySchema.isPrimary
|
|
650
|
+
? undefined
|
|
651
|
+
: `${sortProperty.property}Index`;
|
|
652
|
+
scanAscending = sortProperty.sortDirection === SortDirection.Ascending;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const attributeNames = { "#partitionId": "partitionId" };
|
|
656
|
+
const attributeValues = {
|
|
657
|
+
[`:${DynamoDbEntityStorageConnector._PARTITION_ID_NAME}`]: {
|
|
658
|
+
S: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
const expressions = this.buildQueryParameters("", conditions, attributeNames, attributeValues, secondaryIndex);
|
|
662
|
+
let keyExpression = "#partitionId = :partitionId";
|
|
663
|
+
if (expressions.keyCondition.length > 0) {
|
|
664
|
+
keyExpression += ` AND ${expressions.keyCondition}`;
|
|
665
|
+
}
|
|
666
|
+
const query = new QueryCommand({
|
|
667
|
+
TableName: this._config.tableName,
|
|
668
|
+
IndexName: indexName,
|
|
669
|
+
KeyConditionExpression: keyExpression,
|
|
670
|
+
FilterExpression: Is.stringValue(expressions.filterCondition)
|
|
671
|
+
? expressions.filterCondition
|
|
672
|
+
: undefined,
|
|
673
|
+
ExpressionAttributeNames: attributeNames,
|
|
674
|
+
ExpressionAttributeValues: attributeValues,
|
|
675
|
+
ProjectionExpression: properties?.map(p => p).join(", "),
|
|
676
|
+
Limit: returnSize,
|
|
677
|
+
ScanIndexForward: scanAscending,
|
|
678
|
+
ExclusiveStartKey: Is.empty(cursor)
|
|
679
|
+
? undefined
|
|
680
|
+
: ObjectHelper.fromBytes(Converter.base64ToBytes(cursor))
|
|
681
|
+
});
|
|
682
|
+
const connection = this.createDocClient();
|
|
683
|
+
const results = await connection.send(query);
|
|
684
|
+
let entities = [];
|
|
685
|
+
if (Is.arrayValue(results.Items)) {
|
|
686
|
+
entities = results.Items.map(item => {
|
|
687
|
+
const unmarshalled = unmarshall(item);
|
|
688
|
+
delete unmarshalled[DynamoDbEntityStorageConnector._PARTITION_ID_NAME];
|
|
689
|
+
return unmarshalled;
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
entities,
|
|
694
|
+
cursor: Is.empty(results.LastEvaluatedKey)
|
|
695
|
+
? undefined
|
|
696
|
+
: Converter.bytesToBase64(ObjectHelper.toBytes(results.LastEvaluatedKey))
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
catch (err) {
|
|
700
|
+
if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
701
|
+
throw new GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
702
|
+
table: this._config.tableName
|
|
703
|
+
}, err);
|
|
704
|
+
}
|
|
705
|
+
throw new GeneralError(this.CLASS_NAME, "queryFailed", undefined, err);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Build the condition expression for the query.
|
|
710
|
+
* @param conditions The conditions to build the expression from.
|
|
711
|
+
* @returns The condition expression.
|
|
712
|
+
* @throws GeneralError if the property is not found in the schema.
|
|
713
|
+
* @internal
|
|
714
|
+
*/
|
|
715
|
+
buildConditionExpression(conditions) {
|
|
716
|
+
let conditionExpression;
|
|
717
|
+
let attributeNames;
|
|
718
|
+
let attributeValues;
|
|
719
|
+
if (Is.arrayValue(conditions)) {
|
|
720
|
+
const expressions = [];
|
|
721
|
+
for (const c of conditions) {
|
|
722
|
+
const schemaProp = this._entitySchema.properties?.find(p => p.property === c.property);
|
|
723
|
+
if (Is.undefined(schemaProp)) {
|
|
724
|
+
throw new GeneralError(this.CLASS_NAME, "propertyNotFound", {
|
|
725
|
+
property: c.property
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
const attributeName = `#${c.property}`;
|
|
729
|
+
const attributeValueName = `:${c.property}`;
|
|
730
|
+
attributeNames ??= {};
|
|
731
|
+
attributeValues ??= {};
|
|
732
|
+
attributeNames[attributeName] = c.property;
|
|
733
|
+
attributeValues[attributeValueName] = c.value;
|
|
734
|
+
expressions.push(`${attributeName} = ${attributeValueName}`);
|
|
735
|
+
}
|
|
736
|
+
conditionExpression = expressions.join(" AND ");
|
|
737
|
+
}
|
|
738
|
+
return { conditionExpression, attributeNames, attributeValues };
|
|
739
|
+
}
|
|
657
740
|
}
|
|
658
741
|
|
|
659
742
|
export { DynamoDbEntityStorageConnector };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type EntityCondition, type IEntitySchema,
|
|
1
|
+
import { type EntityCondition, type IEntitySchema, SortDirection } from "@twin.org/entity";
|
|
2
2
|
import type { IEntityStorageConnector } from "@twin.org/entity-storage-models";
|
|
3
|
-
import type {
|
|
3
|
+
import type { IDynamoDbEntityStorageConnectorConstructorOptions } from "./models/IDynamoDbEntityStorageConnectorConstructorOptions";
|
|
4
4
|
/**
|
|
5
5
|
* Class for performing entity storage operations using Dynamo DB.
|
|
6
6
|
*/
|
|
@@ -12,15 +12,8 @@ export declare class DynamoDbEntityStorageConnector<T = unknown> implements IEnt
|
|
|
12
12
|
/**
|
|
13
13
|
* Create a new instance of DynamoDbEntityStorageConnector.
|
|
14
14
|
* @param options The options for the connector.
|
|
15
|
-
* @param options.entitySchema The schema for the entity.
|
|
16
|
-
* @param options.loggingConnectorType The type of logging connector to use, defaults to no logging.
|
|
17
|
-
* @param options.config The configuration for the connector.
|
|
18
15
|
*/
|
|
19
|
-
constructor(options:
|
|
20
|
-
entitySchema: string;
|
|
21
|
-
loggingConnectorType?: string;
|
|
22
|
-
config: IDynamoDbEntityStorageConnectorConfig;
|
|
23
|
-
});
|
|
16
|
+
constructor(options: IDynamoDbEntityStorageConnectorConstructorOptions);
|
|
24
17
|
/**
|
|
25
18
|
* Bootstrap the component by creating and initializing any resources it needs.
|
|
26
19
|
* @param nodeLoggingConnectorType The node logging connector type, defaults to "node-logging".
|
|
@@ -36,21 +29,33 @@ export declare class DynamoDbEntityStorageConnector<T = unknown> implements IEnt
|
|
|
36
29
|
* Get an entity.
|
|
37
30
|
* @param id The id of the entity to get, or the index value if secondaryIndex is set.
|
|
38
31
|
* @param secondaryIndex Get the item using a secondary index.
|
|
32
|
+
* @param conditions The optional conditions to match for the entities.
|
|
39
33
|
* @returns The object if it can be found or undefined.
|
|
40
34
|
*/
|
|
41
|
-
get(id: string, secondaryIndex?: keyof T
|
|
35
|
+
get(id: string, secondaryIndex?: keyof T, conditions?: {
|
|
36
|
+
property: keyof T;
|
|
37
|
+
value: unknown;
|
|
38
|
+
}[]): Promise<T | undefined>;
|
|
42
39
|
/**
|
|
43
40
|
* Set an entity.
|
|
44
41
|
* @param entity The entity to set.
|
|
42
|
+
* @param conditions The optional conditions to match for the entities.
|
|
45
43
|
* @returns The id of the entity.
|
|
46
44
|
*/
|
|
47
|
-
set(entity: T
|
|
45
|
+
set(entity: T, conditions?: {
|
|
46
|
+
property: keyof T;
|
|
47
|
+
value: unknown;
|
|
48
|
+
}[]): Promise<void>;
|
|
48
49
|
/**
|
|
49
50
|
* Remove the entity.
|
|
50
51
|
* @param id The id of the entity to remove.
|
|
52
|
+
* @param conditions The optional conditions to match for the entities.
|
|
51
53
|
* @returns Nothing.
|
|
52
54
|
*/
|
|
53
|
-
remove(id: string
|
|
55
|
+
remove(id: string, conditions?: {
|
|
56
|
+
property: keyof T;
|
|
57
|
+
value: unknown;
|
|
58
|
+
}[]): Promise<void>;
|
|
54
59
|
/**
|
|
55
60
|
* Find all the entities which match the conditions.
|
|
56
61
|
* @param conditions The conditions to match for the entities.
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IDynamoDbEntityStorageConnectorConfig } from "./IDynamoDbEntityStorageConnectorConfig";
|
|
2
|
+
/**
|
|
3
|
+
* Options for the Dynamo DB Entity Storage Connector constructor.
|
|
4
|
+
*/
|
|
5
|
+
export interface IDynamoDbEntityStorageConnectorConstructorOptions {
|
|
6
|
+
/**
|
|
7
|
+
* The schema for the entity
|
|
8
|
+
*/
|
|
9
|
+
entitySchema: string;
|
|
10
|
+
/**
|
|
11
|
+
* The type of logging connector to use, defaults to no logging.
|
|
12
|
+
*/
|
|
13
|
+
loggingConnectorType?: string;
|
|
14
|
+
/**
|
|
15
|
+
* The configuration for the connector.
|
|
16
|
+
*/
|
|
17
|
+
config: IDynamoDbEntityStorageConnectorConfig;
|
|
18
|
+
}
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,110 @@
|
|
|
1
1
|
# @twin.org/entity-storage-connector-dynamodb - Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.0.1 (2025-07-04)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add production release automation ([1eb4c8e](https://github.com/twinfoundation/entity-storage/commit/1eb4c8ee3eb099defdfc2d063ae44935276dcae8))
|
|
9
|
+
* release to production ([a309051](https://github.com/twinfoundation/entity-storage/commit/a3090519adebf7943232b4df12e4c6bd5afe7eed))
|
|
10
|
+
* update dependencies ([7ccc0c4](https://github.com/twinfoundation/entity-storage/commit/7ccc0c429125d073dc60b3de6cf101abc8cc6cba))
|
|
11
|
+
* update docs ([5203e62](https://github.com/twinfoundation/entity-storage/commit/5203e621c3ae368f6fefb29ea5146f024f4a1b0d))
|
|
12
|
+
* use shared store mechanism ([#34](https://github.com/twinfoundation/entity-storage/issues/34)) ([68b6b71](https://github.com/twinfoundation/entity-storage/commit/68b6b71e7a96d7d016cd57bfff36775b56bf3f93))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* query params force coercion ([dd6aa87](https://github.com/twinfoundation/entity-storage/commit/dd6aa87efdfb60bab7d6756a86888863c45c51a7))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Dependencies
|
|
21
|
+
|
|
22
|
+
* The following workspace dependencies were updated
|
|
23
|
+
* dependencies
|
|
24
|
+
* @twin.org/entity-storage-models bumped from ^0.0.0 to ^0.0.1
|
|
25
|
+
* devDependencies
|
|
26
|
+
* @twin.org/entity-storage-connector-memory bumped from ^0.0.0 to ^0.0.1
|
|
27
|
+
|
|
28
|
+
## [0.0.1-next.31](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-dynamodb-v0.0.1-next.30...entity-storage-connector-dynamodb-v0.0.1-next.31) (2025-06-20)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Bug Fixes
|
|
32
|
+
|
|
33
|
+
* query params force coercion ([dd6aa87](https://github.com/twinfoundation/entity-storage/commit/dd6aa87efdfb60bab7d6756a86888863c45c51a7))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Dependencies
|
|
37
|
+
|
|
38
|
+
* The following workspace dependencies were updated
|
|
39
|
+
* dependencies
|
|
40
|
+
* @twin.org/entity-storage-models bumped from 0.0.1-next.30 to 0.0.1-next.31
|
|
41
|
+
* devDependencies
|
|
42
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.1-next.30 to 0.0.1-next.31
|
|
43
|
+
|
|
44
|
+
## [0.0.1-next.30](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-dynamodb-v0.0.1-next.29...entity-storage-connector-dynamodb-v0.0.1-next.30) (2025-06-12)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Features
|
|
48
|
+
|
|
49
|
+
* update dependencies ([7ccc0c4](https://github.com/twinfoundation/entity-storage/commit/7ccc0c429125d073dc60b3de6cf101abc8cc6cba))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Dependencies
|
|
53
|
+
|
|
54
|
+
* The following workspace dependencies were updated
|
|
55
|
+
* dependencies
|
|
56
|
+
* @twin.org/entity-storage-models bumped from 0.0.1-next.29 to 0.0.1-next.30
|
|
57
|
+
* devDependencies
|
|
58
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.1-next.29 to 0.0.1-next.30
|
|
59
|
+
|
|
60
|
+
## [0.0.1-next.29](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-dynamodb-v0.0.1-next.28...entity-storage-connector-dynamodb-v0.0.1-next.29) (2025-04-17)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
### Features
|
|
64
|
+
|
|
65
|
+
* use shared store mechanism ([#34](https://github.com/twinfoundation/entity-storage/issues/34)) ([68b6b71](https://github.com/twinfoundation/entity-storage/commit/68b6b71e7a96d7d016cd57bfff36775b56bf3f93))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
### Dependencies
|
|
69
|
+
|
|
70
|
+
* The following workspace dependencies were updated
|
|
71
|
+
* dependencies
|
|
72
|
+
* @twin.org/entity-storage-models bumped from 0.0.1-next.28 to 0.0.1-next.29
|
|
73
|
+
* devDependencies
|
|
74
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.1-next.28 to 0.0.1-next.29
|
|
75
|
+
|
|
76
|
+
## [0.0.1-next.28](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-dynamodb-v0.0.1-next.27...entity-storage-connector-dynamodb-v0.0.1-next.28) (2025-04-09)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
### Features
|
|
80
|
+
|
|
81
|
+
* update docs ([5203e62](https://github.com/twinfoundation/entity-storage/commit/5203e621c3ae368f6fefb29ea5146f024f4a1b0d))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
### Dependencies
|
|
85
|
+
|
|
86
|
+
* The following workspace dependencies were updated
|
|
87
|
+
* dependencies
|
|
88
|
+
* @twin.org/entity-storage-models bumped from 0.0.1-next.27 to 0.0.1-next.28
|
|
89
|
+
* devDependencies
|
|
90
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.1-next.27 to 0.0.1-next.28
|
|
91
|
+
|
|
92
|
+
## [0.0.1-next.27](https://github.com/twinfoundation/entity-storage/compare/entity-storage-connector-dynamodb-v0.0.1-next.26...entity-storage-connector-dynamodb-v0.0.1-next.27) (2025-03-28)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
### Miscellaneous Chores
|
|
96
|
+
|
|
97
|
+
* **entity-storage-connector-dynamodb:** Synchronize repo versions
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
### Dependencies
|
|
101
|
+
|
|
102
|
+
* The following workspace dependencies were updated
|
|
103
|
+
* dependencies
|
|
104
|
+
* @twin.org/entity-storage-models bumped from 0.0.1-next.26 to 0.0.1-next.27
|
|
105
|
+
* devDependencies
|
|
106
|
+
* @twin.org/entity-storage-connector-memory bumped from 0.0.1-next.26 to 0.0.1-next.27
|
|
107
|
+
|
|
108
|
+
## v0.0.1-next.26
|
|
4
109
|
|
|
5
110
|
- Initial Release
|