@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 +59 -7
- package/doc/scan.md +614 -0
- package/index.js +3 -3
- package/package.json +30 -6
- package/src/DynamoDBSharedLib.js +211 -3
package/README.md
CHANGED
|
@@ -1,15 +1,67 @@
|
|
|
1
1
|
# izara-core-library-dynamodb
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
+
```
|
|
10
58
|
|
|
11
|
-
|
|
59
|
+
### Publish to NPM
|
|
60
|
+
```bash
|
|
12
61
|
npm publish
|
|
62
|
+
```
|
|
13
63
|
|
|
14
|
-
|
|
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
|
|
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.
|
|
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.
|
|
17
|
+
"jest": "^30.4.0"
|
|
18
18
|
},
|
|
19
19
|
"jest": {
|
|
20
20
|
"testEnvironment": "node"
|
|
21
21
|
},
|
|
22
22
|
"type": "module",
|
|
23
|
-
"
|
|
24
|
-
"@aws-sdk/client-dynamodb": "^3.
|
|
25
|
-
"@aws-sdk/lib-dynamodb": "^3.
|
|
26
|
-
"@aws-sdk/util-dynamodb": "^3.
|
|
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
|
}
|
package/src/DynamoDBSharedLib.js
CHANGED
|
@@ -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
|
|
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,
|