@izara_project/izara-core-library-asynchronous-flow 1.0.1 → 1.0.3

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/index.js ADDED
@@ -0,0 +1,58 @@
1
+ /*
2
+ Copyright (C) 2021 Sven Mason <http://izara.io>
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ 'use strict';
19
+
20
+ // Import the Asynchronous Flow shared library functions
21
+ const asyncFlowSharedLib = require('./src/AsyncFlowSharedLib');
22
+
23
+ // Export all the functions
24
+ module.exports = {
25
+ // Identifier creation
26
+ createFieldNameUniqueRequestId: asyncFlowSharedLib.createFieldNameUniqueRequestId,
27
+ createConcatenatedAwaitingStepId: asyncFlowSharedLib.createConcatenatedAwaitingStepId,
28
+ createConcatenatedPendingStepId: asyncFlowSharedLib.createConcatenatedPendingStepId,
29
+ createAwaitingStepId: asyncFlowSharedLib.createAwaitingStepId,
30
+ createPendingStepId: asyncFlowSharedLib.createPendingStepId,
31
+ explodePendingStepId: asyncFlowSharedLib.explodePendingStepId,
32
+
33
+ // Awaiting Multiple Steps
34
+ createAwaitingMultipleStepsWithAdditionalAttributes: asyncFlowSharedLib.createAwaitingMultipleStepsWithAdditionalAttributes,
35
+ createAwaitingMultipleSteps: asyncFlowSharedLib.createAwaitingMultipleSteps,
36
+
37
+ // Awaiting Step
38
+ createAwaitingStep: asyncFlowSharedLib.createAwaitingStep,
39
+ findPendingStepIdsAwaitingStep: asyncFlowSharedLib.findPendingStepIdsAwaitingStep,
40
+ findPendingStepsAwaitingStep: asyncFlowSharedLib.findPendingStepsAwaitingStep,
41
+
42
+ // Checking and clearing steps
43
+ checkAllAwaitingStepsFinished: asyncFlowSharedLib.checkAllAwaitingStepsFinished,
44
+ clearAllAwaitingSteps: asyncFlowSharedLib.clearAllAwaitingSteps,
45
+ removeAwaitingStep: asyncFlowSharedLib.removeAwaitingStep,
46
+ removeAwaitingMultipleStep: asyncFlowSharedLib.removeAwaitingMultipleStep,
47
+ removeAwaitingStepWithCheckUniqueRequestId: asyncFlowSharedLib.removeAwaitingStepWithCheckUniqueRequestId,
48
+
49
+ // Request and cache processing
50
+ checkUniqueRequestProcessing: asyncFlowSharedLib.checkUniqueRequestProcessing,
51
+ checkTimeCacheComplete: asyncFlowSharedLib.checkTimeCacheComplete,
52
+ checkAndGetTimeCacheComplete: asyncFlowSharedLib.checkAndGetTimeCacheComplete,
53
+ checkCacheUniqueRequestId: asyncFlowSharedLib.checkCacheUniqueRequestId,
54
+
55
+ // Pagination and invocation helpers
56
+ validateStartKeyParam: asyncFlowSharedLib.validateStartKeyParam,
57
+ validateMultipleInvocations: asyncFlowSharedLib.validateMultipleInvocations
58
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izara_project/izara-core-library-asynchronous-flow",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Shared asynchronous flow logic",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -20,9 +20,11 @@
20
20
  "testEnvironment": "node"
21
21
  },
22
22
  "dependencies": {
23
+ "@izara_project/izara-core-library-core": "^1.0.16",
24
+ "@izara_project/izara-core-library-dynamodb": "^1.0.2",
23
25
  "@izara_project/izara-core-library-external-request": "^1.0.17",
24
- "@izara_project/izara-core-library-service-schemas": "^1.0.38",
25
- "@izara_project/izara-shared": "^1.0.121",
26
- "aws-jwt-verify": "^4.0.1"
26
+ "@izara_project/izara-core-library-logger": "^1.0.6",
27
+ "@izara_project/izara-core-library-sqs": "^1.0.2",
28
+ "@izara_project/izara-core-library-trigger-cache": "^1.0.2"
27
29
  }
28
- }
30
+ }
@@ -0,0 +1,866 @@
1
+ /*
2
+ Copyright (C) 2021 Sven Mason <http://izara.io>
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ 'use strict';
19
+
20
+ // Core libraries
21
+ const { NoRetryError } = require('@izara_project/izara-core-library-core');
22
+ const Logger = require('@izara_project/izara-core-library-logger');
23
+
24
+ // Data and service interaction libraries
25
+ const dynamodbSharedLib = require('@izara_project/izara-core-library-dynamodb');
26
+ const sqsSharedLib = require('@izara_project/izara-core-library-sqs');
27
+ const triggeredCacheSharedLib = require('@izara_project/izara-core-library-trigger-cache');
28
+
29
+ // External service interactions
30
+ const { sqs } = require('@izara_project/izara-core-library-external-request');
31
+
32
+ module.exports.createFieldNameUniqueRequestId = (
33
+ prefix,
34
+ uniqueRequestIdFieldName = 'UniqueRequestId'
35
+ ) => {
36
+ return prefix + uniqueRequestIdFieldName;
37
+ }
38
+
39
+ module.exports.createConcatenatedAwaitingStepId = (
40
+ partitionKey,
41
+ sortId,
42
+ prefix = ""
43
+ ) => {
44
+ return prefix + partitionKey + "_" + sortId
45
+ }
46
+
47
+ module.exports.createConcatenatedPendingStepId = (
48
+ parentId,
49
+ childId
50
+ ) => {
51
+ return parentId + "_" + childId
52
+ }
53
+
54
+ module.exports.createAwaitingStepId = (
55
+ partitionKey,
56
+ prefix = ""
57
+ ) => {
58
+ return prefix + partitionKey
59
+ }
60
+
61
+ module.exports.createPendingStepId = (
62
+ identifierId,
63
+ prefix = ""
64
+ ) => {
65
+ return prefix + identifierId
66
+ }
67
+
68
+ module.exports.explodePendingStepId = (
69
+ pendingStepId,
70
+ prefix = ""
71
+ ) => {
72
+ if (prefix === "") {
73
+ return pendingStepId;
74
+ } else {
75
+ let identifierId = pendingStepId.slice(prefix.length)
76
+ // ** validate identifierId == prefix
77
+ if (identifierId == prefix) {
78
+ throw new NoRetryError("IdentifierId should not be like prefix.");
79
+ }
80
+
81
+ Logger.debug("return explode:", identifierId);
82
+ return identifierId;
83
+ }
84
+
85
+ }
86
+
87
+ //====================================== AwaitingMultipleSteps
88
+ // AwaitingMultipleSteps is when a flow is awaiting multiple external flows before it can continue
89
+ // One awaitingStepId can have multiple pendingStepIds that are awaiting it
90
+ // One pendingStepId can have multiple awaitingStepIds it is awaiting
91
+ // logic can prepend any prefix to awaitingStepId if want to be share one table and differentiate different external step ids
92
+
93
+ //not yet used ?
94
+ module.exports.createAwaitingMultipleStepsWithAdditionalAttributes = async (
95
+ _izContext,
96
+ records, // array of object including properties: awaitingStepId / (optional) additionalAttributes
97
+ pendingStepId,
98
+ tableName = "AwaitingMultipleSteps",
99
+ byPendingTableName = "AwaitingMultipleStepByPending"
100
+ ) => {
101
+ _izContext.logger.debug("Lib:createAwaitingMultipleStepsWithAdditionalAttributes", {
102
+ records: records,
103
+ pendingStepId: pendingStepId,
104
+ tableName: tableName,
105
+ byPendingTableName: byPendingTableName
106
+ });
107
+ let promiseArray = []
108
+
109
+ for (let record of records) {
110
+
111
+ let attributesWhenCreate = {
112
+ awaitingStepId: record.awaitingStepId,
113
+ pendingStepId: pendingStepId,
114
+ timestamp: Date.now(),
115
+ ...record.additionalAttributes
116
+ }
117
+
118
+ promiseArray.push(
119
+ dynamodbSharedLib.putItem(
120
+ _izContext,
121
+ await dynamodbSharedLib.tableName(_izContext, tableName),
122
+ attributesWhenCreate
123
+ )
124
+ );
125
+ promiseArray.push(
126
+ dynamodbSharedLib.putItem(
127
+ _izContext,
128
+ await dynamodbSharedLib.tableName(_izContext, byPendingTableName),
129
+ {
130
+ pendingStepId: pendingStepId,
131
+ awaitingStepId: record.awaitingStepId,
132
+ timestamp: Date.now(),
133
+ }
134
+ ));
135
+
136
+ }
137
+
138
+ await Promise.all(promiseArray);
139
+
140
+ }
141
+
142
+ module.exports.createAwaitingMultipleSteps = async (
143
+ _izContext,
144
+ records, // array of awaitingStepIds
145
+ pendingStepId,
146
+ tableName = "AwaitingMultipleSteps",
147
+ byPendingTableName = "AwaitingMultipleStepByPending"
148
+ ) => {
149
+ _izContext.logger.debug("Lib:createAwaitingMultipleSteps", {
150
+ records: records,
151
+ pendingStepId: pendingStepId,
152
+ tableName: tableName,
153
+ byPendingTableName: byPendingTableName
154
+ });
155
+
156
+ // // 0001 ORG.
157
+ let promiseArray = []
158
+
159
+ for (let record of records) {
160
+
161
+ let attributesWhenCreate = {
162
+ awaitingStepId: record,
163
+ pendingStepId: pendingStepId,
164
+ timestamp: Date.now()
165
+ }
166
+ _izContext.logger.debug("putItem:AwaitingMultipleSteps +++++++", { awaitingStepId: attributesWhenCreate.awaitingStepId })
167
+ promiseArray.push(
168
+ dynamodbSharedLib.putItem(
169
+ _izContext,
170
+ await dynamodbSharedLib.tableName(_izContext, tableName),
171
+ attributesWhenCreate
172
+ )
173
+ );
174
+
175
+
176
+ _izContext.logger.debug("putItem:AwaitingMultipleStepByPending", { awaitingStepId: attributesWhenCreate.awaitingStepId })
177
+ promiseArray.push(
178
+ dynamodbSharedLib.putItem(
179
+ _izContext,
180
+ await dynamodbSharedLib.tableName(_izContext, byPendingTableName),
181
+ attributesWhenCreate
182
+ )
183
+ );
184
+
185
+ _izContext.logger.debug("check 0")
186
+
187
+ }
188
+ await Promise.all(promiseArray);
189
+
190
+ }
191
+
192
+ //====================================== AwaitingStep //======================================
193
+ // AwaitingStep is when a flow is awaiting one external flow, and will continue it's flow once that one external flow is complete
194
+ // One awaitingStepId can have multiple pendingStepIds that are awaiting it
195
+ // logic can prepend any prefix to awaitingStepId if want to be share one table and differentiate different external step ids
196
+
197
+ module.exports.createAwaitingStep = async (
198
+ _izContext,
199
+ awaitingStepId,
200
+ pendingStepId,
201
+ tableName = "AwaitingStep",
202
+ additionalAttributes = {} // save in top level
203
+ ) => {
204
+
205
+ let attributesWhenCreate = {
206
+ awaitingStepId: awaitingStepId,
207
+ pendingStepId: pendingStepId,
208
+ timestamp: Date.now(),
209
+ uniqueRequestId: _izContext.uniqueRequestId,
210
+ }
211
+ // loop if not empty
212
+ for (let additionalAttribute in additionalAttributes) {
213
+ attributesWhenCreate[additionalAttribute] = additionalAttributes[additionalAttribute]
214
+ }
215
+
216
+ await dynamodbSharedLib.putItem(
217
+ _izContext,
218
+ await dynamodbSharedLib.tableName(_izContext, tableName),
219
+ attributesWhenCreate
220
+ );
221
+ }
222
+
223
+
224
+
225
+
226
+ module.exports.findPendingStepIdsAwaitingStep = async (
227
+ _izContext,
228
+ awaitingStepId,
229
+ tableName = "AwaitingStep"
230
+ ) => {
231
+
232
+ let pendingStepIds = [];
233
+
234
+ let listPendingStepIds = await dynamodbSharedLib.query(
235
+ _izContext,
236
+ await dynamodbSharedLib.tableName(_izContext, tableName),
237
+ {
238
+ awaitingStepId: awaitingStepId
239
+ }
240
+ );
241
+
242
+ for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
243
+ pendingStepIds.push(listPendingStepIds.Items[idx].pendingStepId)
244
+ }
245
+
246
+ return pendingStepIds
247
+ }
248
+
249
+ module.exports.findPendingStepsAwaitingStep = async (
250
+ _izContext,
251
+ awaitingStepId,
252
+ tableName = "AwaitingStep"
253
+ ) => {
254
+
255
+ let listPendingSteps = await dynamodbSharedLib.query(
256
+ _izContext,
257
+ await dynamodbSharedLib.tableName(_izContext, tableName),
258
+ {
259
+ awaitingStepId: awaitingStepId
260
+ }
261
+ );
262
+ _izContext.logger.debug("Record of awaitingStepId", listPendingSteps)
263
+ return listPendingSteps.Items;
264
+ }
265
+
266
+
267
+
268
+ module.exports.checkAllAwaitingStepsFinished = async (
269
+ _izContext,
270
+ pendingStepId,
271
+ currentAwaitingStepId = null,
272
+ tableName = 'AwaitingMultipleSteps',
273
+ byPendingTableName = "AwaitingMultipleStepByPending"
274
+ // indexName = ''
275
+ ) => {
276
+ _izContext.logger.debug("Lib:checkAllAwaitingStepsFinished", {
277
+ pendingStepId: pendingStepId,
278
+ currentAwaitingStepId: currentAwaitingStepId,
279
+ tableName: tableName,
280
+ byPendingTableName: byPendingTableName
281
+ });
282
+
283
+ if (currentAwaitingStepId) {
284
+
285
+ //* not need to save complete in AwaitingMultipleSteps Table but save in AwaitingMultipleStepByPending
286
+ // await dynamodbSharedLib.updateItem(
287
+ // _izContext,
288
+ // dynamodbSharedLib.tableName(tableName),
289
+ // {
290
+ // awaitingStepId: currentAwaitingStepId,
291
+ // pendingStepId: pendingStepId
292
+ // },
293
+ // {
294
+ // complete: true
295
+ // },
296
+ // )
297
+
298
+ _izContext.logger.debug(`---- update ${byPendingTableName} table ----`);
299
+ await dynamodbSharedLib.updateItem(
300
+ _izContext,
301
+ await dynamodbSharedLib.tableName(_izContext, byPendingTableName),
302
+ {
303
+ pendingStepId: pendingStepId,
304
+ awaitingStepId: currentAwaitingStepId
305
+ },
306
+ {
307
+ complete: true
308
+ },
309
+ );
310
+
311
+ }
312
+ let listPendingStepIds = await dynamodbSharedLib.query(
313
+ _izContext,
314
+ await dynamodbSharedLib.tableName(_izContext, byPendingTableName),
315
+ {
316
+ pendingStepId: pendingStepId
317
+ },
318
+ {
319
+ limit: 50 // arbritrary limit so not pull too many results, need >2 because some others might be marked as "complete" = true
320
+ }
321
+ );
322
+ for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
323
+
324
+ if (currentAwaitingStepId && listPendingStepIds.Items[idx].awaitingStepId == currentAwaitingStepId) {
325
+ continue;
326
+ }
327
+ // if any record found not set to complete then there are steps not yet finished
328
+ if (!listPendingStepIds.Items[idx].complete) {
329
+ return false;
330
+ }
331
+ }
332
+
333
+ return true;
334
+
335
+
336
+ // // mark currentAwaitingStepId as complete, this is done to protect against race condition in between checking all complete
337
+ // // and then removing the record, we need to let other requests know this record is complete and need to do this before
338
+ // // querying all remaining records, otherwise if have only two remaining requests they might both show as records remaining
339
+ // // (seeing each other) and not continue flow, then each deletes their record and no records remain to trigger next step.
340
+ // // can result in race condition where multiple requests see all records finished, code has to account for this, but should already do so for idempotence
341
+ // // add query that updates the currentAwaitingStepId to have attribute "complete" = true
342
+ // if (currentAwaitingStepId) {
343
+ // await dynamodbSharedLib.updateItem(
344
+ // _izContext,
345
+ // dynamodbSharedLib.tableName(tableName),
346
+ // {
347
+ // awaitingStepId: currentAwaitingStepId,
348
+ // pendingStepId: pendingStepId
349
+ // },
350
+ // {
351
+ // complete: true
352
+ // },
353
+ // )
354
+ // }
355
+ // let listPendingStepIds = await dynamodbSharedLib.query(
356
+ // _izContext,
357
+ // dynamodbSharedLib.tableName(tableName),
358
+ // {
359
+ // pendingStepId: pendingStepId
360
+ // },
361
+ // {
362
+ // indexName: dynamodbSharedLib.tableName(tableName + 'Index'),
363
+ // limit: 50 // arbritrary limit so not pull too many results, need >2 because some others might be marked as "complete" = true
364
+ // }
365
+ // );
366
+ // for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
367
+
368
+ // if (currentAwaitingStepId && listPendingStepIds.Items[idx].awaitingStepId == currentAwaitingStepId) {
369
+ // continue;
370
+ // }
371
+ // // if any record found not set to complete then there are steps not yet finished
372
+ // if (!listPendingStepIds.Items[idx].complete) {
373
+ // return false;
374
+ // }
375
+ // }
376
+
377
+ // return true;
378
+ }
379
+
380
+ module.exports.clearAllAwaitingSteps = async (
381
+ _izContext,
382
+ pendingStepId,
383
+ tableName = 'AwaitingMultipleSteps',
384
+ byPendingTableName = "AwaitingMultipleStepByPending"
385
+ ) => {
386
+ _izContext.logger.debug("Lib:clearAllAwaitingSteps", {
387
+ pendingStepId: pendingStepId,
388
+ tableName: tableName,
389
+ byPendingTableName: byPendingTableName
390
+ })
391
+
392
+ tableName = await dynamodbSharedLib.tableName(_izContext, tableName)
393
+
394
+ // ----- query items from GSI table
395
+ let listPendingStepIds = await dynamodbSharedLib.query(
396
+ _izContext,
397
+ await dynamodbSharedLib.tableName(_izContext, byPendingTableName),
398
+ {
399
+ pendingStepId: pendingStepId
400
+ },
401
+ );
402
+ _izContext.logger.debug('listPendingStepIds', listPendingStepIds);
403
+
404
+
405
+ let promiseArray = []
406
+
407
+ for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
408
+
409
+ let keyValues = {
410
+ awaitingStepId: listPendingStepIds.Items[idx].awaitingStepId,
411
+ pendingStepId: listPendingStepIds.Items[idx].pendingStepId
412
+ }
413
+ _izContext.logger.debug('deleting ... ', keyValues);
414
+
415
+ _izContext.logger.debug(`delete item in dynamodb AwaitingMultipleSteps ==> awaitingStepId:${keyValues.awaitingStepId}`)
416
+ promiseArray.push(
417
+ dynamodbSharedLib.deleteItem(
418
+ _izContext,
419
+ tableName,
420
+ keyValues
421
+ ));
422
+ _izContext.logger.debug(`delete item in dynamodb AwaitingMultipleStepByPending ==> awaitingStepId:${keyValues.awaitingStepId}`)
423
+ promiseArray.push(
424
+ dynamodbSharedLib.deleteItem(
425
+ _izContext,
426
+ await dynamodbSharedLib.tableName(_izContext, byPendingTableName),
427
+ keyValues
428
+ ));
429
+
430
+ }
431
+
432
+ await Promise.all(promiseArray);
433
+
434
+ return true;
435
+ }
436
+
437
+
438
+ module.exports.removeAwaitingStep = async (
439
+ _izContext,
440
+ awaitingStepId,
441
+ pendingStepId,
442
+ tableName = "AwaitingStep"
443
+ ) => {
444
+
445
+ await dynamodbSharedLib.deleteItem(
446
+ _izContext,
447
+ await dynamodbSharedLib.tableName(_izContext, tableName),
448
+ {
449
+ awaitingStepId: awaitingStepId,
450
+ pendingStepId: pendingStepId
451
+ }
452
+ );
453
+ }
454
+
455
+ module.exports.removeAwaitingMultipleStep = async (
456
+ _izContext,
457
+ awaitingStepId,
458
+ pendingStepId,
459
+ tableName = "AwaitingMultipleSteps",
460
+ byPendingTableName = "AwaitingMultipleStepByPending"
461
+ ) => {
462
+ _izContext.logger.debug("Lib:removeAwaitingMultipleStep", {
463
+ awaitingStepId: awaitingStepId,
464
+ pendingStepId: pendingStepId,
465
+ tableName: tableName,
466
+ byPendingTableName: byPendingTableName
467
+ })
468
+
469
+ let promiseArray = []
470
+
471
+ let keyValues = {
472
+ awaitingStepId: awaitingStepId,
473
+ pendingStepId: pendingStepId
474
+ }
475
+ _izContext.logger.debug('deleting ... ', keyValues);
476
+
477
+ _izContext.logger.debug(`delete item in dynamodb AwaitingMultipleSteps ==> awaitingStepId:${keyValues.awaitingStepId}`)
478
+ promiseArray.push(
479
+ dynamodbSharedLib.deleteItem(
480
+ _izContext,
481
+ await dynamodbSharedLib.tableName(_izContext, tableName),
482
+ keyValues
483
+ ));
484
+
485
+ _izContext.logger.debug(`delete item in dynamodb AwaitingMultipleStepByPending ==> awaitingStepId:${keyValues.awaitingStepId}`)
486
+ promiseArray.push(
487
+ dynamodbSharedLib.deleteItem(
488
+ _izContext,
489
+ await dynamodbSharedLib.tableName(_izContext, byPendingTableName),
490
+ keyValues
491
+ ));
492
+
493
+ await Promise.all(promiseArray);
494
+
495
+ }
496
+
497
+ // only works with AwaitingStep, does not work with AwaitingMultipleSteps, need to make new function that checks conditional pass? If pass delete from byPending table too
498
+ module.exports.removeAwaitingStepWithCheckUniqueRequestId = async (
499
+ _izContext,
500
+ awaitingStepId,
501
+ pendingStepId,
502
+ checkUniqueRequestId,
503
+ prefix = "",
504
+ tableName = "AwaitingStep"
505
+ ) => {
506
+
507
+ // if logicalElements add queryElements.logicalElements
508
+ // set condition uniqueRequestId match with exists
509
+ const queryElementsCondition = {
510
+ additionalAttributes: {
511
+ checkUniqueRequestId: checkUniqueRequestId,
512
+ },
513
+ logicalElements: [
514
+ {
515
+ type: "logical",
516
+ comparison: "equals",
517
+ lhsAttribute: this.createFieldNameUniqueRequestId(prefix),
518
+ rhsReference: "checkUniqueRequestId"
519
+ },
520
+ ]
521
+ }
522
+
523
+ await dynamodbSharedLib.deleteItem(
524
+ _izContext,
525
+ await dynamodbSharedLib.tableName(_izContext, tableName),
526
+ {
527
+ awaitingStepId: awaitingStepId,
528
+ pendingStepId: pendingStepId
529
+ },
530
+ queryElementsCondition,
531
+ {
532
+ errorOnConditionalExpNotPass: false
533
+ }
534
+ );
535
+ }
536
+
537
+
538
+
539
+ //====================================== end AwaitingStep
540
+
541
+
542
+
543
+
544
+
545
+ /**
546
+ * Checks if record has uniqueRequestId set, if not set to this requests uniqueRequestId and continue to process
547
+ * if already set check if matches this request uniqueRequestId, if yes continue to process, if not then stop processing
548
+ * status: "process"|"stop"|"recordNotFound"
549
+
550
+ * if unable to set uniqueRequestId (because other thread updated it) throw NoRetryError
551
+ * conditional update existingRecord set uniqueRequestId = _izContext.uniqueRequestId
552
+ * conditional check uniqueRequestId still not exist in case another thread added
553
+ * if conditional not pass "continue" and try again
554
+
555
+ * @returns [string] [returnStatus, existingRecord]
556
+ */
557
+ module.exports.checkUniqueRequestProcessing = async (
558
+ _izContext,
559
+ tableName,
560
+ primaryKey,
561
+ prefix = '',
562
+ overwriteUniqueRequestId = null,
563
+ uniqueRequestIdFieldName = "UniqueRequestId" // if set will check/add this fieldname with _izContext.uniqueRequestId value
564
+ ) => {
565
+ try {
566
+
567
+ _izContext.logger.debug('current uniqueRequestId:', _izContext.uniqueRequestId)
568
+
569
+ let uniqueRequestId = _izContext.uniqueRequestId;
570
+ if (overwriteUniqueRequestId) {
571
+ uniqueRequestId = overwriteUniqueRequestId;
572
+ }
573
+
574
+ // add prefix
575
+ uniqueRequestIdFieldName = this.createFieldNameUniqueRequestId(
576
+ prefix,
577
+ uniqueRequestIdFieldName
578
+ );
579
+
580
+ //loop 3 times incase status changes, on 3rd iteration throw error (so checks 2 times)
581
+ for (let i = 1; i <= 3; i++) {
582
+ if (i == 3) {
583
+ throw new NoRetryError(`unable to set uniqueRequestId in table ${tableName}, record`, primaryKey);
584
+ }
585
+ //* get record from dynamo
586
+ let existingRecord = await dynamodbSharedLib.getItem(
587
+ _izContext,
588
+ await dynamodbSharedLib.tableName(_izContext, tableName),
589
+ primaryKey,
590
+ )
591
+ _izContext.logger.debug('existingRecord:', existingRecord);
592
+
593
+ let returnStatus = "process";
594
+
595
+ if (!existingRecord) {
596
+ // return ["recordNotFound", null]; // cannot return null, js cannot found object and throw error and dont know where it is?
597
+ return ["recordNotFound", {}];
598
+
599
+ } else if (!existingRecord[uniqueRequestIdFieldName]) {
600
+ // uniqueRequestId not yet exist
601
+
602
+ let updateAttributes = [
603
+ {
604
+ attributeName: uniqueRequestIdFieldName,
605
+ action: "set",
606
+ value: _izContext.uniqueRequestId,
607
+ },
608
+ ]
609
+ //* conditional check uniqueRequestId still not exist in case another thread added
610
+ const queryElementsWhenUpdate = {
611
+ logicalElements: [
612
+ {
613
+ type: "function",
614
+ function: "attribute_not_exists",
615
+ attribute: uniqueRequestIdFieldName,
616
+ }
617
+ ],
618
+ returnValues: 'ALL_NEW'
619
+ }
620
+ // // ----- main query ---------
621
+ let updateRecord = null;
622
+ try {
623
+ updateRecord = await dynamodbSharedLib.updateItem(
624
+ _izContext,
625
+ await dynamodbSharedLib.tableName(_izContext, tableName),
626
+ primaryKey,
627
+ updateAttributes,
628
+ queryElementsWhenUpdate,
629
+ { complexAttributes: true }, // force to complex
630
+ )
631
+
632
+ } catch (err) { // catch error when put and handle errors.
633
+ _izContext.logger.debug('updateItem storedCache expired err', err);
634
+ if (err.name == 'ConditionalCheckFailedException') {
635
+ continue;
636
+ } else { // e.g. ProvisionedThroughputExceededException
637
+ // throw new Error('Unhandled Error when putItem', err) // TT, if throw will stop
638
+ throw (err) // TT, if throw will stop
639
+ }
640
+ } //end catch err
641
+
642
+ } else if (existingRecord[uniqueRequestIdFieldName] !== uniqueRequestId) {
643
+
644
+ // when another request/uniqueRequestId need to stop process
645
+ returnStatus = "stop";
646
+ }
647
+ // if existingRecord[uniqueRequestIdFieldName] == uniqueRequestId then return "process"
648
+
649
+ return [returnStatus, existingRecord];
650
+
651
+ } // end loop 2 times
652
+ //should never get here
653
+ throw new NoRetryError('unexpected logic flow in checkUniqueRequestProcessing');
654
+
655
+ } catch (err) {
656
+ _izContext.logger.error('ERROR checkUniqueRequestProcessing:', err)
657
+ throw (err)
658
+ }
659
+ };
660
+
661
+ // ----- Helper funtion, common use case in triggerFlow ---------
662
+ module.exports.checkTimeCacheComplete = async (
663
+ _izContext,
664
+ fullMainTableName,
665
+ keyValues,
666
+ timeCacheComplete,
667
+ prefix
668
+ ) => {
669
+ let [returnValue, newTimeCacheComplete, uniqueRequestId] = await this.checkAndGetTimeCacheComplete(
670
+ _izContext,
671
+ fullMainTableName,
672
+ keyValues,
673
+ timeCacheComplete,
674
+ prefix
675
+ );
676
+ return [returnValue, uniqueRequestId];
677
+ }
678
+ module.exports.checkAndGetTimeCacheComplete = async (
679
+ _izContext,
680
+ fullMainTableName,
681
+ keyValues,
682
+ timeCacheComplete,
683
+ prefix
684
+ ) => {
685
+ _izContext.logger.debug('event checkTimeCacheComplete', {
686
+ fullMainTableName: fullMainTableName,
687
+ keyValues: keyValues,
688
+ timeCacheComplete: timeCacheComplete,
689
+ prefix: prefix
690
+ });
691
+
692
+ let uniqueRequestIdFieldName = triggeredCacheSharedLib.createFieldNameUniqueRequestId(prefix);
693
+ let cacheCompleteFieldName = triggeredCacheSharedLib.createFieldNameCacheComplete(prefix);
694
+ let statusFieldName = triggeredCacheSharedLib.createFieldNameStatus(prefix);
695
+
696
+ let getTimeCacheComplete = await dynamodbSharedLib.getItem(
697
+ _izContext,
698
+ fullMainTableName,
699
+ keyValues
700
+ )
701
+ _izContext.logger.debug(`getTimeCacheComplete from ${fullMainTableName}: `, getTimeCacheComplete);
702
+
703
+ if (!getTimeCacheComplete) {
704
+ throw new NoRetryError(`cannot getTimeCacheComplete from ${fullMainTableName}`);
705
+ }
706
+
707
+ // check if exist and match with initail create not changing in flow.
708
+ if (!getTimeCacheComplete[uniqueRequestIdFieldName]
709
+ // || !getTimeCacheComplete[cacheCompleteFieldName]
710
+ || (getTimeCacheComplete[cacheCompleteFieldName]
711
+ && getTimeCacheComplete[cacheCompleteFieldName] !== timeCacheComplete
712
+ && getTimeCacheComplete[statusFieldName] != "complete")
713
+ ) {
714
+ return [false, getTimeCacheComplete[cacheCompleteFieldName]];
715
+ } else {
716
+ return [true, getTimeCacheComplete[cacheCompleteFieldName], getTimeCacheComplete[uniqueRequestIdFieldName]];
717
+ }
718
+
719
+
720
+ }
721
+
722
+
723
+ // ----- shared both stored cache and triggered cache, check uniqueRequestId changed ---------
724
+ module.exports.checkCacheUniqueRequestId = async (
725
+ _izContext,
726
+ fullMainTableName,
727
+ keyValues,
728
+ checkUniqueRequestId,
729
+ uniqueRequestIdCompleteFieldName
730
+ ) => {
731
+
732
+ let cacheObject = await dynamodbSharedLib.getItem(
733
+ _izContext,
734
+ fullMainTableName,
735
+ keyValues
736
+ )
737
+ _izContext.logger.debug('cacheObject', cacheObject);
738
+
739
+ if (!cacheObject[uniqueRequestIdCompleteFieldName] || cacheObject[uniqueRequestIdCompleteFieldName] !== checkUniqueRequestId) {
740
+ return false
741
+ } else {
742
+ return true
743
+ }
744
+ }
745
+
746
+
747
+
748
+
749
+
750
+
751
+ // use shared findPendingStepIdsAwaitingStep
752
+ // module.exports.findPendingStepIdsAwaitingStepM = async (
753
+ // _izContext,
754
+ // awaitingStepId,
755
+ // tableName = "AwaitingMultipleSteps"
756
+ // ) => {
757
+
758
+ // let pendingStepIds = [];
759
+
760
+ // let listPendingStepIds = await dynamodbSharedLib.query(
761
+ // _izContext,
762
+ // dynamodbSharedLib.tableName(tableName),
763
+ // {
764
+ // awaitingStepId: awaitingStepId
765
+ // }
766
+ // );
767
+
768
+ // for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
769
+ // pendingStepIds.push(listPendingStepIds.Items[idx].pendingStepId)
770
+ // }
771
+
772
+ // return pendingStepIds
773
+ // }
774
+
775
+
776
+
777
+
778
+ // use shared removeAwaitingStep
779
+ // module.exports.removeAwaitingStep = async (
780
+ // _izContext,
781
+ // awaitingStepId,
782
+ // pendingStepId,
783
+ // tableName = "AwaitingMultipleSteps"
784
+ // ) => {
785
+
786
+ // await dynamodbSharedLib.deleteItem(
787
+ // _izContext,
788
+ // dynamodbSharedLib.tableName(tableName),
789
+ // {
790
+ // awaitingStepId: awaitingStepId,
791
+ // pendingStepId: pendingStepId
792
+ // }
793
+ // );
794
+ // }
795
+
796
+
797
+ //====================================== end AwaitingMultipleSteps
798
+
799
+
800
+
801
+ //====================================== end Multiple Lambda Invocations (Logic pagination of handling results)
802
+
803
+ module.exports.validateStartKeyParam = (
804
+ _izContext,
805
+ startKey,
806
+ partitionKeyFieldName,
807
+ sortKeyFieldName = null
808
+ ) => {
809
+
810
+ if (startKey && Object.keys(startKey).length != 0) {
811
+ if (
812
+ startKey.constructor !== Object ||
813
+ !startKey[partitionKeyFieldName]
814
+ ) {
815
+ startKey = false; // we can type check for false to see if invalid
816
+ }
817
+ if (sortKeyFieldName !== null && sortKeyFieldName !== undefined && !startKey[sortKeyFieldName]) {
818
+ startKey = false;
819
+ }
820
+ } else {
821
+ startKey = null; // if empty set to null so lib functions process correctly (not want empty object)
822
+ }
823
+
824
+ return startKey;
825
+ }
826
+
827
+
828
+ module.exports.validateMultipleInvocations = async (
829
+ _izContext,
830
+ messageProperty,
831
+ passOnStartKey = {},
832
+ numberInvocation,
833
+ queueName
834
+ ) => {
835
+ _izContext.logger.debug("Lib:validateMultipleInvocations", {
836
+ messageProperty: messageProperty,
837
+ passOnStartKey: passOnStartKey,
838
+ numberInvocation: numberInvocation,
839
+ queueName: queueName
840
+ });
841
+ try {
842
+ const maxProcessInvocationCountImport = 20
843
+ if (numberInvocation >= maxProcessInvocationCountImport) {
844
+ _izContext.logger.error("numberInvocation exceeds maxProcessInvocationCountImport limit");
845
+ throw new NoRetryError("numberInvocation exceeds maxProcessInvocationCountImport limit");
846
+ };
847
+
848
+ if (passOnStartKey) {
849
+ messageProperty["startKey"] = passOnStartKey
850
+ };
851
+ messageProperty["numberInvocation"] = numberInvocation;
852
+
853
+ let messageReInvokeFunction = {
854
+ MessageBody: JSON.stringify(
855
+ messageProperty
856
+ ),
857
+ QueueUrl: await sqsSharedLib.sqsQueueUrl(_izContext, queueName)
858
+ };
859
+ _izContext.logger.debug(`Send mesage to Dsq:${queueName} `, messageReInvokeFunction);
860
+ await sqs.sendMessage(_izContext, messageReInvokeFunction);
861
+
862
+
863
+ } catch (err) {
864
+ throw (err)
865
+ }
866
+ }