@twin.org/entity-storage-connector-dynamodb 0.0.1-next.8 → 0.0.1-next.9
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
CHANGED
|
@@ -225,13 +225,14 @@ class DynamoDbEntityStorageConnector {
|
|
|
225
225
|
* Get an entity.
|
|
226
226
|
* @param id The id of the entity to get, or the index value if secondaryIndex is set.
|
|
227
227
|
* @param secondaryIndex Get the item using a secondary index.
|
|
228
|
+
* @param conditions The optional conditions to match for the entities.
|
|
228
229
|
* @returns The object if it can be found or undefined.
|
|
229
230
|
*/
|
|
230
|
-
async get(id, secondaryIndex) {
|
|
231
|
+
async get(id, secondaryIndex, conditions) {
|
|
231
232
|
core.Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
232
233
|
try {
|
|
233
234
|
const docClient = this.createDocClient();
|
|
234
|
-
if (core.Is.
|
|
235
|
+
if (core.Is.empty(secondaryIndex) && core.Is.empty(conditions)) {
|
|
235
236
|
const getCommand = new libDynamodb.GetCommand({
|
|
236
237
|
TableName: this._config.tableName,
|
|
237
238
|
Key: {
|
|
@@ -243,27 +244,27 @@ class DynamoDbEntityStorageConnector {
|
|
|
243
244
|
delete response.Item?.[DynamoDbEntityStorageConnector._PARTITION_ID_NAME];
|
|
244
245
|
return response.Item;
|
|
245
246
|
}
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
247
|
+
const finalConditions = {
|
|
248
|
+
conditions: []
|
|
249
|
+
};
|
|
250
|
+
if (core.Is.stringValue(secondaryIndex)) {
|
|
251
|
+
finalConditions.conditions.push({
|
|
252
|
+
property: secondaryIndex,
|
|
253
|
+
comparison: entity.ComparisonOperator.Equals,
|
|
254
|
+
value: id
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if (core.Is.arrayValue(conditions)) {
|
|
258
|
+
for (const c of conditions) {
|
|
259
|
+
finalConditions.conditions.push({
|
|
260
|
+
property: c.property,
|
|
261
|
+
comparison: entity.ComparisonOperator.Equals,
|
|
262
|
+
value: c.value
|
|
263
|
+
});
|
|
261
264
|
}
|
|
262
|
-
});
|
|
263
|
-
const response = await docClient.send(queryCommand);
|
|
264
|
-
if (response.Items?.length === 1) {
|
|
265
|
-
return utilDynamodb.unmarshall(response.Items[0]);
|
|
266
265
|
}
|
|
266
|
+
const queryResult = await this.internalQuery(finalConditions, undefined, undefined, undefined, 1, secondaryIndex);
|
|
267
|
+
return queryResult.entities[0];
|
|
267
268
|
}
|
|
268
269
|
catch (err) {
|
|
269
270
|
if (core.BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
@@ -275,24 +276,28 @@ class DynamoDbEntityStorageConnector {
|
|
|
275
276
|
id
|
|
276
277
|
}, err);
|
|
277
278
|
}
|
|
278
|
-
return undefined;
|
|
279
279
|
}
|
|
280
280
|
/**
|
|
281
281
|
* Set an entity.
|
|
282
282
|
* @param entity The entity to set.
|
|
283
|
+
* @param conditions The optional conditions to match for the entities.
|
|
283
284
|
* @returns The id of the entity.
|
|
284
285
|
*/
|
|
285
|
-
async set(entity) {
|
|
286
|
+
async set(entity, conditions) {
|
|
286
287
|
core.Guards.object(this.CLASS_NAME, "entity", entity);
|
|
287
288
|
const id = entity[this._primaryKey.property];
|
|
288
289
|
try {
|
|
289
290
|
const docClient = this.createDocClient();
|
|
291
|
+
const { conditionExpression, attributeNames, attributeValues } = this.buildConditionExpression(conditions);
|
|
290
292
|
const putCommand = new libDynamodb.PutCommand({
|
|
291
293
|
TableName: this._config.tableName,
|
|
292
294
|
Item: {
|
|
293
295
|
[DynamoDbEntityStorageConnector._PARTITION_ID_NAME]: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE,
|
|
294
296
|
...entity
|
|
295
|
-
}
|
|
297
|
+
},
|
|
298
|
+
ConditionExpression: conditionExpression,
|
|
299
|
+
ExpressionAttributeNames: attributeNames,
|
|
300
|
+
ExpressionAttributeValues: attributeValues
|
|
296
301
|
});
|
|
297
302
|
await docClient.send(putCommand);
|
|
298
303
|
}
|
|
@@ -310,22 +315,30 @@ class DynamoDbEntityStorageConnector {
|
|
|
310
315
|
/**
|
|
311
316
|
* Remove the entity.
|
|
312
317
|
* @param id The id of the entity to remove.
|
|
318
|
+
* @param conditions The optional conditions to match for the entities.
|
|
313
319
|
* @returns Nothing.
|
|
314
320
|
*/
|
|
315
|
-
async remove(id) {
|
|
321
|
+
async remove(id, conditions) {
|
|
316
322
|
core.Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
317
323
|
try {
|
|
318
324
|
const docClient = this.createDocClient();
|
|
325
|
+
const { conditionExpression, attributeNames, attributeValues } = this.buildConditionExpression(conditions);
|
|
319
326
|
const deleteCommand = new libDynamodb.DeleteCommand({
|
|
320
327
|
TableName: this._config.tableName,
|
|
321
328
|
Key: {
|
|
322
329
|
[DynamoDbEntityStorageConnector._PARTITION_ID_NAME]: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE,
|
|
323
330
|
[this._primaryKey.property]: id
|
|
324
|
-
}
|
|
331
|
+
},
|
|
332
|
+
ConditionExpression: conditionExpression,
|
|
333
|
+
ExpressionAttributeNames: attributeNames,
|
|
334
|
+
ExpressionAttributeValues: attributeValues
|
|
325
335
|
});
|
|
326
336
|
await docClient.send(deleteCommand);
|
|
327
337
|
}
|
|
328
338
|
catch (err) {
|
|
339
|
+
if (core.BaseError.isErrorName(err, "ConditionalCheckFailedException")) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
329
342
|
if (core.BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
330
343
|
throw new core.GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
331
344
|
table: this._config.tableName
|
|
@@ -347,77 +360,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
347
360
|
* and a cursor which can be used to request more entities.
|
|
348
361
|
*/
|
|
349
362
|
async query(conditions, sortProperties, properties, cursor, pageSize) {
|
|
350
|
-
|
|
351
|
-
const returnSize = pageSize ?? DynamoDbEntityStorageConnector._PAGE_SIZE;
|
|
352
|
-
let indexName;
|
|
353
|
-
// If we have a sortable property defined in the descriptor then we must use
|
|
354
|
-
// the secondary index for the query
|
|
355
|
-
if (core.Is.arrayValue(sortProperties)) {
|
|
356
|
-
if (sortProperties.length > 1) {
|
|
357
|
-
throw new core.GeneralError(this.CLASS_NAME, "sortSingle");
|
|
358
|
-
}
|
|
359
|
-
for (const sortProperty of sortProperties) {
|
|
360
|
-
const propertySchema = this._entitySchema.properties?.find(e => e.property === sortProperty.property);
|
|
361
|
-
if (core.Is.undefined(propertySchema) ||
|
|
362
|
-
(!propertySchema.isPrimary &&
|
|
363
|
-
!propertySchema.isSecondary &&
|
|
364
|
-
core.Is.empty(propertySchema.sortDirection))) {
|
|
365
|
-
throw new core.GeneralError(this.CLASS_NAME, "sortNotIndexed", {
|
|
366
|
-
property: sortProperty.property
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
indexName = propertySchema.isPrimary
|
|
370
|
-
? undefined
|
|
371
|
-
: `${sortProperty.property}Index`;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
const attributeNames = { "#partitionId": "partitionId" };
|
|
375
|
-
const attributeValues = {
|
|
376
|
-
[`:${DynamoDbEntityStorageConnector._PARTITION_ID_NAME}`]: {
|
|
377
|
-
S: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
const expressions = this.buildQueryParameters("", conditions, attributeNames, attributeValues);
|
|
381
|
-
let keyExpression = "#partitionId = :partitionId";
|
|
382
|
-
if (expressions.keyCondition.length > 0) {
|
|
383
|
-
keyExpression += ` AND ${expressions.keyCondition}`;
|
|
384
|
-
}
|
|
385
|
-
const query = new clientDynamodb.QueryCommand({
|
|
386
|
-
TableName: this._config.tableName,
|
|
387
|
-
IndexName: indexName,
|
|
388
|
-
KeyConditionExpression: keyExpression,
|
|
389
|
-
FilterExpression: core.Is.stringValue(expressions.filterCondition)
|
|
390
|
-
? expressions.filterCondition
|
|
391
|
-
: undefined,
|
|
392
|
-
ExpressionAttributeNames: attributeNames,
|
|
393
|
-
ExpressionAttributeValues: attributeValues,
|
|
394
|
-
ProjectionExpression: properties?.map(p => p).join(", "),
|
|
395
|
-
Limit: returnSize,
|
|
396
|
-
ExclusiveStartKey: core.Is.empty(cursor)
|
|
397
|
-
? undefined
|
|
398
|
-
: core.ObjectHelper.fromBytes(core.Converter.base64ToBytes(cursor))
|
|
399
|
-
});
|
|
400
|
-
const connection = this.createDocClient();
|
|
401
|
-
const results = await connection.send(query);
|
|
402
|
-
let entities = [];
|
|
403
|
-
if (core.Is.arrayValue(results.Items)) {
|
|
404
|
-
entities = results.Items.map(item => utilDynamodb.unmarshall(item));
|
|
405
|
-
}
|
|
406
|
-
return {
|
|
407
|
-
entities,
|
|
408
|
-
cursor: core.Is.empty(results.LastEvaluatedKey)
|
|
409
|
-
? undefined
|
|
410
|
-
: core.Converter.bytesToBase64(core.ObjectHelper.toBytes(results.LastEvaluatedKey))
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
catch (err) {
|
|
414
|
-
if (core.BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
415
|
-
throw new core.GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
416
|
-
table: this._config.tableName
|
|
417
|
-
}, err);
|
|
418
|
-
}
|
|
419
|
-
throw new core.GeneralError(this.CLASS_NAME, "queryFailed", undefined, err);
|
|
420
|
-
}
|
|
363
|
+
return this.internalQuery(conditions, sortProperties, properties, cursor, pageSize);
|
|
421
364
|
}
|
|
422
365
|
/**
|
|
423
366
|
* Delete the table.
|
|
@@ -439,7 +382,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
439
382
|
* @returns The condition clause.
|
|
440
383
|
* @internal
|
|
441
384
|
*/
|
|
442
|
-
buildQueryParameters(objectPath, condition, attributeNames, attributeValues) {
|
|
385
|
+
buildQueryParameters(objectPath, condition, attributeNames, attributeValues, secondaryIndex) {
|
|
443
386
|
// If no conditions are defined then return empty string
|
|
444
387
|
if (core.Is.undefined(condition)) {
|
|
445
388
|
return {
|
|
@@ -455,7 +398,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
455
398
|
};
|
|
456
399
|
}
|
|
457
400
|
// It's a group of comparisons, so check the individual items and combine with the logical operator
|
|
458
|
-
const joinConditions = condition.conditions.map(c => this.buildQueryParameters(objectPath, c, attributeNames, attributeValues));
|
|
401
|
+
const joinConditions = condition.conditions.map(c => this.buildQueryParameters(objectPath, c, attributeNames, attributeValues, secondaryIndex));
|
|
459
402
|
const logicalOperator = this.mapConditionalOperator(condition.logicalOperator);
|
|
460
403
|
const keyCondition = joinConditions
|
|
461
404
|
.filter(j => j.keyCondition.length > 0)
|
|
@@ -473,9 +416,10 @@ class DynamoDbEntityStorageConnector {
|
|
|
473
416
|
const schemaProp = this._entitySchema.properties?.find(p => p.property === condition.property);
|
|
474
417
|
// It's a single value so just create the property comparison for the condition
|
|
475
418
|
const comparison = this.mapComparisonOperator(objectPath, condition, schemaProp?.type, attributeNames, attributeValues);
|
|
419
|
+
const isKey = schemaProp?.isPrimary || (schemaProp?.isSecondary && schemaProp?.property === secondaryIndex);
|
|
476
420
|
return {
|
|
477
|
-
keyCondition:
|
|
478
|
-
filterCondition:
|
|
421
|
+
keyCondition: isKey ? comparison : "",
|
|
422
|
+
filterCondition: !isKey ? comparison : ""
|
|
479
423
|
};
|
|
480
424
|
}
|
|
481
425
|
/**
|
|
@@ -656,6 +600,125 @@ class DynamoDbEntityStorageConnector {
|
|
|
656
600
|
return false;
|
|
657
601
|
}
|
|
658
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* Find all the entities which match the conditions.
|
|
605
|
+
* @param conditions The conditions to match for the entities.
|
|
606
|
+
* @param sortProperties The optional sort order.
|
|
607
|
+
* @param properties The optional properties to return, defaults to all.
|
|
608
|
+
* @param cursor The cursor to request the next page of entities.
|
|
609
|
+
* @param pageSize The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
|
|
610
|
+
* @param secondaryIndex The secondary index to use for the query.
|
|
611
|
+
* @returns All the entities for the storage matching the conditions,
|
|
612
|
+
* and a cursor which can be used to request more entities.
|
|
613
|
+
* @internal
|
|
614
|
+
*/
|
|
615
|
+
async internalQuery(conditions, sortProperties, properties, cursor, pageSize, secondaryIndex) {
|
|
616
|
+
try {
|
|
617
|
+
const returnSize = pageSize ?? DynamoDbEntityStorageConnector._PAGE_SIZE;
|
|
618
|
+
let indexName = core.Is.stringValue(secondaryIndex)
|
|
619
|
+
? `${secondaryIndex}Index`
|
|
620
|
+
: undefined;
|
|
621
|
+
// If we have a sortable property defined in the descriptor then we must use
|
|
622
|
+
// the secondary index for the query
|
|
623
|
+
if (core.Is.arrayValue(sortProperties)) {
|
|
624
|
+
if (sortProperties.length > 1) {
|
|
625
|
+
throw new core.GeneralError(this.CLASS_NAME, "sortSingle");
|
|
626
|
+
}
|
|
627
|
+
for (const sortProperty of sortProperties) {
|
|
628
|
+
const propertySchema = this._entitySchema.properties?.find(e => e.property === sortProperty.property);
|
|
629
|
+
if (core.Is.undefined(propertySchema) ||
|
|
630
|
+
(!propertySchema.isPrimary &&
|
|
631
|
+
!propertySchema.isSecondary &&
|
|
632
|
+
core.Is.empty(propertySchema.sortDirection))) {
|
|
633
|
+
throw new core.GeneralError(this.CLASS_NAME, "sortNotIndexed", {
|
|
634
|
+
property: sortProperty.property
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
indexName = propertySchema.isPrimary
|
|
638
|
+
? undefined
|
|
639
|
+
: `${sortProperty.property}Index`;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const attributeNames = { "#partitionId": "partitionId" };
|
|
643
|
+
const attributeValues = {
|
|
644
|
+
[`:${DynamoDbEntityStorageConnector._PARTITION_ID_NAME}`]: {
|
|
645
|
+
S: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
const expressions = this.buildQueryParameters("", conditions, attributeNames, attributeValues, secondaryIndex);
|
|
649
|
+
let keyExpression = "#partitionId = :partitionId";
|
|
650
|
+
if (expressions.keyCondition.length > 0) {
|
|
651
|
+
keyExpression += ` AND ${expressions.keyCondition}`;
|
|
652
|
+
}
|
|
653
|
+
const query = new clientDynamodb.QueryCommand({
|
|
654
|
+
TableName: this._config.tableName,
|
|
655
|
+
IndexName: indexName,
|
|
656
|
+
KeyConditionExpression: keyExpression,
|
|
657
|
+
FilterExpression: core.Is.stringValue(expressions.filterCondition)
|
|
658
|
+
? expressions.filterCondition
|
|
659
|
+
: undefined,
|
|
660
|
+
ExpressionAttributeNames: attributeNames,
|
|
661
|
+
ExpressionAttributeValues: attributeValues,
|
|
662
|
+
ProjectionExpression: properties?.map(p => p).join(", "),
|
|
663
|
+
Limit: returnSize,
|
|
664
|
+
ExclusiveStartKey: core.Is.empty(cursor)
|
|
665
|
+
? undefined
|
|
666
|
+
: core.ObjectHelper.fromBytes(core.Converter.base64ToBytes(cursor))
|
|
667
|
+
});
|
|
668
|
+
const connection = this.createDocClient();
|
|
669
|
+
const results = await connection.send(query);
|
|
670
|
+
let entities = [];
|
|
671
|
+
if (core.Is.arrayValue(results.Items)) {
|
|
672
|
+
entities = results.Items.map(item => utilDynamodb.unmarshall(item));
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
entities,
|
|
676
|
+
cursor: core.Is.empty(results.LastEvaluatedKey)
|
|
677
|
+
? undefined
|
|
678
|
+
: core.Converter.bytesToBase64(core.ObjectHelper.toBytes(results.LastEvaluatedKey))
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
if (core.BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
683
|
+
throw new core.GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
684
|
+
table: this._config.tableName
|
|
685
|
+
}, err);
|
|
686
|
+
}
|
|
687
|
+
throw new core.GeneralError(this.CLASS_NAME, "queryFailed", undefined, err);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Build the condition expression for the query.
|
|
692
|
+
* @param conditions The conditions to build the expression from.
|
|
693
|
+
* @returns The condition expression.
|
|
694
|
+
* @throws GeneralError if the property is not found in the schema.
|
|
695
|
+
* @internal
|
|
696
|
+
*/
|
|
697
|
+
buildConditionExpression(conditions) {
|
|
698
|
+
let conditionExpression;
|
|
699
|
+
let attributeNames;
|
|
700
|
+
let attributeValues;
|
|
701
|
+
if (core.Is.arrayValue(conditions)) {
|
|
702
|
+
const expressions = [];
|
|
703
|
+
for (const c of conditions) {
|
|
704
|
+
const schemaProp = this._entitySchema.properties?.find(p => p.property === c.property);
|
|
705
|
+
if (core.Is.undefined(schemaProp)) {
|
|
706
|
+
throw new core.GeneralError(this.CLASS_NAME, "propertyNotFound", {
|
|
707
|
+
property: c.property
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
const attributeName = `#${c.property}`;
|
|
711
|
+
const attributeValueName = `:${c.property}`;
|
|
712
|
+
attributeNames ??= {};
|
|
713
|
+
attributeValues ??= {};
|
|
714
|
+
attributeNames[attributeName] = c.property;
|
|
715
|
+
attributeValues[attributeValueName] = c.value;
|
|
716
|
+
expressions.push(`${attributeName} = ${attributeValueName}`);
|
|
717
|
+
}
|
|
718
|
+
conditionExpression = expressions.join(" AND ");
|
|
719
|
+
}
|
|
720
|
+
return { conditionExpression, attributeNames, attributeValues };
|
|
721
|
+
}
|
|
659
722
|
}
|
|
660
723
|
|
|
661
724
|
exports.DynamoDbEntityStorageConnector = DynamoDbEntityStorageConnector;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
|
4
|
+
import { Guards, Is, BaseError, GeneralError, Coerce, ObjectHelper, Converter } from '@twin.org/core';
|
|
5
5
|
import { EntitySchemaFactory, EntitySchemaHelper, ComparisonOperator, LogicalOperator } from '@twin.org/entity';
|
|
6
6
|
import { LoggingConnectorFactory } from '@twin.org/logging-models';
|
|
7
7
|
|
|
@@ -223,13 +223,14 @@ class DynamoDbEntityStorageConnector {
|
|
|
223
223
|
* Get an entity.
|
|
224
224
|
* @param id The id of the entity to get, or the index value if secondaryIndex is set.
|
|
225
225
|
* @param secondaryIndex Get the item using a secondary index.
|
|
226
|
+
* @param conditions The optional conditions to match for the entities.
|
|
226
227
|
* @returns The object if it can be found or undefined.
|
|
227
228
|
*/
|
|
228
|
-
async get(id, secondaryIndex) {
|
|
229
|
+
async get(id, secondaryIndex, conditions) {
|
|
229
230
|
Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
230
231
|
try {
|
|
231
232
|
const docClient = this.createDocClient();
|
|
232
|
-
if (Is.
|
|
233
|
+
if (Is.empty(secondaryIndex) && Is.empty(conditions)) {
|
|
233
234
|
const getCommand = new GetCommand({
|
|
234
235
|
TableName: this._config.tableName,
|
|
235
236
|
Key: {
|
|
@@ -241,27 +242,27 @@ class DynamoDbEntityStorageConnector {
|
|
|
241
242
|
delete response.Item?.[DynamoDbEntityStorageConnector._PARTITION_ID_NAME];
|
|
242
243
|
return response.Item;
|
|
243
244
|
}
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
245
|
+
const finalConditions = {
|
|
246
|
+
conditions: []
|
|
247
|
+
};
|
|
248
|
+
if (Is.stringValue(secondaryIndex)) {
|
|
249
|
+
finalConditions.conditions.push({
|
|
250
|
+
property: secondaryIndex,
|
|
251
|
+
comparison: ComparisonOperator.Equals,
|
|
252
|
+
value: id
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (Is.arrayValue(conditions)) {
|
|
256
|
+
for (const c of conditions) {
|
|
257
|
+
finalConditions.conditions.push({
|
|
258
|
+
property: c.property,
|
|
259
|
+
comparison: ComparisonOperator.Equals,
|
|
260
|
+
value: c.value
|
|
261
|
+
});
|
|
259
262
|
}
|
|
260
|
-
});
|
|
261
|
-
const response = await docClient.send(queryCommand);
|
|
262
|
-
if (response.Items?.length === 1) {
|
|
263
|
-
return unmarshall(response.Items[0]);
|
|
264
263
|
}
|
|
264
|
+
const queryResult = await this.internalQuery(finalConditions, undefined, undefined, undefined, 1, secondaryIndex);
|
|
265
|
+
return queryResult.entities[0];
|
|
265
266
|
}
|
|
266
267
|
catch (err) {
|
|
267
268
|
if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
@@ -273,24 +274,28 @@ class DynamoDbEntityStorageConnector {
|
|
|
273
274
|
id
|
|
274
275
|
}, err);
|
|
275
276
|
}
|
|
276
|
-
return undefined;
|
|
277
277
|
}
|
|
278
278
|
/**
|
|
279
279
|
* Set an entity.
|
|
280
280
|
* @param entity The entity to set.
|
|
281
|
+
* @param conditions The optional conditions to match for the entities.
|
|
281
282
|
* @returns The id of the entity.
|
|
282
283
|
*/
|
|
283
|
-
async set(entity) {
|
|
284
|
+
async set(entity, conditions) {
|
|
284
285
|
Guards.object(this.CLASS_NAME, "entity", entity);
|
|
285
286
|
const id = entity[this._primaryKey.property];
|
|
286
287
|
try {
|
|
287
288
|
const docClient = this.createDocClient();
|
|
289
|
+
const { conditionExpression, attributeNames, attributeValues } = this.buildConditionExpression(conditions);
|
|
288
290
|
const putCommand = new PutCommand({
|
|
289
291
|
TableName: this._config.tableName,
|
|
290
292
|
Item: {
|
|
291
293
|
[DynamoDbEntityStorageConnector._PARTITION_ID_NAME]: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE,
|
|
292
294
|
...entity
|
|
293
|
-
}
|
|
295
|
+
},
|
|
296
|
+
ConditionExpression: conditionExpression,
|
|
297
|
+
ExpressionAttributeNames: attributeNames,
|
|
298
|
+
ExpressionAttributeValues: attributeValues
|
|
294
299
|
});
|
|
295
300
|
await docClient.send(putCommand);
|
|
296
301
|
}
|
|
@@ -308,22 +313,30 @@ class DynamoDbEntityStorageConnector {
|
|
|
308
313
|
/**
|
|
309
314
|
* Remove the entity.
|
|
310
315
|
* @param id The id of the entity to remove.
|
|
316
|
+
* @param conditions The optional conditions to match for the entities.
|
|
311
317
|
* @returns Nothing.
|
|
312
318
|
*/
|
|
313
|
-
async remove(id) {
|
|
319
|
+
async remove(id, conditions) {
|
|
314
320
|
Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
315
321
|
try {
|
|
316
322
|
const docClient = this.createDocClient();
|
|
323
|
+
const { conditionExpression, attributeNames, attributeValues } = this.buildConditionExpression(conditions);
|
|
317
324
|
const deleteCommand = new DeleteCommand({
|
|
318
325
|
TableName: this._config.tableName,
|
|
319
326
|
Key: {
|
|
320
327
|
[DynamoDbEntityStorageConnector._PARTITION_ID_NAME]: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE,
|
|
321
328
|
[this._primaryKey.property]: id
|
|
322
|
-
}
|
|
329
|
+
},
|
|
330
|
+
ConditionExpression: conditionExpression,
|
|
331
|
+
ExpressionAttributeNames: attributeNames,
|
|
332
|
+
ExpressionAttributeValues: attributeValues
|
|
323
333
|
});
|
|
324
334
|
await docClient.send(deleteCommand);
|
|
325
335
|
}
|
|
326
336
|
catch (err) {
|
|
337
|
+
if (BaseError.isErrorName(err, "ConditionalCheckFailedException")) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
327
340
|
if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
328
341
|
throw new GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
329
342
|
table: this._config.tableName
|
|
@@ -345,77 +358,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
345
358
|
* and a cursor which can be used to request more entities.
|
|
346
359
|
*/
|
|
347
360
|
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
|
-
}
|
|
361
|
+
return this.internalQuery(conditions, sortProperties, properties, cursor, pageSize);
|
|
419
362
|
}
|
|
420
363
|
/**
|
|
421
364
|
* Delete the table.
|
|
@@ -437,7 +380,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
437
380
|
* @returns The condition clause.
|
|
438
381
|
* @internal
|
|
439
382
|
*/
|
|
440
|
-
buildQueryParameters(objectPath, condition, attributeNames, attributeValues) {
|
|
383
|
+
buildQueryParameters(objectPath, condition, attributeNames, attributeValues, secondaryIndex) {
|
|
441
384
|
// If no conditions are defined then return empty string
|
|
442
385
|
if (Is.undefined(condition)) {
|
|
443
386
|
return {
|
|
@@ -453,7 +396,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
453
396
|
};
|
|
454
397
|
}
|
|
455
398
|
// 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));
|
|
399
|
+
const joinConditions = condition.conditions.map(c => this.buildQueryParameters(objectPath, c, attributeNames, attributeValues, secondaryIndex));
|
|
457
400
|
const logicalOperator = this.mapConditionalOperator(condition.logicalOperator);
|
|
458
401
|
const keyCondition = joinConditions
|
|
459
402
|
.filter(j => j.keyCondition.length > 0)
|
|
@@ -471,9 +414,10 @@ class DynamoDbEntityStorageConnector {
|
|
|
471
414
|
const schemaProp = this._entitySchema.properties?.find(p => p.property === condition.property);
|
|
472
415
|
// It's a single value so just create the property comparison for the condition
|
|
473
416
|
const comparison = this.mapComparisonOperator(objectPath, condition, schemaProp?.type, attributeNames, attributeValues);
|
|
417
|
+
const isKey = schemaProp?.isPrimary || (schemaProp?.isSecondary && schemaProp?.property === secondaryIndex);
|
|
474
418
|
return {
|
|
475
|
-
keyCondition:
|
|
476
|
-
filterCondition:
|
|
419
|
+
keyCondition: isKey ? comparison : "",
|
|
420
|
+
filterCondition: !isKey ? comparison : ""
|
|
477
421
|
};
|
|
478
422
|
}
|
|
479
423
|
/**
|
|
@@ -654,6 +598,125 @@ class DynamoDbEntityStorageConnector {
|
|
|
654
598
|
return false;
|
|
655
599
|
}
|
|
656
600
|
}
|
|
601
|
+
/**
|
|
602
|
+
* Find all the entities which match the conditions.
|
|
603
|
+
* @param conditions The conditions to match for the entities.
|
|
604
|
+
* @param sortProperties The optional sort order.
|
|
605
|
+
* @param properties The optional properties to return, defaults to all.
|
|
606
|
+
* @param cursor The cursor to request the next page of entities.
|
|
607
|
+
* @param pageSize The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
|
|
608
|
+
* @param secondaryIndex The secondary index to use for the query.
|
|
609
|
+
* @returns All the entities for the storage matching the conditions,
|
|
610
|
+
* and a cursor which can be used to request more entities.
|
|
611
|
+
* @internal
|
|
612
|
+
*/
|
|
613
|
+
async internalQuery(conditions, sortProperties, properties, cursor, pageSize, secondaryIndex) {
|
|
614
|
+
try {
|
|
615
|
+
const returnSize = pageSize ?? DynamoDbEntityStorageConnector._PAGE_SIZE;
|
|
616
|
+
let indexName = Is.stringValue(secondaryIndex)
|
|
617
|
+
? `${secondaryIndex}Index`
|
|
618
|
+
: undefined;
|
|
619
|
+
// If we have a sortable property defined in the descriptor then we must use
|
|
620
|
+
// the secondary index for the query
|
|
621
|
+
if (Is.arrayValue(sortProperties)) {
|
|
622
|
+
if (sortProperties.length > 1) {
|
|
623
|
+
throw new GeneralError(this.CLASS_NAME, "sortSingle");
|
|
624
|
+
}
|
|
625
|
+
for (const sortProperty of sortProperties) {
|
|
626
|
+
const propertySchema = this._entitySchema.properties?.find(e => e.property === sortProperty.property);
|
|
627
|
+
if (Is.undefined(propertySchema) ||
|
|
628
|
+
(!propertySchema.isPrimary &&
|
|
629
|
+
!propertySchema.isSecondary &&
|
|
630
|
+
Is.empty(propertySchema.sortDirection))) {
|
|
631
|
+
throw new GeneralError(this.CLASS_NAME, "sortNotIndexed", {
|
|
632
|
+
property: sortProperty.property
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
indexName = propertySchema.isPrimary
|
|
636
|
+
? undefined
|
|
637
|
+
: `${sortProperty.property}Index`;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
const attributeNames = { "#partitionId": "partitionId" };
|
|
641
|
+
const attributeValues = {
|
|
642
|
+
[`:${DynamoDbEntityStorageConnector._PARTITION_ID_NAME}`]: {
|
|
643
|
+
S: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
const expressions = this.buildQueryParameters("", conditions, attributeNames, attributeValues, secondaryIndex);
|
|
647
|
+
let keyExpression = "#partitionId = :partitionId";
|
|
648
|
+
if (expressions.keyCondition.length > 0) {
|
|
649
|
+
keyExpression += ` AND ${expressions.keyCondition}`;
|
|
650
|
+
}
|
|
651
|
+
const query = new QueryCommand({
|
|
652
|
+
TableName: this._config.tableName,
|
|
653
|
+
IndexName: indexName,
|
|
654
|
+
KeyConditionExpression: keyExpression,
|
|
655
|
+
FilterExpression: Is.stringValue(expressions.filterCondition)
|
|
656
|
+
? expressions.filterCondition
|
|
657
|
+
: undefined,
|
|
658
|
+
ExpressionAttributeNames: attributeNames,
|
|
659
|
+
ExpressionAttributeValues: attributeValues,
|
|
660
|
+
ProjectionExpression: properties?.map(p => p).join(", "),
|
|
661
|
+
Limit: returnSize,
|
|
662
|
+
ExclusiveStartKey: Is.empty(cursor)
|
|
663
|
+
? undefined
|
|
664
|
+
: ObjectHelper.fromBytes(Converter.base64ToBytes(cursor))
|
|
665
|
+
});
|
|
666
|
+
const connection = this.createDocClient();
|
|
667
|
+
const results = await connection.send(query);
|
|
668
|
+
let entities = [];
|
|
669
|
+
if (Is.arrayValue(results.Items)) {
|
|
670
|
+
entities = results.Items.map(item => unmarshall(item));
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
entities,
|
|
674
|
+
cursor: Is.empty(results.LastEvaluatedKey)
|
|
675
|
+
? undefined
|
|
676
|
+
: Converter.bytesToBase64(ObjectHelper.toBytes(results.LastEvaluatedKey))
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
catch (err) {
|
|
680
|
+
if (BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
681
|
+
throw new GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
682
|
+
table: this._config.tableName
|
|
683
|
+
}, err);
|
|
684
|
+
}
|
|
685
|
+
throw new GeneralError(this.CLASS_NAME, "queryFailed", undefined, err);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Build the condition expression for the query.
|
|
690
|
+
* @param conditions The conditions to build the expression from.
|
|
691
|
+
* @returns The condition expression.
|
|
692
|
+
* @throws GeneralError if the property is not found in the schema.
|
|
693
|
+
* @internal
|
|
694
|
+
*/
|
|
695
|
+
buildConditionExpression(conditions) {
|
|
696
|
+
let conditionExpression;
|
|
697
|
+
let attributeNames;
|
|
698
|
+
let attributeValues;
|
|
699
|
+
if (Is.arrayValue(conditions)) {
|
|
700
|
+
const expressions = [];
|
|
701
|
+
for (const c of conditions) {
|
|
702
|
+
const schemaProp = this._entitySchema.properties?.find(p => p.property === c.property);
|
|
703
|
+
if (Is.undefined(schemaProp)) {
|
|
704
|
+
throw new GeneralError(this.CLASS_NAME, "propertyNotFound", {
|
|
705
|
+
property: c.property
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
const attributeName = `#${c.property}`;
|
|
709
|
+
const attributeValueName = `:${c.property}`;
|
|
710
|
+
attributeNames ??= {};
|
|
711
|
+
attributeValues ??= {};
|
|
712
|
+
attributeNames[attributeName] = c.property;
|
|
713
|
+
attributeValues[attributeValueName] = c.value;
|
|
714
|
+
expressions.push(`${attributeName} = ${attributeValueName}`);
|
|
715
|
+
}
|
|
716
|
+
conditionExpression = expressions.join(" AND ");
|
|
717
|
+
}
|
|
718
|
+
return { conditionExpression, attributeNames, attributeValues };
|
|
719
|
+
}
|
|
657
720
|
}
|
|
658
721
|
|
|
659
722
|
export { DynamoDbEntityStorageConnector };
|
|
@@ -36,21 +36,33 @@ export declare class DynamoDbEntityStorageConnector<T = unknown> implements IEnt
|
|
|
36
36
|
* Get an entity.
|
|
37
37
|
* @param id The id of the entity to get, or the index value if secondaryIndex is set.
|
|
38
38
|
* @param secondaryIndex Get the item using a secondary index.
|
|
39
|
+
* @param conditions The optional conditions to match for the entities.
|
|
39
40
|
* @returns The object if it can be found or undefined.
|
|
40
41
|
*/
|
|
41
|
-
get(id: string, secondaryIndex?: keyof T
|
|
42
|
+
get(id: string, secondaryIndex?: keyof T, conditions?: {
|
|
43
|
+
property: keyof T;
|
|
44
|
+
value: unknown;
|
|
45
|
+
}[]): Promise<T | undefined>;
|
|
42
46
|
/**
|
|
43
47
|
* Set an entity.
|
|
44
48
|
* @param entity The entity to set.
|
|
49
|
+
* @param conditions The optional conditions to match for the entities.
|
|
45
50
|
* @returns The id of the entity.
|
|
46
51
|
*/
|
|
47
|
-
set(entity: T
|
|
52
|
+
set(entity: T, conditions?: {
|
|
53
|
+
property: keyof T;
|
|
54
|
+
value: unknown;
|
|
55
|
+
}[]): Promise<void>;
|
|
48
56
|
/**
|
|
49
57
|
* Remove the entity.
|
|
50
58
|
* @param id The id of the entity to remove.
|
|
59
|
+
* @param conditions The optional conditions to match for the entities.
|
|
51
60
|
* @returns Nothing.
|
|
52
61
|
*/
|
|
53
|
-
remove(id: string
|
|
62
|
+
remove(id: string, conditions?: {
|
|
63
|
+
property: keyof T;
|
|
64
|
+
value: unknown;
|
|
65
|
+
}[]): Promise<void>;
|
|
54
66
|
/**
|
|
55
67
|
* Find all the entities which match the conditions.
|
|
56
68
|
* @param conditions The conditions to match for the entities.
|
package/docs/changelog.md
CHANGED
|
@@ -98,7 +98,7 @@ The schema for the entities.
|
|
|
98
98
|
|
|
99
99
|
### get()
|
|
100
100
|
|
|
101
|
-
> **get**(`id`, `secondaryIndex`?): `Promise`\<`undefined` \| `T`\>
|
|
101
|
+
> **get**(`id`, `secondaryIndex`?, `conditions`?): `Promise`\<`undefined` \| `T`\>
|
|
102
102
|
|
|
103
103
|
Get an entity.
|
|
104
104
|
|
|
@@ -112,6 +112,10 @@ The id of the entity to get, or the index value if secondaryIndex is set.
|
|
|
112
112
|
|
|
113
113
|
Get the item using a secondary index.
|
|
114
114
|
|
|
115
|
+
• **conditions?**: `object`[]
|
|
116
|
+
|
|
117
|
+
The optional conditions to match for the entities.
|
|
118
|
+
|
|
115
119
|
#### Returns
|
|
116
120
|
|
|
117
121
|
`Promise`\<`undefined` \| `T`\>
|
|
@@ -126,7 +130,7 @@ The object if it can be found or undefined.
|
|
|
126
130
|
|
|
127
131
|
### set()
|
|
128
132
|
|
|
129
|
-
> **set**(`entity`): `Promise`\<`void`\>
|
|
133
|
+
> **set**(`entity`, `conditions`?): `Promise`\<`void`\>
|
|
130
134
|
|
|
131
135
|
Set an entity.
|
|
132
136
|
|
|
@@ -136,6 +140,10 @@ Set an entity.
|
|
|
136
140
|
|
|
137
141
|
The entity to set.
|
|
138
142
|
|
|
143
|
+
• **conditions?**: `object`[]
|
|
144
|
+
|
|
145
|
+
The optional conditions to match for the entities.
|
|
146
|
+
|
|
139
147
|
#### Returns
|
|
140
148
|
|
|
141
149
|
`Promise`\<`void`\>
|
|
@@ -150,7 +158,7 @@ The id of the entity.
|
|
|
150
158
|
|
|
151
159
|
### remove()
|
|
152
160
|
|
|
153
|
-
> **remove**(`id`): `Promise`\<`void`\>
|
|
161
|
+
> **remove**(`id`, `conditions`?): `Promise`\<`void`\>
|
|
154
162
|
|
|
155
163
|
Remove the entity.
|
|
156
164
|
|
|
@@ -160,6 +168,10 @@ Remove the entity.
|
|
|
160
168
|
|
|
161
169
|
The id of the entity to remove.
|
|
162
170
|
|
|
171
|
+
• **conditions?**: `object`[]
|
|
172
|
+
|
|
173
|
+
The optional conditions to match for the entities.
|
|
174
|
+
|
|
163
175
|
#### Returns
|
|
164
176
|
|
|
165
177
|
`Promise`\<`void`\>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin.org/entity-storage-connector-dynamodb",
|
|
3
|
-
"version": "0.0.1-next.
|
|
3
|
+
"version": "0.0.1-next.9",
|
|
4
4
|
"description": "Entity Storage connector implementation using DynamoDb storage",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"@aws-sdk/util-dynamodb": "3.656",
|
|
20
20
|
"@twin.org/core": "next",
|
|
21
21
|
"@twin.org/entity": "next",
|
|
22
|
-
"@twin.org/entity-storage-models": "0.0.1-next.
|
|
22
|
+
"@twin.org/entity-storage-models": "0.0.1-next.9",
|
|
23
23
|
"@twin.org/logging-models": "next",
|
|
24
24
|
"@twin.org/nameof": "next"
|
|
25
25
|
},
|