@izara_project/izara-core-library-asynchronous-flow 1.0.36 → 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,332 +21,251 @@ 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
+ options = {}
115
99
  ) {
116
- _izContext.logger.debug(
117
- '[Lib:AsyncFlow:createAwaitingMultipleStep] Input: ',
118
- {
119
- pendingStepId,
120
- additionalAttributes
121
- }
122
- );
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
+ });
123
111
 
124
- const parentFlowId = createParentFlowId(prefix);
125
- const sharedAttributes = {
126
- complete: false,
127
- errorsFound: []
128
- };
112
+ let finalRecords = records;
129
113
 
130
- _izContext.logger.debug(
131
- '[Lib:AsyncFlow:createAwaitingMultipleStep] sharedAttributes: ',
132
- { awaitingStepId: parentFlowId, pendingStepId, ...sharedAttributes }
133
- );
114
+ if (finalRecords.length === 0 && !publishConfig) {
115
+ finalRecords = [
116
+ {
117
+ awaitingStepId: createParentFlowId(prefix),
118
+ additionalAttributes
119
+ }
120
+ ];
121
+ }
134
122
 
135
- await putAwaitingMultipleStepRecords(
136
- _izContext,
137
- [{ awaitingStepId: parentFlowId, additionalAttributes }],
138
- pendingStepId,
139
- sharedAttributes
140
- );
123
+ if (publishConfig) {
124
+ const { messages, flowType = {} } = publishConfig;
125
+ const { flowTag, serviceTag } = flowType;
141
126
 
142
- _izContext.logger.debug(
143
- '[Lib:AsyncFlow:createAwaitingMultipleStep] Returning: ',
144
- parentFlowId
145
- );
127
+ if (!Array.isArray(messages)) {
128
+ throw new NoRetryError('messages must be an array');
129
+ }
146
130
 
147
- return parentFlowId;
148
- }
131
+ if (!flowTag || !serviceTag) {
132
+ throw new NoRetryError('flowType must both flowTag serviceTag');
133
+ }
149
134
 
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',
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
+ });
147
+ }
148
+
149
+ await _putAwaitingMultipleStepRecords(
150
+ _izContext,
151
+ finalRecords.map(({ awaitingStepId, additionalAttributes: itemAttrs }) => ({
152
+ awaitingStepId,
153
+ additionalAttributes: itemAttrs
154
+ })),
155
+ pendingStepId,
163
156
  {
164
- awaitingStepId: awaitingStepId
157
+ complete: false,
158
+ errorsFound: []
165
159
  }
166
160
  );
167
161
 
168
- if (!awaitingStepId) {
169
- _izContext.logger.error(
170
- '[Lib:findPendingStepIdAwaitingMultipleSteps] awaitingStepId is required'
162
+ if (publishConfig) {
163
+ const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
164
+ _izContext,
165
+ publishConfig.flowType.flowTag,
166
+ publishConfig.flowType.serviceTag
171
167
  );
172
- throw new NoRetryError('awaitingStepId is required');
173
- }
174
168
 
175
- const listPendingSteps = await queryAwaitingMultipleSteps(
176
- _izContext,
177
- awaitingStepId
178
- );
179
- if (listPendingSteps.Items.length !== 1) {
180
- _izContext.logger.error(
181
- '[Lib:findPendingStepIdAwaitingMultipleSteps] 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
+ )
182
177
  );
183
- throw new NoRetryError('listPendingSteps not equals 1');
184
178
  }
185
- const pendingStepId = listPendingSteps.Items[0].pendingStepId;
186
179
 
187
- _izContext.logger.debug(
188
- '[Lib:findPendingStepIdAwaitingMultipleSteps] Pending step ID',
189
- pendingStepId
190
- );
180
+ return finalRecords[0]?.awaitingStepId;
181
+ }
191
182
 
192
- return pendingStepId;
183
+ export async function createAwaitingMultipleStepsWithAdditionalAttributes(
184
+ _izContext,
185
+ records,
186
+ pendingStepId
187
+ ) {
188
+ return createAwaitingMultipleSteps(_izContext, pendingStepId, { records });
193
189
  }
194
190
 
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(
191
+ export async function createAwaitingMultipleStep(
203
192
  _izContext,
204
- awaitingStepId
193
+ pendingStepId,
194
+ prefix = '',
195
+ additionalAttributes = {}
205
196
  ) {
206
- _izContext.logger.debug(
207
- '[Lib:findPendingStepsAwaitingMultipleSteps] Input parameters',
208
- {
209
- awaitingStepId: awaitingStepId
197
+ return createAwaitingMultipleSteps(_izContext, pendingStepId, {
198
+ prefix,
199
+ additionalAttributes
200
+ });
201
+ }
202
+
203
+ export async function createAwaitingMultipleStepsWithAdditionalAttributesAndPublish(
204
+ _izContext,
205
+ messages,
206
+ prefix = '',
207
+ flowType = {},
208
+ additionalAttributes = {},
209
+ pendingStepId
210
+ ) {
211
+ return createAwaitingMultipleSteps(_izContext, pendingStepId, {
212
+ prefix,
213
+ additionalAttributes,
214
+ publishConfig: {
215
+ messages,
216
+ flowType
210
217
  }
211
- );
218
+ });
219
+ }
212
220
 
213
- if (!awaitingStepId) {
214
- _izContext.logger.error(
215
- '[Lib:findPendingStepsAwaitingMultipleSteps] awaitingStepId is required'
216
- );
217
- throw new NoRetryError('awaitingStepId is required');
218
- }
221
+ export async function findPendingStepAwaitingMultipleSteps(
222
+ _izContext,
223
+ awaitingStepId
224
+ ) {
225
+ _izContext.logger.debug('[Lib:findPendingStepAwaitingMultipleSteps] Input', {
226
+ awaitingStepId
227
+ });
219
228
 
220
- const listPendingSteps = await queryAwaitingMultipleSteps(
229
+ const items = await _queryPendingStepsByAwaitingStepId(
221
230
  _izContext,
222
231
  awaitingStepId
223
232
  );
224
233
 
225
- _izContext.logger.debug(
226
- '[Lib:findPendingStepsAwaitingMultipleSteps] Record of awaitingStepId',
227
- listPendingSteps
228
- );
229
-
230
- return listPendingSteps.Items;
234
+ return items[0];
231
235
  }
232
236
 
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(
237
+ export async function findPendingStepsAwaitingMultipleSteps(
242
238
  _izContext,
243
239
  awaitingStepId
244
240
  ) {
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
- }
241
+ return _queryPendingStepsByAwaitingStepId(_izContext, awaitingStepId);
242
+ }
258
243
 
259
- const listPendingSteps = await queryAwaitingMultipleSteps(
244
+ export async function findPendingStepIdAwaitingMultipleSteps(
245
+ _izContext,
246
+ awaitingStepId
247
+ ) {
248
+ const items = await _queryPendingStepsByAwaitingStepId(
260
249
  _izContext,
261
250
  awaitingStepId
262
251
  );
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];
252
+ return items[0]?.pendingStepId;
277
253
  }
278
254
 
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
255
  export async function findAwaitingMultipleStepByPending(
293
256
  _izContext,
294
257
  pendingStepId
295
258
  ) {
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;
259
+ return _queryAwaitingStepsByPendingStepId(_izContext, pendingStepId);
323
260
  }
324
261
 
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
262
  export async function checkAllAwaitingStepsFinishedWithReturnParams(
343
263
  _izContext,
344
264
  pendingStepId,
345
265
  currentAwaitingStepId = null,
346
266
  errorsFound = [],
347
267
  returnValues = {},
348
- settings = {
349
- checkIsAllError: true
350
- }
268
+ settings = { checkIsAllError: true }
351
269
  ) {
352
270
  _izContext.logger.debug(
353
271
  '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Input parameters',
@@ -359,36 +277,35 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
359
277
  }
360
278
  );
361
279
 
280
+ _assertPendingStepId(pendingStepId);
281
+
362
282
  if (currentAwaitingStepId) {
363
283
  await dynamodbSharedLib.updateItem(
364
284
  _izContext,
365
285
  dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
366
286
  {
367
- pendingStepId: pendingStepId,
287
+ pendingStepId,
368
288
  awaitingStepId: currentAwaitingStepId
369
289
  },
370
290
  {
371
291
  complete: true,
372
- errorsFound: errorsFound,
373
- returnValues: returnValues
292
+ errorsFound,
293
+ returnValues
374
294
  }
375
295
  );
376
296
  }
377
297
 
378
- const awaitingStepItems = await dynamodbSharedLib.query(
298
+ const awaitingStepItems = await _queryAwaitingStepsByPendingStepId(
379
299
  _izContext,
380
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
381
- { pendingStepId },
382
- { limit: 50 }
300
+ pendingStepId
383
301
  );
384
302
 
385
303
  _izContext.logger.debug(
386
- '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] awaitingStepItems:::',
304
+ '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] awaitingStepItems',
387
305
  awaitingStepItems
388
306
  );
389
307
 
390
- // Pass 1: check completeness of all other steps before collecting any data
391
- const hasIncomplete = awaitingStepItems.Items.some(
308
+ const hasIncomplete = awaitingStepItems.some(
392
309
  item => item.awaitingStepId !== currentAwaitingStepId && !item.complete
393
310
  );
394
311
 
@@ -401,125 +318,72 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
401
318
  };
402
319
  }
403
320
 
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
321
  const collectedAttributes = {};
410
322
  let sharedReturnValues = null;
411
323
 
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
324
+ for (const item of awaitingStepItems) {
325
+ if (!item.returnValues) {
326
+ continue;
416
327
  }
328
+
329
+ collectedAttributes[item.awaitingStepId] = item.returnValues;
330
+ sharedReturnValues ??= item.returnValues;
417
331
  }
418
332
 
419
- const collectedErrors = awaitingStepItems.Items.flatMap(item =>
333
+ const collectedErrors = awaitingStepItems.flatMap(item =>
420
334
  item.errorsFound?.length > 0
421
335
  ? [`Error found in step ${item.awaitingStepId}`, ...item.errorsFound]
422
336
  : []
423
337
  );
424
338
 
425
- _izContext.logger.debug(
426
- '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] Pass 2: all steps are complete — collect errors and attributes'
427
- );
428
-
429
339
  return {
430
340
  isComplete: true,
431
341
  collectedAttributes,
432
342
  collectedErrors,
433
343
  returnValues: sharedReturnValues,
434
- additionalAttributes:
435
- awaitingStepItems.Items[0]?.additionalAttributes || null,
344
+ additionalAttributes: awaitingStepItems[0]?.additionalAttributes || null,
436
345
  ...(settings.checkIsAllError && {
437
- isAllError: awaitingStepItems.Items.every(
438
- item => item.errorsFound?.length > 0
439
- )
346
+ isAllError: awaitingStepItems.every(item => item.errorsFound?.length > 0)
440
347
  })
441
348
  };
442
349
  }
443
350
 
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
351
  export async function clearAllAwaitingSteps(_izContext, pendingStepId) {
452
352
  _izContext.logger.debug('[Lib:clearAllAwaitingSteps] Input parameters', {
453
- pendingStepId: pendingStepId
353
+ pendingStepId
454
354
  });
455
355
 
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(
356
+ const listPendingStepIds = await _queryAwaitingStepsByPendingStepId(
464
357
  _izContext,
465
- dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleStepByPending'),
466
- {
467
- pendingStepId: pendingStepId
468
- }
358
+ pendingStepId
469
359
  );
470
- _izContext.logger.debug(
471
- '[Lib:clearAllAwaitingSteps] listPendingStepIds',
472
- listPendingStepIds
360
+ const tableSteps = dynamodbSharedLib.tableName(
361
+ _izContext,
362
+ 'AwaitingMultipleSteps'
363
+ );
364
+ const tablePending = dynamodbSharedLib.tableName(
365
+ _izContext,
366
+ 'AwaitingMultipleStepByPending'
473
367
  );
474
368
 
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);
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
+ );
509
383
 
510
384
  return true;
511
385
  }
512
386
 
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
387
  export async function removeAwaitingMultipleStep(
524
388
  _izContext,
525
389
  awaitingStepId,
@@ -527,154 +391,38 @@ export async function removeAwaitingMultipleStep(
527
391
  errorsFound = []
528
392
  ) {
529
393
  _izContext.logger.debug('[Lib:removeAwaitingMultipleStep] Input parameters', {
530
- awaitingStepId: awaitingStepId,
531
- pendingStepId: pendingStepId,
532
- errorsFound: errorsFound
394
+ awaitingStepId,
395
+ pendingStepId,
396
+ errorsFound
533
397
  });
534
398
 
535
399
  if (!awaitingStepId || !pendingStepId) {
536
400
  throw new NoRetryError(
537
- '[Lib:removeAwaitingMultipleStep] awaitingStepId or pendingStepId is required'
401
+ '[Lib:removeAwaitingMultipleStep] awaitingStepId or pendingStepId required'
538
402
  );
539
403
  }
540
404
 
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(
405
+ const keys = { awaitingStepId, pendingStepId };
406
+ const promises = [
565
407
  dynamodbSharedLib.deleteItem(
566
408
  _izContext,
567
409
  dynamodbSharedLib.tableName(_izContext, 'AwaitingMultipleSteps'),
568
- keyValues
410
+ keys
569
411
  )
570
- );
412
+ ];
571
413
 
572
- if (errorsFound.length == 0) {
573
- _izContext.logger.debug(
574
- `delete item in dynamodb AwaitingMultipleStepByPending ==> awaitingStepId:${keyValues.awaitingStepId}`
575
- );
576
- promiseArray.push(
414
+ if (errorsFound.length === 0) {
415
+ promises.push(
577
416
  dynamodbSharedLib.deleteItem(
578
417
  _izContext,
579
418
  dynamodbSharedLib.tableName(
580
419
  _izContext,
581
420
  'AwaitingMultipleStepByPending'
582
421
  ),
583
- keyValuesForByPending
422
+ keys
584
423
  )
585
424
  );
586
425
  }
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
426
 
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
- );
427
+ await Promise.all(promises);
680
428
  }