@izara_project/izara-core-library-asynchronous-flow 1.0.35 → 1.0.36

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 CHANGED
@@ -8,98 +8,19 @@ License, or (at your option) any later version.
8
8
 
9
9
  This program is distributed in the hope that it will be useful,
10
10
  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
12
  GNU Affero General Public License for more details.
13
13
 
14
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/>.
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import {
19
- createFieldNameUniqueRequestId,
20
- createConcatenatedAwaitingStepId,
21
- createConcatenatedPendingStepId,
22
- createAwaitingStepId,
23
- createPendingStepId,
24
- createParentFlowId,
25
- explodePendingStepId,
26
- createFieldNameCacheComplete,
27
- createFieldNameStatus,
28
- checkUniqueRequestProcessing,
29
- checkTimeCacheComplete,
30
- checkAndGetTimeCacheComplete,
31
- checkCacheUniqueRequestId,
32
- validateStartKeyParam,
33
- validateMultipleInvocations
34
- } from './src/asyncFlowSharedLib.js';
35
-
36
- import {
37
- createAwaitingStep,
38
- findPendingStepIdsAwaitingStep,
39
- findPendingStepsAwaitingStep,
40
- findPendingStepAwaitingStep,
41
- findPendingStepAwaitingStepWithCallingFlow,
42
- removeAwaitingStep,
43
- removeAwaitingStepWithCheckUniqueRequestId
44
- } from './src/awaitingStep.js';
45
-
46
- import {
47
- createAwaitingMultipleStepsWithAdditionalAttributes,
48
- initAwaitingStepsWithAttrs,
49
- createAwaitingMultipleSteps,
50
- createNewAwaitingMultipleStep,
51
- findPendingStepIdsAwaitingMultipleSteps,
52
- findPendingStepsAwaitingMultipleSteps,
53
- findPendingStepAwaitingMultipleSteps,
54
- findAwaitingMultipleStepByPending,
55
- checkAllAwaitingStepsFinishedShared,
56
- checkAllAwaitingStepsFinished,
57
- checkAllAwaitingStepsFinishedWithReturnParams,
58
- clearAllAwaitingSteps,
59
- removeAwaitingMultipleStep,
60
- createAwaitingMultipleStepsWithAdditionalAttributesAndPublish
61
- } from './src/awaitingMultipleSteps.js';
18
+ import * as asyncFlowSharedLib from './src/asyncFlowSharedLib.js';
19
+ import * as awaitingStep from './src/awaitingStep.js';
20
+ import * as awaitingMultipleSteps from './src/awaitingMultipleSteps.js';
62
21
 
63
22
  export default {
64
- // asyncFlowSharedLib
65
- createFieldNameUniqueRequestId,
66
- createConcatenatedAwaitingStepId,
67
- createConcatenatedPendingStepId,
68
- createAwaitingStepId,
69
- createPendingStepId,
70
- createParentFlowId,
71
- explodePendingStepId,
72
- createFieldNameCacheComplete,
73
- createFieldNameStatus,
74
- checkUniqueRequestProcessing,
75
- checkTimeCacheComplete,
76
- checkAndGetTimeCacheComplete,
77
- checkCacheUniqueRequestId,
78
- validateStartKeyParam,
79
- validateMultipleInvocations,
80
-
81
- // awaitingStep
82
- createAwaitingStep,
83
- findPendingStepIdsAwaitingStep,
84
- findPendingStepsAwaitingStep,
85
- findPendingStepAwaitingStep,
86
- findPendingStepAwaitingStepWithCallingFlow,
87
- removeAwaitingStep,
88
- removeAwaitingStepWithCheckUniqueRequestId,
89
-
90
- // awaitingMultipleSteps
91
- createAwaitingMultipleStepsWithAdditionalAttributes,
92
- initAwaitingStepsWithAttrs,
93
- createAwaitingMultipleSteps,
94
- createNewAwaitingMultipleStep,
95
- findPendingStepIdsAwaitingMultipleSteps,
96
- findPendingStepsAwaitingMultipleSteps,
97
- findPendingStepAwaitingMultipleSteps,
98
- findAwaitingMultipleStepByPending,
99
- checkAllAwaitingStepsFinishedShared,
100
- checkAllAwaitingStepsFinished,
101
- checkAllAwaitingStepsFinishedWithReturnParams,
102
- clearAllAwaitingSteps,
103
- removeAwaitingMultipleStep,
104
- createAwaitingMultipleStepsWithAdditionalAttributesAndPublish
23
+ ...asyncFlowSharedLib,
24
+ ...awaitingStep,
25
+ ...awaitingMultipleSteps
105
26
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "author": "Sven Mason <thebarbariansven@gmail.com>",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "homepage": "https://bitbucket.org/izara-core-libraries/izara-core-library-asynchronous-flow#readme",
6
- "version": "1.0.35",
6
+ "version": "1.0.36",
7
7
  "description": "Shared asynchronous flow logic",
8
8
  "type": "module",
9
9
  "main": "index.js",
@@ -106,7 +106,7 @@ export function createParentFlowId(prefix = '') {
106
106
  */
107
107
  export function explodePendingStepId(pendingStepId, prefix = '') {
108
108
  if (!pendingStepId) {
109
- throw NoRetryError('pendingStepId is required');
109
+ throw new NoRetryError('pendingStepId is required');
110
110
  }
111
111
 
112
112
  if (prefix === '') {
@@ -195,7 +195,7 @@ export async function checkUniqueRequestProcessing(
195
195
  );
196
196
 
197
197
  if (!tableName || !primaryKey) {
198
- throw NoRetryError('tableName and primaryKey are required');
198
+ throw new NoRetryError('tableName and primaryKey are required');
199
199
  }
200
200
 
201
201
  try {
@@ -327,7 +327,7 @@ export async function checkTimeCacheComplete(
327
327
  });
328
328
 
329
329
  if (!fullMainTableName || !keyValues || !timeCacheComplete || !prefix) {
330
- throw NoRetryError(
330
+ throw new NoRetryError(
331
331
  'fullMainTableName, keyValues, timeCacheComplete and prefix are required'
332
332
  );
333
333
  }
@@ -22,191 +22,79 @@ import snsSharedLib from '@izara_project/izara-core-library-sns';
22
22
 
23
23
  import { createParentFlowId } from './asyncFlowSharedLib.js';
24
24
 
25
- //====================================== AwaitingMultipleSteps
26
- // AwaitingMultipleSteps is when a flow is awaiting multiple external flows before it can continue
27
- // One awaitingStepId can have multiple pendingStepIds that are awaiting it
28
- // One pendingStepId can have multiple awaitingStepIds it is awaiting
29
- // logic can prepend any prefix to awaitingStepId if want to be share one table and differentiate different external step ids
30
-
31
- /**
32
- * Create multiple awaiting records with optional per-record additional attributes.
33
- * Writes to both "AwaitingMultipleSteps" and "AwaitingMultipleStepByPending".
34
- * @async
35
- * @param {IzContext} _izContext
36
- * @param {AwaitingMultiInputRecord[]} records
37
- * @param {string} pendingStepId
38
- * @returns {Promise<void>}
39
- */
40
- export async function createAwaitingMultipleStepsWithAdditionalAttributes(
25
+ async function putAwaitingMultipleStepRecords(
41
26
  _izContext,
42
- records, // array of object including properties: awaitingStepId / (optional) additionalAttributes
43
- pendingStepId
27
+ records,
28
+ pendingStepId,
29
+ sharedAttributes = {}
44
30
  ) {
45
- _izContext.logger.debug(
46
- '[Lib:AsyncFlow:createAwaitingMultipleStepsWithAdditionalAttributes] Input: ',
47
- {
48
- records,
49
- pendingStepId
50
- }
31
+ const multipleStepsTable = dynamodbSharedLib.tableName(
32
+ _izContext,
33
+ 'AwaitingMultipleSteps'
34
+ );
35
+ const byPendingTable = dynamodbSharedLib.tableName(
36
+ _izContext,
37
+ 'AwaitingMultipleStepByPending'
51
38
  );
52
39
 
53
- const promiseArray = [];
54
-
55
- for (const record of records) {
56
- const attributesWhenCreate = {
57
- awaitingStepId: record.awaitingStepId,
58
- pendingStepId: pendingStepId,
59
- timestamp: Date.now(),
60
- ...record.additionalAttributes
61
- };
62
-
63
- promiseArray.push(
64
- dynamodbSharedLib.putItem(
65
- _izContext,
66
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
67
- attributesWhenCreate
68
- )
69
- );
70
- promiseArray.push(
71
- dynamodbSharedLib.putItem(
72
- _izContext,
73
- dynamodbSharedLib.tableName(
74
- _izContext,
75
- 'AwaitingMultipleStepByPending'
76
- ),
77
- {
78
- pendingStepId: pendingStepId,
79
- awaitingStepId: record.awaitingStepId,
80
- timestamp: Date.now()
81
- }
82
- )
83
- );
84
- }
40
+ await Promise.all(
41
+ records.flatMap(({ awaitingStepId, additionalAttributes }) => {
42
+ const baseAttrs = {
43
+ awaitingStepId,
44
+ pendingStepId,
45
+ ...sharedAttributes
46
+ };
85
47
 
86
- await Promise.all(promiseArray);
48
+ return [
49
+ dynamodbSharedLib.putItem(_izContext, multipleStepsTable, baseAttrs),
50
+ dynamodbSharedLib.putItem(_izContext, byPendingTable, {
51
+ ...baseAttrs,
52
+ additionalAttributes
53
+ })
54
+ ];
55
+ })
56
+ );
87
57
  }
88
58
 
89
- /**
90
- * Batch initializes multiple pending steps in DynamoDB.
91
- * Records status in both the primary tracking table and the pending-relation table.
92
- * * @async
93
- * @param {Object} _izContext - The system context providing logger and shared utilities.
94
- * @param {Array<Object>} records - Array of step objects to be initialized.
95
- * @param {string} records[].awaitingStepId - The unique identifier for the specific step.
96
- * @param {Object} [records[].additionalAttributes] - Optional metadata to store with the step.
97
- * @param {string} pendingStepId - The parent ID linking all related steps together.
98
- * @returns {Promise<void>}
99
- */
100
- export async function initAwaitingStepsWithAttrs(
101
- _izContext,
102
- records,
103
- pendingStepId
104
- ) {
105
- _izContext.logger.debug(
106
- '[Lib:AsyncFlow:initAwaitingStepsWithAttrs] Input: ',
59
+ async function queryAwaitingMultipleSteps(_izContext, awaitingStepId) {
60
+ return dynamodbSharedLib.query(
61
+ _izContext,
62
+ dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
107
63
  {
108
- records,
109
- pendingStepId
64
+ awaitingStepId
110
65
  }
111
66
  );
112
-
113
- const timestamp = Date.now();
114
- const promiseArray = [];
115
-
116
- for (const record of records) {
117
- promiseArray.push(
118
- dynamodbSharedLib.putItem(
119
- _izContext,
120
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
121
- {
122
- awaitingStepId: record.awaitingStepId,
123
- pendingStepId,
124
- timestamp
125
- }
126
- )
127
- );
128
- promiseArray.push(
129
- dynamodbSharedLib.putItem(
130
- _izContext,
131
- dynamodbSharedLib.tableName(
132
- _izContext,
133
- 'AwaitingMultipleStepByPending'
134
- ),
135
- {
136
- pendingStepId,
137
- awaitingStepId: record.awaitingStepId,
138
- timestamp,
139
- ...record.additionalAttributes
140
- }
141
- )
142
- );
143
- }
144
-
145
- await Promise.all(promiseArray);
146
67
  }
147
68
 
69
+ //====================================== AwaitingMultipleSteps
70
+ // AwaitingMultipleSteps is when a flow is awaiting multiple external flows before it can continue
71
+ // One awaitingStepId can have multiple pendingStepIds that are awaiting it
72
+ // One pendingStepId can have multiple awaitingStepIds it is awaiting
73
+ // logic can prepend any prefix to awaitingStepId if want to be share one table and differentiate different external step ids
74
+
148
75
  /**
149
- * Create multiple awaiting records given an array of awaitingStepIds.
76
+ * Create multiple awaiting records with optional per-record additional attributes.
150
77
  * Writes to both "AwaitingMultipleSteps" and "AwaitingMultipleStepByPending".
151
78
  * @async
152
79
  * @param {IzContext} _izContext
153
- * @param {string[]} records - array of awaitingStepIds
80
+ * @param {AwaitingMultiInputRecord[]} records
154
81
  * @param {string} pendingStepId
155
82
  * @returns {Promise<void>}
156
83
  */
157
- export async function createAwaitingMultipleSteps(
84
+ export async function createAwaitingMultipleStepsWithAdditionalAttributes(
158
85
  _izContext,
159
- records, // array of awaitingStepIds
86
+ records, // array of object including properties: awaitingStepId / (optional) additionalAttributes
160
87
  pendingStepId
161
88
  ) {
162
89
  _izContext.logger.debug(
163
- '[Lib:AsyncFlow:createAwaitingMultipleSteps] Input: ',
164
- {
165
- records: records,
166
- pendingStepId: pendingStepId
167
- }
90
+ '[Lib:AsyncFlow:createAwaitingMultipleStepsWithAdditionalAttributes] Input: ',
91
+ { records, pendingStepId }
168
92
  );
169
93
 
170
- const promiseArray = [];
171
-
172
- for (const record of records) {
173
- const attributesWhenCreate = {
174
- awaitingStepId: record,
175
- pendingStepId: pendingStepId,
176
- timestamp: Date.now()
177
- };
178
- _izContext.logger.debug(
179
- '[Lib:AsyncFlow:createAwaitingMultipleSteps] attributesWhenCreate: ',
180
- attributesWhenCreate
181
- );
182
- promiseArray.push(
183
- dynamodbSharedLib.putItem(
184
- _izContext,
185
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
186
- attributesWhenCreate
187
- )
188
- );
189
-
190
- _izContext.logger.debug(
191
- '[Lib:AsyncFlow:createAwaitingMultipleSteps] putItem:AwaitingMultipleStepByPending',
192
- {
193
- awaitingStepId: attributesWhenCreate.awaitingStepId
194
- }
195
- );
196
-
197
- promiseArray.push(
198
- dynamodbSharedLib.putItem(
199
- _izContext,
200
- dynamodbSharedLib.tableName(
201
- _izContext,
202
- 'AwaitingMultipleStepByPending'
203
- ),
204
- attributesWhenCreate
205
- )
206
- );
207
- }
208
-
209
- await Promise.all(promiseArray);
94
+ const timestamp = Date.now();
95
+ await putAwaitingMultipleStepRecords(_izContext, records, pendingStepId, {
96
+ timestamp
97
+ });
210
98
  }
211
99
 
212
100
  /**
@@ -219,14 +107,14 @@ export async function createAwaitingMultipleSteps(
219
107
  * The same object is applied to all steps (caller cannot know the generated UUIDs beforehand).
220
108
  * @returns {Promise<string>} The awaiting step ID created.
221
109
  */
222
- export async function createNewAwaitingMultipleStep(
110
+ export async function createAwaitingMultipleStep(
223
111
  _izContext,
224
112
  pendingStepId,
225
113
  prefix = '',
226
114
  additionalAttributes = {}
227
115
  ) {
228
116
  _izContext.logger.debug(
229
- '[Lib:AsyncFlow:createNewAwaitingMultipleStep] Input: ',
117
+ '[Lib:AsyncFlow:createAwaitingMultipleStep] Input: ',
230
118
  {
231
119
  pendingStepId,
232
120
  additionalAttributes
@@ -234,38 +122,25 @@ export async function createNewAwaitingMultipleStep(
234
122
  );
235
123
 
236
124
  const parentFlowId = createParentFlowId(prefix);
237
-
238
- const attributesWhenCreate = {
239
- awaitingStepId: parentFlowId,
240
- pendingStepId: pendingStepId,
125
+ const sharedAttributes = {
241
126
  complete: false,
242
127
  errorsFound: []
243
128
  };
244
129
 
245
130
  _izContext.logger.debug(
246
- '[Lib:AsyncFlow:createNewAwaitingMultipleStep] attributesWhenCreate: ',
247
- attributesWhenCreate
248
- );
249
-
250
- await dynamodbSharedLib.putItem(
251
- _izContext,
252
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
253
- attributesWhenCreate
131
+ '[Lib:AsyncFlow:createAwaitingMultipleStep] sharedAttributes: ',
132
+ { awaitingStepId: parentFlowId, pendingStepId, ...sharedAttributes }
254
133
  );
255
134
 
256
- const attributesForPending = {
257
- ...attributesWhenCreate,
258
- additionalAttributes: additionalAttributes
259
- };
260
-
261
- await dynamodbSharedLib.putItem(
135
+ await putAwaitingMultipleStepRecords(
262
136
  _izContext,
263
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
264
- attributesForPending
137
+ [{ awaitingStepId: parentFlowId, additionalAttributes }],
138
+ pendingStepId,
139
+ sharedAttributes
265
140
  );
266
141
 
267
142
  _izContext.logger.debug(
268
- '[Lib:AsyncFlow:createNewAwaitingMultipleStep] Returning: ',
143
+ '[Lib:AsyncFlow:createAwaitingMultipleStep] Returning: ',
269
144
  parentFlowId
270
145
  );
271
146
 
@@ -273,18 +148,18 @@ export async function createNewAwaitingMultipleStep(
273
148
  }
274
149
 
275
150
  /**
276
- * Find all pendingStepIds that are waiting on a given awaitingStepId (multiple).
151
+ * Find one pendingStepId waiting on a given awaitingStepId (multiple).
277
152
  * @async
278
153
  * @param {IzContext} _izContext
279
154
  * @param {string} awaitingStepId
280
- * @returns {Promise<string[]>}
155
+ * @returns {Promise<string>}
281
156
  */
282
- export async function findPendingStepIdsAwaitingMultipleSteps(
157
+ export async function findPendingStepIdAwaitingMultipleSteps(
283
158
  _izContext,
284
159
  awaitingStepId
285
160
  ) {
286
161
  _izContext.logger.debug(
287
- '[Lib:findPendingStepIdsAwaitingMultipleSteps] Input parameters',
162
+ '[Lib:findPendingStepIdAwaitingMultipleSteps] Input parameters',
288
163
  {
289
164
  awaitingStepId: awaitingStepId
290
165
  }
@@ -292,31 +167,29 @@ export async function findPendingStepIdsAwaitingMultipleSteps(
292
167
 
293
168
  if (!awaitingStepId) {
294
169
  _izContext.logger.error(
295
- '[Lib:findPendingStepIdsAwaitingMultipleSteps] awaitingStepId is required'
170
+ '[Lib:findPendingStepIdAwaitingMultipleSteps] awaitingStepId is required'
296
171
  );
297
172
  throw new NoRetryError('awaitingStepId is required');
298
173
  }
299
174
 
300
- const pendingStepIds = [];
301
-
302
- const listPendingStepIds = await dynamodbSharedLib.query(
175
+ const listPendingSteps = await queryAwaitingMultipleSteps(
303
176
  _izContext,
304
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
305
- {
306
- awaitingStepId: awaitingStepId
307
- }
177
+ awaitingStepId
308
178
  );
309
-
310
- for (const item of listPendingStepIds.Items) {
311
- pendingStepIds.push(item.pendingStepId);
179
+ if (listPendingSteps.Items.length !== 1) {
180
+ _izContext.logger.error(
181
+ '[Lib:findPendingStepIdAwaitingMultipleSteps] listPendingSteps not equals 1'
182
+ );
183
+ throw new NoRetryError('listPendingSteps not equals 1');
312
184
  }
185
+ const pendingStepId = listPendingSteps.Items[0].pendingStepId;
313
186
 
314
187
  _izContext.logger.debug(
315
- '[Lib:findPendingStepIdsAwaitingMultipleSteps] Pending step IDs',
316
- pendingStepIds
188
+ '[Lib:findPendingStepIdAwaitingMultipleSteps] Pending step ID',
189
+ pendingStepId
317
190
  );
318
191
 
319
- return pendingStepIds;
192
+ return pendingStepId;
320
193
  }
321
194
 
322
195
  /**
@@ -344,12 +217,9 @@ export async function findPendingStepsAwaitingMultipleSteps(
344
217
  throw new NoRetryError('awaitingStepId is required');
345
218
  }
346
219
 
347
- const listPendingSteps = await dynamodbSharedLib.query(
220
+ const listPendingSteps = await queryAwaitingMultipleSteps(
348
221
  _izContext,
349
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
350
- {
351
- awaitingStepId: awaitingStepId
352
- }
222
+ awaitingStepId
353
223
  );
354
224
 
355
225
  _izContext.logger.debug(
@@ -386,12 +256,9 @@ export async function findPendingStepAwaitingMultipleSteps(
386
256
  throw new NoRetryError('awaitingStepId is required');
387
257
  }
388
258
 
389
- const listPendingSteps = await dynamodbSharedLib.query(
259
+ const listPendingSteps = await queryAwaitingMultipleSteps(
390
260
  _izContext,
391
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
392
- {
393
- awaitingStepId: awaitingStepId
394
- }
261
+ awaitingStepId
395
262
  );
396
263
 
397
264
  _izContext.logger.debug(
@@ -455,102 +322,6 @@ export async function findAwaitingMultipleStepByPending(
455
322
  return listAwaitingSteps.Items;
456
323
  }
457
324
 
458
- /**
459
- * Updates the completion status of a specific awaiting step within a multiple-step pending process.
460
- * Marks the given awaiting step as complete and stores any encountered errors or additional attributes.
461
- *
462
- * @param {Object} _izContext - The execution context object containing logger and other utilities.
463
- * @param {string} pendingStepId - The unique identifier of the parent pending step.
464
- * @param {string|null} [currentAwaitingStepId=null] - The unique identifier of the specific awaiting step being processed. If null, no update is performed.
465
- * @param {Array<Object|string>} [errorsFound=[]] - An array of errors encountered during the step's execution.
466
- * @param {Object} [additionalAttributes={}] - Any extra data to save along with the completion status.
467
- * @returns {Promise<void>} Resolves when the update operation is complete.
468
- */
469
- export async function checkAllAwaitingStepsFinishedShared(
470
- _izContext,
471
- pendingStepId,
472
- currentAwaitingStepId = null,
473
- errorsFound = [],
474
- additionalAttributes = {}
475
- ) {
476
- _izContext.logger.debug(
477
- '[Lib:checkAllAwaitingStepsFinishedShared] Input parameters',
478
- {
479
- pendingStepId: pendingStepId,
480
- currentAwaitingStepId: currentAwaitingStepId,
481
- errorsFound: errorsFound
482
- }
483
- );
484
-
485
- if (currentAwaitingStepId) {
486
- await dynamodbSharedLib.updateItem(
487
- _izContext,
488
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
489
- {
490
- pendingStepId: pendingStepId,
491
- awaitingStepId: currentAwaitingStepId
492
- },
493
- {
494
- complete: true,
495
- errorsFound: errorsFound,
496
- additionalAttributes: additionalAttributes
497
- }
498
- );
499
- }
500
- }
501
-
502
- /**
503
- * Same as checkAllAwaitingStepsFinished but writes errors/additionalAttributes to the *_ByPending row.
504
- * @async
505
- * @param {IzContext} _izContext
506
- * @param {string} pendingStepId
507
- * @param {string|null} [currentAwaitingStepId=null]
508
- * @param {any[]} [errorsFound=[]]
509
- * @param {Attrs} [additionalAttributes={}]
510
- * @returns {Promise<boolean>}
511
- */
512
- export async function checkAllAwaitingStepsFinished(
513
- _izContext,
514
- pendingStepId,
515
- currentAwaitingStepId = null,
516
- errorsFound = [],
517
- additionalAttributes = {}
518
- ) {
519
- _izContext.logger.debug(
520
- '[Lib:checkAllAwaitingStepsFinished] Input parameters',
521
- {
522
- pendingStepId: pendingStepId,
523
- currentAwaitingStepId: currentAwaitingStepId,
524
- errorsFound: errorsFound
525
- }
526
- );
527
-
528
- await checkAllAwaitingStepsFinishedShared(
529
- _izContext,
530
- pendingStepId,
531
- currentAwaitingStepId,
532
- errorsFound,
533
- additionalAttributes
534
- );
535
-
536
- const items = await dynamodbSharedLib.query(
537
- _izContext,
538
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
539
- { pendingStepId },
540
- { limit: 50 }
541
- );
542
-
543
- for (const item of items.Items) {
544
- if (item.awaitingStepId === currentAwaitingStepId) continue;
545
- if (!item.complete) return false;
546
- }
547
-
548
- _izContext.logger.debug(
549
- '[Lib:checkAllAwaitingStepsFinished] All steps are complete'
550
- );
551
- return true;
552
- }
553
-
554
325
  /**
555
326
  * Checks if all awaiting steps for a pending step are complete after marking the current one as complete.
556
327
  * If all steps are finished, returns an object with `isComplete: true` and the collected data.
@@ -560,7 +331,7 @@ export async function checkAllAwaitingStepsFinished(
560
331
  * @param {string} pendingStepId
561
332
  * @param {string|null} [currentAwaitingStepId=null] - The step that has just finished.
562
333
  * @param {any[]} [errorsFound=[]]
563
- * @param {Attrs} [additionalAttributes={}]
334
+ * @param {Attrs} [returnValues={}]
564
335
  * @returns {Promise<{
565
336
  * isComplete: boolean,
566
337
  * collectedAttributes: Record<string, any>|null,
@@ -573,7 +344,10 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
573
344
  pendingStepId,
574
345
  currentAwaitingStepId = null,
575
346
  errorsFound = [],
576
- additionalAttributes = {}
347
+ returnValues = {},
348
+ settings = {
349
+ checkIsAllError: true
350
+ }
577
351
  ) {
578
352
  _izContext.logger.debug(
579
353
  '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Input parameters',
@@ -581,24 +355,26 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
581
355
  pendingStepId,
582
356
  currentAwaitingStepId,
583
357
  errorsFound,
584
- additionalAttributes
358
+ returnValues
585
359
  }
586
360
  );
587
361
 
588
- if (!pendingStepId || !currentAwaitingStepId) {
589
- throw NoRetryError(
590
- '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] pendingStepId or currentAwaitingStepId is required'
362
+ if (currentAwaitingStepId) {
363
+ await dynamodbSharedLib.updateItem(
364
+ _izContext,
365
+ dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
366
+ {
367
+ pendingStepId: pendingStepId,
368
+ awaitingStepId: currentAwaitingStepId
369
+ },
370
+ {
371
+ complete: true,
372
+ errorsFound: errorsFound,
373
+ returnValues: returnValues
374
+ }
591
375
  );
592
376
  }
593
377
 
594
- await checkAllAwaitingStepsFinishedShared(
595
- _izContext,
596
- pendingStepId,
597
- currentAwaitingStepId,
598
- errorsFound,
599
- additionalAttributes
600
- );
601
-
602
378
  const awaitingStepItems = await dynamodbSharedLib.query(
603
379
  _izContext,
604
380
  dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
@@ -612,15 +388,17 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
612
388
  );
613
389
 
614
390
  // Pass 1: check completeness of all other steps before collecting any data
615
- for (const item of awaitingStepItems.Items) {
616
- if (item.awaitingStepId !== currentAwaitingStepId && !item.complete) {
617
- return {
618
- isComplete: false,
619
- collectedAttributes: null,
620
- collectedErrors: [],
621
- sharedAdditionalAttributes: null
622
- };
623
- }
391
+ const hasIncomplete = awaitingStepItems.Items.some(
392
+ item => item.awaitingStepId !== currentAwaitingStepId && !item.complete
393
+ );
394
+
395
+ if (hasIncomplete) {
396
+ return {
397
+ isComplete: false,
398
+ collectedAttributes: null,
399
+ collectedErrors: [],
400
+ returnValues: null
401
+ };
624
402
  }
625
403
 
626
404
  _izContext.logger.debug(
@@ -629,22 +407,21 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
629
407
 
630
408
  // Pass 2: all steps are complete — collect errors and attributes
631
409
  const collectedAttributes = {};
632
- const collectedErrors = [];
633
- let sharedAdditionalAttributes = null;
410
+ let sharedReturnValues = null;
634
411
 
635
412
  for (const item of awaitingStepItems.Items) {
636
- if (item.errorsFound?.length > 0) {
637
- collectedErrors.push(...item.errorsFound);
638
- }
639
-
640
- if (item.additionalAttributes) {
641
- collectedAttributes[item.awaitingStepId] = item.additionalAttributes;
642
- if (sharedAdditionalAttributes === null) {
643
- sharedAdditionalAttributes = item.additionalAttributes;
644
- }
413
+ if (item.returnValues) {
414
+ collectedAttributes[item.awaitingStepId] = item.returnValues;
415
+ sharedReturnValues ??= item.returnValues; // ponytail: use nullish coalescing
645
416
  }
646
417
  }
647
418
 
419
+ const collectedErrors = awaitingStepItems.Items.flatMap(item =>
420
+ item.errorsFound?.length > 0
421
+ ? [`Error found in step ${item.awaitingStepId}`, ...item.errorsFound]
422
+ : []
423
+ );
424
+
648
425
  _izContext.logger.debug(
649
426
  '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Pass 2: all steps are complete — collect errors and attributes'
650
427
  );
@@ -653,7 +430,14 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
653
430
  isComplete: true,
654
431
  collectedAttributes,
655
432
  collectedErrors,
656
- sharedAdditionalAttributes
433
+ returnValues: sharedReturnValues,
434
+ additionalAttributes:
435
+ awaitingStepItems.Items[0]?.additionalAttributes || null,
436
+ ...(settings.checkIsAllError && {
437
+ isAllError: awaitingStepItems.Items.every(
438
+ item => item.errorsFound?.length > 0
439
+ )
440
+ })
657
441
  };
658
442
  }
659
443
 
@@ -670,7 +454,9 @@ export async function clearAllAwaitingSteps(_izContext, pendingStepId) {
670
454
  });
671
455
 
672
456
  if (!pendingStepId) {
673
- throw NoRetryError('[Lib:clearAllAwaitingSteps] pendingStepId is required');
457
+ throw new NoRetryError(
458
+ '[Lib:clearAllAwaitingSteps] pendingStepId is required'
459
+ );
674
460
  }
675
461
 
676
462
  // ----- query items from GSI table
@@ -747,7 +533,7 @@ export async function removeAwaitingMultipleStep(
747
533
  });
748
534
 
749
535
  if (!awaitingStepId || !pendingStepId) {
750
- throw NoRetryError(
536
+ throw new NoRetryError(
751
537
  '[Lib:removeAwaitingMultipleStep] awaitingStepId or pendingStepId is required'
752
538
  );
753
539
  }
@@ -836,14 +622,14 @@ export async function createAwaitingMultipleStepsWithAdditionalAttributesAndPubl
836
622
  );
837
623
 
838
624
  if (!Array.isArray(messages)) {
839
- throw NoRetryError('messages must be an array');
625
+ throw new NoRetryError('messages must be an array');
840
626
  }
841
627
 
842
628
  const flowTag = flowType.flowTag;
843
629
  const serviceTag = flowType.serviceTag;
844
630
 
845
631
  if (flowTag === undefined || serviceTag === undefined) {
846
- throw NoRetryError('flowType must have both flowTag and serviceTag');
632
+ throw new NoRetryError('flowType must have both flowTag and serviceTag');
847
633
  }
848
634
 
849
635
  const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
@@ -857,21 +643,27 @@ export async function createAwaitingMultipleStepsWithAdditionalAttributesAndPubl
857
643
  topicArn
858
644
  );
859
645
 
860
- const messagesWithParentId = await Promise.all(
861
- messages.map(async message => {
862
- const parentFlowId = await createNewAwaitingMultipleStep(
863
- _izContext,
864
- pendingStepId,
865
- prefix,
866
- additionalAttributes
867
- );
868
- return {
869
- ...message,
870
- parentFlowId
871
- };
872
- })
646
+ // Generate IDs and prepare messages
647
+ const messagesWithParentId = messages.map(message => {
648
+ const parentFlowId = createParentFlowId(prefix);
649
+ return { ...message, parentFlowId };
650
+ });
651
+
652
+ // 1. Wait for all DynamoDB records to be created first (to avoid race conditions)
653
+ await putAwaitingMultipleStepRecords(
654
+ _izContext,
655
+ messagesWithParentId.map(({ parentFlowId }) => ({
656
+ awaitingStepId: parentFlowId,
657
+ additionalAttributes
658
+ })),
659
+ pendingStepId,
660
+ {
661
+ complete: false,
662
+ errorsFound: []
663
+ }
873
664
  );
874
665
 
666
+ // 2. Then publish messages concurrently
875
667
  await Promise.all(
876
668
  messagesWithParentId.map(message =>
877
669
  sns.publishAsync(_izContext, {
@@ -19,6 +19,16 @@ import { NoRetryError } from '@izara_project/izara-core-library-core';
19
19
  import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
20
20
  import { createFieldNameUniqueRequestId } from './asyncFlowSharedLib.js';
21
21
 
22
+ async function queryAwaitingSteps(_izContext, awaitingStepId) {
23
+ return dynamodbSharedLib.query(
24
+ _izContext,
25
+ dynamodbSharedLib.tableName(_izContext, 'AwaitingStep'),
26
+ {
27
+ awaitingStepId
28
+ }
29
+ );
30
+ }
31
+
22
32
  //====================================== AwaitingStep //======================================
23
33
  // AwaitingStep is when a flow is awaiting one external flow, and will continue it's flow once that one external flow is complete
24
34
  // One awaitingStepId can have multiple pendingStepIds that are awaiting it
@@ -90,24 +100,11 @@ export async function findPendingStepIdsAwaitingStep(
90
100
  );
91
101
 
92
102
  if (!awaitingStepId) {
93
- throw NoRetryError('awaitingStepId is required');
103
+ throw new NoRetryError('awaitingStepId is required');
94
104
  }
95
105
 
96
- const pendingStepIds = [];
97
-
98
- const listPendingStepIds = await dynamodbSharedLib.query(
99
- _izContext,
100
- dynamodbSharedLib.tableName(_izContext, 'AwaitingStep'),
101
- {
102
- awaitingStepId: awaitingStepId
103
- }
104
- );
105
-
106
- for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
107
- pendingStepIds.push(listPendingStepIds.Items[idx].pendingStepId);
108
- }
109
-
110
- return pendingStepIds;
106
+ const listPendingStepIds = await queryAwaitingSteps(_izContext, awaitingStepId);
107
+ return listPendingStepIds.Items.map(item => item.pendingStepId);
111
108
  }
112
109
 
113
110
  /**
@@ -126,16 +123,10 @@ export async function findPendingStepsAwaitingStep(_izContext, awaitingStepId) {
126
123
  );
127
124
 
128
125
  if (!awaitingStepId) {
129
- throw NoRetryError('awaitingStepId is required');
126
+ throw new NoRetryError('awaitingStepId is required');
130
127
  }
131
128
 
132
- const listPendingSteps = await dynamodbSharedLib.query(
133
- _izContext,
134
- dynamodbSharedLib.tableName(_izContext, 'AwaitingStep'),
135
- {
136
- awaitingStepId: awaitingStepId
137
- }
138
- );
129
+ const listPendingSteps = await queryAwaitingSteps(_izContext, awaitingStepId);
139
130
  _izContext.logger.debug('Record of awaitingStepId', listPendingSteps);
140
131
  return listPendingSteps.Items;
141
132
  }
@@ -157,16 +148,10 @@ export async function findPendingStepAwaitingStep(_izContext, awaitingStepId) {
157
148
  );
158
149
 
159
150
  if (!awaitingStepId) {
160
- throw NoRetryError('awaitingStepId is required');
151
+ throw new NoRetryError('awaitingStepId is required');
161
152
  }
162
153
 
163
- const listPendingSteps = await dynamodbSharedLib.query(
164
- _izContext,
165
- dynamodbSharedLib.tableName(_izContext, 'AwaitingStep'),
166
- {
167
- awaitingStepId: awaitingStepId
168
- }
169
- );
154
+ const listPendingSteps = await queryAwaitingSteps(_izContext, awaitingStepId);
170
155
 
171
156
  _izContext.logger.debug(
172
157
  '[Lib:AsyncFlow:findPendingStepAwaitingStep] Found records: ',
@@ -174,7 +159,7 @@ export async function findPendingStepAwaitingStep(_izContext, awaitingStepId) {
174
159
  );
175
160
 
176
161
  if (listPendingSteps.Items.length !== 1) {
177
- throw NoRetryError('listPendingSteps not equals 1');
162
+ throw new NoRetryError('listPendingSteps not equals 1');
178
163
  }
179
164
  return listPendingSteps.Items[0];
180
165
  }
@@ -200,7 +185,7 @@ export async function findPendingStepAwaitingStepWithCallingFlow(
200
185
  );
201
186
 
202
187
  if (!awaitingStepId) {
203
- throw NoRetryError('awaitingStepId is required');
188
+ throw new NoRetryError('awaitingStepId is required');
204
189
  }
205
190
 
206
191
  let callingFlowConfig = null;
@@ -239,7 +224,7 @@ export async function removeAwaitingStep(
239
224
  });
240
225
 
241
226
  if (!awaitingStepId || !pendingStepId) {
242
- throw NoRetryError('awaitingStepId and pendingStepId are required');
227
+ throw new NoRetryError('awaitingStepId and pendingStepId are required');
243
228
  }
244
229
 
245
230
  await dynamodbSharedLib.deleteItem(
@@ -282,7 +267,7 @@ export async function removeAwaitingStepWithCheckUniqueRequestId(
282
267
  );
283
268
 
284
269
  if (!awaitingStepId || !pendingStepId || !checkUniqueRequestId) {
285
- throw NoRetryError(
270
+ throw new NoRetryError(
286
271
  'awaitingStepId, pendingStepId and checkUniqueRequestId are required'
287
272
  );
288
273
  }