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

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/index.js CHANGED
@@ -15,9 +15,9 @@ You should have received a copy of the GNU Affero General Public License
15
15
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- 'use strict';
19
-
20
18
  // Re-export everything from DynamoDBSharedLib
21
19
  import dynamoDBSharedLib from './src/DynamoDBSharedLib.js';
22
20
 
23
- export default dynamoDBSharedLib;
21
+ export default {
22
+
23
+ };
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.16",
4
4
  "description": "Connecting with AWS DynamoDB Resource",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -14,17 +14,41 @@
14
14
  "license": "AGPL-3.0-or-later",
15
15
  "homepage": "https://bitbucket.org/izara-core-libraries/izara-core-library-dynamodb/src/master/README.md",
16
16
  "devDependencies": {
17
- "jest": "^30.2.0"
17
+ "jest": "^30.4.0"
18
18
  },
19
19
  "jest": {
20
20
  "testEnvironment": "node"
21
21
  },
22
22
  "type": "module",
23
- "dependencies": {
24
- "@aws-sdk/client-dynamodb": "^3.932.0",
25
- "@aws-sdk/lib-dynamodb": "^3.932.0",
26
- "@aws-sdk/util-dynamodb": "^3.932.0",
23
+ "peerDependencies": {
24
+ "@aws-sdk/client-dynamodb": "^3.0.0",
25
+ "@aws-sdk/lib-dynamodb": "^3.0.0",
26
+ "@aws-sdk/util-dynamodb": "^3.0.0",
27
27
  "@izara_project/izara-core-library-core": "^1.0.28",
28
28
  "lodash.clonedeep": "^4.5.0"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "@aws-sdk/client-dynamodb": {
32
+ "optional": false
33
+ },
34
+ "@aws-sdk/lib-dynamodb": {
35
+ "optional": false
36
+ },
37
+ "@aws-sdk/util-dynamodb": {
38
+ "optional": false
39
+ },
40
+ "@izara_project/izara-core-library-core": {
41
+ "optional": false
42
+ },
43
+ "lodash.clonedeep": {
44
+ "optional": true
45
+ }
46
+ },
47
+ "dependencies": {
48
+ "@aws-sdk/client-dynamodb": "^3.1045.0",
49
+ "@aws-sdk/lib-dynamodb": "^3.1045.0",
50
+ "@aws-sdk/util-dynamodb": "^3.996.2",
51
+ "@izara_project/izara-core-library-core": "^1.0.32",
52
+ "lodash.clonedeep": "^4.5.0"
29
53
  }
30
54
  }
@@ -15,8 +15,6 @@ You should have received a copy of the GNU Affero General Public License
15
15
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- 'use strict';
19
-
20
18
  import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb';
21
19
  import { DynamoDB } from '@aws-sdk/client-dynamodb';
22
20
  import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
@@ -826,6 +824,214 @@ async function queryResults(
826
824
  return queryResults.Items;
827
825
  }
828
826
 
827
+ /**
828
+ * Executes a Scan operation on DynamoDB
829
+ * Scans the entire table or a secondary index with optional filters.
830
+ * NOTE: Scan operations are expensive and should be used sparingly.
831
+ *
832
+ * @param {object} _izContext - Izara context object for logging
833
+ * @param {string} tableName - Name of the DynamoDB table
834
+ * @param {object} [queryElements={}] - Optional query parameters
835
+ * @param {object[]} queryElements.logicalElements - Filter conditions (uses FilterExpression)
836
+ * @param {object} queryElements.additionalAttributes - Additional attribute values for filters
837
+ * @param {string[]} queryElements.projectionExpression - Array of attribute names to return
838
+ * @param {string} queryElements.select - Return mode: ALL_ATTRIBUTES | COUNT | SPECIFIC_ATTRIBUTES
839
+ * @param {number} queryElements.limit - Maximum items per page
840
+ * @param {object} queryElements.exclusiveStartKey - Pagination start key
841
+ * @param {string} queryElements.indexName - Global secondary index name
842
+ * @param {object} [settings={}] - Operation settings
843
+ * @param {number} [settings.numPagesToRequest=1] - Number of pages to auto-paginate (default: 1)
844
+ *
845
+ * @returns {Promise<object>} Object containing Items, Count, ScannedCount, and LastEvaluatedKey
846
+ */
847
+ async function scan(_izContext, tableName, queryElements = {}, settings = {}) {
848
+ try {
849
+ // ------------ Build payload ---------------
850
+ let payload = {
851
+ TableName: tableName, // required
852
+ ReturnConsumedCapacity: 'TOTAL' // NONE(default) | TOTAL | INDEXES
853
+ };
854
+
855
+ // ---- FilterExpression : Add filter conditions using logicalElements ----
856
+ const [filterExpression, expressionAttributeValues] =
857
+ reformConditionExpression(_izContext, queryElements);
858
+ if (filterExpression) {
859
+ payload.FilterExpression = filterExpression;
860
+ }
861
+ console.log('filterExpression', filterExpression);
862
+ console.log('expressionAttributeValues', expressionAttributeValues);
863
+
864
+ if (expressionAttributeValues) {
865
+ payload.ExpressionAttributeValues = expressionAttributeValues;
866
+ }
867
+
868
+ // NOTE: we need to choose projectionExpression or select, not both (except SPECIFIC_ATTRIBUTES)
869
+ if (
870
+ queryElements.projectionExpression &&
871
+ queryElements.select &&
872
+ queryElements.select != 'SPECIFIC_ATTRIBUTES'
873
+ ) {
874
+ throw new NoRetryError(
875
+ 'invalid setting, choose projectionExpression or select, or set projectionExpression with select is "SPECIFIC_ATTRIBUTES" '
876
+ );
877
+ }
878
+
879
+ // ---- projectionExpression : Return specific attributes ----
880
+ if (
881
+ queryElements.projectionExpression &&
882
+ Array.isArray(queryElements.projectionExpression) &&
883
+ queryElements.projectionExpression.length !== 0
884
+ ) {
885
+ payload.ProjectionExpression =
886
+ queryElements.projectionExpression.join(', ');
887
+ }
888
+
889
+ // ---- select : Return mode ----
890
+ if (queryElements.select) {
891
+ queryElements.select = queryElements.select.toUpperCase();
892
+ if (
893
+ queryElements.select == 'ALL_ATTRIBUTES' ||
894
+ queryElements.select == 'ALL_PROJECTED_ATTRIBUTES' ||
895
+ queryElements.select == 'COUNT' ||
896
+ queryElements.select == 'SPECIFIC_ATTRIBUTES'
897
+ ) {
898
+ payload.Select = queryElements.select;
899
+ } else {
900
+ throw new NoRetryError(
901
+ 'select invalid, should be "ALL_ATTRIBUTES" | "ALL_PROJECTED_ATTRIBUTES" | "COUNT" | "SPECIFIC_ATTRIBUTES".'
902
+ );
903
+ }
904
+ }
905
+
906
+ // ---- exclusiveStartKey : Pagination ----
907
+ if (queryElements.exclusiveStartKey) {
908
+ payload.ExclusiveStartKey = queryElements.exclusiveStartKey;
909
+ }
910
+
911
+ // ---- indexName : Global secondary index (GSI) ----
912
+ if (queryElements.indexName) {
913
+ payload.IndexName = queryElements.indexName;
914
+ }
915
+
916
+ // ---- Limit : Items per page ----
917
+ if (queryElements.limit) {
918
+ if (typeof queryElements.limit !== 'number' || queryElements.limit < 1) {
919
+ throw new NoRetryError('limit incorrectly formatted');
920
+ }
921
+ payload.Limit = Math.floor(queryElements.limit);
922
+ }
923
+
924
+ _izContext.logger.debug('payload before scan', payload);
925
+
926
+ // ---- Execute scan with pagination handling ----
927
+ if (
928
+ !settings.hasOwnProperty('numPagesToRequest') ||
929
+ settings.numPagesToRequest == 1
930
+ ) {
931
+ // Single page scan
932
+ _izContext.logger.debug('CASE: numPagesToRequest = 1');
933
+
934
+ const returnValue = await dynamodb.scan(payload);
935
+ _izContext.logger.debug('scan status: ', returnValue['$metadata']);
936
+ await captureCapacityUsed(
937
+ _izContext,
938
+ returnValue.ConsumedCapacity,
939
+ 'read',
940
+ 'scan'
941
+ );
942
+ return returnValue; // return in DynamoDB syntax
943
+ } else {
944
+ // Multi-page scan
945
+ _izContext.logger.debug('CASE: numPagesToRequest > 1');
946
+
947
+ const getAllData = async payload => {
948
+ const _getAllData = async (payload, startKey) => {
949
+ if (startKey) {
950
+ payload.ExclusiveStartKey = startKey;
951
+ }
952
+ scanPage++;
953
+ let data = await dynamodb.scan(payload); // ---- main scan
954
+ _izContext.logger.debug('scan status: ', data['$metadata']);
955
+ await captureCapacityUsed(
956
+ _izContext,
957
+ data.ConsumedCapacity,
958
+ 'read',
959
+ 'scan'
960
+ );
961
+ return data;
962
+ };
963
+
964
+ // --- declare variables for scan
965
+ let lastEvaluatedKey = null;
966
+ let lastEvaluatedKeyReturnValue = null;
967
+ let rows = [];
968
+ let scanPage = 0;
969
+ let count = 0;
970
+ let scannedCount = 0;
971
+
972
+ do {
973
+ const result = await _getAllData(payload, lastEvaluatedKey);
974
+
975
+ // ---- handle response
976
+ count += result.Count;
977
+ scannedCount += result.ScannedCount;
978
+ lastEvaluatedKey = result.LastEvaluatedKey;
979
+ lastEvaluatedKeyReturnValue = result.LastEvaluatedKey;
980
+
981
+ if (result.hasOwnProperty('Items')) {
982
+ rows = rows.concat(result.Items);
983
+ }
984
+ if (scanPage >= settings.numPagesToRequest) {
985
+ lastEvaluatedKey = null; // exit loop
986
+ }
987
+
988
+ if (scanPage === 1000) {
989
+ throw new NoRetryError(
990
+ 'too many paginated database results, 1000 limit reached, stopped execution in dynamodb.scan'
991
+ );
992
+ }
993
+ } while (lastEvaluatedKey);
994
+
995
+ return {
996
+ Items: rows,
997
+ Count: count,
998
+ ScannedCount: scannedCount,
999
+ LastEvaluatedKey: lastEvaluatedKeyReturnValue
1000
+ };
1001
+ };
1002
+ return await getAllData(payload);
1003
+ }
1004
+ } catch (err) {
1005
+ _izContext.logger.warn('warning', err);
1006
+ throw new Error('Unhandled Error, scan: ', err);
1007
+ }
1008
+ }
1009
+
1010
+ /**
1011
+ * Executes a Scan on DynamoDB and returns only Items
1012
+ * @param {object} _izContext - Izara context object
1013
+ * @param {string} tableName - Name of the DynamoDB table
1014
+ * @param {object} [queryElements={}] - Optional query parameters
1015
+ * @param {object} [settings={}] - Operation settings
1016
+ * @param {number} [settings.numPagesToRequest=1] - Number of pages to request
1017
+ *
1018
+ * @returns {Promise<object[]>} Array of items from scan result
1019
+ */
1020
+ async function scanResults(
1021
+ _izContext,
1022
+ tableName,
1023
+ queryElements = {},
1024
+ settings = {}
1025
+ ) {
1026
+ const scanResults = await scan(
1027
+ _izContext,
1028
+ tableName,
1029
+ queryElements,
1030
+ settings
1031
+ );
1032
+ return scanResults.Items;
1033
+ }
1034
+
829
1035
  /**
830
1036
  * Executes a PutItem query on DynamoDB
831
1037
  * @param {string} tableName
@@ -2010,7 +2216,7 @@ async function captureCapacityUsed(
2010
2216
  }
2011
2217
 
2012
2218
  // Consolidated exports
2013
- export default {
2219
+ export {
2014
2220
  // Table name functions
2015
2221
  tableName,
2016
2222
 
@@ -2025,6 +2231,8 @@ export default {
2025
2231
  getItem,
2026
2232
  query,
2027
2233
  queryResults,
2234
+ scan,
2235
+ scanResults,
2028
2236
  putItem,
2029
2237
  updateItem,
2030
2238
  deleteItem,