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

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,332 +21,257 @@ import snsSharedLib from '@izara_project/izara-core-library-sns';
22
21
 
23
22
  import { createParentFlowId } from './asyncFlowSharedLib.js';
24
23
 
25
- async function putAwaitingMultipleStepRecords(
24
+ function _assertPendingStepId(pendingStepId) {
25
+ if (!pendingStepId) {
26
+ throw new NoRetryError('pendingStepId required');
27
+ }
28
+ }
29
+
30
+ async function _putAwaitingMultipleStepRecords(
26
31
  _izContext,
27
32
  records,
28
33
  pendingStepId,
29
34
  sharedAttributes = {}
30
35
  ) {
31
- const multipleStepsTable = dynamodbSharedLib.tableName(
36
+ const tableSteps = dynamodbSharedLib.tableName(
32
37
  _izContext,
33
38
  'AwaitingMultipleSteps'
34
39
  );
35
- const byPendingTable = dynamodbSharedLib.tableName(
40
+ const tablePending = dynamodbSharedLib.tableName(
36
41
  _izContext,
37
42
  'AwaitingMultipleStepByPending'
38
43
  );
39
44
 
40
45
  await Promise.all(
41
46
  records.flatMap(({ awaitingStepId, additionalAttributes }) => {
42
- const baseAttrs = {
43
- awaitingStepId,
44
- pendingStepId,
45
- ...sharedAttributes
46
- };
47
+ const keys = { awaitingStepId, pendingStepId };
47
48
 
48
49
  return [
49
- dynamodbSharedLib.putItem(_izContext, multipleStepsTable, baseAttrs),
50
- dynamodbSharedLib.putItem(_izContext, byPendingTable, {
51
- ...baseAttrs,
52
- additionalAttributes
50
+ dynamodbSharedLib.putItem(_izContext, tableSteps, keys),
51
+ dynamodbSharedLib.putItem(_izContext, tablePending, {
52
+ ...keys,
53
+ ...sharedAttributes,
54
+ ...(additionalAttributes && { additionalAttributes })
53
55
  })
54
56
  ];
55
57
  })
56
58
  );
57
59
  }
58
60
 
59
- async function queryAwaitingMultipleSteps(_izContext, awaitingStepId) {
60
- return dynamodbSharedLib.query(
61
+ async function _queryPendingStepsByAwaitingStepId(_izContext, awaitingStepId) {
62
+ if (!awaitingStepId) {
63
+ throw new NoRetryError('awaitingStepId required');
64
+ }
65
+
66
+ const { Items } = await dynamodbSharedLib.query(
61
67
  _izContext,
62
68
  dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
63
- {
64
- awaitingStepId
65
- }
69
+ { awaitingStepId }
66
70
  );
71
+
72
+ return Items;
67
73
  }
68
74
 
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
-
75
- /**
76
- * Create multiple awaiting records with optional per-record additional attributes.
77
- * Writes to both "AwaitingMultipleSteps" and "AwaitingMultipleStepByPending".
78
- * @async
79
- * @param {IzContext} _izContext
80
- * @param {AwaitingMultiInputRecord[]} records
81
- * @param {string} pendingStepId
82
- * @returns {Promise<void>}
83
- */
84
- export async function createAwaitingMultipleStepsWithAdditionalAttributes(
85
- _izContext,
86
- records, // array of object including properties: awaitingStepId / (optional) additionalAttributes
87
- pendingStepId
88
- ) {
89
- _izContext.logger.debug(
90
- '[Lib:AsyncFlow:createAwaitingMultipleStepsWithAdditionalAttributes] Input: ',
91
- { records, pendingStepId }
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 }
92
83
  );
93
84
 
94
- const timestamp = Date.now();
95
- await putAwaitingMultipleStepRecords(_izContext, records, pendingStepId, {
96
- timestamp
97
- });
85
+ return Items;
98
86
  }
99
87
 
100
- /**
101
- * Create new awaiting multiple steps for a pending step.
102
- * @async
103
- * @param {IzContext} _izContext
104
- * @param {string} pendingStepId
105
- * @param {string[]} parendIds
106
- * @param {Object.<string, any>} [additionalAttributes={}] - Shared attributes stored for every step in AwaitingMultipleStepByPending.
107
- * The same object is applied to all steps (caller cannot know the generated UUIDs beforehand).
108
- * @returns {Promise<string>} The awaiting step ID created.
109
- */
110
- export async function createAwaitingMultipleStep(
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(
111
96
  _izContext,
112
97
  pendingStepId,
113
- prefix = '',
114
- additionalAttributes = {}
98
+ records = [],
99
+ options = {}
115
100
  ) {
116
- _izContext.logger.debug(
117
- '[Lib:AsyncFlow:createAwaitingMultipleStep] Input: ',
118
- {
119
- pendingStepId,
120
- additionalAttributes
121
- }
122
- );
101
+ const {
102
+ prefix = '',
103
+ additionalAttributes = {},
104
+ publishConfig = null
105
+ } = options;
123
106
 
124
- const parentFlowId = createParentFlowId(prefix);
125
- const sharedAttributes = {
126
- complete: false,
127
- errorsFound: []
128
- };
107
+ _izContext.logger.debug('[Lib:AsyncFlow:createAwaitingMultipleSteps] Input', {
108
+ pendingStepId,
109
+ options
110
+ });
129
111
 
130
- _izContext.logger.debug(
131
- '[Lib:AsyncFlow:createAwaitingMultipleStep] sharedAttributes: ',
132
- { awaitingStepId: parentFlowId, pendingStepId, ...sharedAttributes }
133
- );
112
+ const formattedPrefix = prefix ? (prefix.endsWith('_') ? prefix : `${prefix}_`) : '';
113
+ const finalPendingStepId =
114
+ formattedPrefix && !pendingStepId.startsWith(formattedPrefix)
115
+ ? formattedPrefix + pendingStepId
116
+ : pendingStepId;
134
117
 
135
- await putAwaitingMultipleStepRecords(
136
- _izContext,
137
- [{ awaitingStepId: parentFlowId, additionalAttributes }],
138
- pendingStepId,
139
- sharedAttributes
140
- );
118
+ let finalRecords = records;
141
119
 
142
- _izContext.logger.debug(
143
- '[Lib:AsyncFlow:createAwaitingMultipleStep] Returning: ',
144
- parentFlowId
145
- );
120
+ if (finalRecords.length === 0 && !publishConfig) {
121
+ finalRecords = [
122
+ {
123
+ awaitingStepId: createParentFlowId(prefix),
124
+ additionalAttributes
125
+ }
126
+ ];
127
+ }
146
128
 
147
- return parentFlowId;
148
- }
129
+ if (publishConfig) {
130
+ const { messages, flowType = {} } = publishConfig;
131
+ const { flowTag, serviceTag } = flowType;
149
132
 
150
- /**
151
- * Find one pendingStepId waiting on a given awaitingStepId (multiple).
152
- * @async
153
- * @param {IzContext} _izContext
154
- * @param {string} awaitingStepId
155
- * @returns {Promise<string>}
156
- */
157
- export async function findPendingStepIdAwaitingMultipleSteps(
158
- _izContext,
159
- awaitingStepId
160
- ) {
161
- _izContext.logger.debug(
162
- '[Lib:findPendingStepIdAwaitingMultipleSteps] Input parameters',
163
- {
164
- awaitingStepId: awaitingStepId
133
+ if (!Array.isArray(messages)) {
134
+ throw new NoRetryError('messages must be an array');
165
135
  }
166
- );
167
136
 
168
- if (!awaitingStepId) {
169
- _izContext.logger.error(
170
- '[Lib:findPendingStepIdAwaitingMultipleSteps] awaitingStepId is required'
171
- );
172
- throw new NoRetryError('awaitingStepId is required');
137
+ if (!flowTag || !serviceTag) {
138
+ throw new NoRetryError('flowType must both flowTag serviceTag');
139
+ }
140
+
141
+ finalRecords = messages.map(message => {
142
+ const parentFlowId = createParentFlowId(prefix);
143
+
144
+ return {
145
+ awaitingStepId: parentFlowId,
146
+ additionalAttributes,
147
+ message: {
148
+ ...message,
149
+ parentFlowId
150
+ }
151
+ };
152
+ });
173
153
  }
174
154
 
175
- const listPendingSteps = await queryAwaitingMultipleSteps(
155
+ await _putAwaitingMultipleStepRecords(
176
156
  _izContext,
177
- awaitingStepId
157
+ finalRecords.map(({ awaitingStepId, additionalAttributes: itemAttrs }) => ({
158
+ awaitingStepId,
159
+ additionalAttributes: itemAttrs
160
+ })),
161
+ finalPendingStepId,
162
+ {
163
+ complete: false,
164
+ errorsFound: []
165
+ }
178
166
  );
179
- if (listPendingSteps.Items.length !== 1) {
180
- _izContext.logger.error(
181
- '[Lib:findPendingStepIdAwaitingMultipleSteps] listPendingSteps not equals 1'
167
+
168
+ if (publishConfig) {
169
+ const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
170
+ _izContext,
171
+ publishConfig.flowType.flowTag,
172
+ publishConfig.flowType.serviceTag
173
+ );
174
+
175
+ await Promise.all(
176
+ finalRecords.map(({ message }) =>
177
+ sns.publishAsync(_izContext, {
178
+ TopicArn: `${topicArn}_In`,
179
+ Message: JSON.stringify(message),
180
+ MessageAttributes: {}
181
+ })
182
+ )
182
183
  );
183
- throw new NoRetryError('listPendingSteps not equals 1');
184
184
  }
185
- const pendingStepId = listPendingSteps.Items[0].pendingStepId;
186
185
 
187
- _izContext.logger.debug(
188
- '[Lib:findPendingStepIdAwaitingMultipleSteps] Pending step ID',
189
- pendingStepId
190
- );
186
+ return finalRecords[0]?.awaitingStepId;
187
+ }
191
188
 
192
- return pendingStepId;
189
+ export async function createAwaitingMultipleStepsWithAdditionalAttributes(
190
+ _izContext,
191
+ records,
192
+ pendingStepId
193
+ ) {
194
+ return createAwaitingMultipleSteps(_izContext, pendingStepId, records);
193
195
  }
194
196
 
195
- /**
196
- * Return the raw items waiting on a given awaitingStepId (multiple).
197
- * @async
198
- * @param {IzContext} _izContext
199
- * @param {string} awaitingStepId
200
- * @returns {Promise<AwaitingMultipleStepsItem[]>}
201
- */
202
- export async function findPendingStepsAwaitingMultipleSteps(
197
+ export async function createAwaitingMultipleStep(
203
198
  _izContext,
204
- awaitingStepId
199
+ pendingStepId,
200
+ prefix = '',
201
+ additionalAttributes = {}
205
202
  ) {
206
- _izContext.logger.debug(
207
- '[Lib:findPendingStepsAwaitingMultipleSteps] Input parameters',
208
- {
209
- awaitingStepId: awaitingStepId
203
+ return createAwaitingMultipleSteps(_izContext, pendingStepId, [], {
204
+ prefix,
205
+ additionalAttributes
206
+ });
207
+ }
208
+
209
+ export async function createAwaitingMultipleStepsWithAdditionalAttributesAndPublish(
210
+ _izContext,
211
+ messages,
212
+ prefix = '',
213
+ flowType = {},
214
+ additionalAttributes = {},
215
+ pendingStepId
216
+ ) {
217
+ return createAwaitingMultipleSteps(_izContext, pendingStepId, [], {
218
+ prefix,
219
+ additionalAttributes,
220
+ publishConfig: {
221
+ messages,
222
+ flowType
210
223
  }
211
- );
224
+ });
225
+ }
212
226
 
213
- if (!awaitingStepId) {
214
- _izContext.logger.error(
215
- '[Lib:findPendingStepsAwaitingMultipleSteps] awaitingStepId is required'
216
- );
217
- throw new NoRetryError('awaitingStepId is required');
218
- }
227
+ export async function findPendingStepAwaitingMultipleSteps(
228
+ _izContext,
229
+ awaitingStepId
230
+ ) {
231
+ _izContext.logger.debug('[Lib:findPendingStepAwaitingMultipleSteps] Input', {
232
+ awaitingStepId
233
+ });
219
234
 
220
- const listPendingSteps = await queryAwaitingMultipleSteps(
235
+ const items = await _queryPendingStepsByAwaitingStepId(
221
236
  _izContext,
222
237
  awaitingStepId
223
238
  );
224
239
 
225
- _izContext.logger.debug(
226
- '[Lib:findPendingStepsAwaitingMultipleSteps] Record of awaitingStepId',
227
- listPendingSteps
228
- );
229
-
230
- return listPendingSteps.Items;
240
+ return items[0];
231
241
  }
232
242
 
233
- /**
234
- * Fetch exactly one pending step for a given awaitingStepId (throws if not 1).
235
- * @async
236
- * @param {IzContext} _izContext
237
- * @param {string} awaitingStepId
238
- * @returns {Promise<AwaitingMultipleStepsItem>}
239
- * @throws {NoRetryError} if there is not exactly one item
240
- */
241
- export async function findPendingStepAwaitingMultipleSteps(
243
+ export async function findPendingStepsAwaitingMultipleSteps(
242
244
  _izContext,
243
245
  awaitingStepId
244
246
  ) {
245
- _izContext.logger.debug(
246
- '[Lib:findPendingStepAwaitingMultipleSteps] Input parameters',
247
- {
248
- awaitingStepId: awaitingStepId
249
- }
250
- );
251
-
252
- if (!awaitingStepId) {
253
- _izContext.logger.error(
254
- '[Lib:findPendingStepAwaitingMultipleSteps] awaitingStepId is required'
255
- );
256
- throw new NoRetryError('awaitingStepId is required');
257
- }
247
+ return _queryPendingStepsByAwaitingStepId(_izContext, awaitingStepId);
248
+ }
258
249
 
259
- const listPendingSteps = await queryAwaitingMultipleSteps(
250
+ export async function findPendingStepIdAwaitingMultipleSteps(
251
+ _izContext,
252
+ awaitingStepId
253
+ ) {
254
+ const items = await _queryPendingStepsByAwaitingStepId(
260
255
  _izContext,
261
256
  awaitingStepId
262
257
  );
263
-
264
- _izContext.logger.debug(
265
- '[Lib:findPendingStepAwaitingMultipleSteps] Record of awaitingStepId',
266
- listPendingSteps
267
- );
268
-
269
- if (listPendingSteps.Items.length !== 1) {
270
- _izContext.logger.error(
271
- '[Lib:findPendingStepAwaitingMultipleSteps] listPendingSteps not equals 1'
272
- );
273
- throw new NoRetryError('listPendingSteps not equals 1');
274
- }
275
-
276
- return listPendingSteps.Items[0];
258
+ return items[0]?.pendingStepId;
277
259
  }
278
260
 
279
- // Use case:
280
- // Use only after checkAllAwaitingStepsFinished
281
- // Do NOT delete records in both tables (AwaitingMultipleSteps, AwaitingMultipleStepByPending) if AwaitingMultipleSteps is not yet finished
282
- // Delete records from both tables only when checkAllAwaitingStepsFinished = true
283
- // Example usage: flow validateCart lambda ProcessCartOrderFromTraversal
284
- /**
285
- * Given a pendingStepId, return all awaiting steps (by the ByPending table).
286
- * Use only after confirming all awaiting steps are finished, and clean up both tables accordingly.
287
- * @async
288
- * @param {IzContext} _izContext
289
- * @param {string} pendingStepId
290
- * @returns {Promise<AwaitingMultipleStepByPendingItem[]>}
291
- */
292
261
  export async function findAwaitingMultipleStepByPending(
293
262
  _izContext,
294
263
  pendingStepId
295
264
  ) {
296
- _izContext.logger.debug(
297
- '[Lib:findAwaitingMultipleStepByPending] Input parameters',
298
- {
299
- pendingStepId: pendingStepId
300
- }
301
- );
302
-
303
- if (!pendingStepId) {
304
- _izContext.logger.error(
305
- '[Lib:findAwaitingMultipleStepByPending] pendingStepId is required'
306
- );
307
- throw new NoRetryError('pendingStepId is required');
308
- }
309
-
310
- const listAwaitingSteps = await dynamodbSharedLib.query(
311
- _izContext,
312
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
313
- {
314
- pendingStepId: pendingStepId
315
- }
316
- );
317
- _izContext.logger.debug(
318
- '[Lib:findAwaitingMultipleStepByPending] Record of AwaitingMultipleStepByPending',
319
- listAwaitingSteps
320
- );
321
-
322
- return listAwaitingSteps.Items;
265
+ return _queryAwaitingStepsByPendingStepId(_izContext, pendingStepId);
323
266
  }
324
267
 
325
- /**
326
- * Checks if all awaiting steps for a pending step are complete after marking the current one as complete.
327
- * If all steps are finished, returns an object with `isComplete: true` and the collected data.
328
- * If any other step is not yet complete, returns `isComplete: false` with null data.
329
- * @async
330
- * @param {IzContext} _izContext
331
- * @param {string} pendingStepId
332
- * @param {string|null} [currentAwaitingStepId=null] - The step that has just finished.
333
- * @param {any[]} [errorsFound=[]]
334
- * @param {Attrs} [returnValues={}]
335
- * @returns {Promise<{
336
- * isComplete: boolean,
337
- * collectedAttributes: Record<string, any>|null,
338
- * collectedErrors: any[],
339
- * sharedAdditionalAttributes: any|null
340
- * }>}
341
- */
342
268
  export async function checkAllAwaitingStepsFinishedWithReturnParams(
343
269
  _izContext,
344
270
  pendingStepId,
345
271
  currentAwaitingStepId = null,
346
272
  errorsFound = [],
347
273
  returnValues = {},
348
- settings = {
349
- checkIsAllError: true
350
- }
274
+ settings = { checkIsAllError: true }
351
275
  ) {
352
276
  _izContext.logger.debug(
353
277
  '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Input parameters',
@@ -359,36 +283,35 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
359
283
  }
360
284
  );
361
285
 
286
+ _assertPendingStepId(pendingStepId);
287
+
362
288
  if (currentAwaitingStepId) {
363
289
  await dynamodbSharedLib.updateItem(
364
290
  _izContext,
365
291
  dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
366
292
  {
367
- pendingStepId: pendingStepId,
293
+ pendingStepId,
368
294
  awaitingStepId: currentAwaitingStepId
369
295
  },
370
296
  {
371
297
  complete: true,
372
- errorsFound: errorsFound,
373
- returnValues: returnValues
298
+ errorsFound,
299
+ returnValues
374
300
  }
375
301
  );
376
302
  }
377
303
 
378
- const awaitingStepItems = await dynamodbSharedLib.query(
304
+ const awaitingStepItems = await _queryAwaitingStepsByPendingStepId(
379
305
  _izContext,
380
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
381
- { pendingStepId },
382
- { limit: 50 }
306
+ pendingStepId
383
307
  );
384
308
 
385
309
  _izContext.logger.debug(
386
- '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] awaitingStepItems:::',
310
+ '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] awaitingStepItems',
387
311
  awaitingStepItems
388
312
  );
389
313
 
390
- // Pass 1: check completeness of all other steps before collecting any data
391
- const hasIncomplete = awaitingStepItems.Items.some(
314
+ const hasIncomplete = awaitingStepItems.some(
392
315
  item => item.awaitingStepId !== currentAwaitingStepId && !item.complete
393
316
  );
394
317
 
@@ -401,125 +324,72 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
401
324
  };
402
325
  }
403
326
 
404
- _izContext.logger.debug(
405
- '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Pass 1: all steps are complete'
406
- );
407
-
408
- // Pass 2: all steps are complete — collect errors and attributes
409
327
  const collectedAttributes = {};
410
328
  let sharedReturnValues = null;
411
329
 
412
- for (const item of awaitingStepItems.Items) {
413
- if (item.returnValues) {
414
- collectedAttributes[item.awaitingStepId] = item.returnValues;
415
- sharedReturnValues ??= item.returnValues; // ponytail: use nullish coalescing
330
+ for (const item of awaitingStepItems) {
331
+ if (!item.returnValues) {
332
+ continue;
416
333
  }
334
+
335
+ collectedAttributes[item.awaitingStepId] = item.returnValues;
336
+ sharedReturnValues ??= item.returnValues;
417
337
  }
418
338
 
419
- const collectedErrors = awaitingStepItems.Items.flatMap(item =>
339
+ const collectedErrors = awaitingStepItems.flatMap(item =>
420
340
  item.errorsFound?.length > 0
421
341
  ? [`Error found in step ${item.awaitingStepId}`, ...item.errorsFound]
422
342
  : []
423
343
  );
424
344
 
425
- _izContext.logger.debug(
426
- '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Pass 2: all steps are complete — collect errors and attributes'
427
- );
428
-
429
345
  return {
430
346
  isComplete: true,
431
347
  collectedAttributes,
432
348
  collectedErrors,
433
349
  returnValues: sharedReturnValues,
434
- additionalAttributes:
435
- awaitingStepItems.Items[0]?.additionalAttributes || null,
350
+ additionalAttributes: awaitingStepItems[0]?.additionalAttributes || null,
436
351
  ...(settings.checkIsAllError && {
437
- isAllError: awaitingStepItems.Items.every(
438
- item => item.errorsFound?.length > 0
439
- )
352
+ isAllError: awaitingStepItems.every(item => item.errorsFound?.length > 0)
440
353
  })
441
354
  };
442
355
  }
443
356
 
444
- /**
445
- * Remove all awaiting records for a given pendingStepId from both tables.
446
- * @async
447
- * @param {IzContext} _izContext
448
- * @param {string} pendingStepId
449
- * @returns {Promise<boolean>} true if delete flow processed
450
- */
451
357
  export async function clearAllAwaitingSteps(_izContext, pendingStepId) {
452
358
  _izContext.logger.debug('[Lib:clearAllAwaitingSteps] Input parameters', {
453
- pendingStepId: pendingStepId
359
+ pendingStepId
454
360
  });
455
361
 
456
- if (!pendingStepId) {
457
- throw new NoRetryError(
458
- '[Lib:clearAllAwaitingSteps] pendingStepId is required'
459
- );
460
- }
461
-
462
- // ----- query items from GSI table
463
- const listPendingStepIds = await dynamodbSharedLib.query(
362
+ const listPendingStepIds = await _queryAwaitingStepsByPendingStepId(
464
363
  _izContext,
465
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
466
- {
467
- pendingStepId: pendingStepId
468
- }
364
+ pendingStepId
469
365
  );
470
- _izContext.logger.debug(
471
- '[Lib:clearAllAwaitingSteps] listPendingStepIds',
472
- listPendingStepIds
366
+ const tableSteps = dynamodbSharedLib.tableName(
367
+ _izContext,
368
+ 'AwaitingMultipleSteps'
369
+ );
370
+ const tablePending = dynamodbSharedLib.tableName(
371
+ _izContext,
372
+ 'AwaitingMultipleStepByPending'
473
373
  );
474
374
 
475
- const promiseArray = [];
476
-
477
- for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
478
- _izContext.logger.debug(`delete item in dynamodb AwaitingMultipleSteps`);
479
- promiseArray.push(
480
- dynamodbSharedLib.deleteItem(
481
- _izContext,
482
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
483
- {
484
- awaitingStepId: listPendingStepIds.Items[idx].awaitingStepId,
485
- pendingStepId: listPendingStepIds.Items[idx].pendingStepId
486
- }
487
- )
488
- );
489
-
490
- _izContext.logger.debug(
491
- '[Lib:clearAllAwaitingSteps] delete item in dynamodb AwaitingMultipleStepByPending'
492
- );
493
- promiseArray.push(
494
- dynamodbSharedLib.deleteItem(
495
- _izContext,
496
- dynamodbSharedLib.tableName(
497
- _izContext,
498
- 'AwaitingMultipleStepByPending'
499
- ),
500
- {
501
- pendingStepId: listPendingStepIds.Items[idx].pendingStepId,
502
- awaitingStepId: listPendingStepIds.Items[idx].awaitingStepId
503
- }
504
- )
505
- );
506
- }
507
-
508
- await Promise.all(promiseArray);
375
+ await Promise.all(
376
+ listPendingStepIds.flatMap(
377
+ ({ awaitingStepId, pendingStepId: itemPendingStepId }) => [
378
+ dynamodbSharedLib.deleteItem(_izContext, tableSteps, {
379
+ awaitingStepId,
380
+ pendingStepId: itemPendingStepId
381
+ }),
382
+ dynamodbSharedLib.deleteItem(_izContext, tablePending, {
383
+ awaitingStepId,
384
+ pendingStepId: itemPendingStepId
385
+ })
386
+ ]
387
+ )
388
+ );
509
389
 
510
390
  return true;
511
391
  }
512
392
 
513
- /**
514
- * Remove items for AwaitingMultipleSteps and (conditionally) AwaitingMultipleStepByPending.
515
- * If errorsFound is empty, delete from ByPending table as well.
516
- * @async
517
- * @param {IzContext} _izContext
518
- * @param {string} awaitingStepId
519
- * @param {string} pendingStepId
520
- * @param {any[]} [errorsFound=[]]
521
- * @returns {Promise<void>}
522
- */
523
393
  export async function removeAwaitingMultipleStep(
524
394
  _izContext,
525
395
  awaitingStepId,
@@ -527,154 +397,38 @@ export async function removeAwaitingMultipleStep(
527
397
  errorsFound = []
528
398
  ) {
529
399
  _izContext.logger.debug('[Lib:removeAwaitingMultipleStep] Input parameters', {
530
- awaitingStepId: awaitingStepId,
531
- pendingStepId: pendingStepId,
532
- errorsFound: errorsFound
400
+ awaitingStepId,
401
+ pendingStepId,
402
+ errorsFound
533
403
  });
534
404
 
535
405
  if (!awaitingStepId || !pendingStepId) {
536
406
  throw new NoRetryError(
537
- '[Lib:removeAwaitingMultipleStep] awaitingStepId or pendingStepId is required'
407
+ '[Lib:removeAwaitingMultipleStep] awaitingStepId or pendingStepId required'
538
408
  );
539
409
  }
540
410
 
541
- const promiseArray = [];
542
-
543
- const keyValues = {
544
- awaitingStepId: awaitingStepId,
545
- pendingStepId: pendingStepId
546
- };
547
-
548
- const keyValuesForByPending = {
549
- pendingStepId: pendingStepId,
550
- awaitingStepId: awaitingStepId
551
- };
552
- _izContext.logger.debug('deleting ... ', {
553
- keyValues,
554
- keyValuesForByPending
555
- });
556
-
557
- _izContext.logger.debug(
558
- '[Lib:removeAwaitingMultipleStep] delete item in dynamodb AwaitingMultipleSteps',
559
- {
560
- awaitingStepId: keyValues.awaitingStepId,
561
- pendingStepId: keyValues.pendingStepId
562
- }
563
- );
564
- promiseArray.push(
411
+ const keys = { awaitingStepId, pendingStepId };
412
+ const promises = [
565
413
  dynamodbSharedLib.deleteItem(
566
414
  _izContext,
567
415
  dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
568
- keyValues
416
+ keys
569
417
  )
570
- );
418
+ ];
571
419
 
572
- if (errorsFound.length == 0) {
573
- _izContext.logger.debug(
574
- `delete item in dynamodb AwaitingMultipleStepByPending ==> awaitingStepId:${keyValues.awaitingStepId}`
575
- );
576
- promiseArray.push(
420
+ if (errorsFound.length === 0) {
421
+ promises.push(
577
422
  dynamodbSharedLib.deleteItem(
578
423
  _izContext,
579
424
  dynamodbSharedLib.tableName(
580
425
  _izContext,
581
426
  'AwaitingMultipleStepByPending'
582
427
  ),
583
- keyValuesForByPending
428
+ keys
584
429
  )
585
430
  );
586
431
  }
587
- await Promise.all(promiseArray);
588
- }
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
- */
605
- export async function createAwaitingMultipleStepsWithAdditionalAttributesAndPublish(
606
- _izContext,
607
- messages,
608
- prefix = '',
609
- flowType = {},
610
- additionalAttributes = {},
611
- pendingStepId
612
- ) {
613
- _izContext.logger.debug(
614
- '[Lib:asyncFlow:createAwaitingMultipleStepsWithAdditionalAttributesAndPublish] Input parameters',
615
- {
616
- messages,
617
- prefix,
618
- flowType,
619
- additionalAttributes,
620
- pendingStepId
621
- }
622
- );
623
-
624
- if (!Array.isArray(messages)) {
625
- throw new NoRetryError('messages must be an array');
626
- }
627
432
 
628
- const flowTag = flowType.flowTag;
629
- const serviceTag = flowType.serviceTag;
630
-
631
- if (flowTag === undefined || serviceTag === undefined) {
632
- throw new NoRetryError('flowType must have both flowTag and serviceTag');
633
- }
634
-
635
- const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
636
- _izContext,
637
- flowType.flowTag,
638
- flowType.serviceTag
639
- );
640
-
641
- _izContext.logger.debug(
642
- '[Lib:asyncFlow:createAwaitingMultipleStepsWithAdditionalAttributesAndPublish] Topic Arn',
643
- topicArn
644
- );
645
-
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
- }
664
- );
665
-
666
- // 2. Then publish messages concurrently
667
- await Promise.all(
668
- messagesWithParentId.map(message =>
669
- sns.publishAsync(_izContext, {
670
- TopicArn: `${topicArn}_In`,
671
- Message: JSON.stringify(message),
672
- MessageAttributes: {}
673
- })
674
- )
675
- );
676
-
677
- _izContext.logger.debug(
678
- '[Lib:asyncFlow:createAwaitingMultipleStepsWithAdditionalAttributesAndPublish] Exit point'
679
- );
433
+ await Promise.all(promises);
680
434
  }