@baadal-sdk/dapi 0.31.1 → 0.31.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baadal-sdk/dapi",
3
- "version": "0.31.1",
3
+ "version": "0.31.4",
4
4
  "description": "Dead-simple API wrappers",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
package/src/aws/db.ts CHANGED
@@ -33,7 +33,7 @@ import { dbClient } from './client';
33
33
  import { StringIndexable } from '../common/common.model';
34
34
  import { CustomError } from '../common/error';
35
35
  import { warn, error } from '../common/logger';
36
- import { BATCH_SIZE, CHUNK_SIZE } from '../common/const';
36
+ import { BATCH_SIZE, CHUNK_SIZE, MAX_RETRY_ATTEMPTS } from '../common/const';
37
37
 
38
38
  const DynamoDBError = (msg: string) => new CustomError(msg, { name: 'DynamoDBError' });
39
39
 
@@ -70,7 +70,13 @@ const tryInit = (silent = false) => {
70
70
  // auto-initialize on load
71
71
  tryInit(true);
72
72
 
73
- const writeItemForceHelper = async <T = any>(table: string, item: T, key: string, i: number): Promise<T | null> => {
73
+ const writeItemForceHelper = async <T = any>(
74
+ table: string,
75
+ item: T,
76
+ key: string,
77
+ i: number,
78
+ imax?: number
79
+ ): Promise<T | null> => {
74
80
  if (!dbClient.client) tryInit();
75
81
  if (!dbClient.client) return null;
76
82
  if (!table || !item) return null;
@@ -80,7 +86,7 @@ const writeItemForceHelper = async <T = any>(table: string, item: T, key: string
80
86
  }
81
87
  const cmdParams: PutCommandInput = { TableName: table, Item: item, ConditionExpression: `attribute_not_exists(${key})` };
82
88
  const command = new PutCommand(cmdParams);
83
- const numberOfAttempts = 3;
89
+ const numberOfAttempts = imax ?? MAX_RETRY_ATTEMPTS;
84
90
 
85
91
  try {
86
92
  await dbClient.client.send(command);
@@ -88,11 +94,15 @@ const writeItemForceHelper = async <T = any>(table: string, item: T, key: string
88
94
  if (err.name === 'ConditionalCheckFailedException') {
89
95
  if (i < numberOfAttempts - 1) {
90
96
  (item as any)[key] = short.uuid(); // new primary key
91
- const ret: T | null = await writeItemForceHelper(table, item, key, i + 1);
97
+ const ret: T | null = await writeItemForceHelper(table, item, key, i + 1, imax);
92
98
  return ret;
93
99
  }
94
100
  console.error('PutCommandInput:', cmdParams);
95
- error('[ERROR] Maximum attempts overflow!');
101
+ if (numberOfAttempts === 1) {
102
+ error(`[ERROR] An item with the same key(${(item as any)[key]}) already exists!`);
103
+ } else {
104
+ error('[ERROR] Maximum attempts overflow!');
105
+ }
96
106
  }
97
107
  return null;
98
108
  }
@@ -106,6 +116,8 @@ export interface WriteItemForceInput<T = any> {
106
116
  key?: string;
107
117
  }
108
118
 
119
+ export type WriteItemUniqueInput<T = any> = WriteItemForceInput<T>;
120
+
109
121
  /**
110
122
  * Write an item to a DynamoDB table, retry in case of key conflict
111
123
  * @param input input command object
@@ -130,6 +142,32 @@ export const writeItemForce = async <T = any>(input: WriteItemForceInput<T>): Pr
130
142
  return writeItemForceHelper<T>(input.table, input.item, key, 0);
131
143
  };
132
144
 
145
+ /**
146
+ * Write an item (uniquely) to a DynamoDB table.
147
+ * Unlike `writeItemForce`, it does not retry in case of key conflict
148
+ * Unlike `writeItem`, it does not overwrite an item with the same key (if it exists)
149
+ * @param input input command object
150
+ * @returns the created item, null in case of error or key conflict (i.e., if item with same key already exists)
151
+ *
152
+ * ```js
153
+ * writeItemUnique({
154
+ * table: 'lesson_list',
155
+ * item: { id: 'id_001', title: 'My Lesson' },
156
+ * key: 'id',
157
+ * });
158
+ *
159
+ * interface WriteItemUniqueInput<T = any> {
160
+ * table: string;
161
+ * item: T;
162
+ * key?: string; // default: `id`
163
+ * }
164
+ * ```
165
+ */
166
+ export const writeItemUnique = async <T = any>(input: WriteItemUniqueInput<T>): Promise<T | null> => {
167
+ const key = input.key || 'id';
168
+ return writeItemForceHelper<T>(input.table, input.item, key, 0, 1);
169
+ };
170
+
133
171
  export interface WriteItemInput {
134
172
  table: string;
135
173
  item: StringIndexable;
@@ -177,7 +215,7 @@ const batchWriteItems = async (table: string, items: StringIndexable[]) => {
177
215
  if (!dbClient.client) tryInit();
178
216
  if (!dbClient.client) return null;
179
217
  if (!table || !items || !Array.isArray(items)) return null;
180
- if (!items.length) return false;
218
+ if (!items.length) return true;
181
219
 
182
220
  const reqList = items.map(item => ({ PutRequest: { Item: item } }));
183
221
  const cmdParams: BatchWriteCommandInput = {
@@ -206,7 +244,7 @@ export interface WriteItemsAllInput {
206
244
  }
207
245
 
208
246
  /**
209
- * Write an list of items to a DynamoDB table
247
+ * Write a list of items to a DynamoDB table
210
248
  * @param input input command object
211
249
  * @returns true if successful, null in case of error
212
250
  *
@@ -226,7 +264,7 @@ export const writeItemsAll = async (input: WriteItemsAllInput) => {
226
264
  if (!dbClient.client) tryInit();
227
265
  if (!dbClient.client) return null;
228
266
  if (!input.table || !input.items || !Array.isArray(input.items)) return null;
229
- if (!input.items.length) return false;
267
+ if (!input.items.length) return true;
230
268
 
231
269
  let errFlag = false;
232
270
 
@@ -308,6 +346,7 @@ export interface ReadItemInput {
308
346
  key: StringIndexable;
309
347
  projection?: string;
310
348
  attrNames?: StringIndexable;
349
+ loud?: boolean;
311
350
  }
312
351
 
313
352
  /**
@@ -354,8 +393,11 @@ export const readItem = async <T = any>(input: ReadItemInput) => {
354
393
  } catch (err) {
355
394
  console.error('GetCommandInput:', cmdParams);
356
395
  console.error(err);
357
- return null;
358
- // throw err;
396
+ if (input.loud) {
397
+ throw err;
398
+ } else {
399
+ return null;
400
+ }
359
401
  }
360
402
 
361
403
  return contents;
@@ -652,7 +694,7 @@ const batchDeleteItems = async (table: string, keys: StringIndexable[]) => {
652
694
  if (!dbClient.client) tryInit();
653
695
  if (!dbClient.client) return null;
654
696
  if (!table || !keys || !Array.isArray(keys)) return null;
655
- if (!keys.length) return false;
697
+ if (!keys.length) return true;
656
698
 
657
699
  const reqList = keys.map(key => ({ DeleteRequest: { Key: key } }));
658
700
  const cmdParams: BatchWriteCommandInput = {
@@ -701,7 +743,7 @@ export const deleteItemsAll = async (input: DeleteItemsAllInput) => {
701
743
  if (!dbClient.client) tryInit();
702
744
  if (!dbClient.client) return null;
703
745
  if (!input.table || !input.keys || !Array.isArray(input.keys)) return null;
704
- if (!input.keys.length) return false;
746
+ if (!input.keys.length) return true;
705
747
 
706
748
  let errFlag = false;
707
749
 
package/src/aws/s3.ts CHANGED
@@ -170,13 +170,13 @@ export const uploadFile = async (bucket: string, file: string, s3path?: string)
170
170
  * @param bucket S3 bucket name
171
171
  * @param files (local) list of file paths to upload
172
172
  * @param s3paths [optional] S3 path to be created, if not provided then derived from `file` path
173
- * @returns true if the write is successful, null in case of error
173
+ * @returns true if all uploads are successful, null in case of error
174
174
  */
175
175
  export const uploadFilesAll = async (bucket: string, files: string[], s3paths?: string[]) => {
176
176
  if (!s3Client.client) tryInit();
177
177
  if (!s3Client.client) return null;
178
178
  if (!bucket || !files || !Array.isArray(files)) return null;
179
- if (!files.length) return false;
179
+ if (!files.length) return true;
180
180
  if (s3paths && (!Array.isArray(s3paths) || !s3paths.length || files.length !== s3paths.length)) return null;
181
181
 
182
182
  let errFlag = false;
@@ -383,7 +383,7 @@ export const getObjectHeadsAll = async (bucket: string, s3paths: string[]) => {
383
383
  contents = contents.filter(e => !!e);
384
384
  }
385
385
 
386
- return contents;
386
+ return contents as HeadObject[];
387
387
  };
388
388
 
389
389
  /**
@@ -416,7 +416,7 @@ const batchDeleteObjects = async (bucket: string, s3paths: string[]) => {
416
416
  if (!s3Client.client) tryInit();
417
417
  if (!s3Client.client) return null;
418
418
  if (!bucket || !s3paths || !Array.isArray(s3paths)) return null;
419
- if (!s3paths.length) return false;
419
+ if (!s3paths.length) return true;
420
420
 
421
421
  const keys = s3paths.map(key => ({ Key: key }));
422
422
  const cmdParams: DeleteObjectsCommandInput = { Bucket: bucket, Delete: { Objects: keys } };
@@ -444,7 +444,7 @@ export const deleteObjectsAll = async (bucket: string, s3paths: string[]) => {
444
444
  if (!s3Client.client) tryInit();
445
445
  if (!s3Client.client) return null;
446
446
  if (!bucket || !s3paths || !Array.isArray(s3paths)) return null;
447
- if (!s3paths.length) return false;
447
+ if (!s3paths.length) return true;
448
448
 
449
449
  let errFlag = false;
450
450
 
@@ -1,2 +1,3 @@
1
1
  export const BATCH_SIZE = 20; // (max) number of items in a batch request
2
2
  export const CHUNK_SIZE = 10; // (max) number of parallel requests at a time
3
+ export const MAX_RETRY_ATTEMPTS = 3; // (max) number of retry attempts in case of key conflict