@izara_project/izara-core-library-asynchronous-flow 1.0.34 → 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.34",
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
131
+ '[Lib:AsyncFlow:createAwaitingMultipleStep] sharedAttributes: ',
132
+ { awaitingStepId: parentFlowId, pendingStepId, ...sharedAttributes }
248
133
  );
249
134
 
250
- await dynamodbSharedLib.putItem(
135
+ await putAwaitingMultipleStepRecords(
251
136
  _izContext,
252
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
253
- attributesWhenCreate
254
- );
255
-
256
- const attributesForPending = {
257
- ...attributesWhenCreate,
258
- additionalAttributes: additionalAttributes
259
- };
260
-
261
- await dynamodbSharedLib.putItem(
262
- _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,91 +322,6 @@ export async function findAwaitingMultipleStepByPending(
455
322
  return listAwaitingSteps.Items;
456
323
  }
457
324
 
458
- export async function checkAllAwaitingStepsFinishedShared(
459
- _izContext,
460
- pendingStepId,
461
- currentAwaitingStepId = null,
462
- errorsFound = [],
463
- additionalAttributes = {}
464
- ) {
465
- _izContext.logger.debug(
466
- '[Lib:checkAllAwaitingStepsFinishedShared] Input parameters',
467
- {
468
- pendingStepId: pendingStepId,
469
- currentAwaitingStepId: currentAwaitingStepId,
470
- errorsFound: errorsFound
471
- }
472
- );
473
-
474
- if (currentAwaitingStepId) {
475
- await dynamodbSharedLib.updateItem(
476
- _izContext,
477
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
478
- {
479
- pendingStepId: pendingStepId,
480
- awaitingStepId: currentAwaitingStepId
481
- },
482
- {
483
- complete: true,
484
- errorsFound: errorsFound,
485
- ...additionalAttributes
486
- }
487
- );
488
- }
489
- }
490
-
491
- /**
492
- * Same as checkAllAwaitingStepsFinished but writes errors/additionalAttributes to the *_ByPending row.
493
- * @async
494
- * @param {IzContext} _izContext
495
- * @param {string} pendingStepId
496
- * @param {string|null} [currentAwaitingStepId=null]
497
- * @param {any[]} [errorsFound=[]]
498
- * @param {Attrs} [additionalAttributes={}]
499
- * @returns {Promise<boolean>}
500
- */
501
- export async function checkAllAwaitingStepsFinished(
502
- _izContext,
503
- pendingStepId,
504
- currentAwaitingStepId = null,
505
- errorsFound = [],
506
- additionalAttributes = {}
507
- ) {
508
- _izContext.logger.debug(
509
- '[Lib:checkAllAwaitingStepsFinished] Input parameters',
510
- {
511
- pendingStepId: pendingStepId,
512
- currentAwaitingStepId: currentAwaitingStepId,
513
- errorsFound: errorsFound
514
- }
515
- );
516
-
517
- await checkAllAwaitingStepsFinishedShared(
518
- _izContext,
519
- pendingStepId,
520
- currentAwaitingStepId,
521
- errorsFound,
522
- additionalAttributes
523
- );
524
-
525
- const items = await dynamodbSharedLib.query(
526
- _izContext,
527
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
528
- { pendingStepId },
529
- { limit: 50 }
530
- );
531
-
532
- for (const item of items.Items) {
533
- if (item.awaitingStepId === currentAwaitingStepId) continue;
534
- if (!item.complete) return false;
535
- }
536
-
537
- _izContext.logger.debug(
538
- '[Lib:checkAllAwaitingStepsFinished] All steps are complete'
539
- );
540
- return true;
541
- }
542
-
543
325
  /**
544
326
  * Checks if all awaiting steps for a pending step are complete after marking the current one as complete.
545
327
  * If all steps are finished, returns an object with `isComplete: true` and the collected data.
@@ -549,7 +331,7 @@ export async function checkAllAwaitingStepsFinished(
549
331
  * @param {string} pendingStepId
550
332
  * @param {string|null} [currentAwaitingStepId=null] - The step that has just finished.
551
333
  * @param {any[]} [errorsFound=[]]
552
- * @param {Attrs} [additionalAttributes={}]
334
+ * @param {Attrs} [returnValues={}]
553
335
  * @returns {Promise<{
554
336
  * isComplete: boolean,
555
337
  * collectedAttributes: Record<string, any>|null,
@@ -562,7 +344,10 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
562
344
  pendingStepId,
563
345
  currentAwaitingStepId = null,
564
346
  errorsFound = [],
565
- additionalAttributes = {}
347
+ returnValues = {},
348
+ settings = {
349
+ checkIsAllError: true
350
+ }
566
351
  ) {
567
352
  _izContext.logger.debug(
568
353
  '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Input parameters',
@@ -570,24 +355,26 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
570
355
  pendingStepId,
571
356
  currentAwaitingStepId,
572
357
  errorsFound,
573
- additionalAttributes
358
+ returnValues
574
359
  }
575
360
  );
576
361
 
577
- if (!pendingStepId || !currentAwaitingStepId) {
578
- throw NoRetryError(
579
- '[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
+ }
580
375
  );
581
376
  }
582
377
 
583
- await checkAllAwaitingStepsFinishedShared(
584
- _izContext,
585
- pendingStepId,
586
- currentAwaitingStepId,
587
- errorsFound,
588
- additionalAttributes
589
- );
590
-
591
378
  const awaitingStepItems = await dynamodbSharedLib.query(
592
379
  _izContext,
593
380
  dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
@@ -601,15 +388,17 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
601
388
  );
602
389
 
603
390
  // Pass 1: check completeness of all other steps before collecting any data
604
- for (const item of awaitingStepItems.Items) {
605
- if (item.awaitingStepId !== currentAwaitingStepId && !item.complete) {
606
- return {
607
- isComplete: false,
608
- collectedAttributes: null,
609
- collectedErrors: [],
610
- sharedAdditionalAttributes: null
611
- };
612
- }
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
+ };
613
402
  }
614
403
 
615
404
  _izContext.logger.debug(
@@ -618,22 +407,21 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
618
407
 
619
408
  // Pass 2: all steps are complete — collect errors and attributes
620
409
  const collectedAttributes = {};
621
- const collectedErrors = [];
622
- let sharedAdditionalAttributes = null;
410
+ let sharedReturnValues = null;
623
411
 
624
412
  for (const item of awaitingStepItems.Items) {
625
- if (item.errorsFound?.length > 0) {
626
- collectedErrors.push(...item.errorsFound);
627
- }
628
-
629
- if (item.additionalAttributes) {
630
- collectedAttributes[item.awaitingStepId] = item.additionalAttributes;
631
- if (sharedAdditionalAttributes === null) {
632
- sharedAdditionalAttributes = item.additionalAttributes;
633
- }
413
+ if (item.returnValues) {
414
+ collectedAttributes[item.awaitingStepId] = item.returnValues;
415
+ sharedReturnValues ??= item.returnValues; // ponytail: use nullish coalescing
634
416
  }
635
417
  }
636
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
+
637
425
  _izContext.logger.debug(
638
426
  '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Pass 2: all steps are complete — collect errors and attributes'
639
427
  );
@@ -642,7 +430,14 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
642
430
  isComplete: true,
643
431
  collectedAttributes,
644
432
  collectedErrors,
645
- 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
+ })
646
441
  };
647
442
  }
648
443
 
@@ -659,7 +454,9 @@ export async function clearAllAwaitingSteps(_izContext, pendingStepId) {
659
454
  });
660
455
 
661
456
  if (!pendingStepId) {
662
- throw NoRetryError('[Lib:clearAllAwaitingSteps] pendingStepId is required');
457
+ throw new NoRetryError(
458
+ '[Lib:clearAllAwaitingSteps] pendingStepId is required'
459
+ );
663
460
  }
664
461
 
665
462
  // ----- query items from GSI table
@@ -736,7 +533,7 @@ export async function removeAwaitingMultipleStep(
736
533
  });
737
534
 
738
535
  if (!awaitingStepId || !pendingStepId) {
739
- throw NoRetryError(
536
+ throw new NoRetryError(
740
537
  '[Lib:removeAwaitingMultipleStep] awaitingStepId or pendingStepId is required'
741
538
  );
742
539
  }
@@ -790,6 +587,21 @@ export async function removeAwaitingMultipleStep(
790
587
  await Promise.all(promiseArray);
791
588
  }
792
589
 
590
+ /**
591
+ * Creates multiple awaiting steps with additional attributes and publishes corresponding messages to an SNS topic.
592
+ * Each message is augmented with a new `parentFlowId` representing its awaiting step before being published.
593
+ *
594
+ * @param {Object} _izContext - The execution context object containing logger and other utilities.
595
+ * @param {Array<Object>} messages - An array of messages to be augmented and published.
596
+ * @param {string} [prefix=''] - An optional prefix used when generating new awaiting step IDs.
597
+ * @param {Object} [flowType={}] - Defines the target SNS topic. Must contain `flowTag` and `serviceTag`.
598
+ * @param {string} flowType.flowTag - The flow tag identifier.
599
+ * @param {string} flowType.serviceTag - The service tag identifier.
600
+ * @param {Object} [additionalAttributes={}] - Additional attributes to save with each new awaiting step.
601
+ * @param {string} pendingStepId - The unique identifier of the parent pending step that waits for these new steps.
602
+ * @returns {Promise<void>} Resolves when all awaiting steps are created and messages are published.
603
+ * @throws {Error} Throws a NoRetryError if `messages` is not an array, or if `flowType` is missing `flowTag` or `serviceTag`.
604
+ */
793
605
  export async function createAwaitingMultipleStepsWithAdditionalAttributesAndPublish(
794
606
  _izContext,
795
607
  messages,
@@ -810,14 +622,14 @@ export async function createAwaitingMultipleStepsWithAdditionalAttributesAndPubl
810
622
  );
811
623
 
812
624
  if (!Array.isArray(messages)) {
813
- throw NoRetryError('messages must be an array');
625
+ throw new NoRetryError('messages must be an array');
814
626
  }
815
627
 
816
628
  const flowTag = flowType.flowTag;
817
629
  const serviceTag = flowType.serviceTag;
818
630
 
819
631
  if (flowTag === undefined || serviceTag === undefined) {
820
- throw NoRetryError('flowType must have both flowTag and serviceTag');
632
+ throw new NoRetryError('flowType must have both flowTag and serviceTag');
821
633
  }
822
634
 
823
635
  const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
@@ -831,21 +643,27 @@ export async function createAwaitingMultipleStepsWithAdditionalAttributesAndPubl
831
643
  topicArn
832
644
  );
833
645
 
834
- const messagesWithParentId = await Promise.all(
835
- messages.map(async message => {
836
- const parentFlowId = await createNewAwaitingMultipleStep(
837
- _izContext,
838
- pendingStepId,
839
- prefix,
840
- additionalAttributes
841
- );
842
- return {
843
- ...message,
844
- parentFlowId
845
- };
846
- })
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
+ }
847
664
  );
848
665
 
666
+ // 2. Then publish messages concurrently
849
667
  await Promise.all(
850
668
  messagesWithParentId.map(message =>
851
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
  }