@izara_project/izara-core-library-asynchronous-flow 1.0.42 → 1.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "author": "Sven Mason <thebarbariansven@gmail.com>",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "homepage": "https://bitbucket.org/izara-core-libraries/izara-core-library-asynchronous-flow#readme",
6
- "version": "1.0.42",
6
+ "version": "1.0.43",
7
7
  "description": "Shared asynchronous flow logic",
8
8
  "type": "module",
9
9
  "main": "index.js",
@@ -36,20 +36,25 @@ import { identifierUuid } from '@izara_project/izara-shared-core';
36
36
  * @returns {string}
37
37
  */
38
38
  function _joinPrefixAndId(id, prefix = '') {
39
- if (!prefix) return id;
39
+ if (!prefix) {
40
+ return id;
41
+ }
42
+
43
+ let cleanPrefix = prefix;
44
+ if (prefix.endsWith('_')) {
45
+ cleanPrefix = prefix.slice(0, -1);
46
+ }
40
47
 
41
- const cleanPrefix = prefix.endsWith('_') ? prefix.slice(0, -1) : prefix;
42
- const cleanId = id.startsWith('_') ? id.slice(1) : id;
48
+ let cleanId = id;
49
+ if (id.startsWith('_')) {
50
+ cleanId = id.slice(1);
51
+ }
43
52
 
44
53
  return `${cleanPrefix}_${cleanId}`;
45
54
  }
46
55
 
47
56
  /**
48
57
  * Create awaitingStepId by concatenating prefix and partitionKey.
49
- * If prefix ends with an underscore, it's removed before concatenation.
50
- * If partitionKey starts with an underscore, it's removed before concatenation.
51
- * This ensures a clean "PREFIX_PARTITIONKEY" format without double underscores.
52
- *
53
58
  * @param {string} partitionKey
54
59
  * @param {string} [prefix='']
55
60
  * @returns {string}
@@ -60,10 +65,6 @@ function createAwaitingStepId(partitionKey, prefix = '') {
60
65
 
61
66
  /**
62
67
  * Create pendingStepId by concatenating prefix and identifierId.
63
- * If prefix ends with an underscore, it's removed before concatenation.
64
- * If identifierId starts with an underscore, it's removed before concatenation.
65
- * This ensures a clean "PREFIX_IDENTIFIER" format without double underscores.
66
- *
67
68
  * @param {string} identifierId
68
69
  * @param {string} [prefix='']
69
70
  * @returns {string}
@@ -73,7 +74,7 @@ function createPendingStepId(identifierId, prefix = '') {
73
74
  }
74
75
 
75
76
  /**
76
- * Create a field name for storing the unique-request id, with optional prefix.
77
+ * Create field name for storing unique-request id, with optional prefix.
77
78
  * @param {string} prefix
78
79
  * @param {string} [uniqueRequestIdFieldName='UniqueRequestId']
79
80
  * @returns {string}
@@ -117,26 +118,29 @@ function createParentFlowId(prefix = '') {
117
118
  }
118
119
 
119
120
  /**
120
- * Remove the known prefix from a pendingStepId and validate it is not equal to the prefix.
121
- * If prefix is empty, the original ID is returned.
121
+ * Remove known prefix from pendingStepId and validate not equal to prefix.
122
122
  * @param {string} pendingStepId
123
123
  * @param {string} [prefix=""]
124
124
  * @returns {string}
125
- * @throws {NoRetryError} If the stripped value equals the prefix (invalid)
125
+ * @throws {NoRetryError} If stripped value equals prefix (invalid)
126
126
  */
127
127
  function explodePendingStepId(pendingStepId, prefix = '') {
128
- if (!pendingStepId) throw new NoRetryError('pendingStepId is required');
129
- if (!prefix) return pendingStepId;
128
+ if (!pendingStepId) {
129
+ throw new NoRetryError('pendingStepId is required');
130
+ }
131
+ if (!prefix) {
132
+ return pendingStepId;
133
+ }
130
134
 
131
135
  const identifierId = pendingStepId.slice(prefix.length);
132
- if (identifierId === prefix)
136
+ if (identifierId === prefix) {
133
137
  throw new NoRetryError('IdentifierId should not be like prefix.');
138
+ }
134
139
 
135
- Logger.debug('return explode:', identifierId);
140
+ Logger.debug('return explode:', { identifierId });
136
141
  return identifierId;
137
142
  }
138
143
 
139
- // copy from izara-core-library-trigger-cache
140
144
  /**
141
145
  * Checks if record has uniqueRequestId set, if not set to this requests uniqueRequestId and continue to process
142
146
  * if already set check if matches this request uniqueRequestId, if yes continue to process, if not then stop processing
@@ -161,11 +165,11 @@ function explodePendingStepId(pendingStepId, prefix = '') {
161
165
  *
162
166
  * @async
163
167
  * @param {IzContext} _izContext
164
- * @param {string} tableName - Logical table name known by dynamodbSharedLib.tableName
165
- * @param {DynamoKey} primaryKey - Primary key for the target record
168
+ * @param {string} tableName - Logical table name
169
+ * @param {DynamoKey} primaryKey - Primary key for target record
166
170
  * @param {string} [prefix=''] - Optional field-name prefix
167
- * @param {string|null} [overwriteUniqueRequestId=null] - If provided, use this as the "current" unique request id
168
- * @param {string} [uniqueRequestIdFieldName="UniqueRequestId"] - Base field name before prefix
171
+ * @param {string|null} [overwriteUniqueRequestId=null] - Overwrite ID
172
+ * @param {string} [uniqueRequestIdFieldName="UniqueRequestId"] - Base name
169
173
  * @returns {Promise<[UniqueRequestStatus, Attrs]>}
170
174
  * @throws {NoRetryError} If unable to set uniqueRequestId after 2 tries
171
175
  */
@@ -178,7 +182,7 @@ async function checkUniqueRequestProcessing(
178
182
  uniqueRequestIdFieldName = 'UniqueRequestId' // if set will check/add this fieldname with _izContext.uniqueRequestId value
179
183
  ) {
180
184
  _izContext.logger.debug(
181
- '[Lib:AsyncFlow:checkUniqueRequestProcessing] Input: ',
185
+ '[Lib:AsyncFlow:checkUniqueRequestProcessing] Input:',
182
186
  {
183
187
  _izContext,
184
188
  tableName,
@@ -194,13 +198,15 @@ async function checkUniqueRequestProcessing(
194
198
  }
195
199
 
196
200
  try {
197
- _izContext.logger.debug(
198
- 'current uniqueRequestId:',
199
- _izContext.uniqueRequestId
200
- );
201
+ _izContext.logger.debug('[Lib:checkUniqueRequestProcessing] reqId', {
202
+ uniqueRequestId: _izContext.uniqueRequestId
203
+ });
204
+
205
+ let uniqueRequestId = overwriteUniqueRequestId;
206
+ if (!uniqueRequestId) {
207
+ uniqueRequestId = _izContext.uniqueRequestId;
208
+ }
201
209
 
202
- const uniqueRequestId =
203
- overwriteUniqueRequestId || _izContext.uniqueRequestId;
204
210
  uniqueRequestIdFieldName = createFieldNameUniqueRequestId(
205
211
  prefix,
206
212
  uniqueRequestIdFieldName
@@ -213,11 +219,17 @@ async function checkUniqueRequestProcessing(
213
219
  primaryKey
214
220
  );
215
221
 
216
- if (!existingRecord) return ['recordNotFound', {}];
222
+ if (!existingRecord) {
223
+ return ['recordNotFound', {}];
224
+ }
217
225
 
218
226
  const currentReqId = existingRecord[uniqueRequestIdFieldName];
219
- if (currentReqId === uniqueRequestId) return ['process', existingRecord];
220
- if (currentReqId) return ['stop', existingRecord];
227
+ if (currentReqId === uniqueRequestId) {
228
+ return ['process', existingRecord];
229
+ }
230
+ if (currentReqId) {
231
+ return ['stop', existingRecord];
232
+ }
221
233
 
222
234
  try {
223
235
  const updateRecord = await dynamodbSharedLib.updateItem(
@@ -245,17 +257,22 @@ async function checkUniqueRequestProcessing(
245
257
  );
246
258
  return ['process', updateRecord];
247
259
  } catch (err) {
248
- if (err.name === 'ConditionalCheckFailedException') continue;
260
+ if (err.name === 'ConditionalCheckFailedException') {
261
+ continue;
262
+ }
249
263
  throw err;
250
264
  }
251
265
  }
252
266
 
253
267
  throw new NoRetryError(
254
- `unable to set uniqueRequestId in table ${tableName}, record`,
255
- primaryKey
268
+ `unable to set uniqueRequestId in table ${tableName}`,
269
+ { primaryKey }
256
270
  );
257
271
  } catch (err) {
258
- _izContext.logger.error('ERROR checkUniqueRequestProcessing:', err);
272
+ _izContext.logger.error(
273
+ '[Lib:checkUniqueRequestProcessing] Error',
274
+ { err }
275
+ );
259
276
  throw err;
260
277
  }
261
278
  }
@@ -266,9 +283,9 @@ async function checkUniqueRequestProcessing(
266
283
  * Returns [isSameOrUnset, uniqueRequestIdWhenComplete].
267
284
  * @async
268
285
  * @param {IzContext} _izContext
269
- * @param {string} fullMainTableName - Fully resolved table name (not logical)
286
+ * @param {string} fullMainTableName - Fully resolved table name
270
287
  * @param {DynamoKey} keyValues
271
- * @param {number|string} timeCacheComplete - Caller’s expected cache mark
288
+ * @param {number|string} timeCacheComplete - Expected cache mark
272
289
  * @param {string} prefix - Field prefix used in cache fields
273
290
  * @returns {Promise<[boolean, string|undefined]>}
274
291
  */
@@ -279,7 +296,7 @@ async function checkTimeCacheComplete(
279
296
  timeCacheComplete,
280
297
  prefix
281
298
  ) {
282
- _izContext.logger.debug('[Lib:AsyncFlow:checkTimeCacheComplete] Input: ', {
299
+ _izContext.logger.debug('[Lib:AsyncFlow:checkTimeCacheComplete] Input:', {
283
300
  _izContext,
284
301
  fullMainTableName,
285
302
  keyValues,
@@ -289,11 +306,12 @@ async function checkTimeCacheComplete(
289
306
 
290
307
  if (!fullMainTableName || !keyValues || !timeCacheComplete || !prefix) {
291
308
  throw new NoRetryError(
292
- 'fullMainTableName, keyValues, timeCacheComplete and prefix are required'
309
+ 'fullMainTableName, keyValues, timeCacheComplete' +
310
+ ' and prefix are required'
293
311
  );
294
312
  }
295
313
 
296
- let [returnValue, newTimeCacheComplete, uniqueRequestId] =
314
+ const [returnValue, , uniqueRequestId] =
297
315
  await checkAndGetTimeCacheComplete(
298
316
  _izContext,
299
317
  fullMainTableName,
@@ -310,7 +328,7 @@ async function checkTimeCacheComplete(
310
328
  * Otherwise returns [true, storedValue, uniqueRequestId].
311
329
  * @async
312
330
  * @param {IzContext} _izContext
313
- * @param {string} fullMainTableName - Fully resolved table name (not logical)
331
+ * @param {string} fullMainTableName - Fully resolved table name
314
332
  * @param {DynamoKey} keyValues
315
333
  * @param {number|string} timeCacheComplete
316
334
  * @param {string} prefix
@@ -325,27 +343,26 @@ async function checkAndGetTimeCacheComplete(
325
343
  prefix
326
344
  ) {
327
345
  _izContext.logger.debug(
328
- '[Lib:AsyncFlow:checkAndGetTimeCacheComplete] Input: ',
346
+ '[Lib:AsyncFlow:checkAndGetTimeCacheComplete] Input:',
329
347
  {
330
- fullMainTableName: fullMainTableName,
331
- keyValues: keyValues,
332
- timeCacheComplete: timeCacheComplete,
333
- prefix: prefix
348
+ fullMainTableName,
349
+ keyValues,
350
+ timeCacheComplete,
351
+ prefix
334
352
  }
335
353
  );
336
354
 
337
355
  const uniqueRequestIdFieldName = createFieldNameUniqueRequestId('cache');
338
356
  const cacheCompleteFieldName = prefix + 'CacheComplete';
339
- const statusFieldName = prefix + 'Status';
340
357
 
341
- let getTimeCacheComplete = await dynamodbSharedLib.getItem(
358
+ const getTimeCacheComplete = await dynamodbSharedLib.getItem(
342
359
  _izContext,
343
360
  fullMainTableName,
344
361
  keyValues
345
362
  );
346
363
  _izContext.logger.debug(
347
- `getTimeCacheComplete from ${fullMainTableName}: `,
348
- getTimeCacheComplete
364
+ `[Lib:checkAndGetTimeCacheComplete] ${fullMainTableName}`,
365
+ { getTimeCacheComplete }
349
366
  );
350
367
 
351
368
  if (!getTimeCacheComplete) {
@@ -371,13 +388,13 @@ async function checkAndGetTimeCacheComplete(
371
388
  getTimeCacheComplete[cacheCompleteFieldName] !== timeCacheComplete
372
389
  ) {
373
390
  return [false, getTimeCacheComplete[cacheCompleteFieldName]];
374
- } else {
375
- return [
376
- true,
377
- getTimeCacheComplete[cacheCompleteFieldName],
378
- getTimeCacheComplete[uniqueRequestIdFieldName]
379
- ];
380
391
  }
392
+
393
+ return [
394
+ true,
395
+ getTimeCacheComplete[cacheCompleteFieldName],
396
+ getTimeCacheComplete[uniqueRequestIdFieldName]
397
+ ];
381
398
  }
382
399
 
383
400
  // ----- shared both stored cache and triggered cache, check uniqueRequestId changed ---------
@@ -403,9 +420,13 @@ async function checkCacheUniqueRequestId(
403
420
  fullMainTableName,
404
421
  keyValues
405
422
  );
406
- _izContext.logger.debug('cacheObject', cacheObject);
423
+ _izContext.logger.debug('[Lib:checkCacheUniqueRequestId] Output', {
424
+ cacheObject
425
+ });
407
426
 
408
- return cacheObject[uniqueRequestIdCompleteFieldName] === checkUniqueRequestId;
427
+ return (
428
+ cacheObject[uniqueRequestIdCompleteFieldName] === checkUniqueRequestId
429
+ );
409
430
  }
410
431
 
411
432
  //====================================== end Multiple Lambda Invocations (Logic pagination of handling results)
@@ -427,7 +448,7 @@ function validateStartKeyParam(
427
448
  partitionKeyFieldName,
428
449
  sortKeyFieldName
429
450
  ) {
430
- _izContext.logger.debug('[Lib:AsyncFlow:validateStartKeyParam] Input: ', {
451
+ _izContext.logger.debug('[Lib:AsyncFlow:validateStartKeyParam] Input:', {
431
452
  startKey,
432
453
  partitionKeyFieldName,
433
454
  sortKeyFieldName
@@ -442,14 +463,16 @@ function validateStartKeyParam(
442
463
  !stringNotEmptyRegex.test(sortKeyFieldName)
443
464
  ) {
444
465
  throw new NoRetryError(
445
- 'validateStartKeyParam: Invalid partitionKeyFieldName or sortKeyFieldName'
466
+ 'validateStartKeyParam:' +
467
+ ' Invalid partitionKeyFieldName or sortKeyFieldName'
446
468
  );
447
469
  }
448
470
 
449
471
  if (startKey && Object.keys(startKey).length !== 0) {
450
472
  if (!startKey[partitionKeyFieldName] || !startKey[sortKeyFieldName]) {
451
473
  throw new NoRetryError(
452
- 'validateStartKeyParam: Invalid startKey, missing partitionKeyFieldName or sortKeyFieldName'
474
+ 'validateStartKeyParam: Invalid startKey,' +
475
+ ' missing partitionKeyFieldName or sortKeyFieldName'
453
476
  );
454
477
  }
455
478
  return startKey;
@@ -464,12 +487,12 @@ function validateStartKeyParam(
464
487
  * and re-enqueues the message to the specified SQS queue.
465
488
  * @async
466
489
  * @param {IzContext} _izContext
467
- * @param {Attrs} messageProperty - The message body object to re-dispatch
490
+ * @param {Attrs} messageProperty - Message body object to re-dispatch
468
491
  * @param {Attrs} [passOnStartKey={}] - Optional startKey to pass along
469
492
  * @param {number} numberInvocation - Current invocation count
470
- * @param {string} queueName - Logical queue name resolvable via sqsSharedLib.sqsQueueUrl
493
+ * @param {string} queueName - Logical queue name
471
494
  * @returns {Promise<void>}
472
- * @throws {NoRetryError} If `numberInvocation` exceeds internal safety limit
495
+ * @throws {NoRetryError} If numberInvocation exceeds safety limit
473
496
  */
474
497
  async function validateMultipleInvocations(
475
498
  _izContext,
@@ -479,12 +502,12 @@ async function validateMultipleInvocations(
479
502
  queueName
480
503
  ) {
481
504
  _izContext.logger.debug(
482
- '[Lib:AsyncFlow:validateMultipleInvocations] Input: ',
505
+ '[Lib:AsyncFlow:validateMultipleInvocations] Input:',
483
506
  {
484
- messageProperty: messageProperty,
485
- passOnStartKey: passOnStartKey,
486
- numberInvocation: numberInvocation,
487
- queueName: queueName
507
+ messageProperty,
508
+ passOnStartKey,
509
+ numberInvocation,
510
+ queueName
488
511
  }
489
512
  );
490
513
 
@@ -509,11 +532,15 @@ async function validateMultipleInvocations(
509
532
  QueueUrl: await sqsSharedLib.sqsQueueUrl(_izContext, queueName)
510
533
  };
511
534
  _izContext.logger.debug(
512
- `Send message to Dsq:${queueName} `,
513
- messageReInvokeFunction
535
+ `[Lib:validateMultipleInvocations] Send message to Dsq:${queueName}`,
536
+ { messageReInvokeFunction }
514
537
  );
515
538
  await sqs.sendMessage(_izContext, messageReInvokeFunction);
516
539
  } catch (err) {
540
+ _izContext.logger.error(
541
+ '[Lib:validateMultipleInvocations] Error',
542
+ { err }
543
+ );
517
544
  throw err;
518
545
  }
519
546
  }
@@ -49,13 +49,17 @@ async function _putAwaitingMultipleStepRecords(
49
49
  records.flatMap(({ awaitingStepId, additionalAttributes }) => {
50
50
  const keys = { awaitingStepId, pendingStepId };
51
51
 
52
+ const itemPending = {
53
+ ...keys,
54
+ ...sharedAttributes
55
+ };
56
+ if (additionalAttributes) {
57
+ itemPending.additionalAttributes = additionalAttributes;
58
+ }
59
+
52
60
  return [
53
61
  dynamodbSharedLib.putItem(_izContext, tableSteps, keys),
54
- dynamodbSharedLib.putItem(_izContext, tablePending, {
55
- ...keys,
56
- ...sharedAttributes,
57
- ...(additionalAttributes && { additionalAttributes })
58
- })
62
+ dynamodbSharedLib.putItem(_izContext, tablePending, itemPending)
59
63
  ];
60
64
  })
61
65
  );
@@ -96,23 +100,24 @@ async function _queryAwaitingStepsByPendingStepId(_izContext, pendingStepId) {
96
100
  // One pendingStepId can have multiple awaitingStepIds awaiting it.
97
101
 
98
102
  /**
99
- * Create awaiting multiple step records to block a flow until multiple steps complete.
103
+ * Create awaiting multiple step records to block a flow
104
+ * until multiple steps complete.
100
105
  * @async
101
106
  * @param {IzContext} _izContext - The Izara context.
102
- * @param {string} pendingStepId - The ID of the pending step that is waiting.
107
+ * @param {string} pendingStepId - The ID of pending step.
103
108
  * @param {Array<object>} [records=[]] - Array of step items.
104
- * @param {string} [records[].awaitingStepId] - Optional ID of the awaiting step. If not provided, a random UUID will be generated.
105
- * @param {object} [records[].message] - Optional message payload to publish if flowType is provided.
106
- * @param {object} [records[].flowType] - Optional flowType override for this specific step record.
107
- * @param {string} records[].flowType.flowTag - Tag identifying the flow.
108
- * @param {string} records[].flowType.serviceTag - Tag identifying the service.
109
+ * @param {string} [records[].awaitingStepId] - Awaiting step ID.
110
+ * @param {object} [records[].message] - Message payload to publish.
111
+ * @param {object} [records[].flowType] - FlowType override.
112
+ * @param {string} records[].flowType.flowTag - Tag identifying flow.
113
+ * @param {string} records[].flowType.serviceTag - Tag identifying service.
109
114
  * @param {object} [options={}] - Optional configurations.
110
- * @param {string} [options.prefix=''] - Prefix to prepend to pendingStepId.
111
- * @param {object} [options.additionalAttributes={}] - Default additional attributes to associate with each pending step record.
112
- * @param {object|null} [options.flowType=null] - Default flow schema/service metadata if starting external tasks.
113
- * @param {string} [options.flowType.flowTag] - Tag identifying the default flow.
114
- * @param {string} [options.flowType.serviceTag] - Tag identifying the default service.
115
- * @returns {Promise<string|undefined>} The awaitingStepId of the first mapped record.
115
+ * @param {string} [options.prefix=''] - Prefix for pendingStepId.
116
+ * @param {object} [options.additionalAttributes={}] - Default attributes.
117
+ * @param {object|null} [options.flowType=null] - Default flow schema.
118
+ * @param {string} [options.flowType.flowTag] - Default flow tag.
119
+ * @param {string} [options.flowType.serviceTag] - Default service tag.
120
+ * @returns {Promise<string|null>} The awaitingStepId of first record.
116
121
  */
117
122
  export async function createAwaitingMultipleSteps(
118
123
  _izContext,
@@ -174,21 +179,27 @@ export async function createAwaitingMultipleSteps(
174
179
  });
175
180
  }
176
181
 
177
- await _putAwaitingMultipleStepRecords(
178
- _izContext,
179
- finalRecords.map(({ awaitingStepId, additionalAttributes: itemAttrs }) => {
182
+ const mappedStepRecords = finalRecords.map(
183
+ ({ awaitingStepId, additionalAttributes: itemAttrs }) => {
180
184
  const hasItemAttrs = itemAttrs && Object.keys(itemAttrs).length > 0;
181
185
  const hasOptAttrs =
182
186
  additionalAttributes && Object.keys(additionalAttributes).length > 0;
183
187
 
188
+ let mergedAttributes = null;
189
+ if (hasItemAttrs || hasOptAttrs) {
190
+ mergedAttributes = { ...additionalAttributes, ...itemAttrs };
191
+ }
192
+
184
193
  return {
185
194
  awaitingStepId,
186
- additionalAttributes:
187
- hasItemAttrs || hasOptAttrs
188
- ? { ...additionalAttributes, ...itemAttrs }
189
- : undefined
195
+ additionalAttributes: mergedAttributes
190
196
  };
191
- }),
197
+ }
198
+ );
199
+
200
+ await _putAwaitingMultipleStepRecords(
201
+ _izContext,
202
+ mappedStepRecords,
192
203
  finalPendingStepId,
193
204
  {
194
205
  complete: false,
@@ -198,16 +209,19 @@ export async function createAwaitingMultipleSteps(
198
209
 
199
210
  if (hasFlowPublish) {
200
211
  await Promise.all(
201
- finalRecords.map(({ message, topicArn }) =>
202
- sns.publishAsync(_izContext, {
212
+ finalRecords.map(async ({ message, topicArn }) => {
213
+ await sns.publishAsync(_izContext, {
203
214
  TopicArn: `${topicArn}_In`,
204
215
  Message: JSON.stringify(message)
205
- })
206
- )
216
+ });
217
+ })
207
218
  );
208
219
  }
209
220
 
210
- return finalRecords[0]?.awaitingStepId;
221
+ if (finalRecords[0] && finalRecords[0].awaitingStepId) {
222
+ return finalRecords[0].awaitingStepId;
223
+ }
224
+ return null;
211
225
  }
212
226
 
213
227
  /**
@@ -246,11 +260,11 @@ export async function updateAwaitingMultipleStep(
246
260
  }
247
261
 
248
262
  /**
249
- * Query and return the first pending step item that matches the given awaitingStepId.
263
+ * Query and return the first pending step item that matches awaitingStepId.
250
264
  * @async
251
265
  * @param {IzContext} _izContext - The Izara context.
252
266
  * @param {string} awaitingStepId - The ID of the awaiting step.
253
- * @returns {Promise<object|undefined>} The found item or undefined.
267
+ * @returns {Promise<object|null>} The found item or null.
254
268
  */
255
269
  export async function findPendingStepAwaitingMultipleSteps(
256
270
  _izContext,
@@ -265,11 +279,14 @@ export async function findPendingStepAwaitingMultipleSteps(
265
279
  awaitingStepId
266
280
  );
267
281
 
268
- return items[0];
282
+ if (items && items.length > 0) {
283
+ return items[0];
284
+ }
285
+ return null;
269
286
  }
270
287
 
271
288
  /**
272
- * Query and return all pending step items matching the given awaitingStepId.
289
+ * Query and return all pending step items matching awaitingStepId.
273
290
  * @async
274
291
  * @param {IzContext} _izContext - The Izara context.
275
292
  * @param {string} awaitingStepId - The ID of the awaiting step.
@@ -283,11 +300,11 @@ export async function findPendingStepsAwaitingMultipleSteps(
283
300
  }
284
301
 
285
302
  /**
286
- * Retrieve the pendingStepId of the first step matching the given awaitingStepId.
303
+ * Retrieve pendingStepId of first step matching awaitingStepId.
287
304
  * @async
288
305
  * @param {IzContext} _izContext - The Izara context.
289
306
  * @param {string} awaitingStepId - The ID of the awaiting step.
290
- * @returns {Promise<string|undefined>} The pendingStepId of the first item found.
307
+ * @returns {Promise<string|null>} pendingStepId of first item found or null.
291
308
  */
292
309
  export async function findPendingStepIdAwaitingMultipleSteps(
293
310
  _izContext,
@@ -297,11 +314,14 @@ export async function findPendingStepIdAwaitingMultipleSteps(
297
314
  _izContext,
298
315
  awaitingStepId
299
316
  );
300
- return items[0]?.pendingStepId;
317
+ if (items && items[0] && items[0].pendingStepId) {
318
+ return items[0].pendingStepId;
319
+ }
320
+ return null;
301
321
  }
302
322
 
303
323
  /**
304
- * Query and return all awaiting steps matching the given pendingStepId from byPending index table.
324
+ * Query and return all awaiting steps matching pendingStepId.
305
325
  * @async
306
326
  * @param {IzContext} _izContext - The Izara context.
307
327
  * @param {string} pendingStepId - The ID of the pending step.
@@ -315,17 +335,16 @@ export async function findAwaitingMultipleStepByPending(
315
335
  }
316
336
 
317
337
  /**
318
- * Check if all awaiting steps for a pendingStepId are completed. If a current awaitingStepId
319
- * is provided, it updates its status to complete and returns values/errors.
338
+ * Check if all awaiting steps for a pendingStepId are completed.
320
339
  * @async
321
340
  * @param {IzContext} _izContext - The Izara context.
322
341
  * @param {string} pendingStepId - The ID of the pending step.
323
- * @param {string|null} [currentAwaitingStepId=null] - The ID of the current awaiting step that completed.
324
- * @param {Array<any>} [errorsFound=[]] - Errors collected from the current step.
325
- * @param {object} [returnValues={}] - Values returned from the current step.
326
- * @param {object} [settings={ checkIsAllError: false }] - Execution settings.
327
- * @param {boolean} [settings.checkIsAllError=false] - Whether to verify if all steps failed.
328
- * @returns {Promise<object>} Status object containing isComplete, collectedAttributes, collectedErrors, returnValues, additionalAttributes, etc.
342
+ * @param {string|null} [currentAwaitingStepId=null] - Completed step ID.
343
+ * @param {Array<any>} [errorsFound=[]] - Errors collected from current step.
344
+ * @param {object} [returnValues={}] - Values returned from current step.
345
+ * @param {object} [settings={ checkIsAllError: false }] - Settings.
346
+ * @param {boolean} [settings.checkIsAllError=false] - Verify all failed.
347
+ * @returns {Promise<object>} Status object containing completion details.
329
348
  */
330
349
  export async function checkAllAwaitingStepsFinishedWithReturnParams(
331
350
  _izContext,
@@ -370,24 +389,29 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
370
389
 
371
390
  _izContext.logger.debug(
372
391
  '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] awaitingStepItems',
373
- awaitingStepItems
392
+ { awaitingStepItems }
374
393
  );
375
394
 
376
395
  const hasIncomplete = awaitingStepItems.some(
377
396
  item => item.awaitingStepId !== currentAwaitingStepId && !item.complete
378
397
  );
379
398
 
399
+ let defaultAttrs = null;
400
+ if (awaitingStepItems[0] && awaitingStepItems[0].additionalAttributes) {
401
+ defaultAttrs = awaitingStepItems[0].additionalAttributes;
402
+ }
403
+
380
404
  if (hasIncomplete) {
381
405
  return {
382
406
  isComplete: false,
383
- collectedAttributes: null,
407
+ collectedReturnValueByAwaitingStepId: null,
384
408
  collectedErrors: [],
385
409
  returnValues: null,
386
- additionalAttributes: awaitingStepItems[0]?.additionalAttributes || null
410
+ additionalAttributes: defaultAttrs
387
411
  };
388
412
  }
389
413
 
390
- const collectedAttributes = {};
414
+ const collectedReturnValueByAwaitingStepId = {};
391
415
  let sharedReturnValues = null;
392
416
 
393
417
  for (const item of awaitingStepItems) {
@@ -395,30 +419,42 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
395
419
  continue;
396
420
  }
397
421
 
398
- collectedAttributes[item.awaitingStepId] = item.returnValues;
399
- sharedReturnValues ??= item.returnValues;
422
+ collectedReturnValueByAwaitingStepId[item.awaitingStepId] =
423
+ item.returnValues;
424
+ if (sharedReturnValues === null) {
425
+ sharedReturnValues = item.returnValues;
426
+ }
400
427
  }
401
428
 
402
- const collectedErrors = awaitingStepItems.flatMap(item =>
403
- item.errorsFound?.length > 0
404
- ? [`Error found in step ${item.awaitingStepId}`, ...item.errorsFound]
405
- : []
406
- );
429
+ const collectedErrors = [];
430
+ for (const item of awaitingStepItems) {
431
+ if (item.errorsFound && item.errorsFound.length > 0) {
432
+ collectedErrors.push(
433
+ `Error found in step ${item.awaitingStepId}`,
434
+ ...item.errorsFound
435
+ );
436
+ }
437
+ }
407
438
 
408
- return {
439
+ const result = {
409
440
  isComplete: true,
410
- collectedAttributes,
441
+ collectedReturnValueByAwaitingStepId,
411
442
  collectedErrors,
412
443
  returnValues: sharedReturnValues,
413
- additionalAttributes: awaitingStepItems[0]?.additionalAttributes || null,
414
- ...(settings.checkIsAllError && {
415
- isAllError: awaitingStepItems.every(item => item.errorsFound?.length > 0)
416
- })
444
+ additionalAttributes: defaultAttrs
417
445
  };
446
+
447
+ if (settings.checkIsAllError) {
448
+ result.isAllError = awaitingStepItems.every(
449
+ item => item.errorsFound && item.errorsFound.length > 0
450
+ );
451
+ }
452
+
453
+ return result;
418
454
  }
419
455
 
420
456
  /**
421
- * Clean up/delete all awaiting multiple steps records associated with the pendingStepId from both tables.
457
+ * Clean up/delete all awaiting multiple steps records for pendingStepId.
422
458
  * @async
423
459
  * @param {IzContext} _izContext - The Izara context.
424
460
  * @param {string} pendingStepId - The ID of the pending step.
@@ -429,7 +465,7 @@ export async function clearAllAwaitingSteps(_izContext, pendingStepId) {
429
465
  pendingStepId
430
466
  });
431
467
 
432
- const listPendingStepIds = await _queryAwaitingStepsByPendingStepId(
468
+ const awaitingStepItems = await _queryAwaitingStepsByPendingStepId(
433
469
  _izContext,
434
470
  pendingStepId
435
471
  );
@@ -443,7 +479,7 @@ export async function clearAllAwaitingSteps(_izContext, pendingStepId) {
443
479
  );
444
480
 
445
481
  await Promise.all(
446
- listPendingStepIds.flatMap(
482
+ awaitingStepItems.flatMap(
447
483
  ({ awaitingStepId, pendingStepId: itemPendingStepId }) => [
448
484
  dynamodbSharedLib.deleteItem(_izContext, tableSteps, {
449
485
  awaitingStepId,
@@ -461,12 +497,12 @@ export async function clearAllAwaitingSteps(_izContext, pendingStepId) {
461
497
  }
462
498
 
463
499
  /**
464
- * Delete a specific awaiting step from Steps table (and optionally from byPending table if no errors found).
500
+ * Delete a specific awaiting step from Steps table (and byPending table).
465
501
  * @async
466
502
  * @param {IzContext} _izContext - The Izara context.
467
503
  * @param {string} awaitingStepId - The ID of the awaiting step.
468
504
  * @param {string} pendingStepId - The ID of the pending step.
469
- * @param {Array<any>} [errorsFound=[]] - List of errors. If empty, the record is removed from byPending too.
505
+ * @param {Array<any>} [errorsFound=[]] - List of errors.
470
506
  * @returns {Promise<void>}
471
507
  */
472
508
  export async function removeAwaitingMultipleStep(
@@ -483,7 +519,8 @@ export async function removeAwaitingMultipleStep(
483
519
 
484
520
  if (!awaitingStepId || !pendingStepId) {
485
521
  throw new NoRetryError(
486
- '[Lib:removeAwaitingMultipleStep] awaitingStepId or pendingStepId required'
522
+ '[Lib:removeAwaitingMultipleStep]' +
523
+ ' awaitingStepId or pendingStepId required'
487
524
  );
488
525
  }
489
526
 
@@ -44,16 +44,18 @@ async function _findSinglePendingStep(_izContext, awaitingStepId) {
44
44
  //======================================
45
45
  // AwaitingStep
46
46
  //======================================
47
- // AwaitingStep flow awaiting one external flow, will continue its flow once one external flow completes.
47
+ // AwaitingStep flow awaiting one external flow, will continue its flow once
48
+ // one external flow completes.
48
49
  // One awaitingStepId can have multiple pendingStepIds awaiting it.
49
50
 
50
51
  /**
51
- * Create a single awaiting step record to block a flow until a specific external flow step completes.
52
+ * Create a single awaiting step record to block a flow until a specific
53
+ * external flow step completes.
52
54
  * @async
53
55
  * @param {IzContext} _izContext - The Izara context.
54
56
  * @param {string} awaitingStepId - The ID of the awaiting step.
55
- * @param {string} pendingStepId - The ID of the pending step that is waiting.
56
- * @param {object} [additionalAttributes={}] - Additional attributes to save with the record.
57
+ * @param {string} pendingStepId - The ID of pending step that is waiting.
58
+ * @param {object} [additionalAttributes={}] - Attributes to save.
57
59
  * @param {object} [callingFlowConfig={}] - Calling flow configuration.
58
60
  * @returns {Promise<void>}
59
61
  */
@@ -91,15 +93,15 @@ export async function createAwaitingStep(
91
93
  }
92
94
 
93
95
  /**
94
- * Query and return the single pending step item that matches the given awaitingStepId.
96
+ * Query and return single pending step item matching given awaitingStepId.
95
97
  * @async
96
98
  * @param {IzContext} _izContext - The Izara context.
97
99
  * @param {string} awaitingStepId - The ID of the awaiting step.
98
100
  * @returns {Promise<object>} The found pending step record.
99
- * @throws {NoRetryError} If awaitingStepId is missing or if not exactly one record is found.
101
+ * @throws {NoRetryError} If missing or if not exactly one record found.
100
102
  */
101
103
  export async function findPendingStep(_izContext, awaitingStepId) {
102
- return _findSinglePendingStep(_izContext, awaitingStepId);
104
+ return _findSinglePendingStep(_izContext, awaitingStepId);
103
105
  }
104
106
 
105
107
  /**
@@ -122,7 +124,7 @@ export async function removeAwaitingStep(
122
124
  });
123
125
 
124
126
  if (!awaitingStepId || !pendingStepId) {
125
- throw new NoRetryError('awaitingStepId pendingStepId required');
127
+ throw new NoRetryError('awaitingStepId, pendingStepId required');
126
128
  }
127
129
 
128
130
  await dynamodbSharedLib.deleteItem(
@@ -133,7 +135,7 @@ export async function removeAwaitingStep(
133
135
  }
134
136
 
135
137
  /**
136
- * Delete an awaiting step record from AwaitingStep table, conditional on uniqueRequestId.
138
+ * Delete awaiting step record from AwaitingStep table by uniqueRequestId.
137
139
  * @async
138
140
  * @param {IzContext} _izContext - The Izara context.
139
141
  * @param {string} awaitingStepId - The ID of the awaiting step.
@@ -157,7 +159,7 @@ export async function removeAwaitingStepWithCheckUniqueRequestId(
157
159
 
158
160
  if (!awaitingStepId || !pendingStepId || !checkUniqueRequestId) {
159
161
  throw new NoRetryError(
160
- 'awaitingStepId, pendingStepId checkUniqueRequestId required'
162
+ 'awaitingStepId, pendingStepId, checkUniqueRequestId required'
161
163
  );
162
164
  }
163
165