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

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.
@@ -1,5 +1,4 @@
1
- /*
2
- Copyright (C) 2021 Sven Mason <http://izara.io>
1
+ /* Copyright (C) 2021 Sven Mason <http://izara.io>
3
2
 
4
3
  This program is free software: you can redistribute it and/or modify
5
4
  it under the terms of the GNU Affero General Public License as
@@ -8,11 +7,11 @@ License, or (at your option) any later version.
8
7
 
9
8
  This program is distributed in the hope that it will be useful,
10
9
  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
11
  GNU Affero General Public License for more details.
13
12
 
14
13
  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/>.
14
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
15
  */
17
16
 
18
17
  import { NoRetryError } from '@izara_project/izara-core-library-core';
@@ -22,558 +21,251 @@ import snsSharedLib from '@izara_project/izara-core-library-sns';
22
21
 
23
22
  import { createParentFlowId } from './asyncFlowSharedLib.js';
24
23
 
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(
41
- _izContext,
42
- records, // array of object including properties: awaitingStepId / (optional) additionalAttributes
43
- pendingStepId
44
- ) {
45
- _izContext.logger.debug(
46
- '[Lib:AsyncFlow:createAwaitingMultipleStepsWithAdditionalAttributes] Input: ',
47
- {
48
- records,
49
- pendingStepId
50
- }
51
- );
52
-
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
- );
24
+ function _assertPendingStepId(pendingStepId) {
25
+ if (!pendingStepId) {
26
+ throw new NoRetryError('pendingStepId required');
84
27
  }
85
-
86
- await Promise.all(promiseArray);
87
28
  }
88
29
 
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(
30
+ async function _putAwaitingMultipleStepRecords(
101
31
  _izContext,
102
32
  records,
103
- pendingStepId
104
- ) {
105
- _izContext.logger.debug(
106
- '[Lib:AsyncFlow:initAwaitingStepsWithAttrs] Input: ',
107
- {
108
- records,
109
- pendingStepId
110
- }
111
- );
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
- }
147
-
148
- /**
149
- * Create multiple awaiting records given an array of awaitingStepIds.
150
- * Writes to both "AwaitingMultipleSteps" and "AwaitingMultipleStepByPending".
151
- * @async
152
- * @param {IzContext} _izContext
153
- * @param {string[]} records - array of awaitingStepIds
154
- * @param {string} pendingStepId
155
- * @returns {Promise<void>}
156
- */
157
- export async function createAwaitingMultipleSteps(
158
- _izContext,
159
- records, // array of awaitingStepIds
160
- pendingStepId
161
- ) {
162
- _izContext.logger.debug(
163
- '[Lib:AsyncFlow:createAwaitingMultipleSteps] Input: ',
164
- {
165
- records: records,
166
- pendingStepId: pendingStepId
167
- }
168
- );
169
-
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);
210
- }
211
-
212
- /**
213
- * Create new awaiting multiple steps for a pending step.
214
- * @async
215
- * @param {IzContext} _izContext
216
- * @param {string} pendingStepId
217
- * @param {string[]} parendIds
218
- * @param {Object.<string, any>} [additionalAttributes={}] - Shared attributes stored for every step in AwaitingMultipleStepByPending.
219
- * The same object is applied to all steps (caller cannot know the generated UUIDs beforehand).
220
- * @returns {Promise<string>} The awaiting step ID created.
221
- */
222
- export async function createNewAwaitingMultipleStep(
223
- _izContext,
224
33
  pendingStepId,
225
- prefix = '',
226
- additionalAttributes = {}
34
+ sharedAttributes = {}
227
35
  ) {
228
- _izContext.logger.debug(
229
- '[Lib:AsyncFlow:createNewAwaitingMultipleStep] Input: ',
230
- {
231
- pendingStepId,
232
- additionalAttributes
233
- }
234
- );
235
-
236
- const parentFlowId = createParentFlowId(prefix);
237
-
238
- const attributesWhenCreate = {
239
- awaitingStepId: parentFlowId,
240
- pendingStepId: pendingStepId,
241
- complete: false,
242
- errorsFound: []
243
- };
244
-
245
- _izContext.logger.debug(
246
- '[Lib:AsyncFlow:createNewAwaitingMultipleStep] attributesWhenCreate: ',
247
- attributesWhenCreate
248
- );
249
-
250
- await dynamodbSharedLib.putItem(
36
+ const tableSteps = dynamodbSharedLib.tableName(
251
37
  _izContext,
252
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
253
- attributesWhenCreate
38
+ 'AwaitingMultipleSteps'
254
39
  );
255
-
256
- const attributesForPending = {
257
- ...attributesWhenCreate,
258
- additionalAttributes: additionalAttributes
259
- };
260
-
261
- await dynamodbSharedLib.putItem(
40
+ const tablePending = dynamodbSharedLib.tableName(
262
41
  _izContext,
263
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
264
- attributesForPending
42
+ 'AwaitingMultipleStepByPending'
265
43
  );
266
44
 
267
- _izContext.logger.debug(
268
- '[Lib:AsyncFlow:createNewAwaitingMultipleStep] Returning: ',
269
- parentFlowId
45
+ await Promise.all(
46
+ records.flatMap(({ awaitingStepId, additionalAttributes }) => {
47
+ const keys = { awaitingStepId, pendingStepId };
48
+
49
+ return [
50
+ dynamodbSharedLib.putItem(_izContext, tableSteps, keys),
51
+ dynamodbSharedLib.putItem(_izContext, tablePending, {
52
+ ...keys,
53
+ ...sharedAttributes,
54
+ ...(additionalAttributes && { additionalAttributes })
55
+ })
56
+ ];
57
+ })
270
58
  );
271
-
272
- return parentFlowId;
273
59
  }
274
60
 
275
- /**
276
- * Find all pendingStepIds that are waiting on a given awaitingStepId (multiple).
277
- * @async
278
- * @param {IzContext} _izContext
279
- * @param {string} awaitingStepId
280
- * @returns {Promise<string[]>}
281
- */
282
- export async function findPendingStepIdsAwaitingMultipleSteps(
283
- _izContext,
284
- awaitingStepId
285
- ) {
286
- _izContext.logger.debug(
287
- '[Lib:findPendingStepIdsAwaitingMultipleSteps] Input parameters',
288
- {
289
- awaitingStepId: awaitingStepId
290
- }
291
- );
292
-
61
+ async function _queryPendingStepsByAwaitingStepId(_izContext, awaitingStepId) {
293
62
  if (!awaitingStepId) {
294
- _izContext.logger.error(
295
- '[Lib:findPendingStepIdsAwaitingMultipleSteps] awaitingStepId is required'
296
- );
297
- throw new NoRetryError('awaitingStepId is required');
63
+ throw new NoRetryError('awaitingStepId required');
298
64
  }
299
65
 
300
- const pendingStepIds = [];
301
-
302
- const listPendingStepIds = await dynamodbSharedLib.query(
66
+ const { Items } = await dynamodbSharedLib.query(
303
67
  _izContext,
304
68
  dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
305
- {
306
- awaitingStepId: awaitingStepId
307
- }
69
+ { awaitingStepId }
308
70
  );
309
71
 
310
- for (const item of listPendingStepIds.Items) {
311
- pendingStepIds.push(item.pendingStepId);
312
- }
72
+ return Items;
73
+ }
313
74
 
314
- _izContext.logger.debug(
315
- '[Lib:findPendingStepIdsAwaitingMultipleSteps] Pending step IDs',
316
- pendingStepIds
75
+ async function _queryAwaitingStepsByPendingStepId(_izContext, pendingStepId) {
76
+ _assertPendingStepId(pendingStepId);
77
+
78
+ const { Items } = await dynamodbSharedLib.query(
79
+ _izContext,
80
+ dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
81
+ { pendingStepId },
82
+ { limit: 50 }
317
83
  );
318
84
 
319
- return pendingStepIds;
85
+ return Items;
320
86
  }
321
87
 
322
- /**
323
- * Return the raw items waiting on a given awaitingStepId (multiple).
324
- * @async
325
- * @param {IzContext} _izContext
326
- * @param {string} awaitingStepId
327
- * @returns {Promise<AwaitingMultipleStepsItem[]>}
328
- */
329
- export async function findPendingStepsAwaitingMultipleSteps(
88
+ //======================================
89
+ // AwaitingMultipleSteps
90
+ //======================================
91
+ // AwaitingMultipleSteps flow awaits multiple external flows before continuing.
92
+ // One awaitingStepId can have multiple pendingStepIds awaiting it.
93
+ // One pendingStepId can have multiple awaitingStepIds awaiting it.
94
+
95
+ export async function createAwaitingMultipleSteps(
330
96
  _izContext,
331
- awaitingStepId
97
+ pendingStepId,
98
+ options = {}
332
99
  ) {
333
- _izContext.logger.debug(
334
- '[Lib:findPendingStepsAwaitingMultipleSteps] Input parameters',
335
- {
336
- awaitingStepId: awaitingStepId
337
- }
338
- );
100
+ const {
101
+ records = [],
102
+ prefix = '',
103
+ additionalAttributes = {},
104
+ publishConfig = null
105
+ } = options;
106
+
107
+ _izContext.logger.debug('[Lib:AsyncFlow:createAwaitingMultipleSteps] Input', {
108
+ pendingStepId,
109
+ options
110
+ });
339
111
 
340
- if (!awaitingStepId) {
341
- _izContext.logger.error(
342
- '[Lib:findPendingStepsAwaitingMultipleSteps] awaitingStepId is required'
343
- );
344
- throw new NoRetryError('awaitingStepId is required');
345
- }
112
+ let finalRecords = records;
346
113
 
347
- const listPendingSteps = await dynamodbSharedLib.query(
348
- _izContext,
349
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
350
- {
351
- awaitingStepId: awaitingStepId
352
- }
353
- );
114
+ if (finalRecords.length === 0 && !publishConfig) {
115
+ finalRecords = [
116
+ {
117
+ awaitingStepId: createParentFlowId(prefix),
118
+ additionalAttributes
119
+ }
120
+ ];
121
+ }
354
122
 
355
- _izContext.logger.debug(
356
- '[Lib:findPendingStepsAwaitingMultipleSteps] Record of awaitingStepId',
357
- listPendingSteps
358
- );
123
+ if (publishConfig) {
124
+ const { messages, flowType = {} } = publishConfig;
125
+ const { flowTag, serviceTag } = flowType;
359
126
 
360
- return listPendingSteps.Items;
361
- }
127
+ if (!Array.isArray(messages)) {
128
+ throw new NoRetryError('messages must be an array');
129
+ }
362
130
 
363
- /**
364
- * Fetch exactly one pending step for a given awaitingStepId (throws if not 1).
365
- * @async
366
- * @param {IzContext} _izContext
367
- * @param {string} awaitingStepId
368
- * @returns {Promise<AwaitingMultipleStepsItem>}
369
- * @throws {NoRetryError} if there is not exactly one item
370
- */
371
- export async function findPendingStepAwaitingMultipleSteps(
372
- _izContext,
373
- awaitingStepId
374
- ) {
375
- _izContext.logger.debug(
376
- '[Lib:findPendingStepAwaitingMultipleSteps] Input parameters',
377
- {
378
- awaitingStepId: awaitingStepId
131
+ if (!flowTag || !serviceTag) {
132
+ throw new NoRetryError('flowType must both flowTag serviceTag');
379
133
  }
380
- );
381
134
 
382
- if (!awaitingStepId) {
383
- _izContext.logger.error(
384
- '[Lib:findPendingStepAwaitingMultipleSteps] awaitingStepId is required'
385
- );
386
- throw new NoRetryError('awaitingStepId is required');
135
+ finalRecords = messages.map(message => {
136
+ const parentFlowId = createParentFlowId(prefix);
137
+
138
+ return {
139
+ awaitingStepId: parentFlowId,
140
+ additionalAttributes,
141
+ message: {
142
+ ...message,
143
+ parentFlowId
144
+ }
145
+ };
146
+ });
387
147
  }
388
148
 
389
- const listPendingSteps = await dynamodbSharedLib.query(
149
+ await _putAwaitingMultipleStepRecords(
390
150
  _izContext,
391
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
151
+ finalRecords.map(({ awaitingStepId, additionalAttributes: itemAttrs }) => ({
152
+ awaitingStepId,
153
+ additionalAttributes: itemAttrs
154
+ })),
155
+ pendingStepId,
392
156
  {
393
- awaitingStepId: awaitingStepId
157
+ complete: false,
158
+ errorsFound: []
394
159
  }
395
160
  );
396
161
 
397
- _izContext.logger.debug(
398
- '[Lib:findPendingStepAwaitingMultipleSteps] Record of awaitingStepId',
399
- listPendingSteps
400
- );
162
+ if (publishConfig) {
163
+ const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
164
+ _izContext,
165
+ publishConfig.flowType.flowTag,
166
+ publishConfig.flowType.serviceTag
167
+ );
401
168
 
402
- if (listPendingSteps.Items.length !== 1) {
403
- _izContext.logger.error(
404
- '[Lib:findPendingStepAwaitingMultipleSteps] listPendingSteps not equals 1'
169
+ await Promise.all(
170
+ finalRecords.map(({ message }) =>
171
+ sns.publishAsync(_izContext, {
172
+ TopicArn: `${topicArn}_In`,
173
+ Message: JSON.stringify(message),
174
+ MessageAttributes: {}
175
+ })
176
+ )
405
177
  );
406
- throw new NoRetryError('listPendingSteps not equals 1');
407
178
  }
408
179
 
409
- return listPendingSteps.Items[0];
180
+ return finalRecords[0]?.awaitingStepId;
410
181
  }
411
182
 
412
- // Use case:
413
- // Use only after checkAllAwaitingStepsFinished
414
- // Do NOT delete records in both tables (AwaitingMultipleSteps, AwaitingMultipleStepByPending) if AwaitingMultipleSteps is not yet finished
415
- // Delete records from both tables only when checkAllAwaitingStepsFinished = true
416
- // Example usage: flow validateCart lambda ProcessCartOrderFromTraversal
417
- /**
418
- * Given a pendingStepId, return all awaiting steps (by the ByPending table).
419
- * Use only after confirming all awaiting steps are finished, and clean up both tables accordingly.
420
- * @async
421
- * @param {IzContext} _izContext
422
- * @param {string} pendingStepId
423
- * @returns {Promise<AwaitingMultipleStepByPendingItem[]>}
424
- */
425
- export async function findAwaitingMultipleStepByPending(
183
+ export async function createAwaitingMultipleStepsWithAdditionalAttributes(
426
184
  _izContext,
185
+ records,
427
186
  pendingStepId
428
187
  ) {
429
- _izContext.logger.debug(
430
- '[Lib:findAwaitingMultipleStepByPending] Input parameters',
431
- {
432
- pendingStepId: pendingStepId
433
- }
434
- );
435
-
436
- if (!pendingStepId) {
437
- _izContext.logger.error(
438
- '[Lib:findAwaitingMultipleStepByPending] pendingStepId is required'
439
- );
440
- throw new NoRetryError('pendingStepId is required');
441
- }
442
-
443
- const listAwaitingSteps = await dynamodbSharedLib.query(
444
- _izContext,
445
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
446
- {
447
- pendingStepId: pendingStepId
448
- }
449
- );
450
- _izContext.logger.debug(
451
- '[Lib:findAwaitingMultipleStepByPending] Record of AwaitingMultipleStepByPending',
452
- listAwaitingSteps
453
- );
454
-
455
- return listAwaitingSteps.Items;
188
+ return createAwaitingMultipleSteps(_izContext, pendingStepId, { records });
456
189
  }
457
190
 
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(
191
+ export async function createAwaitingMultipleStep(
470
192
  _izContext,
471
193
  pendingStepId,
472
- currentAwaitingStepId = null,
473
- errorsFound = [],
194
+ prefix = '',
474
195
  additionalAttributes = {}
475
196
  ) {
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
- }
197
+ return createAwaitingMultipleSteps(_izContext, pendingStepId, {
198
+ prefix,
199
+ additionalAttributes
200
+ });
500
201
  }
501
202
 
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(
203
+ export async function createAwaitingMultipleStepsWithAdditionalAttributesAndPublish(
513
204
  _izContext,
514
- pendingStepId,
515
- currentAwaitingStepId = null,
516
- errorsFound = [],
517
- additionalAttributes = {}
205
+ messages,
206
+ prefix = '',
207
+ flowType = {},
208
+ additionalAttributes = {},
209
+ pendingStepId
518
210
  ) {
519
- _izContext.logger.debug(
520
- '[Lib:checkAllAwaitingStepsFinished] Input parameters',
521
- {
522
- pendingStepId: pendingStepId,
523
- currentAwaitingStepId: currentAwaitingStepId,
524
- errorsFound: errorsFound
211
+ return createAwaitingMultipleSteps(_izContext, pendingStepId, {
212
+ prefix,
213
+ additionalAttributes,
214
+ publishConfig: {
215
+ messages,
216
+ flowType
525
217
  }
526
- );
218
+ });
219
+ }
527
220
 
528
- await checkAllAwaitingStepsFinishedShared(
529
- _izContext,
530
- pendingStepId,
531
- currentAwaitingStepId,
532
- errorsFound,
533
- additionalAttributes
534
- );
221
+ export async function findPendingStepAwaitingMultipleSteps(
222
+ _izContext,
223
+ awaitingStepId
224
+ ) {
225
+ _izContext.logger.debug('[Lib:findPendingStepAwaitingMultipleSteps] Input', {
226
+ awaitingStepId
227
+ });
535
228
 
536
- const items = await dynamodbSharedLib.query(
229
+ const items = await _queryPendingStepsByAwaitingStepId(
537
230
  _izContext,
538
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
539
- { pendingStepId },
540
- { limit: 50 }
231
+ awaitingStepId
541
232
  );
542
233
 
543
- for (const item of items.Items) {
544
- if (item.awaitingStepId === currentAwaitingStepId) continue;
545
- if (!item.complete) return false;
546
- }
234
+ return items[0];
235
+ }
547
236
 
548
- _izContext.logger.debug(
549
- '[Lib:checkAllAwaitingStepsFinished] All steps are complete'
237
+ export async function findPendingStepsAwaitingMultipleSteps(
238
+ _izContext,
239
+ awaitingStepId
240
+ ) {
241
+ return _queryPendingStepsByAwaitingStepId(_izContext, awaitingStepId);
242
+ }
243
+
244
+ export async function findPendingStepIdAwaitingMultipleSteps(
245
+ _izContext,
246
+ awaitingStepId
247
+ ) {
248
+ const items = await _queryPendingStepsByAwaitingStepId(
249
+ _izContext,
250
+ awaitingStepId
550
251
  );
551
- return true;
252
+ return items[0]?.pendingStepId;
253
+ }
254
+
255
+ export async function findAwaitingMultipleStepByPending(
256
+ _izContext,
257
+ pendingStepId
258
+ ) {
259
+ return _queryAwaitingStepsByPendingStepId(_izContext, pendingStepId);
552
260
  }
553
261
 
554
- /**
555
- * Checks if all awaiting steps for a pending step are complete after marking the current one as complete.
556
- * If all steps are finished, returns an object with `isComplete: true` and the collected data.
557
- * If any other step is not yet complete, returns `isComplete: false` with null data.
558
- * @async
559
- * @param {IzContext} _izContext
560
- * @param {string} pendingStepId
561
- * @param {string|null} [currentAwaitingStepId=null] - The step that has just finished.
562
- * @param {any[]} [errorsFound=[]]
563
- * @param {Attrs} [additionalAttributes={}]
564
- * @returns {Promise<{
565
- * isComplete: boolean,
566
- * collectedAttributes: Record<string, any>|null,
567
- * collectedErrors: any[],
568
- * sharedAdditionalAttributes: any|null
569
- * }>}
570
- */
571
262
  export async function checkAllAwaitingStepsFinishedWithReturnParams(
572
263
  _izContext,
573
264
  pendingStepId,
574
265
  currentAwaitingStepId = null,
575
266
  errorsFound = [],
576
- additionalAttributes = {}
267
+ returnValues = {},
268
+ settings = { checkIsAllError: true }
577
269
  ) {
578
270
  _izContext.logger.debug(
579
271
  '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Input parameters',
@@ -581,159 +273,117 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
581
273
  pendingStepId,
582
274
  currentAwaitingStepId,
583
275
  errorsFound,
584
- additionalAttributes
276
+ returnValues
585
277
  }
586
278
  );
587
279
 
588
- if (!pendingStepId || !currentAwaitingStepId) {
589
- throw NoRetryError(
590
- '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] pendingStepId or currentAwaitingStepId is required'
280
+ _assertPendingStepId(pendingStepId);
281
+
282
+ if (currentAwaitingStepId) {
283
+ await dynamodbSharedLib.updateItem(
284
+ _izContext,
285
+ dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
286
+ {
287
+ pendingStepId,
288
+ awaitingStepId: currentAwaitingStepId
289
+ },
290
+ {
291
+ complete: true,
292
+ errorsFound,
293
+ returnValues
294
+ }
591
295
  );
592
296
  }
593
297
 
594
- await checkAllAwaitingStepsFinishedShared(
298
+ const awaitingStepItems = await _queryAwaitingStepsByPendingStepId(
595
299
  _izContext,
596
- pendingStepId,
597
- currentAwaitingStepId,
598
- errorsFound,
599
- additionalAttributes
600
- );
601
-
602
- const awaitingStepItems = await dynamodbSharedLib.query(
603
- _izContext,
604
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
605
- { pendingStepId },
606
- { limit: 50 }
300
+ pendingStepId
607
301
  );
608
302
 
609
303
  _izContext.logger.debug(
610
- '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] awaitingStepItems:::',
304
+ '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] awaitingStepItems',
611
305
  awaitingStepItems
612
306
  );
613
307
 
614
- // 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
- }
624
- }
625
-
626
- _izContext.logger.debug(
627
- '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Pass 1: all steps are complete'
308
+ const hasIncomplete = awaitingStepItems.some(
309
+ item => item.awaitingStepId !== currentAwaitingStepId && !item.complete
628
310
  );
629
311
 
630
- // Pass 2: all steps are complete — collect errors and attributes
312
+ if (hasIncomplete) {
313
+ return {
314
+ isComplete: false,
315
+ collectedAttributes: null,
316
+ collectedErrors: [],
317
+ returnValues: null
318
+ };
319
+ }
320
+
631
321
  const collectedAttributes = {};
632
- const collectedErrors = [];
633
- let sharedAdditionalAttributes = null;
322
+ let sharedReturnValues = null;
634
323
 
635
- for (const item of awaitingStepItems.Items) {
636
- if (item.errorsFound?.length > 0) {
637
- collectedErrors.push(...item.errorsFound);
324
+ for (const item of awaitingStepItems) {
325
+ if (!item.returnValues) {
326
+ continue;
638
327
  }
639
328
 
640
- if (item.additionalAttributes) {
641
- collectedAttributes[item.awaitingStepId] = item.additionalAttributes;
642
- if (sharedAdditionalAttributes === null) {
643
- sharedAdditionalAttributes = item.additionalAttributes;
644
- }
645
- }
329
+ collectedAttributes[item.awaitingStepId] = item.returnValues;
330
+ sharedReturnValues ??= item.returnValues;
646
331
  }
647
332
 
648
- _izContext.logger.debug(
649
- '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Pass 2: all steps are complete — collect errors and attributes'
333
+ const collectedErrors = awaitingStepItems.flatMap(item =>
334
+ item.errorsFound?.length > 0
335
+ ? [`Error found in step ${item.awaitingStepId}`, ...item.errorsFound]
336
+ : []
650
337
  );
651
338
 
652
339
  return {
653
340
  isComplete: true,
654
341
  collectedAttributes,
655
342
  collectedErrors,
656
- sharedAdditionalAttributes
343
+ returnValues: sharedReturnValues,
344
+ additionalAttributes: awaitingStepItems[0]?.additionalAttributes || null,
345
+ ...(settings.checkIsAllError && {
346
+ isAllError: awaitingStepItems.every(item => item.errorsFound?.length > 0)
347
+ })
657
348
  };
658
349
  }
659
350
 
660
- /**
661
- * Remove all awaiting records for a given pendingStepId from both tables.
662
- * @async
663
- * @param {IzContext} _izContext
664
- * @param {string} pendingStepId
665
- * @returns {Promise<boolean>} true if delete flow processed
666
- */
667
351
  export async function clearAllAwaitingSteps(_izContext, pendingStepId) {
668
352
  _izContext.logger.debug('[Lib:clearAllAwaitingSteps] Input parameters', {
669
- pendingStepId: pendingStepId
353
+ pendingStepId
670
354
  });
671
355
 
672
- if (!pendingStepId) {
673
- throw NoRetryError('[Lib:clearAllAwaitingSteps] pendingStepId is required');
674
- }
675
-
676
- // ----- query items from GSI table
677
- const listPendingStepIds = await dynamodbSharedLib.query(
356
+ const listPendingStepIds = await _queryAwaitingStepsByPendingStepId(
678
357
  _izContext,
679
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
680
- {
681
- pendingStepId: pendingStepId
682
- }
358
+ pendingStepId
683
359
  );
684
- _izContext.logger.debug(
685
- '[Lib:clearAllAwaitingSteps] listPendingStepIds',
686
- listPendingStepIds
360
+ const tableSteps = dynamodbSharedLib.tableName(
361
+ _izContext,
362
+ 'AwaitingMultipleSteps'
363
+ );
364
+ const tablePending = dynamodbSharedLib.tableName(
365
+ _izContext,
366
+ 'AwaitingMultipleStepByPending'
687
367
  );
688
368
 
689
- const promiseArray = [];
690
-
691
- for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
692
- _izContext.logger.debug(`delete item in dynamodb AwaitingMultipleSteps`);
693
- promiseArray.push(
694
- dynamodbSharedLib.deleteItem(
695
- _izContext,
696
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
697
- {
698
- awaitingStepId: listPendingStepIds.Items[idx].awaitingStepId,
699
- pendingStepId: listPendingStepIds.Items[idx].pendingStepId
700
- }
701
- )
702
- );
703
-
704
- _izContext.logger.debug(
705
- '[Lib:clearAllAwaitingSteps] delete item in dynamodb AwaitingMultipleStepByPending'
706
- );
707
- promiseArray.push(
708
- dynamodbSharedLib.deleteItem(
709
- _izContext,
710
- dynamodbSharedLib.tableName(
711
- _izContext,
712
- 'AwaitingMultipleStepByPending'
713
- ),
714
- {
715
- pendingStepId: listPendingStepIds.Items[idx].pendingStepId,
716
- awaitingStepId: listPendingStepIds.Items[idx].awaitingStepId
717
- }
718
- )
719
- );
720
- }
721
-
722
- await Promise.all(promiseArray);
369
+ await Promise.all(
370
+ listPendingStepIds.flatMap(
371
+ ({ awaitingStepId, pendingStepId: itemPendingStepId }) => [
372
+ dynamodbSharedLib.deleteItem(_izContext, tableSteps, {
373
+ awaitingStepId,
374
+ pendingStepId: itemPendingStepId
375
+ }),
376
+ dynamodbSharedLib.deleteItem(_izContext, tablePending, {
377
+ awaitingStepId,
378
+ pendingStepId: itemPendingStepId
379
+ })
380
+ ]
381
+ )
382
+ );
723
383
 
724
384
  return true;
725
385
  }
726
386
 
727
- /**
728
- * Remove items for AwaitingMultipleSteps and (conditionally) AwaitingMultipleStepByPending.
729
- * If errorsFound is empty, delete from ByPending table as well.
730
- * @async
731
- * @param {IzContext} _izContext
732
- * @param {string} awaitingStepId
733
- * @param {string} pendingStepId
734
- * @param {any[]} [errorsFound=[]]
735
- * @returns {Promise<void>}
736
- */
737
387
  export async function removeAwaitingMultipleStep(
738
388
  _izContext,
739
389
  awaitingStepId,
@@ -741,148 +391,38 @@ export async function removeAwaitingMultipleStep(
741
391
  errorsFound = []
742
392
  ) {
743
393
  _izContext.logger.debug('[Lib:removeAwaitingMultipleStep] Input parameters', {
744
- awaitingStepId: awaitingStepId,
745
- pendingStepId: pendingStepId,
746
- errorsFound: errorsFound
394
+ awaitingStepId,
395
+ pendingStepId,
396
+ errorsFound
747
397
  });
748
398
 
749
399
  if (!awaitingStepId || !pendingStepId) {
750
- throw NoRetryError(
751
- '[Lib:removeAwaitingMultipleStep] awaitingStepId or pendingStepId is required'
400
+ throw new NoRetryError(
401
+ '[Lib:removeAwaitingMultipleStep] awaitingStepId or pendingStepId required'
752
402
  );
753
403
  }
754
404
 
755
- const promiseArray = [];
756
-
757
- const keyValues = {
758
- awaitingStepId: awaitingStepId,
759
- pendingStepId: pendingStepId
760
- };
761
-
762
- const keyValuesForByPending = {
763
- pendingStepId: pendingStepId,
764
- awaitingStepId: awaitingStepId
765
- };
766
- _izContext.logger.debug('deleting ... ', {
767
- keyValues,
768
- keyValuesForByPending
769
- });
770
-
771
- _izContext.logger.debug(
772
- '[Lib:removeAwaitingMultipleStep] delete item in dynamodb AwaitingMultipleSteps',
773
- {
774
- awaitingStepId: keyValues.awaitingStepId,
775
- pendingStepId: keyValues.pendingStepId
776
- }
777
- );
778
- promiseArray.push(
405
+ const keys = { awaitingStepId, pendingStepId };
406
+ const promises = [
779
407
  dynamodbSharedLib.deleteItem(
780
408
  _izContext,
781
409
  dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
782
- keyValues
410
+ keys
783
411
  )
784
- );
412
+ ];
785
413
 
786
- if (errorsFound.length == 0) {
787
- _izContext.logger.debug(
788
- `delete item in dynamodb AwaitingMultipleStepByPending ==> awaitingStepId:${keyValues.awaitingStepId}`
789
- );
790
- promiseArray.push(
414
+ if (errorsFound.length === 0) {
415
+ promises.push(
791
416
  dynamodbSharedLib.deleteItem(
792
417
  _izContext,
793
418
  dynamodbSharedLib.tableName(
794
419
  _izContext,
795
420
  'AwaitingMultipleStepByPending'
796
421
  ),
797
- keyValuesForByPending
422
+ keys
798
423
  )
799
424
  );
800
425
  }
801
- await Promise.all(promiseArray);
802
- }
803
-
804
- /**
805
- * Creates multiple awaiting steps with additional attributes and publishes corresponding messages to an SNS topic.
806
- * Each message is augmented with a new `parentFlowId` representing its awaiting step before being published.
807
- *
808
- * @param {Object} _izContext - The execution context object containing logger and other utilities.
809
- * @param {Array<Object>} messages - An array of messages to be augmented and published.
810
- * @param {string} [prefix=''] - An optional prefix used when generating new awaiting step IDs.
811
- * @param {Object} [flowType={}] - Defines the target SNS topic. Must contain `flowTag` and `serviceTag`.
812
- * @param {string} flowType.flowTag - The flow tag identifier.
813
- * @param {string} flowType.serviceTag - The service tag identifier.
814
- * @param {Object} [additionalAttributes={}] - Additional attributes to save with each new awaiting step.
815
- * @param {string} pendingStepId - The unique identifier of the parent pending step that waits for these new steps.
816
- * @returns {Promise<void>} Resolves when all awaiting steps are created and messages are published.
817
- * @throws {Error} Throws a NoRetryError if `messages` is not an array, or if `flowType` is missing `flowTag` or `serviceTag`.
818
- */
819
- export async function createAwaitingMultipleStepsWithAdditionalAttributesAndPublish(
820
- _izContext,
821
- messages,
822
- prefix = '',
823
- flowType = {},
824
- additionalAttributes = {},
825
- pendingStepId
826
- ) {
827
- _izContext.logger.debug(
828
- '[Lib:asyncFlow:createAwaitingMultipleStepsWithAdditionalAttributesAndPublish] Input parameters',
829
- {
830
- messages,
831
- prefix,
832
- flowType,
833
- additionalAttributes,
834
- pendingStepId
835
- }
836
- );
837
-
838
- if (!Array.isArray(messages)) {
839
- throw NoRetryError('messages must be an array');
840
- }
841
426
 
842
- const flowTag = flowType.flowTag;
843
- const serviceTag = flowType.serviceTag;
844
-
845
- if (flowTag === undefined || serviceTag === undefined) {
846
- throw NoRetryError('flowType must have both flowTag and serviceTag');
847
- }
848
-
849
- const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
850
- _izContext,
851
- flowType.flowTag,
852
- flowType.serviceTag
853
- );
854
-
855
- _izContext.logger.debug(
856
- '[Lib:asyncFlow:createAwaitingMultipleStepsWithAdditionalAttributesAndPublish] Topic Arn',
857
- topicArn
858
- );
859
-
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
- })
873
- );
874
-
875
- await Promise.all(
876
- messagesWithParentId.map(message =>
877
- sns.publishAsync(_izContext, {
878
- TopicArn: `${topicArn}_In`,
879
- Message: JSON.stringify(message),
880
- MessageAttributes: {}
881
- })
882
- )
883
- );
884
-
885
- _izContext.logger.debug(
886
- '[Lib:asyncFlow:createAwaitingMultipleStepsWithAdditionalAttributesAndPublish] Exit point'
887
- );
427
+ await Promise.all(promises);
888
428
  }