@certik/skynet 0.22.1 → 0.22.2

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.
Files changed (102) hide show
  1. package/.vscode/settings.json +5 -0
  2. package/CHANGELOG.md +4 -0
  3. package/build.ts +23 -0
  4. package/bun.lockb +0 -0
  5. package/dist/abi.d.ts +1 -2
  6. package/dist/abi.js +569 -563
  7. package/dist/address.d.ts +0 -1
  8. package/dist/address.js +22 -21
  9. package/dist/api.d.ts +0 -1
  10. package/dist/api.js +235 -120
  11. package/dist/app.d.ts +1 -2
  12. package/dist/app.js +2030 -276
  13. package/dist/availability.d.ts +0 -1
  14. package/dist/availability.js +126 -56
  15. package/dist/cli.d.ts +0 -1
  16. package/dist/cli.js +28 -24
  17. package/dist/const.d.ts +0 -1
  18. package/dist/const.js +153 -132
  19. package/dist/databricks.d.ts +0 -1
  20. package/dist/databricks.js +198 -58
  21. package/dist/date.d.ts +0 -1
  22. package/dist/date.js +48 -21
  23. package/dist/deploy.d.ts +0 -1
  24. package/dist/deploy.js +427 -292
  25. package/dist/dynamodb.d.ts +3 -4
  26. package/dist/dynamodb.js +432 -281
  27. package/dist/env.d.ts +2 -3
  28. package/dist/env.js +16 -9
  29. package/dist/graphql.d.ts +0 -1
  30. package/dist/graphql.js +26 -23
  31. package/dist/indexer.d.ts +0 -1
  32. package/dist/indexer.js +1050 -441
  33. package/dist/log.d.ts +0 -1
  34. package/dist/log.js +53 -52
  35. package/dist/object-hash.d.ts +0 -1
  36. package/dist/object-hash.js +49 -59
  37. package/dist/opsgenie.d.ts +1 -1
  38. package/dist/opsgenie.js +31 -30
  39. package/dist/por.d.ts +0 -1
  40. package/dist/por.js +113 -123
  41. package/dist/s3.d.ts +7 -8
  42. package/dist/s3.js +103 -91
  43. package/dist/search.d.ts +0 -1
  44. package/dist/search.js +100 -25
  45. package/dist/selector.d.ts +0 -1
  46. package/dist/selector.js +34 -38
  47. package/dist/slack.d.ts +0 -1
  48. package/dist/slack.js +27 -21
  49. package/dist/util.d.ts +0 -1
  50. package/dist/util.js +21 -20
  51. package/examples/api.ts +1 -1
  52. package/examples/indexer.ts +1 -1
  53. package/examples/mode-indexer.ts +1 -1
  54. package/package.json +5 -4
  55. package/{graphql.ts → src/graphql.ts} +1 -1
  56. package/{opsgenie.ts → src/opsgenie.ts} +2 -1
  57. package/tsconfig.build.json +2 -5
  58. package/tsconfig.json +11 -20
  59. package/dist/abi.d.ts.map +0 -1
  60. package/dist/address.d.ts.map +0 -1
  61. package/dist/api.d.ts.map +0 -1
  62. package/dist/app.d.ts.map +0 -1
  63. package/dist/availability.d.ts.map +0 -1
  64. package/dist/cli.d.ts.map +0 -1
  65. package/dist/const.d.ts.map +0 -1
  66. package/dist/databricks.d.ts.map +0 -1
  67. package/dist/date.d.ts.map +0 -1
  68. package/dist/deploy.d.ts.map +0 -1
  69. package/dist/dynamodb.d.ts.map +0 -1
  70. package/dist/env.d.ts.map +0 -1
  71. package/dist/graphql.d.ts.map +0 -1
  72. package/dist/indexer.d.ts.map +0 -1
  73. package/dist/log.d.ts.map +0 -1
  74. package/dist/object-hash.d.ts.map +0 -1
  75. package/dist/opsgenie.d.ts.map +0 -1
  76. package/dist/por.d.ts.map +0 -1
  77. package/dist/s3.d.ts.map +0 -1
  78. package/dist/search.d.ts.map +0 -1
  79. package/dist/selector.d.ts.map +0 -1
  80. package/dist/slack.d.ts.map +0 -1
  81. package/dist/util.d.ts.map +0 -1
  82. /package/{abi.ts → src/abi.ts} +0 -0
  83. /package/{address.ts → src/address.ts} +0 -0
  84. /package/{api.ts → src/api.ts} +0 -0
  85. /package/{app.ts → src/app.ts} +0 -0
  86. /package/{availability.ts → src/availability.ts} +0 -0
  87. /package/{cli.ts → src/cli.ts} +0 -0
  88. /package/{const.ts → src/const.ts} +0 -0
  89. /package/{databricks.ts → src/databricks.ts} +0 -0
  90. /package/{date.ts → src/date.ts} +0 -0
  91. /package/{deploy.ts → src/deploy.ts} +0 -0
  92. /package/{dynamodb.ts → src/dynamodb.ts} +0 -0
  93. /package/{env.ts → src/env.ts} +0 -0
  94. /package/{indexer.ts → src/indexer.ts} +0 -0
  95. /package/{log.ts → src/log.ts} +0 -0
  96. /package/{object-hash.ts → src/object-hash.ts} +0 -0
  97. /package/{por.ts → src/por.ts} +0 -0
  98. /package/{s3.ts → src/s3.ts} +0 -0
  99. /package/{search.ts → src/search.ts} +0 -0
  100. /package/{selector.ts → src/selector.ts} +0 -0
  101. /package/{slack.ts → src/slack.ts} +0 -0
  102. /package/{util.ts → src/util.ts} +0 -0
package/dist/dynamodb.js CHANGED
@@ -1,328 +1,479 @@
1
- import { DynamoDBDocumentClient, ScanCommand, BatchWriteCommand, GetCommand, PutCommand, QueryCommand, UpdateCommand, } from "@aws-sdk/lib-dynamodb";
1
+ // src/object-hash.ts
2
+ import xh from "@node-rs/xxhash";
3
+ function getHash(obj) {
4
+ const xxh3 = xh.xxh3.Xxh3.withSeed();
5
+ hash(obj, xxh3);
6
+ return xxh3.digest().toString(16);
7
+ }
8
+ function hash(obj, xxh3) {
9
+ if (obj === null) {
10
+ xxh3.update("null");
11
+ } else if (obj === undefined) {
12
+ xxh3.update("undefined");
13
+ } else if (typeof obj === "string") {
14
+ xxh3.update(obj);
15
+ } else if (typeof obj === "number") {
16
+ xxh3.update(obj.toString());
17
+ } else if (typeof obj === "boolean") {
18
+ xxh3.update(obj.toString());
19
+ } else if (typeof obj === "bigint") {
20
+ xxh3.update(obj.toString());
21
+ } else if (obj instanceof Date) {
22
+ xxh3.update(obj.toISOString());
23
+ } else if (Array.isArray(obj)) {
24
+ arrayHash(obj, xxh3);
25
+ } else if (obj instanceof Set) {
26
+ setHash(obj, xxh3);
27
+ } else if (obj instanceof Map) {
28
+ mapHash(obj, xxh3);
29
+ } else if (typeof obj === "object") {
30
+ objectHash(obj, xxh3);
31
+ } else {
32
+ throw new Error(`Unsupported type: ${obj}`);
33
+ }
34
+ }
35
+ function arrayHash(array, xxh3) {
36
+ xxh3.update("[");
37
+ for (const obj of array) {
38
+ hash(obj, xxh3);
39
+ }
40
+ xxh3.update("]");
41
+ }
42
+ function setHash(_set, _xxh3) {
43
+ throw new Error("Set hashing not implemented");
44
+ }
45
+ function mapHash(map, xxh3) {
46
+ const array = Array.from(map.entries()).sort(([aKey], [bKey]) => aKey.localeCompare(bKey));
47
+ for (const [key, value] of array) {
48
+ hash(key, xxh3);
49
+ hash(value, xxh3);
50
+ }
51
+ }
52
+ function objectHash(obj, xxh3) {
53
+ const array = Object.entries(obj).sort(([aKey], [bKey]) => aKey.localeCompare(bKey));
54
+ for (const [key, value] of array) {
55
+ hash(key, xxh3);
56
+ hash(value, xxh3);
57
+ }
58
+ }
59
+
60
+ // src/availability.ts
61
+ import pThrottle from "p-throttle";
62
+ import pMemoize from "p-memoize";
63
+ import QuickLRU from "quick-lru";
64
+ async function wait(time) {
65
+ return new Promise((resolve) => {
66
+ setTimeout(resolve, time);
67
+ });
68
+ }
69
+ async function exponentialRetry(func, {
70
+ maxRetry,
71
+ initialDuration,
72
+ growFactor,
73
+ test,
74
+ verbose
75
+ }) {
76
+ let retries = maxRetry;
77
+ let duration = initialDuration || 5000;
78
+ const growFactorFinal = growFactor || 2;
79
+ let result = await func();
80
+ while (!test(result) && retries > 0) {
81
+ if (verbose) {
82
+ console.log("failed attempt result", result);
83
+ console.log(`sleep for ${duration}ms after failed attempt, remaining ${retries} attempts`);
84
+ }
85
+ retries = retries - 1;
86
+ await wait(duration);
87
+ result = await func();
88
+ duration = duration * growFactorFinal;
89
+ }
90
+ if (verbose) {
91
+ console.log(`function to retry ends with status ${test(result)}, number of retries done: ${maxRetry - retries}}`);
92
+ }
93
+ return result;
94
+ }
95
+ function withRetry(func, options) {
96
+ let retries = options?.maxRetry || 3;
97
+ let duration = options?.initialDuration || 500;
98
+ const growFactorFinal = options?.growFactor || 2;
99
+ return async (...args) => {
100
+ do {
101
+ try {
102
+ return await func(...args);
103
+ } catch (error) {
104
+ retries = retries - 1;
105
+ if (retries <= 0) {
106
+ throw error;
107
+ }
108
+ await wait(duration);
109
+ duration = duration * growFactorFinal;
110
+ }
111
+ } while (retries > 0);
112
+ throw new Error("unreachable");
113
+ };
114
+ }
115
+ function memoize(func, options) {
116
+ if (!options) {
117
+ options = {};
118
+ }
119
+ if (!options.cache) {
120
+ options.cache = new QuickLRU({ maxSize: options.lruMaxSize || 1e4 });
121
+ }
122
+ if (!options.cacheKey) {
123
+ options.cacheKey = (args) => getHash(args);
124
+ }
125
+ return pMemoize(func, options);
126
+ }
127
+ // src/util.ts
128
+ function range(startAt, endAt, step) {
129
+ const arr = [];
130
+ for (let i = startAt;i <= endAt; i += step) {
131
+ arr.push([i, Math.min(endAt, i + step - 1)]);
132
+ }
133
+ return arr;
134
+ }
135
+ function arrayGroup(array, groupSize) {
136
+ const groups = [];
137
+ for (let i = 0;i < array.length; i += groupSize) {
138
+ groups.push(array.slice(i, i + groupSize));
139
+ }
140
+ return groups;
141
+ }
142
+ function fillRange(start, end) {
143
+ const result = [];
144
+ for (let i = start;i <= end; i++) {
145
+ result.push(i);
146
+ }
147
+ return result;
148
+ }
149
+ // src/dynamodb.ts
150
+ import {
151
+ DynamoDBDocumentClient,
152
+ ScanCommand,
153
+ BatchWriteCommand,
154
+ GetCommand,
155
+ PutCommand,
156
+ QueryCommand,
157
+ UpdateCommand
158
+ } from "@aws-sdk/lib-dynamodb";
2
159
  import { DynamoDBClient, DescribeTableCommand } from "@aws-sdk/client-dynamodb";
3
- import { wait } from "./availability.js";
4
- import { arrayGroup } from "./util.js";
5
- let _dynamoDB;
6
- let _docClient;
160
+ var _dynamoDB;
161
+ var _docClient;
7
162
  function getDynamoDB(forceNew = false) {
8
- if (!_dynamoDB || forceNew) {
9
- _dynamoDB = new DynamoDBClient();
10
- }
11
- return _dynamoDB;
163
+ if (!_dynamoDB || forceNew) {
164
+ _dynamoDB = new DynamoDBClient;
165
+ }
166
+ return _dynamoDB;
12
167
  }
13
168
  function getDocClient(forceNew = false) {
14
- const marshallOptions = {
15
- // Whether to automatically convert empty strings, blobs, and sets to `null`.
16
- convertEmptyValues: true,
17
- // Whether to remove undefined values while marshalling.
18
- removeUndefinedValues: true,
19
- // Whether to convert typeof object to map attribute.
20
- convertClassInstanceToMap: true,
21
- };
22
- const unmarshallOptions = {
23
- // Whether to return numbers as a string instead of converting them to native JavaScript numbers.
24
- wrapNumbers: false, // false, by default.
25
- };
26
- if (!_docClient || forceNew) {
27
- _docClient = DynamoDBDocumentClient.from(getDynamoDB(), {
28
- marshallOptions,
29
- unmarshallOptions,
30
- });
31
- }
32
- return _docClient;
169
+ const marshallOptions = {
170
+ convertEmptyValues: true,
171
+ removeUndefinedValues: true,
172
+ convertClassInstanceToMap: true
173
+ };
174
+ const unmarshallOptions = {
175
+ wrapNumbers: false
176
+ };
177
+ if (!_docClient || forceNew) {
178
+ _docClient = DynamoDBDocumentClient.from(getDynamoDB(), {
179
+ marshallOptions,
180
+ unmarshallOptions
181
+ });
182
+ }
183
+ return _docClient;
33
184
  }
34
185
  async function scanWholeTable(options) {
35
- const dynamodb = getDocClient();
36
- let items = [];
37
- let count = 0;
38
- let scannedCount = 0;
39
- let data = await dynamodb.send(new ScanCommand(options));
40
- while (data.LastEvaluatedKey) {
41
- if (data.Items) {
42
- items = items.concat(data.Items);
43
- }
44
- count += data.Count || 0;
45
- scannedCount += data.ScannedCount || 0;
46
- data = await dynamodb.send(new ScanCommand({ ...options, ExclusiveStartKey: data.LastEvaluatedKey }));
47
- }
186
+ const dynamodb = getDocClient();
187
+ let items = [];
188
+ let count = 0;
189
+ let scannedCount = 0;
190
+ let data = await dynamodb.send(new ScanCommand(options));
191
+ while (data.LastEvaluatedKey) {
48
192
  if (data.Items) {
49
- items = items.concat(data.Items);
193
+ items = items.concat(data.Items);
50
194
  }
51
195
  count += data.Count || 0;
52
196
  scannedCount += data.ScannedCount || 0;
53
- return {
54
- Items: items,
55
- Count: count,
56
- ScannedCount: scannedCount,
57
- };
197
+ data = await dynamodb.send(new ScanCommand({ ...options, ExclusiveStartKey: data.LastEvaluatedKey }));
198
+ }
199
+ if (data.Items) {
200
+ items = items.concat(data.Items);
201
+ }
202
+ count += data.Count || 0;
203
+ scannedCount += data.ScannedCount || 0;
204
+ return {
205
+ Items: items,
206
+ Count: count,
207
+ ScannedCount: scannedCount
208
+ };
58
209
  }
59
210
  async function batchCreateRecords(tableName, records, maxWritingCapacity, verbose = false) {
60
- if (verbose) {
61
- console.log(`creating ${records.length} items in ${tableName}`);
62
- }
63
- const docClient = getDocClient();
64
- let remainingItems = records;
65
- // an arbitrary number to ensure while first loop is entered
66
- let prevRemainingCount = remainingItems.length + 1;
67
- let factor = 1;
68
- let rejection = undefined;
69
- while (remainingItems.length > 0 && factor <= 128 && !rejection) {
70
- if (prevRemainingCount === remainingItems.length) {
71
- await wait(5000 * factor);
72
- factor = factor * 2;
73
- }
74
- if (factor >= 32) {
75
- console.log(`WARNING: no progress for a long time for batchCreateRecords, please check`);
76
- }
77
- const slices = arrayGroup(remainingItems.slice(0, maxWritingCapacity), 25);
78
- const results = await Promise.allSettled(slices.map((rs) => docClient.send(new BatchWriteCommand({
79
- RequestItems: {
80
- [tableName]: rs.map((record) => ({ PutRequest: { Item: record } })),
81
- },
82
- }))));
83
- const isFulfilled = (p) => p.status === "fulfilled";
84
- const isRejected = (p) => p.status === "rejected";
85
- prevRemainingCount = remainingItems.length;
86
- remainingItems = remainingItems.slice(maxWritingCapacity);
87
- results.forEach((rs, idx) => {
88
- if (isRejected(rs)) {
89
- // whole slice fails, redo whole slice
90
- remainingItems = remainingItems.concat(slices[idx]);
91
- rejection = rs;
92
- }
93
- else if (isFulfilled(rs) && rs.value.UnprocessedItems && Object.keys(rs.value.UnprocessedItems).length > 0) {
94
- // partially fails, redo unprocessedItems
95
- const unprocessedItems = rs.value.UnprocessedItems[tableName].map((it) => it.PutRequest?.Item ?? []).flat();
96
- remainingItems = remainingItems.concat(unprocessedItems);
97
- }
98
- });
99
- if (verbose) {
100
- console.log(`processed=${prevRemainingCount - remainingItems.length}, remaining=${remainingItems.length}`);
101
- }
211
+ if (verbose) {
212
+ console.log(`creating ${records.length} items in ${tableName}`);
213
+ }
214
+ const docClient = getDocClient();
215
+ let remainingItems = records;
216
+ let prevRemainingCount = remainingItems.length + 1;
217
+ let factor = 1;
218
+ let rejection = undefined;
219
+ while (remainingItems.length > 0 && factor <= 128 && !rejection) {
220
+ if (prevRemainingCount === remainingItems.length) {
221
+ await wait(5000 * factor);
222
+ factor = factor * 2;
102
223
  }
103
- if (rejection) {
104
- console.log("batchCreateRecords rejected", rejection);
105
- throw new Error(`batchCreateRecords rejected, failed items=${remainingItems.length}`);
224
+ if (factor >= 32) {
225
+ console.log(`WARNING: no progress for a long time for batchCreateRecords, please check`);
106
226
  }
107
- if (remainingItems.length > 0) {
108
- console.log(`failed batchCreateRecords, failed items=${remainingItems.length}`);
109
- throw new Error(`batchCreateRecords retry failed, failed items=${remainingItems.length}`);
227
+ const slices = arrayGroup(remainingItems.slice(0, maxWritingCapacity), 25);
228
+ const results = await Promise.allSettled(slices.map((rs) => docClient.send(new BatchWriteCommand({
229
+ RequestItems: {
230
+ [tableName]: rs.map((record) => ({ PutRequest: { Item: record } }))
231
+ }
232
+ }))));
233
+ const isFulfilled = (p) => p.status === "fulfilled";
234
+ const isRejected = (p) => p.status === "rejected";
235
+ prevRemainingCount = remainingItems.length;
236
+ remainingItems = remainingItems.slice(maxWritingCapacity);
237
+ results.forEach((rs, idx) => {
238
+ if (isRejected(rs)) {
239
+ remainingItems = remainingItems.concat(slices[idx]);
240
+ rejection = rs;
241
+ } else if (isFulfilled(rs) && rs.value.UnprocessedItems && Object.keys(rs.value.UnprocessedItems).length > 0) {
242
+ const unprocessedItems = rs.value.UnprocessedItems[tableName].map((it) => it.PutRequest?.Item ?? []).flat();
243
+ remainingItems = remainingItems.concat(unprocessedItems);
244
+ }
245
+ });
246
+ if (verbose) {
247
+ console.log(`processed=${prevRemainingCount - remainingItems.length}, remaining=${remainingItems.length}`);
110
248
  }
249
+ }
250
+ if (rejection) {
251
+ console.log("batchCreateRecords rejected", rejection);
252
+ throw new Error(`batchCreateRecords rejected, failed items=${remainingItems.length}`);
253
+ }
254
+ if (remainingItems.length > 0) {
255
+ console.log(`failed batchCreateRecords, failed items=${remainingItems.length}`);
256
+ throw new Error(`batchCreateRecords retry failed, failed items=${remainingItems.length}`);
257
+ }
111
258
  }
112
259
  async function createRecord(tableName, fields, verbose = false) {
113
- if (verbose) {
114
- console.log("creating", tableName, fields);
115
- }
116
- const docClient = getDocClient();
117
- const params = {
118
- TableName: tableName,
119
- Item: fields,
120
- };
121
- return docClient.send(new PutCommand(params));
260
+ if (verbose) {
261
+ console.log("creating", tableName, fields);
262
+ }
263
+ const docClient = getDocClient();
264
+ const params = {
265
+ TableName: tableName,
266
+ Item: fields
267
+ };
268
+ return docClient.send(new PutCommand(params));
122
269
  }
123
270
  async function readRecord(tableName, key, verbose = false) {
124
- if (verbose) {
125
- console.log("reading", tableName, key);
126
- }
127
- const docClient = getDocClient();
128
- const record = await docClient.send(new GetCommand({
129
- TableName: tableName,
130
- Key: key,
131
- }));
132
- return record.Item;
271
+ if (verbose) {
272
+ console.log("reading", tableName, key);
273
+ }
274
+ const docClient = getDocClient();
275
+ const record = await docClient.send(new GetCommand({
276
+ TableName: tableName,
277
+ Key: key
278
+ }));
279
+ return record.Item;
133
280
  }
134
281
  async function getRecordsByKey(tableName, keys, indexName) {
135
- const docClient = getDocClient();
136
- const keyNames = Object.keys(keys);
137
- const conditionExpression = keyNames.map((key) => `#${key} = :${key}`).join(" and ");
138
- const params = {
139
- TableName: tableName,
140
- KeyConditionExpression: conditionExpression,
141
- ExpressionAttributeNames: generateExpressionNames(keyNames),
142
- ExpressionAttributeValues: generateExpressionValues(keyNames, keys),
143
- };
144
- if (indexName) {
145
- params.IndexName = indexName;
146
- }
147
- try {
148
- let data = await docClient.send(new QueryCommand(params));
149
- let items = data.Items ?? [];
150
- while (data.LastEvaluatedKey) {
151
- data = await docClient.send(new QueryCommand({
152
- ...params,
153
- ExclusiveStartKey: data.LastEvaluatedKey,
154
- }));
155
- if (data.Items) {
156
- items = items.concat(data.Items);
157
- }
158
- }
159
- return items;
282
+ const docClient = getDocClient();
283
+ const keyNames = Object.keys(keys);
284
+ const conditionExpression = keyNames.map((key) => `#${key} = :${key}`).join(" and ");
285
+ const params = {
286
+ TableName: tableName,
287
+ KeyConditionExpression: conditionExpression,
288
+ ExpressionAttributeNames: generateExpressionNames(keyNames),
289
+ ExpressionAttributeValues: generateExpressionValues(keyNames, keys)
290
+ };
291
+ if (indexName) {
292
+ params.IndexName = indexName;
293
+ }
294
+ try {
295
+ let data = await docClient.send(new QueryCommand(params));
296
+ let items = data.Items ?? [];
297
+ while (data.LastEvaluatedKey) {
298
+ data = await docClient.send(new QueryCommand({
299
+ ...params,
300
+ ExclusiveStartKey: data.LastEvaluatedKey
301
+ }));
302
+ if (data.Items) {
303
+ items = items.concat(data.Items);
304
+ }
160
305
  }
161
- catch (err) {
162
- console.log(err);
163
- if (err instanceof Error && "statusCode" in err && err.statusCode === 400) {
164
- return null;
165
- }
166
- throw err;
306
+ return items;
307
+ } catch (err) {
308
+ console.log(err);
309
+ if (err instanceof Error && "statusCode" in err && err.statusCode === 400) {
310
+ return null;
167
311
  }
312
+ throw err;
313
+ }
168
314
  }
169
- // Dual purpose for compatibility. If indexName is provided, it will use query command to get the record; if not, use get command which is most efficient.
170
315
  async function getRecordByKey(tableName, keys, indexName) {
171
- if (indexName) {
172
- const records = await getRecordsByKey(tableName, keys, indexName);
173
- if (records) {
174
- return records[0];
175
- }
176
- else {
177
- return null;
178
- }
179
- }
180
- else {
181
- return readRecord(tableName, keys);
316
+ if (indexName) {
317
+ const records = await getRecordsByKey(tableName, keys, indexName);
318
+ if (records) {
319
+ return records[0];
320
+ } else {
321
+ return null;
182
322
  }
323
+ } else {
324
+ return readRecord(tableName, keys);
325
+ }
183
326
  }
184
327
  function generateExpressionNames(keys) {
185
- return keys.reduce((acc, key) => ({ ...acc, [`#${key}`]: key }), {});
328
+ return keys.reduce((acc, key) => ({ ...acc, [`#${key}`]: key }), {});
186
329
  }
187
330
  function generateExpressionValues(keys, fields) {
188
- return keys.reduce((acc, key) => ({ ...acc, [`:${key}`]: fields[key] }), {});
331
+ return keys.reduce((acc, key) => ({ ...acc, [`:${key}`]: fields[key] }), {});
189
332
  }
190
333
  async function updateRecordByKey(tableName, idKey, fields, conditionExpressions = null, verbose = false) {
334
+ if (verbose) {
335
+ console.log("update", tableName, idKey, fields);
336
+ }
337
+ const docClient = getDocClient();
338
+ const idKeyNames = Object.keys(idKey);
339
+ const fieldsToDelete = Object.keys(fields).filter((f) => fields[f] === undefined);
340
+ const fieldsToUpdate = Object.keys(fields).filter((k) => !idKeyNames.includes(k) && !fieldsToDelete.includes(k));
341
+ let data;
342
+ if (fieldsToDelete.length > 0) {
191
343
  if (verbose) {
192
- console.log("update", tableName, idKey, fields);
344
+ console.log("delete fields", tableName, fieldsToDelete);
193
345
  }
194
- const docClient = getDocClient();
195
- const idKeyNames = Object.keys(idKey);
196
- const fieldsToDelete = Object.keys(fields).filter((f) => fields[f] === undefined);
197
- const fieldsToUpdate = Object.keys(fields).filter((k) => !idKeyNames.includes(k) && !fieldsToDelete.includes(k));
198
- let data;
199
- if (fieldsToDelete.length > 0) {
200
- if (verbose) {
201
- console.log("delete fields", tableName, fieldsToDelete);
202
- }
203
- const deleteParams = {
204
- TableName: tableName,
205
- Key: idKey,
206
- ExpressionAttributeNames: generateExpressionNames(fieldsToDelete),
207
- UpdateExpression: `REMOVE ${fieldsToDelete.map((f) => `#${f}`).join(", ")}`,
208
- ReturnValues: "ALL_NEW",
209
- };
210
- if (conditionExpressions) {
211
- deleteParams.ConditionExpression = conditionExpressions;
212
- }
213
- data = await docClient.send(new UpdateCommand(deleteParams));
346
+ const deleteParams = {
347
+ TableName: tableName,
348
+ Key: idKey,
349
+ ExpressionAttributeNames: generateExpressionNames(fieldsToDelete),
350
+ UpdateExpression: `REMOVE ${fieldsToDelete.map((f) => `#${f}`).join(", ")}`,
351
+ ReturnValues: "ALL_NEW"
352
+ };
353
+ if (conditionExpressions) {
354
+ deleteParams.ConditionExpression = conditionExpressions;
214
355
  }
215
- if (fieldsToUpdate.length > 0) {
216
- if (verbose) {
217
- console.log("update fields", tableName, fieldsToUpdate);
218
- }
219
- const updateExpressions = fieldsToUpdate.map((key) => `#${key} = :${key}`);
220
- const params = {
221
- TableName: tableName,
222
- Key: idKey,
223
- ExpressionAttributeNames: generateExpressionNames(fieldsToUpdate),
224
- ExpressionAttributeValues: generateExpressionValues(fieldsToUpdate, fields),
225
- UpdateExpression: `SET ${updateExpressions.join(", ")}`,
226
- ReturnValues: "ALL_NEW",
227
- };
228
- if (conditionExpressions) {
229
- params.ConditionExpression = conditionExpressions;
230
- }
231
- data = await docClient.send(new UpdateCommand(params));
356
+ data = await docClient.send(new UpdateCommand(deleteParams));
357
+ }
358
+ if (fieldsToUpdate.length > 0) {
359
+ if (verbose) {
360
+ console.log("update fields", tableName, fieldsToUpdate);
361
+ }
362
+ const updateExpressions = fieldsToUpdate.map((key) => `#${key} = :${key}`);
363
+ const params = {
364
+ TableName: tableName,
365
+ Key: idKey,
366
+ ExpressionAttributeNames: generateExpressionNames(fieldsToUpdate),
367
+ ExpressionAttributeValues: generateExpressionValues(fieldsToUpdate, fields),
368
+ UpdateExpression: `SET ${updateExpressions.join(", ")}`,
369
+ ReturnValues: "ALL_NEW"
370
+ };
371
+ if (conditionExpressions) {
372
+ params.ConditionExpression = conditionExpressions;
232
373
  }
233
- return data?.Attributes;
374
+ data = await docClient.send(new UpdateCommand(params));
375
+ }
376
+ return data?.Attributes;
234
377
  }
235
378
  async function batchDeleteRecords(tableName, keys) {
236
- const docClient = getDocClient();
237
- for (let start = 0; start < keys.length; start += 25) {
238
- const slice = keys.slice(start, start + 25);
239
- await docClient.send(new BatchWriteCommand({
240
- RequestItems: {
241
- [tableName]: slice.map((key) => {
242
- return { DeleteRequest: { Key: key } };
243
- }),
244
- },
245
- }));
246
- }
379
+ const docClient = getDocClient();
380
+ for (let start = 0;start < keys.length; start += 25) {
381
+ const slice = keys.slice(start, start + 25);
382
+ await docClient.send(new BatchWriteCommand({
383
+ RequestItems: {
384
+ [tableName]: slice.map((key) => {
385
+ return { DeleteRequest: { Key: key } };
386
+ })
387
+ }
388
+ }));
389
+ }
247
390
  }
248
391
  function getKeyName(keySchema, type) {
249
- const key = keySchema.find((k) => k.KeyType === type);
250
- return key?.AttributeName;
392
+ const key = keySchema.find((k) => k.KeyType === type);
393
+ return key?.AttributeName;
251
394
  }
252
395
  function getIndexKeyName(globalSecondaryIndexes, indexName, type) {
253
- const idx = globalSecondaryIndexes.find((i) => i.IndexName === indexName);
254
- return idx?.KeySchema && getKeyName(idx.KeySchema, type);
396
+ const idx = globalSecondaryIndexes.find((i) => i.IndexName === indexName);
397
+ return idx?.KeySchema && getKeyName(idx.KeySchema, type);
255
398
  }
256
399
  async function deleteRecordsByHashKey(tableName, indexName, hashKeyValue, verbose = false) {
257
- const docClient = getDocClient();
258
- const meta = await getDynamoDB().send(new DescribeTableCommand({ TableName: tableName }));
259
- if (!meta.Table) {
260
- throw new Error(`cannot find table ${tableName}`);
261
- }
262
- if (indexName && !meta.Table.GlobalSecondaryIndexes) {
263
- throw new Error(`cannot find global secondary indexes for table ${tableName}`);
264
- }
265
- if (!meta.Table.KeySchema) {
266
- throw new Error(`cannot find key schema for table ${tableName}`);
267
- }
268
- const hashKeyName = indexName
269
- ? getIndexKeyName(meta.Table.GlobalSecondaryIndexes, indexName, "HASH")
270
- : getKeyName(meta.Table.KeySchema, "HASH");
271
- if (!hashKeyName) {
272
- throw new Error(`cannot find hash key name for table ${tableName}`);
273
- }
274
- const mainHashKeyName = getKeyName(meta.Table.KeySchema, "HASH");
275
- if (!mainHashKeyName) {
276
- throw new Error(`cannot find main hash key name for table ${tableName}`);
277
- }
278
- const mainRangeKeyName = getKeyName(meta.Table.KeySchema, "RANGE");
279
- if (!mainRangeKeyName) {
280
- throw new Error(`cannot find main range key name for table ${tableName}`);
281
- }
282
- let totalDeleted = 0;
283
- const params = {
284
- TableName: tableName,
285
- KeyConditionExpression: "#hashKeyName = :hashKeyValue",
286
- ExpressionAttributeNames: { "#hashKeyName": hashKeyName },
287
- ExpressionAttributeValues: { ":hashKeyValue": hashKeyValue },
288
- };
289
- if (indexName) {
290
- params.IndexName = indexName;
291
- }
292
- let data = await docClient.send(new QueryCommand(params));
400
+ const docClient = getDocClient();
401
+ const meta = await getDynamoDB().send(new DescribeTableCommand({ TableName: tableName }));
402
+ if (!meta.Table) {
403
+ throw new Error(`cannot find table ${tableName}`);
404
+ }
405
+ if (indexName && !meta.Table.GlobalSecondaryIndexes) {
406
+ throw new Error(`cannot find global secondary indexes for table ${tableName}`);
407
+ }
408
+ if (!meta.Table.KeySchema) {
409
+ throw new Error(`cannot find key schema for table ${tableName}`);
410
+ }
411
+ const hashKeyName = indexName ? getIndexKeyName(meta.Table.GlobalSecondaryIndexes, indexName, "HASH") : getKeyName(meta.Table.KeySchema, "HASH");
412
+ if (!hashKeyName) {
413
+ throw new Error(`cannot find hash key name for table ${tableName}`);
414
+ }
415
+ const mainHashKeyName = getKeyName(meta.Table.KeySchema, "HASH");
416
+ if (!mainHashKeyName) {
417
+ throw new Error(`cannot find main hash key name for table ${tableName}`);
418
+ }
419
+ const mainRangeKeyName = getKeyName(meta.Table.KeySchema, "RANGE");
420
+ if (!mainRangeKeyName) {
421
+ throw new Error(`cannot find main range key name for table ${tableName}`);
422
+ }
423
+ let totalDeleted = 0;
424
+ const params = {
425
+ TableName: tableName,
426
+ KeyConditionExpression: "#hashKeyName = :hashKeyValue",
427
+ ExpressionAttributeNames: { "#hashKeyName": hashKeyName },
428
+ ExpressionAttributeValues: { ":hashKeyValue": hashKeyValue }
429
+ };
430
+ if (indexName) {
431
+ params.IndexName = indexName;
432
+ }
433
+ let data = await docClient.send(new QueryCommand(params));
434
+ if (data.Items) {
435
+ await batchDeleteRecords(tableName, data.Items.map((item) => mainRangeKeyName ? {
436
+ [mainHashKeyName]: item[mainHashKeyName],
437
+ [mainRangeKeyName]: item[mainRangeKeyName]
438
+ } : {
439
+ [mainHashKeyName]: item[mainHashKeyName]
440
+ }));
441
+ totalDeleted += data.Items.length;
442
+ }
443
+ while (data.LastEvaluatedKey) {
444
+ data = await docClient.send(new QueryCommand({
445
+ ...params,
446
+ ExclusiveStartKey: data.LastEvaluatedKey
447
+ }));
293
448
  if (data.Items) {
294
- await batchDeleteRecords(tableName, data.Items.map((item) => mainRangeKeyName
295
- ? {
296
- [mainHashKeyName]: item[mainHashKeyName],
297
- [mainRangeKeyName]: item[mainRangeKeyName],
298
- }
299
- : {
300
- [mainHashKeyName]: item[mainHashKeyName],
301
- }));
302
- totalDeleted += data.Items.length;
303
- }
304
- while (data.LastEvaluatedKey) {
305
- data = await docClient.send(new QueryCommand({
306
- ...params,
307
- ExclusiveStartKey: data.LastEvaluatedKey,
308
- }));
309
- if (data.Items) {
310
- await batchDeleteRecords(tableName, data.Items.map((item) => mainRangeKeyName
311
- ? {
312
- [mainHashKeyName]: item[mainHashKeyName],
313
- [mainRangeKeyName]: item[mainRangeKeyName],
314
- }
315
- : {
316
- [mainHashKeyName]: item[mainHashKeyName],
317
- }));
318
- totalDeleted += data.Items.length;
319
- }
320
- }
321
- if (verbose) {
322
- console.log(`successfully delete ${totalDeleted} items`);
449
+ await batchDeleteRecords(tableName, data.Items.map((item) => mainRangeKeyName ? {
450
+ [mainHashKeyName]: item[mainHashKeyName],
451
+ [mainRangeKeyName]: item[mainRangeKeyName]
452
+ } : {
453
+ [mainHashKeyName]: item[mainHashKeyName]
454
+ }));
455
+ totalDeleted += data.Items.length;
323
456
  }
324
- return totalDeleted;
457
+ }
458
+ if (verbose) {
459
+ console.log(`successfully delete ${totalDeleted} items`);
460
+ }
461
+ return totalDeleted;
325
462
  }
326
- export { getDocClient,
327
- // export common dynamodb commands to make it easier to use
328
- ScanCommand, BatchWriteCommand, GetCommand, PutCommand, QueryCommand, UpdateCommand, scanWholeTable, batchCreateRecords, createRecord, getRecordsByKey, getRecordByKey, updateRecordByKey, batchDeleteRecords, deleteRecordsByHashKey, };
463
+ export {
464
+ updateRecordByKey,
465
+ scanWholeTable,
466
+ getRecordsByKey,
467
+ getRecordByKey,
468
+ getDocClient,
469
+ deleteRecordsByHashKey,
470
+ createRecord,
471
+ batchDeleteRecords,
472
+ batchCreateRecords,
473
+ UpdateCommand,
474
+ ScanCommand,
475
+ QueryCommand,
476
+ PutCommand,
477
+ GetCommand,
478
+ BatchWriteCommand
479
+ };