@izara_project/izara-core-library-dynamodb 1.0.14 → 1.0.15

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 CHANGED
@@ -1,15 +1,67 @@
1
1
  # izara-core-library-dynamodb
2
2
 
3
- # update version
4
- - commit code first
5
- npm version patch || npm version minor || npm version major
3
+ A comprehensive DynamoDB utility library for the Izara project, providing simplified interfaces for common DynamoDB operations including scan, query, put, update, and delete operations.
6
4
 
7
- # can also push to repo ..
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @izara_project/izara-core-library-dynamodb
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```javascript
14
+ import dynamoDBSharedLib from '@izara_project/izara-core-library-dynamodb';
15
+ ```
16
+
17
+ ## Documentation
18
+
19
+ Detailed documentation for each operation can be found in the `src/doc/` directory:
20
+
21
+ - **[Scan Operation](doc/scan.md)** - Complete guide with 21 use cases for scanning DynamoDB tables
22
+
23
+ ## Available Operations
24
+
25
+ - `scan` / `scanResults` - Scan entire table or index with filters
26
+ - `query` / `queryResults` - Query items by partition key
27
+ - `getItem` - Retrieve a single item by key
28
+ - `putItem` - Create or replace an item
29
+ - `updateItem` - Update existing item attributes
30
+ - `deleteItem` - Delete an item
31
+ - `batchPutItems` - Batch write items
32
+ - `batchDeleteItems` - Batch delete items
33
+ - `transactWriteItems` - Transactional writes
34
+
35
+ ---
36
+
37
+
38
+ ## Publishing
39
+
40
+ ### Update Version
41
+ Commit code first:
42
+ ```bash
43
+ git add .
44
+ git commit -m "Your commit message"
45
+ ```
46
+
47
+ Update version:
48
+ ```bash
49
+ npm version patch # Bug fixes
50
+ npm version minor # New features
51
+ npm version major # Breaking changes
52
+ ```
53
+
54
+ ### Push to Repository
55
+ ```bash
8
56
  git push
9
- # make sure commits look correct
57
+ ```
10
58
 
11
- # push to npm
59
+ ### Publish to NPM
60
+ ```bash
12
61
  npm publish
62
+ ```
13
63
 
14
- # update project that uses this package
64
+ ### Update in Projects
65
+ ```bash
15
66
  npm update @izara_project/izara-core-library-dynamodb
67
+ ```
package/doc/scan.md ADDED
@@ -0,0 +1,614 @@
1
+ # Scan Operation
2
+
3
+ The `scan` operation scans the entire table or a secondary index with optional filters. **Note: Scan operations are expensive and should be used sparingly.**
4
+
5
+ ## Function Signature
6
+
7
+ ```javascript
8
+ async function scan(
9
+ _izContext,
10
+ tableName,
11
+ queryElements = {},
12
+ settings = {}
13
+ )
14
+ ```
15
+
16
+ ## Parameters
17
+
18
+ - `_izContext` - Izara context object for logging
19
+ - `tableName` - Name of the DynamoDB table
20
+ - `queryElements` - Optional query parameters:
21
+ - `logicalElements` - Filter conditions (uses FilterExpression)
22
+ - `additionalAttributes` - Additional attribute values for filters
23
+ - `projectionExpression` - Array of attribute names to return
24
+ - `select` - Return mode: `ALL_ATTRIBUTES | COUNT | SPECIFIC_ATTRIBUTES`
25
+ - `limit` - Maximum items per page
26
+ - `exclusiveStartKey` - Pagination start key
27
+ - `indexName` - Global secondary index name
28
+ - `settings` - Operation settings:
29
+ - `numPagesToRequest` - Number of pages to auto-paginate (default: 1)
30
+
31
+ ## Use Cases
32
+
33
+ ### 1. Basic Scan - No Filters
34
+
35
+ Scan all items in the table without any filters.
36
+
37
+ ```javascript
38
+ const _izContext = { logger: console };
39
+ const tableName = 'config';
40
+
41
+ const queryElements = {};
42
+ const result = await dynamoDBSharedLib.scan(
43
+ _izContext,
44
+ tableName,
45
+ queryElements,
46
+ { numPagesToRequest: 1 }
47
+ );
48
+ console.log('All items:', result.Items);
49
+ ```
50
+
51
+ ### 2. Scan with Filter Condition (Equals)
52
+
53
+ Filter items where `configValue` equals `'production'`.
54
+
55
+ ```javascript
56
+ const queryElements = {
57
+ additionalAttributes: {
58
+ envValue: 'production'
59
+ },
60
+ logicalElements: [
61
+ {
62
+ type: 'logical',
63
+ comparison: 'equals',
64
+ lhsAttribute: 'configValue',
65
+ rhsReference: 'envValue'
66
+ }
67
+ ]
68
+ };
69
+
70
+ const result = await dynamoDBSharedLib.scan(
71
+ _izContext,
72
+ tableName,
73
+ queryElements,
74
+ { numPagesToRequest: 1 }
75
+ );
76
+ ```
77
+
78
+ ### 3. Scan with Not Equals Filter
79
+
80
+ Filter items where `status` is not equal to `'disabled'`.
81
+
82
+ ```javascript
83
+ const queryElements = {
84
+ additionalAttributes: {
85
+ disabledStatus: 'disabled'
86
+ },
87
+ logicalElements: [
88
+ {
89
+ type: 'logical',
90
+ comparison: 'notEquals',
91
+ lhsAttribute: 'status',
92
+ rhsReference: 'disabledStatus'
93
+ }
94
+ ]
95
+ };
96
+
97
+ const result = await dynamoDBSharedLib.scan(
98
+ _izContext,
99
+ tableName,
100
+ queryElements,
101
+ { numPagesToRequest: 1 }
102
+ );
103
+ ```
104
+
105
+ ### 4. Scan with Multiple Conditions (AND)
106
+
107
+ Filter items where `environment` equals `'staging'` AND `priority` equals `'high'`.
108
+
109
+ ```javascript
110
+ const queryElements = {
111
+ additionalAttributes: {
112
+ envType: 'staging',
113
+ priorityLevel: 'high'
114
+ },
115
+ logicalElements: [
116
+ {
117
+ type: 'logical',
118
+ comparison: 'equals',
119
+ lhsAttribute: 'environment',
120
+ rhsReference: 'envType'
121
+ },
122
+ {
123
+ type: 'logicalOperator',
124
+ operator: 'and'
125
+ },
126
+ {
127
+ type: 'logical',
128
+ comparison: 'equals',
129
+ lhsAttribute: 'priority',
130
+ rhsReference: 'priorityLevel'
131
+ }
132
+ ]
133
+ };
134
+
135
+ const result = await dynamoDBSharedLib.scan(
136
+ _izContext,
137
+ tableName,
138
+ queryElements,
139
+ { numPagesToRequest: 1 }
140
+ );
141
+ ```
142
+
143
+ ### 5. Scan with OR Condition
144
+
145
+ Filter items where `configKey` equals `'database'` OR `configKey` equals `'cache'`.
146
+
147
+ ```javascript
148
+ const queryElements = {
149
+ additionalAttributes: {
150
+ dbKey: 'database',
151
+ cacheKey: 'cache'
152
+ },
153
+ logicalElements: [
154
+ {
155
+ type: 'logical',
156
+ comparison: 'equals',
157
+ lhsAttribute: 'configKey',
158
+ rhsReference: 'dbKey'
159
+ },
160
+ {
161
+ type: 'logicalOperator',
162
+ operator: 'or'
163
+ },
164
+ {
165
+ type: 'logical',
166
+ comparison: 'equals',
167
+ lhsAttribute: 'configKey',
168
+ rhsReference: 'cacheKey'
169
+ }
170
+ ]
171
+ };
172
+
173
+ const result = await dynamoDBSharedLib.scan(
174
+ _izContext,
175
+ tableName,
176
+ queryElements,
177
+ { numPagesToRequest: 1 }
178
+ );
179
+ ```
180
+
181
+ ### 6. Scan with Greater Than Comparison
182
+
183
+ Filter items where `version` is greater than `2`.
184
+
185
+ ```javascript
186
+ const queryElements = {
187
+ additionalAttributes: {
188
+ minVersion: 2
189
+ },
190
+ logicalElements: [
191
+ {
192
+ type: 'logical',
193
+ comparison: 'greaterThan',
194
+ lhsAttribute: 'version',
195
+ rhsReference: 'minVersion'
196
+ }
197
+ ]
198
+ };
199
+
200
+ const result = await dynamoDBSharedLib.scan(
201
+ _izContext,
202
+ tableName,
203
+ queryElements,
204
+ { numPagesToRequest: 1 }
205
+ );
206
+ ```
207
+
208
+ ### 7. Scan with Less Than Comparison
209
+
210
+ Filter items where `retryCount` is less than `5`.
211
+
212
+ ```javascript
213
+ const queryElements = {
214
+ additionalAttributes: {
215
+ maxRetries: 5
216
+ },
217
+ logicalElements: [
218
+ {
219
+ type: 'logical',
220
+ comparison: 'lessThan',
221
+ lhsAttribute: 'retryCount',
222
+ rhsReference: 'maxRetries'
223
+ }
224
+ ]
225
+ };
226
+
227
+ const result = await dynamoDBSharedLib.scan(
228
+ _izContext,
229
+ tableName,
230
+ queryElements,
231
+ { numPagesToRequest: 1 }
232
+ );
233
+ ```
234
+
235
+ ### 8. Scan with Between Comparison
236
+
237
+ Filter items where `timestamp` is between two values.
238
+
239
+ ```javascript
240
+ const queryElements = {
241
+ additionalAttributes: {
242
+ startTime: 1640000000000,
243
+ endTime: 1650000000000
244
+ },
245
+ logicalElements: [
246
+ {
247
+ type: 'logical',
248
+ comparison: 'between',
249
+ lhsAttribute: 'timestamp',
250
+ betweenStartReference: 'startTime',
251
+ betweenEndReference: 'endTime'
252
+ }
253
+ ]
254
+ };
255
+
256
+ const result = await dynamoDBSharedLib.scan(
257
+ _izContext,
258
+ tableName,
259
+ queryElements,
260
+ { numPagesToRequest: 1 }
261
+ );
262
+ ```
263
+
264
+ ### 9. Scan with Function - attribute_exists
265
+
266
+ Filter items where the `description` attribute exists.
267
+
268
+ ```javascript
269
+ const queryElements = {
270
+ logicalElements: [
271
+ {
272
+ type: 'function',
273
+ function: 'attribute_exists',
274
+ attribute: 'description'
275
+ }
276
+ ]
277
+ };
278
+
279
+ const result = await dynamoDBSharedLib.scan(
280
+ _izContext,
281
+ tableName,
282
+ queryElements,
283
+ { numPagesToRequest: 1 }
284
+ );
285
+ ```
286
+
287
+ ### 10. Scan with Function - attribute_not_exists
288
+
289
+ Filter items where the `deprecatedAt` attribute does not exist.
290
+
291
+ ```javascript
292
+ const queryElements = {
293
+ logicalElements: [
294
+ {
295
+ type: 'function',
296
+ function: 'attribute_not_exists',
297
+ attribute: 'deprecatedAt'
298
+ }
299
+ ]
300
+ };
301
+
302
+ const result = await dynamoDBSharedLib.scan(
303
+ _izContext,
304
+ tableName,
305
+ queryElements,
306
+ { numPagesToRequest: 1 }
307
+ );
308
+ ```
309
+
310
+ ### 11. Scan with Function - begins_with
311
+
312
+ Filter items where `configTag` begins with `'prod-'`.
313
+
314
+ ```javascript
315
+ const queryElements = {
316
+ additionalAttributes: {
317
+ tagPrefix: 'prod-'
318
+ },
319
+ logicalElements: [
320
+ {
321
+ type: 'function',
322
+ function: 'begins_with',
323
+ attribute: 'configTag',
324
+ substrReference: 'tagPrefix'
325
+ }
326
+ ]
327
+ };
328
+
329
+ const result = await dynamoDBSharedLib.scan(
330
+ _izContext,
331
+ tableName,
332
+ queryElements,
333
+ { numPagesToRequest: 1 }
334
+ );
335
+ ```
336
+
337
+ ### 12. Scan with Projection Expression
338
+
339
+ Return only specific attributes: `configKey`, `configTag`, and `configValue`.
340
+
341
+ ```javascript
342
+ const queryElements = {
343
+ projectionExpression: ['configKey', 'configTag', 'configValue']
344
+ };
345
+
346
+ const result = await dynamoDBSharedLib.scan(
347
+ _izContext,
348
+ tableName,
349
+ queryElements,
350
+ { numPagesToRequest: 1 }
351
+ );
352
+ ```
353
+
354
+ ### 13. Scan with Select - SPECIFIC_ATTRIBUTES
355
+
356
+ Use projection with select mode for specific attributes.
357
+
358
+ ```javascript
359
+ const queryElements = {
360
+ projectionExpression: ['configKey', 'configValue'],
361
+ select: 'SPECIFIC_ATTRIBUTES'
362
+ };
363
+
364
+ const result = await dynamoDBSharedLib.scan(
365
+ _izContext,
366
+ tableName,
367
+ queryElements,
368
+ { numPagesToRequest: 1 }
369
+ );
370
+ ```
371
+
372
+ ### 14. Scan with Select - COUNT
373
+
374
+ Get count of items without returning the actual items.
375
+
376
+ ```javascript
377
+ const queryElements = {
378
+ select: 'COUNT'
379
+ };
380
+
381
+ const result = await dynamoDBSharedLib.scan(
382
+ _izContext,
383
+ tableName,
384
+ queryElements,
385
+ { numPagesToRequest: 1 }
386
+ );
387
+ console.log('Total items:', result.Count);
388
+ ```
389
+
390
+ ### 15. Scan with Filter and Projection
391
+
392
+ Filter items where `isActive` equals `true` and return only specific attributes.
393
+
394
+ ```javascript
395
+ const queryElements = {
396
+ additionalAttributes: {
397
+ activeStatus: true
398
+ },
399
+ logicalElements: [
400
+ {
401
+ type: 'logical',
402
+ comparison: 'equals',
403
+ lhsAttribute: 'isActive',
404
+ rhsReference: 'activeStatus'
405
+ }
406
+ ],
407
+ projectionExpression: ['configKey', 'configTag', 'lastModified'],
408
+ select: 'SPECIFIC_ATTRIBUTES'
409
+ };
410
+
411
+ const result = await dynamoDBSharedLib.scan(
412
+ _izContext,
413
+ tableName,
414
+ queryElements,
415
+ { numPagesToRequest: 1 }
416
+ );
417
+ ```
418
+
419
+ ### 16. Scan with Limit
420
+
421
+ Limit the number of items per page to 10.
422
+
423
+ ```javascript
424
+ const queryElements = {
425
+ limit: 10
426
+ };
427
+
428
+ const result = await dynamoDBSharedLib.scan(
429
+ _izContext,
430
+ tableName,
431
+ queryElements,
432
+ { numPagesToRequest: 1 }
433
+ );
434
+ ```
435
+
436
+ ### 17. Scan with Pagination
437
+
438
+ Request multiple pages of results (e.g., 3 pages).
439
+
440
+ ```javascript
441
+ const queryElements = {
442
+ limit: 25
443
+ };
444
+
445
+ const result = await dynamoDBSharedLib.scan(
446
+ _izContext,
447
+ tableName,
448
+ queryElements,
449
+ { numPagesToRequest: 3 }
450
+ );
451
+ ```
452
+
453
+ ### 18. Scan with Pagination Token (ExclusiveStartKey)
454
+
455
+ Continue scanning from the last evaluated key.
456
+
457
+ ```javascript
458
+ const queryElements = {
459
+ limit: 20,
460
+ exclusiveStartKey: {
461
+ configKey: 'lastConfigKey',
462
+ configTag: 'lastConfigTag'
463
+ }
464
+ };
465
+
466
+ const result = await dynamoDBSharedLib.scan(
467
+ _izContext,
468
+ tableName,
469
+ queryElements,
470
+ { numPagesToRequest: 1 }
471
+ );
472
+ // Use result.LastEvaluatedKey for next page
473
+ ```
474
+
475
+ ### 19. Scan with Global Secondary Index (GSI)
476
+
477
+ Scan using a global secondary index.
478
+
479
+ ```javascript
480
+ const queryElements = {
481
+ indexName: 'StatusIndex',
482
+ additionalAttributes: {
483
+ activeValue: 'active'
484
+ },
485
+ logicalElements: [
486
+ {
487
+ type: 'logical',
488
+ comparison: 'equals',
489
+ lhsAttribute: 'status',
490
+ rhsReference: 'activeValue'
491
+ }
492
+ ]
493
+ };
494
+
495
+ const result = await dynamoDBSharedLib.scan(
496
+ _izContext,
497
+ tableName,
498
+ queryElements,
499
+ { numPagesToRequest: 1 }
500
+ );
501
+ ```
502
+
503
+ ### 20. Scan with Complex Group Condition
504
+
505
+ Filter using grouped conditions: `(environment = 'prod' OR environment = 'staging') AND status = 'enabled'`.
506
+
507
+ ```javascript
508
+ const queryElements = {
509
+ additionalAttributes: {
510
+ prodEnv: 'prod',
511
+ stagingEnv: 'staging',
512
+ enabledStatus: 'enabled'
513
+ },
514
+ logicalElements: [
515
+ {
516
+ type: 'group',
517
+ elements: [
518
+ {
519
+ type: 'logical',
520
+ comparison: 'equals',
521
+ lhsAttribute: 'environment',
522
+ rhsReference: 'prodEnv'
523
+ },
524
+ {
525
+ type: 'logicalOperator',
526
+ operator: 'or'
527
+ },
528
+ {
529
+ type: 'logical',
530
+ comparison: 'equals',
531
+ lhsAttribute: 'environment',
532
+ rhsReference: 'stagingEnv'
533
+ }
534
+ ]
535
+ },
536
+ {
537
+ type: 'logicalOperator',
538
+ operator: 'and'
539
+ },
540
+ {
541
+ type: 'logical',
542
+ comparison: 'equals',
543
+ lhsAttribute: 'status',
544
+ rhsReference: 'enabledStatus'
545
+ }
546
+ ]
547
+ };
548
+
549
+ const result = await dynamoDBSharedLib.scan(
550
+ _izContext,
551
+ tableName,
552
+ queryElements,
553
+ { numPagesToRequest: 1 }
554
+ );
555
+ ```
556
+
557
+ ### 21. Scan with Multiple Functions
558
+
559
+ Filter items where `configKey` exists AND `configTag` begins with `'v2-'`.
560
+
561
+ ```javascript
562
+ const queryElements = {
563
+ additionalAttributes: {
564
+ versionPrefix: 'v2-'
565
+ },
566
+ logicalElements: [
567
+ {
568
+ type: 'function',
569
+ function: 'attribute_exists',
570
+ attribute: 'configKey'
571
+ },
572
+ {
573
+ type: 'logicalOperator',
574
+ operator: 'and'
575
+ },
576
+ {
577
+ type: 'function',
578
+ function: 'begins_with',
579
+ attribute: 'configTag',
580
+ substrReference: 'versionPrefix'
581
+ }
582
+ ]
583
+ };
584
+
585
+ const result = await dynamoDBSharedLib.scan(
586
+ _izContext,
587
+ tableName,
588
+ queryElements,
589
+ { numPagesToRequest: 1 }
590
+ );
591
+ ```
592
+
593
+ ## Return Value
594
+
595
+ The `scan` function returns an object containing:
596
+ - `Items` - Array of items returned from the scan
597
+ - `Count` - Number of items in the response
598
+ - `ScannedCount` - Number of items evaluated (before filter)
599
+ - `LastEvaluatedKey` - Key to use for pagination (if more items exist)
600
+ - `ConsumedCapacity` - Information about capacity consumed
601
+
602
+ ## Using scanResults
603
+
604
+ For convenience, use `scanResults` to get only the Items array:
605
+
606
+ ```javascript
607
+ const items = await dynamoDBSharedLib.scanResults(
608
+ _izContext,
609
+ tableName,
610
+ queryElements,
611
+ settings
612
+ );
613
+ // Returns only the Items array
614
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izara_project/izara-core-library-dynamodb",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "description": "Connecting with AWS DynamoDB Resource",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -202,7 +202,7 @@ function processLogicalElements(_izContext, logicalElements, level) {
202
202
  if (level > MAX_RECURSION_LEVEL) {
203
203
  throw new Error(
204
204
  `exceeded maximum recursion level for property checks: ` +
205
- MAX_RECURSION_LEVEL
205
+ MAX_RECURSION_LEVEL
206
206
  );
207
207
  }
208
208
 
@@ -616,7 +616,7 @@ async function query(
616
616
  //unrecognised comparison
617
617
  throw new NoRetryError(
618
618
  'Unrecognised comparison in sortKeyCondition ' +
619
- queryElements.sortKeyCondition.comparison
619
+ queryElements.sortKeyCondition.comparison
620
620
  );
621
621
  }
622
622
 
@@ -826,6 +826,220 @@ async function queryResults(
826
826
  return queryResults.Items;
827
827
  }
828
828
 
829
+
830
+ /**
831
+ * Executes a Scan operation on DynamoDB
832
+ * Scans the entire table or a secondary index with optional filters.
833
+ * NOTE: Scan operations are expensive and should be used sparingly.
834
+ *
835
+ * @param {object} _izContext - Izara context object for logging
836
+ * @param {string} tableName - Name of the DynamoDB table
837
+ * @param {object} [queryElements={}] - Optional query parameters
838
+ * @param {object[]} queryElements.logicalElements - Filter conditions (uses FilterExpression)
839
+ * @param {object} queryElements.additionalAttributes - Additional attribute values for filters
840
+ * @param {string[]} queryElements.projectionExpression - Array of attribute names to return
841
+ * @param {string} queryElements.select - Return mode: ALL_ATTRIBUTES | COUNT | SPECIFIC_ATTRIBUTES
842
+ * @param {number} queryElements.limit - Maximum items per page
843
+ * @param {object} queryElements.exclusiveStartKey - Pagination start key
844
+ * @param {string} queryElements.indexName - Global secondary index name
845
+ * @param {object} [settings={}] - Operation settings
846
+ * @param {number} [settings.numPagesToRequest=1] - Number of pages to auto-paginate (default: 1)
847
+ *
848
+ * @returns {Promise<object>} Object containing Items, Count, ScannedCount, and LastEvaluatedKey
849
+ */
850
+ async function scan(
851
+ _izContext,
852
+ tableName,
853
+ queryElements = {},
854
+ settings = {}
855
+ ) {
856
+ try {
857
+ // ------------ Build payload ---------------
858
+ let payload = {
859
+ TableName: tableName, // required
860
+ ReturnConsumedCapacity: 'TOTAL' // NONE(default) | TOTAL | INDEXES
861
+ };
862
+
863
+ // ---- FilterExpression : Add filter conditions using logicalElements ----
864
+ const [filterExpression, expressionAttributeValues] =
865
+ reformConditionExpression(_izContext, queryElements);
866
+ if (filterExpression) {
867
+ payload.FilterExpression = filterExpression;
868
+ }
869
+ console.log('filterExpression', filterExpression);
870
+ console.log('expressionAttributeValues', expressionAttributeValues);
871
+
872
+ if (expressionAttributeValues) {
873
+ payload.ExpressionAttributeValues = expressionAttributeValues;
874
+ }
875
+
876
+ // NOTE: we need to choose projectionExpression or select, not both (except SPECIFIC_ATTRIBUTES)
877
+ if (
878
+ queryElements.projectionExpression &&
879
+ queryElements.select &&
880
+ queryElements.select != 'SPECIFIC_ATTRIBUTES'
881
+ ) {
882
+ throw new NoRetryError(
883
+ 'invalid setting, choose projectionExpression or select, or set projectionExpression with select is "SPECIFIC_ATTRIBUTES" '
884
+ );
885
+ }
886
+
887
+ // ---- projectionExpression : Return specific attributes ----
888
+ if (
889
+ queryElements.projectionExpression &&
890
+ Array.isArray(queryElements.projectionExpression) &&
891
+ queryElements.projectionExpression.length !== 0
892
+ ) {
893
+ payload.ProjectionExpression =
894
+ queryElements.projectionExpression.join(', ');
895
+ }
896
+
897
+ // ---- select : Return mode ----
898
+ if (queryElements.select) {
899
+ queryElements.select = queryElements.select.toUpperCase();
900
+ if (
901
+ queryElements.select == 'ALL_ATTRIBUTES' ||
902
+ queryElements.select == 'ALL_PROJECTED_ATTRIBUTES' ||
903
+ queryElements.select == 'COUNT' ||
904
+ queryElements.select == 'SPECIFIC_ATTRIBUTES'
905
+ ) {
906
+ payload.Select = queryElements.select;
907
+ } else {
908
+ throw new NoRetryError(
909
+ 'select invalid, should be "ALL_ATTRIBUTES" | "ALL_PROJECTED_ATTRIBUTES" | "COUNT" | "SPECIFIC_ATTRIBUTES".'
910
+ );
911
+ }
912
+ }
913
+
914
+ // ---- exclusiveStartKey : Pagination ----
915
+ if (queryElements.exclusiveStartKey) {
916
+ payload.ExclusiveStartKey = queryElements.exclusiveStartKey;
917
+ }
918
+
919
+ // ---- indexName : Global secondary index (GSI) ----
920
+ if (queryElements.indexName) {
921
+ payload.IndexName = queryElements.indexName;
922
+ }
923
+
924
+ // ---- Limit : Items per page ----
925
+ if (queryElements.limit) {
926
+ if (typeof queryElements.limit !== 'number' || queryElements.limit < 1) {
927
+ throw new NoRetryError('limit incorrectly formatted');
928
+ }
929
+ payload.Limit = Math.floor(queryElements.limit);
930
+ }
931
+
932
+ _izContext.logger.debug('payload before scan', payload);
933
+
934
+ // ---- Execute scan with pagination handling ----
935
+ if (
936
+ !settings.hasOwnProperty('numPagesToRequest') ||
937
+ settings.numPagesToRequest == 1
938
+ ) {
939
+ // Single page scan
940
+ _izContext.logger.debug('CASE: numPagesToRequest = 1');
941
+
942
+ const returnValue = await dynamodb.scan(payload);
943
+ _izContext.logger.debug('scan status: ', returnValue['$metadata']);
944
+ await captureCapacityUsed(
945
+ _izContext,
946
+ returnValue.ConsumedCapacity,
947
+ 'read',
948
+ 'scan'
949
+ );
950
+ return returnValue; // return in DynamoDB syntax
951
+ } else {
952
+ // Multi-page scan
953
+ _izContext.logger.debug('CASE: numPagesToRequest > 1');
954
+
955
+ const getAllData = async payload => {
956
+ const _getAllData = async (payload, startKey) => {
957
+ if (startKey) {
958
+ payload.ExclusiveStartKey = startKey;
959
+ }
960
+ scanPage++;
961
+ let data = await dynamodb.scan(payload); // ---- main scan
962
+ _izContext.logger.debug('scan status: ', data['$metadata']);
963
+ await captureCapacityUsed(
964
+ _izContext,
965
+ data.ConsumedCapacity,
966
+ 'read',
967
+ 'scan'
968
+ );
969
+ return data;
970
+ };
971
+
972
+ // --- declare variables for scan
973
+ let lastEvaluatedKey = null;
974
+ let lastEvaluatedKeyReturnValue = null;
975
+ let rows = [];
976
+ let scanPage = 0;
977
+ let count = 0;
978
+ let scannedCount = 0;
979
+
980
+ do {
981
+ const result = await _getAllData(payload, lastEvaluatedKey);
982
+
983
+ // ---- handle response
984
+ count += result.Count;
985
+ scannedCount += result.ScannedCount;
986
+ lastEvaluatedKey = result.LastEvaluatedKey;
987
+ lastEvaluatedKeyReturnValue = result.LastEvaluatedKey;
988
+
989
+ if (result.hasOwnProperty('Items')) {
990
+ rows = rows.concat(result.Items);
991
+ }
992
+ if (scanPage >= settings.numPagesToRequest) {
993
+ lastEvaluatedKey = null; // exit loop
994
+ }
995
+
996
+ if (scanPage === 1000) {
997
+ throw new NoRetryError(
998
+ 'too many paginated database results, 1000 limit reached, stopped execution in dynamodb.scan'
999
+ );
1000
+ }
1001
+ } while (lastEvaluatedKey);
1002
+
1003
+ return {
1004
+ Items: rows,
1005
+ Count: count,
1006
+ ScannedCount: scannedCount,
1007
+ LastEvaluatedKey: lastEvaluatedKeyReturnValue
1008
+ };
1009
+ };
1010
+ return await getAllData(payload);
1011
+ }
1012
+ } catch (err) {
1013
+ _izContext.logger.warn('warning', err);
1014
+ throw new Error('Unhandled Error, scan: ', err);
1015
+ }
1016
+ }
1017
+
1018
+ /**
1019
+ * Executes a Scan on DynamoDB and returns only Items
1020
+ * @param {object} _izContext - Izara context object
1021
+ * @param {string} tableName - Name of the DynamoDB table
1022
+ * @param {object} [queryElements={}] - Optional query parameters
1023
+ * @param {object} [settings={}] - Operation settings
1024
+ * @param {number} [settings.numPagesToRequest=1] - Number of pages to request
1025
+ *
1026
+ * @returns {Promise<object[]>} Array of items from scan result
1027
+ */
1028
+ async function scanResults(
1029
+ _izContext,
1030
+ tableName,
1031
+ queryElements = {},
1032
+ settings = {}
1033
+ ) {
1034
+ const scanResults = await scan(
1035
+ _izContext,
1036
+ tableName,
1037
+ queryElements,
1038
+ settings
1039
+ );
1040
+ return scanResults.Items;
1041
+ }
1042
+
829
1043
  /**
830
1044
  * Executes a PutItem query on DynamoDB
831
1045
  * @param {string} tableName
@@ -1099,12 +1313,12 @@ async function updateItem(
1099
1313
  k
1100
1314
  ) &&
1101
1315
  JSON.stringify(payload.ExpressionAttributeValues[k]) !==
1102
- JSON.stringify(v)
1316
+ JSON.stringify(v)
1103
1317
  ) {
1104
1318
  throw new NoRetryError(
1105
1319
  'Placeholder ' +
1106
- k +
1107
- ' duplicated with different value (from ConditionExpression merge).'
1320
+ k +
1321
+ ' duplicated with different value (from ConditionExpression merge).'
1108
1322
  );
1109
1323
  }
1110
1324
  payload.ExpressionAttributeValues[k] = v;
@@ -1132,12 +1346,12 @@ async function updateItem(
1132
1346
  k
1133
1347
  ) &&
1134
1348
  JSON.stringify(payload.ExpressionAttributeValues[k]) !==
1135
- JSON.stringify(v)
1349
+ JSON.stringify(v)
1136
1350
  ) {
1137
1351
  throw new NoRetryError(
1138
1352
  'Placeholder ' +
1139
- k +
1140
- ' duplicated with different value (from queryElements merge).'
1353
+ k +
1354
+ ' duplicated with different value (from queryElements merge).'
1141
1355
  );
1142
1356
  }
1143
1357
  payload.ExpressionAttributeValues[k] = v;
@@ -1222,7 +1436,7 @@ async function updateItem(
1222
1436
  if (action !== 'SET') {
1223
1437
  throw new NoRetryError(
1224
1438
  JSON.stringify(item) +
1225
- ': listAppend / add_ / subtract_ must be used with "SET" action.'
1439
+ ': listAppend / add_ / subtract_ must be used with "SET" action.'
1226
1440
  );
1227
1441
  }
1228
1442
  }
@@ -2025,6 +2239,8 @@ export default {
2025
2239
  getItem,
2026
2240
  query,
2027
2241
  queryResults,
2242
+ scan,
2243
+ scanResults,
2028
2244
  putItem,
2029
2245
  updateItem,
2030
2246
  deleteItem,