@twin.org/entity-storage-connector-dynamodb 0.0.1-next.3 → 0.0.1-next.30
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/README.md +1 -1
- package/dist/cjs/index.cjs +211 -114
- package/dist/esm/index.mjs +211 -114
- package/dist/types/dynamoDbEntityStorageConnector.d.ts +23 -13
- package/dist/types/index.d.ts +1 -0
- package/dist/types/models/IDynamoDbEntityStorageConnectorConstructorOptions.d.ts +18 -0
- package/docs/changelog.md +65 -1
- package/docs/reference/classes/DynamoDbEntityStorageConnector.md +81 -45
- package/docs/reference/index.md +1 -0
- package/docs/reference/interfaces/IDynamoDbEntityStorageConnectorConstructorOptions.md +27 -0
- package/locales/en.json +1 -1
- package/package.json +9 -39
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install @twin.org/entity-storage-connector-dynamodb
|
|
|
13
13
|
The tests developed are functional tests and need an instance of DynamoDB up and running. To run DynamoDB locally:
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
docker run -p 8000:8000 --name dynamodb --hostname dynamodb -d amazon/dynamodb-local
|
|
16
|
+
docker run -p 8000:8000 --name twin-entity-storage-dynamodb --hostname dynamodb -d amazon/dynamodb-local
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Afterwards you can run the tests as follows:
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -50,9 +50,6 @@ class DynamoDbEntityStorageConnector {
|
|
|
50
50
|
/**
|
|
51
51
|
* Create a new instance of DynamoDbEntityStorageConnector.
|
|
52
52
|
* @param options The options for the connector.
|
|
53
|
-
* @param options.entitySchema The schema for the entity.
|
|
54
|
-
* @param options.loggingConnectorType The type of logging connector to use, defaults to no logging.
|
|
55
|
-
* @param options.config The configuration for the connector.
|
|
56
53
|
*/
|
|
57
54
|
constructor(options) {
|
|
58
55
|
core.Guards.object(this.CLASS_NAME, "options", options);
|
|
@@ -214,17 +211,25 @@ class DynamoDbEntityStorageConnector {
|
|
|
214
211
|
}
|
|
215
212
|
return true;
|
|
216
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Get the schema for the entities.
|
|
216
|
+
* @returns The schema for the entities.
|
|
217
|
+
*/
|
|
218
|
+
getSchema() {
|
|
219
|
+
return this._entitySchema;
|
|
220
|
+
}
|
|
217
221
|
/**
|
|
218
222
|
* Get an entity.
|
|
219
223
|
* @param id The id of the entity to get, or the index value if secondaryIndex is set.
|
|
220
224
|
* @param secondaryIndex Get the item using a secondary index.
|
|
225
|
+
* @param conditions The optional conditions to match for the entities.
|
|
221
226
|
* @returns The object if it can be found or undefined.
|
|
222
227
|
*/
|
|
223
|
-
async get(id, secondaryIndex) {
|
|
228
|
+
async get(id, secondaryIndex, conditions) {
|
|
224
229
|
core.Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
225
230
|
try {
|
|
226
231
|
const docClient = this.createDocClient();
|
|
227
|
-
if (core.Is.
|
|
232
|
+
if (core.Is.empty(secondaryIndex) && core.Is.empty(conditions)) {
|
|
228
233
|
const getCommand = new libDynamodb.GetCommand({
|
|
229
234
|
TableName: this._config.tableName,
|
|
230
235
|
Key: {
|
|
@@ -236,27 +241,27 @@ class DynamoDbEntityStorageConnector {
|
|
|
236
241
|
delete response.Item?.[DynamoDbEntityStorageConnector._PARTITION_ID_NAME];
|
|
237
242
|
return response.Item;
|
|
238
243
|
}
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
244
|
+
const finalConditions = {
|
|
245
|
+
conditions: []
|
|
246
|
+
};
|
|
247
|
+
if (core.Is.stringValue(secondaryIndex)) {
|
|
248
|
+
finalConditions.conditions.push({
|
|
249
|
+
property: secondaryIndex,
|
|
250
|
+
comparison: entity.ComparisonOperator.Equals,
|
|
251
|
+
value: id
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (core.Is.arrayValue(conditions)) {
|
|
255
|
+
for (const c of conditions) {
|
|
256
|
+
finalConditions.conditions.push({
|
|
257
|
+
property: c.property,
|
|
258
|
+
comparison: entity.ComparisonOperator.Equals,
|
|
259
|
+
value: c.value
|
|
260
|
+
});
|
|
254
261
|
}
|
|
255
|
-
});
|
|
256
|
-
const response = await docClient.send(queryCommand);
|
|
257
|
-
if (response.Items?.length === 1) {
|
|
258
|
-
return utilDynamodb.unmarshall(response.Items[0]);
|
|
259
262
|
}
|
|
263
|
+
const queryResult = await this.internalQuery(finalConditions, undefined, undefined, undefined, 1, secondaryIndex);
|
|
264
|
+
return queryResult.entities[0];
|
|
260
265
|
}
|
|
261
266
|
catch (err) {
|
|
262
267
|
if (core.BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
@@ -268,28 +273,40 @@ class DynamoDbEntityStorageConnector {
|
|
|
268
273
|
id
|
|
269
274
|
}, err);
|
|
270
275
|
}
|
|
271
|
-
return undefined;
|
|
272
276
|
}
|
|
273
277
|
/**
|
|
274
278
|
* Set an entity.
|
|
275
279
|
* @param entity The entity to set.
|
|
280
|
+
* @param conditions The optional conditions to match for the entities.
|
|
276
281
|
* @returns The id of the entity.
|
|
277
282
|
*/
|
|
278
|
-
async set(entity) {
|
|
279
|
-
core.Guards.object(this.CLASS_NAME, "entity", entity);
|
|
280
|
-
|
|
283
|
+
async set(entity$1, conditions) {
|
|
284
|
+
core.Guards.object(this.CLASS_NAME, "entity", entity$1);
|
|
285
|
+
entity.EntitySchemaHelper.validateEntity(entity$1, this.getSchema());
|
|
286
|
+
const id = entity$1[this._primaryKey.property];
|
|
281
287
|
try {
|
|
282
288
|
const docClient = this.createDocClient();
|
|
289
|
+
const { conditionExpression, attributeNames, attributeValues } = this.buildConditionExpression(conditions);
|
|
283
290
|
const putCommand = new libDynamodb.PutCommand({
|
|
284
291
|
TableName: this._config.tableName,
|
|
285
292
|
Item: {
|
|
286
293
|
[DynamoDbEntityStorageConnector._PARTITION_ID_NAME]: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE,
|
|
287
|
-
...entity
|
|
288
|
-
}
|
|
294
|
+
...entity$1
|
|
295
|
+
},
|
|
296
|
+
// Only set the condition expression if we have conditions to match
|
|
297
|
+
// and the primary key exists, otherwise we are creating a new object
|
|
298
|
+
ConditionExpression: core.Is.stringValue(conditionExpression)
|
|
299
|
+
? `(attribute_exists(${this._primaryKey.property}) AND ${conditionExpression}) OR attribute_not_exists(${this._primaryKey.property})`
|
|
300
|
+
: undefined,
|
|
301
|
+
ExpressionAttributeNames: attributeNames,
|
|
302
|
+
ExpressionAttributeValues: attributeValues
|
|
289
303
|
});
|
|
290
304
|
await docClient.send(putCommand);
|
|
291
305
|
}
|
|
292
306
|
catch (err) {
|
|
307
|
+
if (core.BaseError.isErrorName(err, "ConditionalCheckFailedException")) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
293
310
|
if (core.BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
294
311
|
throw new core.GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
295
312
|
tableName: this._config.tableName
|
|
@@ -303,22 +320,30 @@ class DynamoDbEntityStorageConnector {
|
|
|
303
320
|
/**
|
|
304
321
|
* Remove the entity.
|
|
305
322
|
* @param id The id of the entity to remove.
|
|
323
|
+
* @param conditions The optional conditions to match for the entities.
|
|
306
324
|
* @returns Nothing.
|
|
307
325
|
*/
|
|
308
|
-
async remove(id) {
|
|
326
|
+
async remove(id, conditions) {
|
|
309
327
|
core.Guards.stringValue(this.CLASS_NAME, "id", id);
|
|
310
328
|
try {
|
|
311
329
|
const docClient = this.createDocClient();
|
|
330
|
+
const { conditionExpression, attributeNames, attributeValues } = this.buildConditionExpression(conditions);
|
|
312
331
|
const deleteCommand = new libDynamodb.DeleteCommand({
|
|
313
332
|
TableName: this._config.tableName,
|
|
314
333
|
Key: {
|
|
315
334
|
[DynamoDbEntityStorageConnector._PARTITION_ID_NAME]: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE,
|
|
316
335
|
[this._primaryKey.property]: id
|
|
317
|
-
}
|
|
336
|
+
},
|
|
337
|
+
ConditionExpression: conditionExpression,
|
|
338
|
+
ExpressionAttributeNames: attributeNames,
|
|
339
|
+
ExpressionAttributeValues: attributeValues
|
|
318
340
|
});
|
|
319
341
|
await docClient.send(deleteCommand);
|
|
320
342
|
}
|
|
321
343
|
catch (err) {
|
|
344
|
+
if (core.BaseError.isErrorName(err, "ConditionalCheckFailedException")) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
322
347
|
if (core.BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
323
348
|
throw new core.GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
324
349
|
table: this._config.tableName
|
|
@@ -340,80 +365,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
340
365
|
* and a cursor which can be used to request more entities.
|
|
341
366
|
*/
|
|
342
367
|
async query(conditions, sortProperties, properties, cursor, pageSize) {
|
|
343
|
-
|
|
344
|
-
try {
|
|
345
|
-
const returnSize = pageSize ?? DynamoDbEntityStorageConnector._PAGE_SIZE;
|
|
346
|
-
let indexName;
|
|
347
|
-
// If we have a sortable property defined in the descriptor then we must use
|
|
348
|
-
// the secondary index for the query
|
|
349
|
-
if (core.Is.arrayValue(sortProperties)) {
|
|
350
|
-
if (sortProperties.length > 1) {
|
|
351
|
-
throw new core.GeneralError(this.CLASS_NAME, "sortSingle");
|
|
352
|
-
}
|
|
353
|
-
for (const sortProperty of sortProperties) {
|
|
354
|
-
const propertySchema = this._entitySchema.properties?.find(e => e.property === sortProperty.property);
|
|
355
|
-
if (core.Is.undefined(propertySchema) ||
|
|
356
|
-
(!propertySchema.isPrimary &&
|
|
357
|
-
!propertySchema.isSecondary &&
|
|
358
|
-
core.Is.empty(propertySchema.sortDirection))) {
|
|
359
|
-
throw new core.GeneralError(this.CLASS_NAME, "sortNotIndexed", {
|
|
360
|
-
property: sortProperty.property
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
indexName = propertySchema.isPrimary
|
|
364
|
-
? undefined
|
|
365
|
-
: `${sortProperty.property}Index`;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
const attributeNames = { "#partitionId": "partitionId" };
|
|
369
|
-
const attributeValues = {
|
|
370
|
-
[`:${DynamoDbEntityStorageConnector._PARTITION_ID_NAME}`]: {
|
|
371
|
-
S: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
const expressions = this.buildQueryParameters("", conditions, attributeNames, attributeValues);
|
|
375
|
-
let keyExpression = "#partitionId = :partitionId";
|
|
376
|
-
if (expressions.keyCondition.length > 0) {
|
|
377
|
-
keyExpression += ` AND ${expressions.keyCondition}`;
|
|
378
|
-
}
|
|
379
|
-
const query = new clientDynamodb.QueryCommand({
|
|
380
|
-
TableName: this._config.tableName,
|
|
381
|
-
IndexName: indexName,
|
|
382
|
-
KeyConditionExpression: keyExpression,
|
|
383
|
-
FilterExpression: core.Is.stringValue(expressions.filterCondition)
|
|
384
|
-
? expressions.filterCondition
|
|
385
|
-
: undefined,
|
|
386
|
-
ExpressionAttributeNames: attributeNames,
|
|
387
|
-
ExpressionAttributeValues: attributeValues,
|
|
388
|
-
ProjectionExpression: properties?.map(p => p).join(", "),
|
|
389
|
-
Limit: returnSize,
|
|
390
|
-
ExclusiveStartKey: core.Is.empty(cursor)
|
|
391
|
-
? undefined
|
|
392
|
-
: core.ObjectHelper.fromBytes(core.Converter.base64ToBytes(cursor))
|
|
393
|
-
});
|
|
394
|
-
const connection = this.createDocClient();
|
|
395
|
-
const results = await connection.send(query);
|
|
396
|
-
let entities = [];
|
|
397
|
-
if (core.Is.arrayValue(results.Items)) {
|
|
398
|
-
entities = results.Items.map(item => utilDynamodb.unmarshall(item));
|
|
399
|
-
}
|
|
400
|
-
return {
|
|
401
|
-
entities,
|
|
402
|
-
cursor: core.Is.empty(results.LastEvaluatedKey)
|
|
403
|
-
? undefined
|
|
404
|
-
: core.Converter.bytesToBase64(core.ObjectHelper.toBytes(results.LastEvaluatedKey))
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
catch (err) {
|
|
408
|
-
if (core.BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
409
|
-
throw new core.GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
410
|
-
table: this._config.tableName
|
|
411
|
-
}, err);
|
|
412
|
-
}
|
|
413
|
-
throw new core.GeneralError(this.CLASS_NAME, "queryFailed", {
|
|
414
|
-
sql
|
|
415
|
-
}, err);
|
|
416
|
-
}
|
|
368
|
+
return this.internalQuery(conditions, sortProperties, properties, cursor, pageSize);
|
|
417
369
|
}
|
|
418
370
|
/**
|
|
419
371
|
* Delete the table.
|
|
@@ -427,7 +379,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
427
379
|
catch { }
|
|
428
380
|
}
|
|
429
381
|
/**
|
|
430
|
-
* Create
|
|
382
|
+
* Create the parameters for a query.
|
|
431
383
|
* @param objectPath The path for the nested object.
|
|
432
384
|
* @param condition The conditions to create the query from.
|
|
433
385
|
* @param attributeNames The attribute names to use in the query.
|
|
@@ -435,7 +387,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
435
387
|
* @returns The condition clause.
|
|
436
388
|
* @internal
|
|
437
389
|
*/
|
|
438
|
-
buildQueryParameters(objectPath, condition, attributeNames, attributeValues) {
|
|
390
|
+
buildQueryParameters(objectPath, condition, attributeNames, attributeValues, secondaryIndex) {
|
|
439
391
|
// If no conditions are defined then return empty string
|
|
440
392
|
if (core.Is.undefined(condition)) {
|
|
441
393
|
return {
|
|
@@ -444,11 +396,21 @@ class DynamoDbEntityStorageConnector {
|
|
|
444
396
|
};
|
|
445
397
|
}
|
|
446
398
|
if ("conditions" in condition) {
|
|
399
|
+
if (condition.conditions.length === 0) {
|
|
400
|
+
return {
|
|
401
|
+
keyCondition: "",
|
|
402
|
+
filterCondition: ""
|
|
403
|
+
};
|
|
404
|
+
}
|
|
447
405
|
// It's a group of comparisons, so check the individual items and combine with the logical operator
|
|
448
|
-
const joinConditions = condition.conditions.map(c => this.buildQueryParameters(objectPath, c, attributeNames, attributeValues));
|
|
406
|
+
const joinConditions = condition.conditions.map(c => this.buildQueryParameters(objectPath, c, attributeNames, attributeValues, secondaryIndex));
|
|
449
407
|
const logicalOperator = this.mapConditionalOperator(condition.logicalOperator);
|
|
450
|
-
const keyCondition = joinConditions
|
|
408
|
+
const keyCondition = joinConditions
|
|
409
|
+
.filter(j => j.keyCondition.length > 0)
|
|
410
|
+
.map(j => j.keyCondition)
|
|
411
|
+
.join(` ${logicalOperator} `);
|
|
451
412
|
const filterCondition = joinConditions
|
|
413
|
+
.filter(j => j.filterCondition.length > 0)
|
|
452
414
|
.map(j => j.filterCondition)
|
|
453
415
|
.join(` ${logicalOperator} `);
|
|
454
416
|
return {
|
|
@@ -459,9 +421,10 @@ class DynamoDbEntityStorageConnector {
|
|
|
459
421
|
const schemaProp = this._entitySchema.properties?.find(p => p.property === condition.property);
|
|
460
422
|
// It's a single value so just create the property comparison for the condition
|
|
461
423
|
const comparison = this.mapComparisonOperator(objectPath, condition, schemaProp?.type, attributeNames, attributeValues);
|
|
424
|
+
const isKey = schemaProp?.isPrimary || (schemaProp?.isSecondary && schemaProp?.property === secondaryIndex);
|
|
462
425
|
return {
|
|
463
|
-
keyCondition:
|
|
464
|
-
filterCondition:
|
|
426
|
+
keyCondition: isKey ? comparison : "",
|
|
427
|
+
filterCondition: !isKey ? comparison : ""
|
|
465
428
|
};
|
|
466
429
|
}
|
|
467
430
|
/**
|
|
@@ -482,6 +445,14 @@ class DynamoDbEntityStorageConnector {
|
|
|
482
445
|
}
|
|
483
446
|
prop += comparator.property;
|
|
484
447
|
let attributeName = this.populateAttributeNames(prop, attributeNames);
|
|
448
|
+
if (core.Is.empty(comparator.value)) {
|
|
449
|
+
if (comparator.comparison === entity.ComparisonOperator.Equals) {
|
|
450
|
+
return `attribute_not_exists(${attributeName})`;
|
|
451
|
+
}
|
|
452
|
+
else if (comparator.comparison === entity.ComparisonOperator.NotEquals) {
|
|
453
|
+
return `attribute_exists(${attributeName})`;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
485
456
|
let propName = `:${attributeName.replace(/\./g, "").replace(/#/g, "")}`;
|
|
486
457
|
if (core.Is.array(comparator.value)) {
|
|
487
458
|
const dbValues = comparator.value.map(v => this.propertyToDbValue(v, type));
|
|
@@ -605,7 +576,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
605
576
|
}
|
|
606
577
|
/**
|
|
607
578
|
* Create a new DB connection.
|
|
608
|
-
* @returns The
|
|
579
|
+
* @returns The Dynamo DB connection.
|
|
609
580
|
* @internal
|
|
610
581
|
*/
|
|
611
582
|
createConnection() {
|
|
@@ -613,7 +584,7 @@ class DynamoDbEntityStorageConnector {
|
|
|
613
584
|
}
|
|
614
585
|
/**
|
|
615
586
|
* Create a new DB connection configuration.
|
|
616
|
-
* @returns The
|
|
587
|
+
* @returns The Dynamo DB connection configuration.
|
|
617
588
|
* @internal
|
|
618
589
|
*/
|
|
619
590
|
createConnectionConfig() {
|
|
@@ -642,6 +613,132 @@ class DynamoDbEntityStorageConnector {
|
|
|
642
613
|
return false;
|
|
643
614
|
}
|
|
644
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* Find all the entities which match the conditions.
|
|
618
|
+
* @param conditions The conditions to match for the entities.
|
|
619
|
+
* @param sortProperties The optional sort order.
|
|
620
|
+
* @param properties The optional properties to return, defaults to all.
|
|
621
|
+
* @param cursor The cursor to request the next page of entities.
|
|
622
|
+
* @param pageSize The suggested number of entities to return in each chunk, in some scenarios can return a different amount.
|
|
623
|
+
* @param secondaryIndex The secondary index to use for the query.
|
|
624
|
+
* @returns All the entities for the storage matching the conditions,
|
|
625
|
+
* and a cursor which can be used to request more entities.
|
|
626
|
+
* @internal
|
|
627
|
+
*/
|
|
628
|
+
async internalQuery(conditions, sortProperties, properties, cursor, pageSize, secondaryIndex) {
|
|
629
|
+
try {
|
|
630
|
+
const returnSize = pageSize ?? DynamoDbEntityStorageConnector._PAGE_SIZE;
|
|
631
|
+
let indexName = core.Is.stringValue(secondaryIndex)
|
|
632
|
+
? `${secondaryIndex}Index`
|
|
633
|
+
: undefined;
|
|
634
|
+
// If we have a sortable property defined in the descriptor then we must use
|
|
635
|
+
// the secondary index for the query
|
|
636
|
+
let scanAscending = true;
|
|
637
|
+
if (core.Is.arrayValue(sortProperties)) {
|
|
638
|
+
if (sortProperties.length > 1) {
|
|
639
|
+
throw new core.GeneralError(this.CLASS_NAME, "sortSingle");
|
|
640
|
+
}
|
|
641
|
+
for (const sortProperty of sortProperties) {
|
|
642
|
+
const propertySchema = this._entitySchema.properties?.find(e => e.property === sortProperty.property);
|
|
643
|
+
if (core.Is.undefined(propertySchema) ||
|
|
644
|
+
(!propertySchema.isPrimary &&
|
|
645
|
+
!propertySchema.isSecondary &&
|
|
646
|
+
core.Is.empty(propertySchema.sortDirection))) {
|
|
647
|
+
throw new core.GeneralError(this.CLASS_NAME, "sortNotIndexed", {
|
|
648
|
+
property: sortProperty.property
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
indexName = propertySchema.isPrimary
|
|
652
|
+
? undefined
|
|
653
|
+
: `${sortProperty.property}Index`;
|
|
654
|
+
scanAscending = sortProperty.sortDirection === entity.SortDirection.Ascending;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const attributeNames = { "#partitionId": "partitionId" };
|
|
658
|
+
const attributeValues = {
|
|
659
|
+
[`:${DynamoDbEntityStorageConnector._PARTITION_ID_NAME}`]: {
|
|
660
|
+
S: DynamoDbEntityStorageConnector._PARTITION_ID_VALUE
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
const expressions = this.buildQueryParameters("", conditions, attributeNames, attributeValues, secondaryIndex);
|
|
664
|
+
let keyExpression = "#partitionId = :partitionId";
|
|
665
|
+
if (expressions.keyCondition.length > 0) {
|
|
666
|
+
keyExpression += ` AND ${expressions.keyCondition}`;
|
|
667
|
+
}
|
|
668
|
+
const query = new clientDynamodb.QueryCommand({
|
|
669
|
+
TableName: this._config.tableName,
|
|
670
|
+
IndexName: indexName,
|
|
671
|
+
KeyConditionExpression: keyExpression,
|
|
672
|
+
FilterExpression: core.Is.stringValue(expressions.filterCondition)
|
|
673
|
+
? expressions.filterCondition
|
|
674
|
+
: undefined,
|
|
675
|
+
ExpressionAttributeNames: attributeNames,
|
|
676
|
+
ExpressionAttributeValues: attributeValues,
|
|
677
|
+
ProjectionExpression: properties?.map(p => p).join(", "),
|
|
678
|
+
Limit: returnSize,
|
|
679
|
+
ScanIndexForward: scanAscending,
|
|
680
|
+
ExclusiveStartKey: core.Is.empty(cursor)
|
|
681
|
+
? undefined
|
|
682
|
+
: core.ObjectHelper.fromBytes(core.Converter.base64ToBytes(cursor))
|
|
683
|
+
});
|
|
684
|
+
const connection = this.createDocClient();
|
|
685
|
+
const results = await connection.send(query);
|
|
686
|
+
let entities = [];
|
|
687
|
+
if (core.Is.arrayValue(results.Items)) {
|
|
688
|
+
entities = results.Items.map(item => {
|
|
689
|
+
const unmarshalled = utilDynamodb.unmarshall(item);
|
|
690
|
+
delete unmarshalled[DynamoDbEntityStorageConnector._PARTITION_ID_NAME];
|
|
691
|
+
return unmarshalled;
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
return {
|
|
695
|
+
entities,
|
|
696
|
+
cursor: core.Is.empty(results.LastEvaluatedKey)
|
|
697
|
+
? undefined
|
|
698
|
+
: core.Converter.bytesToBase64(core.ObjectHelper.toBytes(results.LastEvaluatedKey))
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
catch (err) {
|
|
702
|
+
if (core.BaseError.isErrorCode(err, "ResourceNotFoundException")) {
|
|
703
|
+
throw new core.GeneralError(this.CLASS_NAME, "tableDoesNotExist", {
|
|
704
|
+
table: this._config.tableName
|
|
705
|
+
}, err);
|
|
706
|
+
}
|
|
707
|
+
throw new core.GeneralError(this.CLASS_NAME, "queryFailed", undefined, err);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Build the condition expression for the query.
|
|
712
|
+
* @param conditions The conditions to build the expression from.
|
|
713
|
+
* @returns The condition expression.
|
|
714
|
+
* @throws GeneralError if the property is not found in the schema.
|
|
715
|
+
* @internal
|
|
716
|
+
*/
|
|
717
|
+
buildConditionExpression(conditions) {
|
|
718
|
+
let conditionExpression;
|
|
719
|
+
let attributeNames;
|
|
720
|
+
let attributeValues;
|
|
721
|
+
if (core.Is.arrayValue(conditions)) {
|
|
722
|
+
const expressions = [];
|
|
723
|
+
for (const c of conditions) {
|
|
724
|
+
const schemaProp = this._entitySchema.properties?.find(p => p.property === c.property);
|
|
725
|
+
if (core.Is.undefined(schemaProp)) {
|
|
726
|
+
throw new core.GeneralError(this.CLASS_NAME, "propertyNotFound", {
|
|
727
|
+
property: c.property
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
const attributeName = `#${c.property}`;
|
|
731
|
+
const attributeValueName = `:${c.property}`;
|
|
732
|
+
attributeNames ??= {};
|
|
733
|
+
attributeValues ??= {};
|
|
734
|
+
attributeNames[attributeName] = c.property;
|
|
735
|
+
attributeValues[attributeValueName] = c.value;
|
|
736
|
+
expressions.push(`${attributeName} = ${attributeValueName}`);
|
|
737
|
+
}
|
|
738
|
+
conditionExpression = expressions.join(" AND ");
|
|
739
|
+
}
|
|
740
|
+
return { conditionExpression, attributeNames, attributeValues };
|
|
741
|
+
}
|
|
645
742
|
}
|
|
646
743
|
|
|
647
744
|
exports.DynamoDbEntityStorageConnector = DynamoDbEntityStorageConnector;
|