@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 +59 -7
- package/doc/scan.md +614 -0
- package/package.json +1 -1
- package/src/DynamoDBSharedLib.js +225 -9
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/package.json
CHANGED
package/src/DynamoDBSharedLib.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1316
|
+
JSON.stringify(v)
|
|
1103
1317
|
) {
|
|
1104
1318
|
throw new NoRetryError(
|
|
1105
1319
|
'Placeholder ' +
|
|
1106
|
-
|
|
1107
|
-
|
|
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
|
-
|
|
1349
|
+
JSON.stringify(v)
|
|
1136
1350
|
) {
|
|
1137
1351
|
throw new NoRetryError(
|
|
1138
1352
|
'Placeholder ' +
|
|
1139
|
-
|
|
1140
|
-
|
|
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
|
-
|
|
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,
|