@certik/skynet 0.7.10 → 0.7.14

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/dynamodb.js CHANGED
@@ -1,456 +1,456 @@
1
- const { DynamoDB } = require("aws-sdk");
2
- const {
3
- getAWSAccessKeyId,
4
- getAWSSecretAccessKey,
5
- getAWSRegion
6
- } = require("./env");
7
- const { wait } = require("./availability");
8
-
9
- function getDynamoDB() {
10
- return new DynamoDB({
11
- accessKeyId: getAWSAccessKeyId(),
12
- secretAccessKey: getAWSSecretAccessKey(),
13
- region: getAWSRegion()
14
- });
15
- }
16
-
17
- function getDocClient() {
18
- return new DynamoDB.DocumentClient({
19
- accessKeyId: getAWSAccessKeyId(),
20
- secretAccessKey: getAWSSecretAccessKey(),
21
- region: getAWSRegion()
22
- });
23
- }
24
-
25
- function mergeQueries(q1, q2) {
26
- return {
27
- Items: q1.Items.concat(q2.Items),
28
- Count: q1.Count + q2.Count,
29
- ScannedCount: q1.ScannedCount + q2.ScannedCount
30
- };
31
- }
32
-
33
- async function scanWholeTable(options) {
34
- const dynamodb = getDocClient();
35
-
36
- let items = {
37
- Items: [],
38
- Count: 0,
39
- ScannedCount: 0
40
- };
41
-
42
- let data = await dynamodb.scan(options).promise();
43
-
44
- while (data.LastEvaluatedKey) {
45
- items = mergeQueries(items, data);
46
-
47
- data = await dynamodb
48
- .scan({
49
- ExclusiveStartKey: data.LastEvaluatedKey,
50
- ...options
51
- })
52
- .promise();
53
- }
54
-
55
- items = mergeQueries(items, data);
56
-
57
- return items;
58
- }
59
-
60
- function toSlices(items) {
61
- let slices = [];
62
-
63
- for (let start = 0; start < items.length; start += 25) {
64
- slices.push(items.slice(start, start + 25));
65
- }
66
-
67
- return slices;
68
- }
69
-
70
- async function batchCreateRecords(
71
- tableName,
72
- records,
73
- maxWritingCapacity,
74
- verbose = false
75
- ) {
76
- if (verbose) {
77
- console.log(`creating ${records.length} items in ${tableName}`);
78
- }
79
- const docClient = getDocClient();
80
-
81
- let remainingItems = records;
82
- // an arbitrary number to ensure while first loop is entered
83
- let prevRemainingCount = remainingItems.length + 1;
84
- let factor = 1;
85
- let rejection = undefined;
86
-
87
- while (remainingItems.length > 0 && factor <= 128 && !rejection) {
88
- if (prevRemainingCount === remainingItems.length) {
89
- await wait(5000 * factor);
90
- factor = factor * 2;
91
- }
92
-
93
- if (factor >= 32) {
94
- console.log(
95
- `WARNING: no progress for a long time for batchCreateRecords, please check`
96
- );
97
- }
98
-
99
- const slices = toSlices(remainingItems.slice(0, maxWritingCapacity));
100
- const results = await Promise.allSettled(
101
- slices.map(rs => {
102
- const params = {
103
- RequestItems: {
104
- [tableName]: rs.map(record => {
105
- return { PutRequest: { Item: record } };
106
- })
107
- }
108
- };
109
-
110
- return docClient.batchWrite(params).promise();
111
- })
112
- );
113
-
114
- prevRemainingCount = remainingItems.length;
115
-
116
- remainingItems = results.reduce((acc, rs, idx) => {
117
- if (rs.status === "rejected") {
118
- // whole slice fails, redo whole slice
119
- acc = acc.concat(slices[idx]);
120
- rejection = rs;
121
- } else if (Object.keys(rs.value.UnprocessedItems).length > 0) {
122
- // partially fails, redo unprocessedItems
123
- acc = acc.concat(
124
- rs.value.UnprocessedItems[tableName].map(it => it.PutRequest.Item)
125
- );
126
- }
127
-
128
- return acc;
129
- }, remainingItems.slice(maxWritingCapacity));
130
-
131
- if (verbose) {
132
- console.log(
133
- `processed=${prevRemainingCount - remainingItems.length}, remaining=${
134
- remainingItems.length
135
- }`
136
- );
137
- }
138
- }
139
-
140
- if (rejection) {
141
- console.log("batchCreateRecords rejected", rejection);
142
-
143
- throw new Error(
144
- `batchCreateRecords rejected, failed items=${remainingItems.length}`
145
- );
146
- }
147
-
148
- if (remainingItems.length > 0) {
149
- console.log(
150
- `failed batchCreateRecords, failed items=${remainingItems.length}`
151
- );
152
-
153
- throw new Error(
154
- `batchCreateRecords retry failed, failed items=${remainingItems.length}`
155
- );
156
- }
157
- }
158
-
159
- async function createRecord(tableName, fields, verbose = false) {
160
- if (verbose) {
161
- console.log("creating", tableName, fields);
162
- }
163
-
164
- const docClient = getDocClient();
165
- const params = {
166
- TableName: tableName,
167
- Item: fields
168
- };
169
-
170
- return docClient.put(params).promise();
171
- }
172
-
173
- async function getRecordsByKey(tableName, keys, indexName) {
174
- const docClient = getDocClient();
175
-
176
- const keyNames = Object.keys(keys);
177
- const conditionExpression = keyNames
178
- .map(key => `#${key} = :${key}`)
179
- .join(" and ");
180
- const expressionNames = keyNames.reduce((acc, key) => {
181
- acc[`#${key}`] = key;
182
- return acc;
183
- }, {});
184
- const expressionValues = keyNames.reduce((acc, key) => {
185
- acc[`:${key}`] = keys[key];
186
- return acc;
187
- }, {});
188
-
189
- const params = {
190
- TableName: tableName,
191
- KeyConditionExpression: conditionExpression,
192
- ExpressionAttributeNames: expressionNames,
193
- ExpressionAttributeValues: expressionValues
194
- };
195
-
196
- if (indexName) {
197
- params.IndexName = indexName;
198
- }
199
-
200
- let data;
201
-
202
- try {
203
- data = await docClient.query(params).promise();
204
- return data.Items;
205
- } catch (err) {
206
- if (err.statusCode && err.statusCode === 400) {
207
- return null;
208
- }
209
-
210
- throw err;
211
- }
212
- }
213
-
214
- async function getRecordByKey(tableName, keys, indexName) {
215
- const records = await getRecordsByKey(tableName, keys, indexName);
216
-
217
- if (records) {
218
- return records[0];
219
- } else {
220
- return null;
221
- }
222
- }
223
-
224
- async function updateRecordByKey(
225
- tableName,
226
- idKey,
227
- fields,
228
- conditionExpressions = null,
229
- verbose = false
230
- ) {
231
- if (verbose) {
232
- console.log("update", tableName, idKey, fields);
233
- }
234
-
235
- const idKeyNames = Object.keys(idKey);
236
- const keys = Object.keys(fields).filter(k => !idKeyNames.includes(k));
237
- const updateExpressions = keys.map(key => `#${key} = :${key}`);
238
- const expressionNames = keys.reduce((acc, key) => {
239
- acc[`#${key}`] = key;
240
- return acc;
241
- }, {});
242
- const expressionValues = keys.reduce((acc, key) => {
243
- acc[`:${key}`] = fields[key];
244
- return acc;
245
- }, {});
246
- const docClient = getDocClient();
247
- const params = {
248
- TableName: tableName,
249
- Key: idKey,
250
- ExpressionAttributeNames: expressionNames,
251
- ExpressionAttributeValues: expressionValues,
252
- UpdateExpression: `SET ${updateExpressions.join(", ")}`,
253
- ReturnValues: "ALL_NEW"
254
- };
255
-
256
- if (conditionExpressions) {
257
- params.ConditionExpression = conditionExpressions;
258
- }
259
-
260
- const data = await docClient.update(params).promise();
261
-
262
- return data.Attributes;
263
- }
264
-
265
- async function createTableIfNotExist(
266
- tableName,
267
- attributeDefinitions = [
268
- {
269
- AttributeName: "id",
270
- AttributeType: "S"
271
- }
272
- ],
273
- keySchema = [
274
- {
275
- AttributeName: "id",
276
- KeyType: "HASH"
277
- }
278
- ],
279
- otherOptions = {},
280
- verbose = false
281
- ) {
282
- const dynamodb = getDynamoDB();
283
-
284
- try {
285
- const table = await dynamodb
286
- .describeTable({ TableName: tableName })
287
- .promise();
288
-
289
- if (verbose) {
290
- console.log(`table ${tableName} already exist`, table);
291
- }
292
- return table;
293
- } catch (_tableDoesNotExistErr) {
294
- const createRequest = await dynamodb
295
- .createTable({
296
- TableName: tableName,
297
- AttributeDefinitions: attributeDefinitions,
298
- KeySchema: keySchema,
299
- ProvisionedThroughput: {
300
- ReadCapacityUnits: 5,
301
- WriteCapacityUnits: 5
302
- },
303
- ...otherOptions
304
- })
305
- .promise();
306
-
307
- if (verbose) {
308
- console.log(`table ${tableName} creating`);
309
- }
310
-
311
- let isCreated = false;
312
-
313
- while (!isCreated) {
314
- await wait(5000);
315
-
316
- const t = await dynamodb
317
- .describeTable({ TableName: tableName })
318
- .promise();
319
-
320
- console.log("waiting for table to become active");
321
-
322
- isCreated = t.Table.TableStatus === "ACTIVE";
323
- }
324
-
325
- return createRequest;
326
- }
327
- }
328
-
329
- async function batchDeleteRecords(tableName, keys) {
330
- const docClient = getDocClient();
331
-
332
- for (let start = 0; start < keys.length; start += 25) {
333
- const slice = keys.slice(start, start + 25);
334
-
335
- await docClient
336
- .batchWrite({
337
- RequestItems: {
338
- [tableName]: slice.map(key => {
339
- return { DeleteRequest: { Key: key } };
340
- })
341
- }
342
- })
343
- .promise();
344
- }
345
- }
346
-
347
- function getKeyName(keySchema, type) {
348
- const key = keySchema.find(k => k.KeyType === type);
349
-
350
- return key && key.AttributeName;
351
- }
352
-
353
- function getIndexKeyName(globalSecondaryIndexes, indexName, type) {
354
- const idx = globalSecondaryIndexes.find(i => i.IndexName === indexName);
355
-
356
- return idx && getKeyName(idx.KeySchema, type);
357
- }
358
-
359
- async function deleteRecordsByHashKey(
360
- tableName,
361
- indexName,
362
- hashKeyValue,
363
- verbose = false
364
- ) {
365
- const docClient = getDocClient();
366
-
367
- const meta = await getDynamoDB()
368
- .describeTable({ TableName: tableName })
369
- .promise();
370
-
371
- const hashKeyName = indexName
372
- ? getIndexKeyName(meta.Table.GlobalSecondaryIndexes, indexName, "HASH")
373
- : getKeyName(meta.Table.KeySchema, "HASH");
374
-
375
- if (!hashKeyName) {
376
- throw new Error(`cannot find corresponding index`, indexName);
377
- }
378
-
379
- const mainHashKeyName = getKeyName(meta.Table.KeySchema, "HASH");
380
- const mainRangeKeyName = getKeyName(meta.Table.KeySchema, "RANGE");
381
-
382
- let totalDeleted = 0;
383
-
384
- let params = {
385
- TableName: tableName,
386
- KeyConditionExpression: "#hashKeyName = :hashKeyValue",
387
- ExpressionAttributeNames: { "#hashKeyName": hashKeyName },
388
- ExpressionAttributeValues: { ":hashKeyValue": hashKeyValue }
389
- };
390
-
391
- if (indexName) {
392
- params.IndexName = indexName;
393
- }
394
-
395
- let data = await docClient.query(params).promise();
396
-
397
- await batchDeleteRecords(
398
- tableName,
399
- data.Items.map(item =>
400
- mainRangeKeyName
401
- ? {
402
- [mainHashKeyName]: item[mainHashKeyName],
403
- [mainRangeKeyName]: item[mainRangeKeyName]
404
- }
405
- : {
406
- [mainHashKeyName]: item[mainHashKeyName]
407
- }
408
- )
409
- );
410
-
411
- totalDeleted += data.Items.length;
412
-
413
- while (data.LastEvaluatedKey) {
414
- data = await docClient
415
- .query({
416
- ...params,
417
- ExclusiveStartKey: data.LastEvaluatedKey
418
- })
419
- .promise();
420
-
421
- await batchDeleteRecords(
422
- tableName,
423
- data.Items.map(item =>
424
- mainRangeKeyName
425
- ? {
426
- [mainHashKeyName]: item[mainHashKeyName],
427
- [mainRangeKeyName]: item[mainRangeKeyName]
428
- }
429
- : {
430
- [mainHashKeyName]: item[mainHashKeyName]
431
- }
432
- )
433
- );
434
-
435
- totalDeleted += data.Items.length;
436
- }
437
-
438
- if (verbose) {
439
- console.log(`successfully delete ${totalDeleted} items`);
440
- }
441
-
442
- return totalDeleted;
443
- }
444
-
445
- module.exports = {
446
- getDocClient,
447
- scanWholeTable,
448
- batchCreateRecords,
449
- createRecord,
450
- getRecordsByKey,
451
- getRecordByKey,
452
- updateRecordByKey,
453
- batchDeleteRecords,
454
- deleteRecordsByHashKey,
455
- createTableIfNotExist
456
- };
1
+ const { DynamoDB } = require("aws-sdk");
2
+ const {
3
+ getAWSAccessKeyId,
4
+ getAWSSecretAccessKey,
5
+ getAWSRegion
6
+ } = require("./env");
7
+ const { wait } = require("./availability");
8
+
9
+ function getDynamoDB() {
10
+ return new DynamoDB({
11
+ accessKeyId: getAWSAccessKeyId(),
12
+ secretAccessKey: getAWSSecretAccessKey(),
13
+ region: getAWSRegion()
14
+ });
15
+ }
16
+
17
+ function getDocClient() {
18
+ return new DynamoDB.DocumentClient({
19
+ accessKeyId: getAWSAccessKeyId(),
20
+ secretAccessKey: getAWSSecretAccessKey(),
21
+ region: getAWSRegion()
22
+ });
23
+ }
24
+
25
+ function mergeQueries(q1, q2) {
26
+ return {
27
+ Items: q1.Items.concat(q2.Items),
28
+ Count: q1.Count + q2.Count,
29
+ ScannedCount: q1.ScannedCount + q2.ScannedCount
30
+ };
31
+ }
32
+
33
+ async function scanWholeTable(options) {
34
+ const dynamodb = getDocClient();
35
+
36
+ let items = {
37
+ Items: [],
38
+ Count: 0,
39
+ ScannedCount: 0
40
+ };
41
+
42
+ let data = await dynamodb.scan(options).promise();
43
+
44
+ while (data.LastEvaluatedKey) {
45
+ items = mergeQueries(items, data);
46
+
47
+ data = await dynamodb
48
+ .scan({
49
+ ExclusiveStartKey: data.LastEvaluatedKey,
50
+ ...options
51
+ })
52
+ .promise();
53
+ }
54
+
55
+ items = mergeQueries(items, data);
56
+
57
+ return items;
58
+ }
59
+
60
+ function toSlices(items) {
61
+ let slices = [];
62
+
63
+ for (let start = 0; start < items.length; start += 25) {
64
+ slices.push(items.slice(start, start + 25));
65
+ }
66
+
67
+ return slices;
68
+ }
69
+
70
+ async function batchCreateRecords(
71
+ tableName,
72
+ records,
73
+ maxWritingCapacity,
74
+ verbose = false
75
+ ) {
76
+ if (verbose) {
77
+ console.log(`creating ${records.length} items in ${tableName}`);
78
+ }
79
+ const docClient = getDocClient();
80
+
81
+ let remainingItems = records;
82
+ // an arbitrary number to ensure while first loop is entered
83
+ let prevRemainingCount = remainingItems.length + 1;
84
+ let factor = 1;
85
+ let rejection = undefined;
86
+
87
+ while (remainingItems.length > 0 && factor <= 128 && !rejection) {
88
+ if (prevRemainingCount === remainingItems.length) {
89
+ await wait(5000 * factor);
90
+ factor = factor * 2;
91
+ }
92
+
93
+ if (factor >= 32) {
94
+ console.log(
95
+ `WARNING: no progress for a long time for batchCreateRecords, please check`
96
+ );
97
+ }
98
+
99
+ const slices = toSlices(remainingItems.slice(0, maxWritingCapacity));
100
+ const results = await Promise.allSettled(
101
+ slices.map(rs => {
102
+ const params = {
103
+ RequestItems: {
104
+ [tableName]: rs.map(record => {
105
+ return { PutRequest: { Item: record } };
106
+ })
107
+ }
108
+ };
109
+
110
+ return docClient.batchWrite(params).promise();
111
+ })
112
+ );
113
+
114
+ prevRemainingCount = remainingItems.length;
115
+
116
+ remainingItems = results.reduce((acc, rs, idx) => {
117
+ if (rs.status === "rejected") {
118
+ // whole slice fails, redo whole slice
119
+ acc = acc.concat(slices[idx]);
120
+ rejection = rs;
121
+ } else if (Object.keys(rs.value.UnprocessedItems).length > 0) {
122
+ // partially fails, redo unprocessedItems
123
+ acc = acc.concat(
124
+ rs.value.UnprocessedItems[tableName].map(it => it.PutRequest.Item)
125
+ );
126
+ }
127
+
128
+ return acc;
129
+ }, remainingItems.slice(maxWritingCapacity));
130
+
131
+ if (verbose) {
132
+ console.log(
133
+ `processed=${prevRemainingCount - remainingItems.length}, remaining=${
134
+ remainingItems.length
135
+ }`
136
+ );
137
+ }
138
+ }
139
+
140
+ if (rejection) {
141
+ console.log("batchCreateRecords rejected", rejection);
142
+
143
+ throw new Error(
144
+ `batchCreateRecords rejected, failed items=${remainingItems.length}`
145
+ );
146
+ }
147
+
148
+ if (remainingItems.length > 0) {
149
+ console.log(
150
+ `failed batchCreateRecords, failed items=${remainingItems.length}`
151
+ );
152
+
153
+ throw new Error(
154
+ `batchCreateRecords retry failed, failed items=${remainingItems.length}`
155
+ );
156
+ }
157
+ }
158
+
159
+ async function createRecord(tableName, fields, verbose = false) {
160
+ if (verbose) {
161
+ console.log("creating", tableName, fields);
162
+ }
163
+
164
+ const docClient = getDocClient();
165
+ const params = {
166
+ TableName: tableName,
167
+ Item: fields
168
+ };
169
+
170
+ return docClient.put(params).promise();
171
+ }
172
+
173
+ async function getRecordsByKey(tableName, keys, indexName) {
174
+ const docClient = getDocClient();
175
+
176
+ const keyNames = Object.keys(keys);
177
+ const conditionExpression = keyNames
178
+ .map(key => `#${key} = :${key}`)
179
+ .join(" and ");
180
+ const expressionNames = keyNames.reduce((acc, key) => {
181
+ acc[`#${key}`] = key;
182
+ return acc;
183
+ }, {});
184
+ const expressionValues = keyNames.reduce((acc, key) => {
185
+ acc[`:${key}`] = keys[key];
186
+ return acc;
187
+ }, {});
188
+
189
+ const params = {
190
+ TableName: tableName,
191
+ KeyConditionExpression: conditionExpression,
192
+ ExpressionAttributeNames: expressionNames,
193
+ ExpressionAttributeValues: expressionValues
194
+ };
195
+
196
+ if (indexName) {
197
+ params.IndexName = indexName;
198
+ }
199
+
200
+ let data;
201
+
202
+ try {
203
+ data = await docClient.query(params).promise();
204
+ return data.Items;
205
+ } catch (err) {
206
+ if (err.statusCode && err.statusCode === 400) {
207
+ return null;
208
+ }
209
+
210
+ throw err;
211
+ }
212
+ }
213
+
214
+ async function getRecordByKey(tableName, keys, indexName) {
215
+ const records = await getRecordsByKey(tableName, keys, indexName);
216
+
217
+ if (records) {
218
+ return records[0];
219
+ } else {
220
+ return null;
221
+ }
222
+ }
223
+
224
+ async function updateRecordByKey(
225
+ tableName,
226
+ idKey,
227
+ fields,
228
+ conditionExpressions = null,
229
+ verbose = false
230
+ ) {
231
+ if (verbose) {
232
+ console.log("update", tableName, idKey, fields);
233
+ }
234
+
235
+ const idKeyNames = Object.keys(idKey);
236
+ const keys = Object.keys(fields).filter(k => !idKeyNames.includes(k));
237
+ const updateExpressions = keys.map(key => `#${key} = :${key}`);
238
+ const expressionNames = keys.reduce((acc, key) => {
239
+ acc[`#${key}`] = key;
240
+ return acc;
241
+ }, {});
242
+ const expressionValues = keys.reduce((acc, key) => {
243
+ acc[`:${key}`] = fields[key];
244
+ return acc;
245
+ }, {});
246
+ const docClient = getDocClient();
247
+ const params = {
248
+ TableName: tableName,
249
+ Key: idKey,
250
+ ExpressionAttributeNames: expressionNames,
251
+ ExpressionAttributeValues: expressionValues,
252
+ UpdateExpression: `SET ${updateExpressions.join(", ")}`,
253
+ ReturnValues: "ALL_NEW"
254
+ };
255
+
256
+ if (conditionExpressions) {
257
+ params.ConditionExpression = conditionExpressions;
258
+ }
259
+
260
+ const data = await docClient.update(params).promise();
261
+
262
+ return data.Attributes;
263
+ }
264
+
265
+ async function createTableIfNotExist(
266
+ tableName,
267
+ attributeDefinitions = [
268
+ {
269
+ AttributeName: "id",
270
+ AttributeType: "S"
271
+ }
272
+ ],
273
+ keySchema = [
274
+ {
275
+ AttributeName: "id",
276
+ KeyType: "HASH"
277
+ }
278
+ ],
279
+ otherOptions = {},
280
+ verbose = false
281
+ ) {
282
+ const dynamodb = getDynamoDB();
283
+
284
+ try {
285
+ const table = await dynamodb
286
+ .describeTable({ TableName: tableName })
287
+ .promise();
288
+
289
+ if (verbose) {
290
+ console.log(`table ${tableName} already exist`, table);
291
+ }
292
+ return table;
293
+ } catch (_tableDoesNotExistErr) {
294
+ const createRequest = await dynamodb
295
+ .createTable({
296
+ TableName: tableName,
297
+ AttributeDefinitions: attributeDefinitions,
298
+ KeySchema: keySchema,
299
+ ProvisionedThroughput: {
300
+ ReadCapacityUnits: 5,
301
+ WriteCapacityUnits: 5
302
+ },
303
+ ...otherOptions
304
+ })
305
+ .promise();
306
+
307
+ if (verbose) {
308
+ console.log(`table ${tableName} creating`);
309
+ }
310
+
311
+ let isCreated = false;
312
+
313
+ while (!isCreated) {
314
+ await wait(5000);
315
+
316
+ const t = await dynamodb
317
+ .describeTable({ TableName: tableName })
318
+ .promise();
319
+
320
+ console.log("waiting for table to become active");
321
+
322
+ isCreated = t.Table.TableStatus === "ACTIVE";
323
+ }
324
+
325
+ return createRequest;
326
+ }
327
+ }
328
+
329
+ async function batchDeleteRecords(tableName, keys) {
330
+ const docClient = getDocClient();
331
+
332
+ for (let start = 0; start < keys.length; start += 25) {
333
+ const slice = keys.slice(start, start + 25);
334
+
335
+ await docClient
336
+ .batchWrite({
337
+ RequestItems: {
338
+ [tableName]: slice.map(key => {
339
+ return { DeleteRequest: { Key: key } };
340
+ })
341
+ }
342
+ })
343
+ .promise();
344
+ }
345
+ }
346
+
347
+ function getKeyName(keySchema, type) {
348
+ const key = keySchema.find(k => k.KeyType === type);
349
+
350
+ return key && key.AttributeName;
351
+ }
352
+
353
+ function getIndexKeyName(globalSecondaryIndexes, indexName, type) {
354
+ const idx = globalSecondaryIndexes.find(i => i.IndexName === indexName);
355
+
356
+ return idx && getKeyName(idx.KeySchema, type);
357
+ }
358
+
359
+ async function deleteRecordsByHashKey(
360
+ tableName,
361
+ indexName,
362
+ hashKeyValue,
363
+ verbose = false
364
+ ) {
365
+ const docClient = getDocClient();
366
+
367
+ const meta = await getDynamoDB()
368
+ .describeTable({ TableName: tableName })
369
+ .promise();
370
+
371
+ const hashKeyName = indexName
372
+ ? getIndexKeyName(meta.Table.GlobalSecondaryIndexes, indexName, "HASH")
373
+ : getKeyName(meta.Table.KeySchema, "HASH");
374
+
375
+ if (!hashKeyName) {
376
+ throw new Error(`cannot find corresponding index`, indexName);
377
+ }
378
+
379
+ const mainHashKeyName = getKeyName(meta.Table.KeySchema, "HASH");
380
+ const mainRangeKeyName = getKeyName(meta.Table.KeySchema, "RANGE");
381
+
382
+ let totalDeleted = 0;
383
+
384
+ let params = {
385
+ TableName: tableName,
386
+ KeyConditionExpression: "#hashKeyName = :hashKeyValue",
387
+ ExpressionAttributeNames: { "#hashKeyName": hashKeyName },
388
+ ExpressionAttributeValues: { ":hashKeyValue": hashKeyValue }
389
+ };
390
+
391
+ if (indexName) {
392
+ params.IndexName = indexName;
393
+ }
394
+
395
+ let data = await docClient.query(params).promise();
396
+
397
+ await batchDeleteRecords(
398
+ tableName,
399
+ data.Items.map(item =>
400
+ mainRangeKeyName
401
+ ? {
402
+ [mainHashKeyName]: item[mainHashKeyName],
403
+ [mainRangeKeyName]: item[mainRangeKeyName]
404
+ }
405
+ : {
406
+ [mainHashKeyName]: item[mainHashKeyName]
407
+ }
408
+ )
409
+ );
410
+
411
+ totalDeleted += data.Items.length;
412
+
413
+ while (data.LastEvaluatedKey) {
414
+ data = await docClient
415
+ .query({
416
+ ...params,
417
+ ExclusiveStartKey: data.LastEvaluatedKey
418
+ })
419
+ .promise();
420
+
421
+ await batchDeleteRecords(
422
+ tableName,
423
+ data.Items.map(item =>
424
+ mainRangeKeyName
425
+ ? {
426
+ [mainHashKeyName]: item[mainHashKeyName],
427
+ [mainRangeKeyName]: item[mainRangeKeyName]
428
+ }
429
+ : {
430
+ [mainHashKeyName]: item[mainHashKeyName]
431
+ }
432
+ )
433
+ );
434
+
435
+ totalDeleted += data.Items.length;
436
+ }
437
+
438
+ if (verbose) {
439
+ console.log(`successfully delete ${totalDeleted} items`);
440
+ }
441
+
442
+ return totalDeleted;
443
+ }
444
+
445
+ module.exports = {
446
+ getDocClient,
447
+ scanWholeTable,
448
+ batchCreateRecords,
449
+ createRecord,
450
+ getRecordsByKey,
451
+ getRecordByKey,
452
+ updateRecordByKey,
453
+ batchDeleteRecords,
454
+ deleteRecordsByHashKey,
455
+ createTableIfNotExist
456
+ };