@baadal-sdk/dapi 0.28.4 → 0.31.0
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/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/types/aws/client.d.ts +13 -0
- package/dist/types/aws/client.d.ts.map +1 -0
- package/dist/types/aws/db.d.ts +261 -9
- package/dist/types/aws/db.d.ts.map +1 -1
- package/dist/types/aws/index.d.ts +10 -11
- package/dist/types/aws/index.d.ts.map +1 -1
- package/dist/types/aws/s3.d.ts +90 -0
- package/dist/types/aws/s3.d.ts.map +1 -0
- package/dist/types/common/const.d.ts +3 -0
- package/dist/types/common/const.d.ts.map +1 -0
- package/dist/types/fs/index.d.ts +29 -25
- package/dist/types/fs/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +6 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/package.json +5 -1
- package/src/aws/client.ts +18 -0
- package/src/aws/db.ts +534 -105
- package/src/aws/index.ts +22 -8
- package/src/aws/s3.ts +476 -0
- package/src/common/const.ts +2 -0
- package/src/fs/index.ts +60 -54
- package/src/index.ts +3 -2
- package/src/utils/index.ts +39 -0
- package/dist/types/aws/db-client.d.ts +0 -7
- package/dist/types/aws/db-client.d.ts.map +0 -1
- package/src/aws/db-client.ts +0 -6
package/src/aws/db.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* Examples:
|
|
3
|
+
* Ref: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/dynamodb-examples.html
|
|
4
|
+
*
|
|
2
5
|
* Partition key vs Composite primary key:
|
|
3
6
|
* Ref: https://aws.amazon.com/premiumsupport/knowledge-center/primary-key-dynamodb-table/
|
|
4
7
|
*/
|
|
@@ -8,8 +11,12 @@ import {
|
|
|
8
11
|
DynamoDBDocumentClient,
|
|
9
12
|
GetCommand,
|
|
10
13
|
GetCommandInput,
|
|
14
|
+
BatchGetCommand,
|
|
15
|
+
BatchGetCommandInput,
|
|
11
16
|
PutCommand,
|
|
12
17
|
PutCommandInput,
|
|
18
|
+
BatchWriteCommand,
|
|
19
|
+
BatchWriteCommandInput,
|
|
13
20
|
UpdateCommand,
|
|
14
21
|
UpdateCommandInput,
|
|
15
22
|
QueryCommand,
|
|
@@ -20,86 +27,141 @@ import {
|
|
|
20
27
|
DeleteCommandInput,
|
|
21
28
|
} from '@aws-sdk/lib-dynamodb';
|
|
22
29
|
import short from 'short-uuid';
|
|
30
|
+
import { chunkifyArray } from '@baadal-sdk/utils';
|
|
23
31
|
|
|
24
|
-
import dbClient from './
|
|
32
|
+
import { dbClient } from './client';
|
|
25
33
|
import { StringIndexable } from '../common/common.model';
|
|
26
34
|
import { CustomError } from '../common/error';
|
|
35
|
+
import { warn, error } from '../common/logger';
|
|
36
|
+
import { BATCH_SIZE, CHUNK_SIZE } from '../common/const';
|
|
27
37
|
|
|
28
38
|
const DynamoDBError = (msg: string) => new CustomError(msg, { name: 'DynamoDBError' });
|
|
29
39
|
|
|
40
|
+
/** @internal */
|
|
30
41
|
export const init = (region: string) => {
|
|
31
|
-
const dydbClient = new DynamoDBClient({ region });
|
|
32
|
-
|
|
33
42
|
// Ref: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_lib_dynamodb.html#configuration
|
|
34
|
-
if (!dbClient.
|
|
35
|
-
|
|
43
|
+
if (!dbClient.client) {
|
|
44
|
+
const dydbClient = new DynamoDBClient({ region }); // may also pass `credentials`
|
|
45
|
+
dbClient.client = DynamoDBDocumentClient.from(dydbClient);
|
|
36
46
|
dbClient.id = short.uuid();
|
|
37
47
|
return true;
|
|
38
48
|
}
|
|
39
49
|
return false;
|
|
40
50
|
};
|
|
41
51
|
|
|
52
|
+
/** @internal */
|
|
42
53
|
export const status = () => dbClient.id;
|
|
43
54
|
|
|
44
|
-
const tryInit = () => {
|
|
45
|
-
if (
|
|
55
|
+
const tryInit = (silent = false) => {
|
|
56
|
+
if (dbClient.client) return;
|
|
46
57
|
const region = process.env.AWS_REGION || '';
|
|
47
58
|
if (region) {
|
|
48
|
-
if (init(region))
|
|
59
|
+
if (init(region)) {
|
|
60
|
+
// console.log('Auto-initialization of DynamoDB successful');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!silent) {
|
|
65
|
+
// throw DynamoDBError('DynamoDB is possibly uninitialized!');
|
|
66
|
+
throw DynamoDBError('Could not auto-initialize DynamoDB!');
|
|
49
67
|
}
|
|
50
|
-
// throw DynamoDBError('DynamoDB is possibly uninitialized!');
|
|
51
|
-
throw DynamoDBError('Could not auto-initialize DynamoDB!');
|
|
52
68
|
};
|
|
53
69
|
|
|
54
70
|
// auto-initialize on load
|
|
55
|
-
|
|
71
|
+
tryInit(true);
|
|
56
72
|
|
|
57
|
-
const writeItemForceHelper = async <T = any>(table: string,
|
|
58
|
-
if (!dbClient.
|
|
59
|
-
if (!dbClient.
|
|
60
|
-
if (!table || !
|
|
73
|
+
const writeItemForceHelper = async <T = any>(table: string, item: T, key: string, i: number): Promise<T | null> => {
|
|
74
|
+
if (!dbClient.client) tryInit();
|
|
75
|
+
if (!dbClient.client) return null;
|
|
76
|
+
if (!table || !item) return null;
|
|
61
77
|
|
|
62
|
-
if (!(
|
|
63
|
-
(
|
|
78
|
+
if (!(item as any)[key]) {
|
|
79
|
+
(item as any)[key] = short.uuid();
|
|
64
80
|
}
|
|
65
|
-
const cmdParams: PutCommandInput = { TableName: table, Item:
|
|
81
|
+
const cmdParams: PutCommandInput = { TableName: table, Item: item, ConditionExpression: `attribute_not_exists(${key})` };
|
|
66
82
|
const command = new PutCommand(cmdParams);
|
|
67
83
|
const numberOfAttempts = 3;
|
|
68
84
|
|
|
69
85
|
try {
|
|
70
|
-
await dbClient.
|
|
86
|
+
await dbClient.client.send(command);
|
|
71
87
|
} catch (err: any) {
|
|
72
|
-
// console.error('PutCommandInput:', cmdParams);
|
|
73
|
-
// console.error(err);
|
|
74
88
|
if (err.name === 'ConditionalCheckFailedException') {
|
|
75
89
|
if (i < numberOfAttempts - 1) {
|
|
76
|
-
(
|
|
77
|
-
const ret: T | null = await writeItemForceHelper(table,
|
|
90
|
+
(item as any)[key] = short.uuid(); // new primary key
|
|
91
|
+
const ret: T | null = await writeItemForceHelper(table, item, key, i + 1);
|
|
78
92
|
return ret;
|
|
79
93
|
}
|
|
80
94
|
console.error('PutCommandInput:', cmdParams);
|
|
81
|
-
|
|
95
|
+
error('[ERROR] Maximum attempts overflow!');
|
|
82
96
|
}
|
|
83
97
|
return null;
|
|
84
98
|
}
|
|
85
99
|
|
|
86
|
-
return
|
|
100
|
+
return item;
|
|
87
101
|
};
|
|
88
102
|
|
|
89
|
-
export
|
|
90
|
-
|
|
103
|
+
export interface WriteItemForceInput<T = any> {
|
|
104
|
+
table: string;
|
|
105
|
+
item: T;
|
|
106
|
+
key?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Write an item to a DynamoDB table, retry in case of key conflict
|
|
111
|
+
* @param input input command object
|
|
112
|
+
* @returns the created item, null in case of error
|
|
113
|
+
*
|
|
114
|
+
* ```js
|
|
115
|
+
* writeItemForce({
|
|
116
|
+
* table: 'lesson_list',
|
|
117
|
+
* item: { title: 'My Lesson' },
|
|
118
|
+
* key: 'id',
|
|
119
|
+
* });
|
|
120
|
+
*
|
|
121
|
+
* interface WriteItemForceInput<T = any> {
|
|
122
|
+
* table: string;
|
|
123
|
+
* item: T;
|
|
124
|
+
* key?: string; // default: `id`
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export const writeItemForce = async <T = any>(input: WriteItemForceInput<T>): Promise<T | null> => {
|
|
129
|
+
const key = input.key || 'id';
|
|
130
|
+
return writeItemForceHelper<T>(input.table, input.item, key, 0);
|
|
91
131
|
};
|
|
92
132
|
|
|
93
|
-
export
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
133
|
+
export interface WriteItemInput {
|
|
134
|
+
table: string;
|
|
135
|
+
item: StringIndexable;
|
|
136
|
+
}
|
|
97
137
|
|
|
98
|
-
|
|
138
|
+
/**
|
|
139
|
+
* Write an item to a DynamoDB table
|
|
140
|
+
* @param input input command object
|
|
141
|
+
* @returns true if successful, null in case of error
|
|
142
|
+
*
|
|
143
|
+
* ```js
|
|
144
|
+
* writeItem({
|
|
145
|
+
* table: 'lesson_list',
|
|
146
|
+
* item: { id: 'id_001', title: 'My Lesson' },
|
|
147
|
+
* });
|
|
148
|
+
*
|
|
149
|
+
* interface WriteItemInput {
|
|
150
|
+
* table: string;
|
|
151
|
+
* item: StringIndexable;
|
|
152
|
+
* }
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export const writeItem = async (input: WriteItemInput) => {
|
|
156
|
+
if (!dbClient.client) tryInit();
|
|
157
|
+
if (!dbClient.client) return null;
|
|
158
|
+
if (!input.table || !input.item) return null;
|
|
159
|
+
|
|
160
|
+
const cmdParams: PutCommandInput = { TableName: input.table, Item: input.item };
|
|
99
161
|
const command = new PutCommand(cmdParams);
|
|
100
162
|
|
|
101
163
|
try {
|
|
102
|
-
await dbClient.
|
|
164
|
+
await dbClient.client.send(command);
|
|
103
165
|
} catch (err) {
|
|
104
166
|
console.error('PutCommandInput:', cmdParams);
|
|
105
167
|
console.error(err);
|
|
@@ -110,28 +172,127 @@ export const writeItem = async (table: string, data: StringIndexable) => {
|
|
|
110
172
|
return true;
|
|
111
173
|
};
|
|
112
174
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
175
|
+
// Ref: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/dynamodb-example-table-read-write-batch.html
|
|
176
|
+
const batchWriteItems = async (table: string, items: StringIndexable[]) => {
|
|
177
|
+
if (!dbClient.client) tryInit();
|
|
178
|
+
if (!dbClient.client) return null;
|
|
179
|
+
if (!table || !items || !Array.isArray(items)) return null;
|
|
180
|
+
if (!items.length) return false;
|
|
181
|
+
|
|
182
|
+
const reqList = items.map(item => ({ PutRequest: { Item: item } }));
|
|
183
|
+
const cmdParams: BatchWriteCommandInput = {
|
|
184
|
+
RequestItems: {
|
|
185
|
+
[table]: reqList,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const command = new BatchWriteCommand(cmdParams);
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await dbClient.client.send(command);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
console.error('BatchWriteCommandInput:', cmdParams);
|
|
195
|
+
console.error(err);
|
|
196
|
+
return null;
|
|
197
|
+
// throw err;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return true;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export interface WriteItemsAllInput {
|
|
204
|
+
table: string;
|
|
205
|
+
items: StringIndexable[];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Write an list of items to a DynamoDB table
|
|
210
|
+
* @param input input command object
|
|
211
|
+
* @returns true if successful, null in case of error
|
|
212
|
+
*
|
|
213
|
+
* ```js
|
|
214
|
+
* writeItemsAll({
|
|
215
|
+
* table: 'lesson_list',
|
|
216
|
+
* items: [{ id: 'id_001', title: 'My Lesson' }, { id: 'id_002', title: 'My Lesson 2' }],
|
|
217
|
+
* });
|
|
218
|
+
*
|
|
219
|
+
* interface WriteItemInput {
|
|
220
|
+
* table: string;
|
|
221
|
+
* items: StringIndexable[];
|
|
222
|
+
* }
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
export const writeItemsAll = async (input: WriteItemsAllInput) => {
|
|
226
|
+
if (!dbClient.client) tryInit();
|
|
227
|
+
if (!dbClient.client) return null;
|
|
228
|
+
if (!input.table || !input.items || !Array.isArray(input.items)) return null;
|
|
229
|
+
if (!input.items.length) return false;
|
|
230
|
+
|
|
231
|
+
let errFlag = false;
|
|
232
|
+
|
|
233
|
+
const batchedItems = chunkifyArray(input.items, BATCH_SIZE);
|
|
234
|
+
const chunkedItems = chunkifyArray(batchedItems, CHUNK_SIZE);
|
|
235
|
+
|
|
236
|
+
for (let i = 0; i < chunkedItems.length; i += 1) {
|
|
237
|
+
const bchunks = chunkedItems[i];
|
|
238
|
+
|
|
239
|
+
const brlist = bchunks.map(iItems => batchWriteItems(input.table, iItems));
|
|
240
|
+
const bslist = await Promise.all(brlist); // eslint-disable-line no-await-in-loop
|
|
241
|
+
|
|
242
|
+
const isSuccess = bslist.every(e => e === true);
|
|
243
|
+
if (!isSuccess) errFlag = true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return errFlag ? null : true;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export interface UpdateItemInput {
|
|
250
|
+
table: string;
|
|
251
|
+
key: StringIndexable;
|
|
252
|
+
update: string;
|
|
253
|
+
attr: StringIndexable;
|
|
254
|
+
attrNames?: StringIndexable;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Update an item in DynamoDB table
|
|
259
|
+
* @param input input command object
|
|
260
|
+
* @returns true if successful, null in case of error
|
|
261
|
+
*
|
|
262
|
+
* ```js
|
|
263
|
+
* updateItem({
|
|
264
|
+
* table: 'lesson_list',
|
|
265
|
+
* key: { id: 'id_001' },
|
|
266
|
+
* update: 'SET status = :status, #rev = 10',
|
|
267
|
+
* attr: { ':status': 'completed' },
|
|
268
|
+
* attrNames: { '#rev': 'revision' },
|
|
269
|
+
* });
|
|
270
|
+
*
|
|
271
|
+
* interface UpdateItemInput {
|
|
272
|
+
* table: string;
|
|
273
|
+
* key: StringIndexable;
|
|
274
|
+
* update: string;
|
|
275
|
+
* attr: StringIndexable;
|
|
276
|
+
* attrNames?: StringIndexable;
|
|
277
|
+
* }
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
export const updateItem = async (input: UpdateItemInput) => {
|
|
281
|
+
if (!dbClient.client) tryInit();
|
|
282
|
+
if (!dbClient.client) return null;
|
|
283
|
+
if (!input.table || !input.key || !input.update || !input.attr) return null;
|
|
123
284
|
|
|
124
285
|
let cmdParams: UpdateCommandInput = {
|
|
125
|
-
TableName: table,
|
|
126
|
-
Key: key,
|
|
127
|
-
UpdateExpression: update,
|
|
128
|
-
ExpressionAttributeValues: attr,
|
|
286
|
+
TableName: input.table,
|
|
287
|
+
Key: input.key,
|
|
288
|
+
UpdateExpression: input.update,
|
|
289
|
+
ExpressionAttributeValues: input.attr,
|
|
129
290
|
};
|
|
130
|
-
if (attrNames) cmdParams = { ...cmdParams, ExpressionAttributeNames: attrNames };
|
|
291
|
+
if (input.attrNames) cmdParams = { ...cmdParams, ExpressionAttributeNames: input.attrNames };
|
|
131
292
|
const command = new UpdateCommand(cmdParams);
|
|
132
293
|
|
|
133
294
|
try {
|
|
134
|
-
await dbClient.
|
|
295
|
+
await dbClient.client.send(command);
|
|
135
296
|
} catch (err) {
|
|
136
297
|
console.error('UpdateCommandInput:', cmdParams);
|
|
137
298
|
console.error(err);
|
|
@@ -142,27 +303,49 @@ export const updateItem = async (
|
|
|
142
303
|
return true;
|
|
143
304
|
};
|
|
144
305
|
|
|
145
|
-
export
|
|
146
|
-
table: string
|
|
147
|
-
key: StringIndexable
|
|
148
|
-
projection?: string
|
|
149
|
-
attrNames?: StringIndexable
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
306
|
+
export interface ReadItemInput {
|
|
307
|
+
table: string;
|
|
308
|
+
key: StringIndexable;
|
|
309
|
+
projection?: string;
|
|
310
|
+
attrNames?: StringIndexable;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Read an item from DynamoDB table
|
|
315
|
+
* @param input input command object
|
|
316
|
+
* @returns item contents, null in case of error
|
|
317
|
+
*
|
|
318
|
+
* ```js
|
|
319
|
+
* readItem({
|
|
320
|
+
* table: 'lesson_list',
|
|
321
|
+
* key: { id: 'id_001' },
|
|
322
|
+
* projection: 'id, lesson, status',
|
|
323
|
+
* });
|
|
324
|
+
*
|
|
325
|
+
* interface ReadItemInput {
|
|
326
|
+
* table: string;
|
|
327
|
+
* key: StringIndexable;
|
|
328
|
+
* projection?: string;
|
|
329
|
+
* attrNames?: StringIndexable;
|
|
330
|
+
* }
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
export const readItem = async <T = any>(input: ReadItemInput) => {
|
|
334
|
+
if (!dbClient.client) tryInit();
|
|
335
|
+
if (!dbClient.client) return null;
|
|
336
|
+
if (!input.table || !input.key) return null;
|
|
154
337
|
|
|
155
338
|
let contents: T | null = null;
|
|
156
|
-
let cmdParams: GetCommandInput = { TableName: table, Key: key };
|
|
339
|
+
let cmdParams: GetCommandInput = { TableName: input.table, Key: input.key };
|
|
157
340
|
|
|
158
341
|
// Ref: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html
|
|
159
|
-
if (projection) cmdParams = { ...cmdParams, ProjectionExpression: projection };
|
|
160
|
-
if (attrNames) cmdParams = { ...cmdParams, ExpressionAttributeNames: attrNames };
|
|
342
|
+
if (input.projection) cmdParams = { ...cmdParams, ProjectionExpression: input.projection };
|
|
343
|
+
if (input.attrNames) cmdParams = { ...cmdParams, ExpressionAttributeNames: input.attrNames };
|
|
161
344
|
|
|
162
345
|
const command = new GetCommand(cmdParams);
|
|
163
346
|
|
|
164
347
|
try {
|
|
165
|
-
const results = await dbClient.
|
|
348
|
+
const results = await dbClient.client.send(command);
|
|
166
349
|
const item = results.Item;
|
|
167
350
|
|
|
168
351
|
if (item) {
|
|
@@ -171,44 +354,180 @@ export const readItem = async <T = any>(
|
|
|
171
354
|
} catch (err) {
|
|
172
355
|
console.error('GetCommandInput:', cmdParams);
|
|
173
356
|
console.error(err);
|
|
357
|
+
return null;
|
|
174
358
|
// throw err;
|
|
175
359
|
}
|
|
176
360
|
|
|
177
361
|
return contents;
|
|
178
362
|
};
|
|
179
363
|
|
|
180
|
-
|
|
364
|
+
// Ref: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/dynamodb-example-table-read-write-batch.html
|
|
365
|
+
const batchReadItems = async <T = any>(
|
|
181
366
|
table: string,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
projection = '',
|
|
186
|
-
desc = false
|
|
367
|
+
keys: StringIndexable[],
|
|
368
|
+
projection?: string,
|
|
369
|
+
attrNames?: StringIndexable
|
|
187
370
|
) => {
|
|
188
|
-
if (!dbClient.
|
|
189
|
-
if (!dbClient.
|
|
190
|
-
if (!table || !
|
|
371
|
+
if (!dbClient.client) tryInit();
|
|
372
|
+
if (!dbClient.client) return null;
|
|
373
|
+
if (!table || !keys || !Array.isArray(keys)) return null;
|
|
374
|
+
if (!keys.length) return [];
|
|
375
|
+
|
|
376
|
+
let contents: StringIndexable<T>[] = [];
|
|
377
|
+
|
|
378
|
+
let reqParams: any = { Keys: keys };
|
|
379
|
+
if (projection) reqParams = { ...reqParams, ProjectionExpression: projection };
|
|
380
|
+
if (attrNames) reqParams = { ...reqParams, ExpressionAttributeNames: attrNames };
|
|
191
381
|
|
|
192
|
-
|
|
382
|
+
const cmdParams: BatchGetCommandInput = {
|
|
383
|
+
RequestItems: {
|
|
384
|
+
[table]: reqParams,
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const command = new BatchGetCommand(cmdParams);
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
const results = await dbClient.client.send(command);
|
|
392
|
+
const items = results.Responses;
|
|
193
393
|
|
|
394
|
+
if (items && items[table]) {
|
|
395
|
+
contents = items[table];
|
|
396
|
+
}
|
|
397
|
+
} catch (err) {
|
|
398
|
+
console.error('BatchGetCommandInput:', cmdParams);
|
|
399
|
+
console.error(err);
|
|
400
|
+
return null;
|
|
401
|
+
// throw err;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return contents;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
export interface ReadItemsAllInput {
|
|
408
|
+
table: string;
|
|
409
|
+
keys: StringIndexable[];
|
|
410
|
+
projection?: string;
|
|
411
|
+
attrNames?: StringIndexable;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Read a list of items from DynamoDB table
|
|
416
|
+
* Note: ordering of items in result may not be same as that in `keys`
|
|
417
|
+
* @param input input command object
|
|
418
|
+
* @returns list of contents for items, null in case of error
|
|
419
|
+
*
|
|
420
|
+
* ```js
|
|
421
|
+
* readItemsAll({
|
|
422
|
+
* table: 'lesson_list',
|
|
423
|
+
* keys: [{ id: 'id_001' }, { id: 'id_002' }],
|
|
424
|
+
* projection: 'id, lesson, status',
|
|
425
|
+
* });
|
|
426
|
+
*
|
|
427
|
+
* interface ReadItemsAllInput {
|
|
428
|
+
* table: string;
|
|
429
|
+
* keys: StringIndexable[];
|
|
430
|
+
* projection?: string;
|
|
431
|
+
* attrNames?: StringIndexable;
|
|
432
|
+
* }
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
export const readItemsAll = async <T = any>(input: ReadItemsAllInput) => {
|
|
436
|
+
if (!dbClient.client) tryInit();
|
|
437
|
+
if (!dbClient.client) return null;
|
|
438
|
+
if (!input.table || !input.keys || !Array.isArray(input.keys)) return null;
|
|
439
|
+
if (!input.keys.length) return [];
|
|
440
|
+
|
|
441
|
+
let contents: StringIndexable<T>[] = [];
|
|
442
|
+
let errFlag = false;
|
|
443
|
+
|
|
444
|
+
const batchedKeys = chunkifyArray(input.keys, BATCH_SIZE);
|
|
445
|
+
const chunkedKeys = chunkifyArray(batchedKeys, CHUNK_SIZE);
|
|
446
|
+
|
|
447
|
+
for (let i = 0; i < chunkedKeys.length; i += 1) {
|
|
448
|
+
const bchunks = chunkedKeys[i];
|
|
449
|
+
|
|
450
|
+
const brlist = bchunks.map(ikeys => batchReadItems(input.table, ikeys, input.projection, input.attrNames));
|
|
451
|
+
const bslist = await Promise.all(brlist); // eslint-disable-line no-await-in-loop
|
|
452
|
+
|
|
453
|
+
const icontents = bslist.flat();
|
|
454
|
+
const isError = icontents.find(e => e === null) === null;
|
|
455
|
+
if (isError) {
|
|
456
|
+
errFlag = true;
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
if (!errFlag) {
|
|
460
|
+
contents = contents.concat(icontents as StringIndexable[]);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return contents;
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
export interface QueryItemsInput {
|
|
468
|
+
table: string;
|
|
469
|
+
indexName?: string;
|
|
470
|
+
cond: string;
|
|
471
|
+
attr: StringIndexable;
|
|
472
|
+
attrNames?: StringIndexable;
|
|
473
|
+
projection?: string;
|
|
474
|
+
desc?: boolean;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Query items from a DynamoDB table based on some condition
|
|
479
|
+
* @param input input command object
|
|
480
|
+
* @returns query results array, null in case of error
|
|
481
|
+
*
|
|
482
|
+
* ```js
|
|
483
|
+
* dbQueryItems({
|
|
484
|
+
* table: 'lesson_list',
|
|
485
|
+
* indexName: 'status-revision-index',
|
|
486
|
+
* cond: 'status = :comp AND #rev >= :rev',
|
|
487
|
+
* attr: { ':comp': 'completed', ':rev': 9 },
|
|
488
|
+
* attrNames: { '#rev': 'revision' },
|
|
489
|
+
* projection: 'id, lesson, status, revision',
|
|
490
|
+
* });
|
|
491
|
+
*
|
|
492
|
+
* interface QueryItemsInput {
|
|
493
|
+
* table: string;
|
|
494
|
+
* indexName?: string;
|
|
495
|
+
* cond: string;
|
|
496
|
+
* attr: StringIndexable;
|
|
497
|
+
* attrNames?: StringIndexable;
|
|
498
|
+
* projection?: string;
|
|
499
|
+
* desc?: boolean;
|
|
500
|
+
* }
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
export const queryItems = async (input: QueryItemsInput) => {
|
|
504
|
+
if (!dbClient.client) tryInit();
|
|
505
|
+
if (!dbClient.client) return null;
|
|
506
|
+
if (!input.table || !input.cond || !input.attr) return null;
|
|
507
|
+
|
|
508
|
+
let contents: StringIndexable[] = [];
|
|
509
|
+
const desc = input.desc || false;
|
|
510
|
+
|
|
511
|
+
// Ref: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html
|
|
194
512
|
// Ref: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html
|
|
195
513
|
// Ref: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/dynamodb-example-query-scan.html
|
|
196
514
|
// Ref: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SQLtoNoSQL.Indexes.QueryAndScan.html#SQLtoNoSQL.Indexes.QueryAndScan.DynamoDB
|
|
197
515
|
let cmdParams: QueryCommandInput = {
|
|
198
|
-
TableName: table,
|
|
199
|
-
KeyConditionExpression: cond,
|
|
200
|
-
ExpressionAttributeValues: attr,
|
|
516
|
+
TableName: input.table,
|
|
517
|
+
KeyConditionExpression: input.cond,
|
|
518
|
+
ExpressionAttributeValues: input.attr,
|
|
201
519
|
// FilterExpression: "contains (category_id, :cid)",
|
|
202
520
|
};
|
|
203
521
|
|
|
204
|
-
if (indexName) cmdParams = { ...cmdParams, IndexName: indexName };
|
|
205
|
-
if (
|
|
522
|
+
if (input.indexName) cmdParams = { ...cmdParams, IndexName: input.indexName };
|
|
523
|
+
if (input.attrNames) cmdParams = { ...cmdParams, ExpressionAttributeNames: input.attrNames };
|
|
524
|
+
if (input.projection) cmdParams = { ...cmdParams, ProjectionExpression: input.projection };
|
|
206
525
|
if (desc) cmdParams = { ...cmdParams, ScanIndexForward: false };
|
|
207
526
|
|
|
208
527
|
const command = new QueryCommand(cmdParams);
|
|
209
528
|
|
|
210
529
|
try {
|
|
211
|
-
const results = await dbClient.
|
|
530
|
+
const results = await dbClient.client.send(command);
|
|
212
531
|
const items = results.Items;
|
|
213
532
|
|
|
214
533
|
if (items) {
|
|
@@ -217,77 +536,187 @@ export const queryItems = async (
|
|
|
217
536
|
} catch (err) {
|
|
218
537
|
console.error('QueryCommandInput:', command.input);
|
|
219
538
|
console.error(err);
|
|
539
|
+
return null;
|
|
220
540
|
// throw err;
|
|
221
541
|
}
|
|
222
542
|
|
|
223
543
|
return contents;
|
|
224
544
|
};
|
|
225
545
|
|
|
226
|
-
export
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
546
|
+
export interface ScanItemsInput {
|
|
547
|
+
table: string;
|
|
548
|
+
projection?: string;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Scan all items in a DynamoDB table
|
|
553
|
+
* Note: avoid using this method in favour of `queryItems` method due to performance reasons
|
|
554
|
+
* @param input input command object
|
|
555
|
+
* @returns results of the scan query, null in case of error
|
|
556
|
+
*
|
|
557
|
+
* ```js
|
|
558
|
+
* scanItems({
|
|
559
|
+
* table: 'lesson_list',
|
|
560
|
+
* projection: 'id, status',
|
|
561
|
+
* });
|
|
562
|
+
*
|
|
563
|
+
* interface ScanItemsInput {
|
|
564
|
+
* table: string;
|
|
565
|
+
* projection?: string;
|
|
566
|
+
* }
|
|
567
|
+
* ```
|
|
568
|
+
*/
|
|
569
|
+
export const scanItems = async (input: ScanItemsInput) => {
|
|
570
|
+
if (!dbClient.client) tryInit();
|
|
571
|
+
if (!dbClient.client) return null;
|
|
572
|
+
if (!input.table) return null;
|
|
230
573
|
|
|
231
|
-
let contents: StringIndexable[]
|
|
574
|
+
let contents: StringIndexable[] = [];
|
|
232
575
|
|
|
233
576
|
// Ref: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/dynamodb-example-query-scan.html
|
|
234
577
|
let cmdParams: ScanCommandInput = {
|
|
235
|
-
TableName: table,
|
|
578
|
+
TableName: input.table,
|
|
236
579
|
};
|
|
237
580
|
|
|
238
|
-
if (projection) cmdParams = { ...cmdParams, ProjectionExpression: projection };
|
|
581
|
+
if (input.projection) cmdParams = { ...cmdParams, ProjectionExpression: input.projection };
|
|
239
582
|
|
|
240
583
|
const command = new ScanCommand(cmdParams);
|
|
241
584
|
|
|
242
585
|
try {
|
|
243
|
-
const results = await dbClient.
|
|
586
|
+
const results = await dbClient.client.send(command);
|
|
244
587
|
const items = results.Items;
|
|
245
588
|
|
|
589
|
+
// Ref: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.Pagination
|
|
590
|
+
if (results.LastEvaluatedKey) {
|
|
591
|
+
warn('[scanItems] Partial results obtained! Consider pagination.');
|
|
592
|
+
}
|
|
593
|
+
|
|
246
594
|
if (items) {
|
|
247
595
|
contents = items;
|
|
248
596
|
}
|
|
249
597
|
} catch (err) {
|
|
250
598
|
console.error('ScanCommandInput:', cmdParams);
|
|
251
599
|
console.error(err);
|
|
600
|
+
return null;
|
|
252
601
|
// throw err;
|
|
253
602
|
}
|
|
254
603
|
|
|
255
604
|
return contents;
|
|
256
605
|
};
|
|
257
606
|
|
|
258
|
-
export
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
607
|
+
export interface DeleteItemInput {
|
|
608
|
+
table: string;
|
|
609
|
+
key: StringIndexable;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Delete an item in a DynamoDB table
|
|
614
|
+
* @param input input command object
|
|
615
|
+
* @returns true if successful, null in case of error
|
|
616
|
+
*
|
|
617
|
+
* ```js
|
|
618
|
+
* deleteItem({
|
|
619
|
+
* table: 'lesson_list',
|
|
620
|
+
* key: { id: 'id_001' },
|
|
621
|
+
* });
|
|
622
|
+
*
|
|
623
|
+
* interface DeleteItemInput {
|
|
624
|
+
* table: string;
|
|
625
|
+
* key: StringIndexable;
|
|
626
|
+
* }
|
|
627
|
+
* ```
|
|
628
|
+
*/
|
|
629
|
+
export const deleteItem = async (input: DeleteItemInput) => {
|
|
630
|
+
if (!dbClient.client) tryInit();
|
|
631
|
+
if (!dbClient.client) return null;
|
|
632
|
+
if (!input.table || !input.key) return null;
|
|
262
633
|
|
|
263
|
-
const cmdParams: DeleteCommandInput = { TableName: table, Key: key };
|
|
634
|
+
const cmdParams: DeleteCommandInput = { TableName: input.table, Key: input.key };
|
|
264
635
|
const command = new DeleteCommand(cmdParams);
|
|
265
636
|
|
|
266
637
|
try {
|
|
267
|
-
await dbClient.
|
|
638
|
+
await dbClient.client.send(command);
|
|
268
639
|
} catch (err) {
|
|
269
640
|
console.error('DeleteCommandInput:', cmdParams);
|
|
270
641
|
console.error(err);
|
|
642
|
+
return null;
|
|
271
643
|
// throw err;
|
|
272
|
-
return false;
|
|
273
644
|
}
|
|
274
645
|
|
|
275
646
|
return true;
|
|
276
647
|
};
|
|
277
648
|
|
|
278
|
-
//
|
|
649
|
+
// Ref: https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/dynamodb-example-table-read-write-batch.html
|
|
650
|
+
// Ref: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/interfaces/batchwriteitemcommandinput.html#requestitems
|
|
651
|
+
const batchDeleteItems = async (table: string, keys: StringIndexable[]) => {
|
|
652
|
+
if (!dbClient.client) tryInit();
|
|
653
|
+
if (!dbClient.client) return null;
|
|
654
|
+
if (!table || !keys || !Array.isArray(keys)) return null;
|
|
655
|
+
if (!keys.length) return false;
|
|
656
|
+
|
|
657
|
+
const reqList = keys.map(key => ({ DeleteRequest: { Key: key } }));
|
|
658
|
+
const cmdParams: BatchWriteCommandInput = {
|
|
659
|
+
RequestItems: {
|
|
660
|
+
[table]: reqList,
|
|
661
|
+
},
|
|
662
|
+
};
|
|
279
663
|
|
|
280
|
-
|
|
664
|
+
const command = new BatchWriteCommand(cmdParams);
|
|
281
665
|
|
|
282
|
-
|
|
283
|
-
|
|
666
|
+
try {
|
|
667
|
+
await dbClient.client.send(command);
|
|
668
|
+
} catch (err) {
|
|
669
|
+
console.error('BatchWriteCommandInput:', cmdParams);
|
|
670
|
+
console.error(err);
|
|
671
|
+
return null;
|
|
672
|
+
// throw err;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return true;
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
export interface DeleteItemsAllInput {
|
|
679
|
+
table: string;
|
|
680
|
+
keys: StringIndexable[];
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Delete a list of items in a DynamoDB table
|
|
685
|
+
* @param input input command object
|
|
686
|
+
* @returns true if successful, null in case of error
|
|
687
|
+
*
|
|
688
|
+
* ```js
|
|
689
|
+
* deleteItemsAll({
|
|
690
|
+
* table: 'lesson_list',
|
|
691
|
+
* keys: [{ id: 'id_001' }, { id: 'id_002' }],
|
|
692
|
+
* });
|
|
693
|
+
*
|
|
694
|
+
* interface DeleteItemsAllInput {
|
|
695
|
+
* table: string;
|
|
696
|
+
* keys: StringIndexable[];
|
|
697
|
+
* }
|
|
698
|
+
* ```
|
|
699
|
+
*/
|
|
700
|
+
export const deleteItemsAll = async (input: DeleteItemsAllInput) => {
|
|
701
|
+
if (!dbClient.client) tryInit();
|
|
702
|
+
if (!dbClient.client) return null;
|
|
703
|
+
if (!input.table || !input.keys || !Array.isArray(input.keys)) return null;
|
|
704
|
+
if (!input.keys.length) return false;
|
|
705
|
+
|
|
706
|
+
let errFlag = false;
|
|
707
|
+
|
|
708
|
+
const batchedItems = chunkifyArray(input.keys, BATCH_SIZE);
|
|
709
|
+
const chunkedItems = chunkifyArray(batchedItems, CHUNK_SIZE);
|
|
710
|
+
|
|
711
|
+
for (let i = 0; i < chunkedItems.length; i += 1) {
|
|
712
|
+
const bchunks = chunkedItems[i];
|
|
284
713
|
|
|
285
|
-
|
|
286
|
-
//
|
|
287
|
-
// { id: 'id_001' },
|
|
288
|
-
// 'set #a = :a, #b = :b',
|
|
289
|
-
// { ':a': 'abhi@raj.me', ':b': 'Abhishek Raj' },
|
|
290
|
-
// { '#a': 'email', '#b': 'name' }
|
|
291
|
-
// );
|
|
714
|
+
const brlist = bchunks.map(ikeys => batchDeleteItems(input.table, ikeys));
|
|
715
|
+
const bslist = await Promise.all(brlist); // eslint-disable-line no-await-in-loop
|
|
292
716
|
|
|
293
|
-
|
|
717
|
+
const isSuccess = bslist.every(e => e === true);
|
|
718
|
+
if (!isSuccess) errFlag = true;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return errFlag ? null : true;
|
|
722
|
+
};
|