@izara_project/izara-core-library-asynchronous-flow 1.0.41 → 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.41",
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,
@@ -146,8 +151,14 @@ export async function createAwaitingMultipleSteps(
146
151
  finalRecords = records.map(record => {
147
152
  const recordFlowType = record.flowType || flowType;
148
153
 
149
- if (!recordFlowType || !recordFlowType.flowTag || !recordFlowType.serviceTag) {
150
- throw new NoRetryError('record.flowType must have both flowTag and serviceTag');
154
+ if (
155
+ !recordFlowType ||
156
+ !recordFlowType.flowTag ||
157
+ !recordFlowType.serviceTag
158
+ ) {
159
+ throw new NoRetryError(
160
+ 'record.flowType must have both flowTag and serviceTag'
161
+ );
151
162
  }
152
163
 
153
164
  const awaitingStepId =
@@ -168,21 +179,27 @@ export async function createAwaitingMultipleSteps(
168
179
  });
169
180
  }
170
181
 
171
- await _putAwaitingMultipleStepRecords(
172
- _izContext,
173
- finalRecords.map(({ awaitingStepId, additionalAttributes: itemAttrs }) => {
182
+ const mappedStepRecords = finalRecords.map(
183
+ ({ awaitingStepId, additionalAttributes: itemAttrs }) => {
174
184
  const hasItemAttrs = itemAttrs && Object.keys(itemAttrs).length > 0;
175
185
  const hasOptAttrs =
176
186
  additionalAttributes && Object.keys(additionalAttributes).length > 0;
177
187
 
188
+ let mergedAttributes = null;
189
+ if (hasItemAttrs || hasOptAttrs) {
190
+ mergedAttributes = { ...additionalAttributes, ...itemAttrs };
191
+ }
192
+
178
193
  return {
179
194
  awaitingStepId,
180
- additionalAttributes:
181
- hasItemAttrs || hasOptAttrs
182
- ? { ...additionalAttributes, ...itemAttrs }
183
- : undefined
195
+ additionalAttributes: mergedAttributes
184
196
  };
185
- }),
197
+ }
198
+ );
199
+
200
+ await _putAwaitingMultipleStepRecords(
201
+ _izContext,
202
+ mappedStepRecords,
186
203
  finalPendingStepId,
187
204
  {
188
205
  complete: false,
@@ -192,16 +209,19 @@ export async function createAwaitingMultipleSteps(
192
209
 
193
210
  if (hasFlowPublish) {
194
211
  await Promise.all(
195
- finalRecords.map(({ message, topicArn }) =>
196
- sns.publishAsync(_izContext, {
212
+ finalRecords.map(async ({ message, topicArn }) => {
213
+ await sns.publishAsync(_izContext, {
197
214
  TopicArn: `${topicArn}_In`,
198
215
  Message: JSON.stringify(message)
199
- })
200
- )
216
+ });
217
+ })
201
218
  );
202
219
  }
203
220
 
204
- return finalRecords[0]?.awaitingStepId;
221
+ if (finalRecords[0] && finalRecords[0].awaitingStepId) {
222
+ return finalRecords[0].awaitingStepId;
223
+ }
224
+ return null;
205
225
  }
206
226
 
207
227
  /**
@@ -240,11 +260,11 @@ export async function updateAwaitingMultipleStep(
240
260
  }
241
261
 
242
262
  /**
243
- * 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.
244
264
  * @async
245
265
  * @param {IzContext} _izContext - The Izara context.
246
266
  * @param {string} awaitingStepId - The ID of the awaiting step.
247
- * @returns {Promise<object|undefined>} The found item or undefined.
267
+ * @returns {Promise<object|null>} The found item or null.
248
268
  */
249
269
  export async function findPendingStepAwaitingMultipleSteps(
250
270
  _izContext,
@@ -259,11 +279,14 @@ export async function findPendingStepAwaitingMultipleSteps(
259
279
  awaitingStepId
260
280
  );
261
281
 
262
- return items[0];
282
+ if (items && items.length > 0) {
283
+ return items[0];
284
+ }
285
+ return null;
263
286
  }
264
287
 
265
288
  /**
266
- * Query and return all pending step items matching the given awaitingStepId.
289
+ * Query and return all pending step items matching awaitingStepId.
267
290
  * @async
268
291
  * @param {IzContext} _izContext - The Izara context.
269
292
  * @param {string} awaitingStepId - The ID of the awaiting step.
@@ -277,11 +300,11 @@ export async function findPendingStepsAwaitingMultipleSteps(
277
300
  }
278
301
 
279
302
  /**
280
- * Retrieve the pendingStepId of the first step matching the given awaitingStepId.
303
+ * Retrieve pendingStepId of first step matching awaitingStepId.
281
304
  * @async
282
305
  * @param {IzContext} _izContext - The Izara context.
283
306
  * @param {string} awaitingStepId - The ID of the awaiting step.
284
- * @returns {Promise<string|undefined>} The pendingStepId of the first item found.
307
+ * @returns {Promise<string|null>} pendingStepId of first item found or null.
285
308
  */
286
309
  export async function findPendingStepIdAwaitingMultipleSteps(
287
310
  _izContext,
@@ -291,11 +314,14 @@ export async function findPendingStepIdAwaitingMultipleSteps(
291
314
  _izContext,
292
315
  awaitingStepId
293
316
  );
294
- return items[0]?.pendingStepId;
317
+ if (items && items[0] && items[0].pendingStepId) {
318
+ return items[0].pendingStepId;
319
+ }
320
+ return null;
295
321
  }
296
322
 
297
323
  /**
298
- * Query and return all awaiting steps matching the given pendingStepId from byPending index table.
324
+ * Query and return all awaiting steps matching pendingStepId.
299
325
  * @async
300
326
  * @param {IzContext} _izContext - The Izara context.
301
327
  * @param {string} pendingStepId - The ID of the pending step.
@@ -309,17 +335,16 @@ export async function findAwaitingMultipleStepByPending(
309
335
  }
310
336
 
311
337
  /**
312
- * Check if all awaiting steps for a pendingStepId are completed. If a current awaitingStepId
313
- * is provided, it updates its status to complete and returns values/errors.
338
+ * Check if all awaiting steps for a pendingStepId are completed.
314
339
  * @async
315
340
  * @param {IzContext} _izContext - The Izara context.
316
341
  * @param {string} pendingStepId - The ID of the pending step.
317
- * @param {string|null} [currentAwaitingStepId=null] - The ID of the current awaiting step that completed.
318
- * @param {Array<any>} [errorsFound=[]] - Errors collected from the current step.
319
- * @param {object} [returnValues={}] - Values returned from the current step.
320
- * @param {object} [settings={ checkIsAllError: false }] - Execution settings.
321
- * @param {boolean} [settings.checkIsAllError=false] - Whether to verify if all steps failed.
322
- * @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.
323
348
  */
324
349
  export async function checkAllAwaitingStepsFinishedWithReturnParams(
325
350
  _izContext,
@@ -364,23 +389,29 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
364
389
 
365
390
  _izContext.logger.debug(
366
391
  '[Lib:checkAllAwaitingStepsFinishedWithReturnParams] awaitingStepItems',
367
- awaitingStepItems
392
+ { awaitingStepItems }
368
393
  );
369
394
 
370
395
  const hasIncomplete = awaitingStepItems.some(
371
396
  item => item.awaitingStepId !== currentAwaitingStepId && !item.complete
372
397
  );
373
398
 
399
+ let defaultAttrs = null;
400
+ if (awaitingStepItems[0] && awaitingStepItems[0].additionalAttributes) {
401
+ defaultAttrs = awaitingStepItems[0].additionalAttributes;
402
+ }
403
+
374
404
  if (hasIncomplete) {
375
405
  return {
376
406
  isComplete: false,
377
- collectedAttributes: null,
407
+ collectedReturnValueByAwaitingStepId: null,
378
408
  collectedErrors: [],
379
- returnValues: null
409
+ returnValues: null,
410
+ additionalAttributes: defaultAttrs
380
411
  };
381
412
  }
382
413
 
383
- const collectedAttributes = {};
414
+ const collectedReturnValueByAwaitingStepId = {};
384
415
  let sharedReturnValues = null;
385
416
 
386
417
  for (const item of awaitingStepItems) {
@@ -388,30 +419,42 @@ export async function checkAllAwaitingStepsFinishedWithReturnParams(
388
419
  continue;
389
420
  }
390
421
 
391
- collectedAttributes[item.awaitingStepId] = item.returnValues;
392
- sharedReturnValues ??= item.returnValues;
422
+ collectedReturnValueByAwaitingStepId[item.awaitingStepId] =
423
+ item.returnValues;
424
+ if (sharedReturnValues === null) {
425
+ sharedReturnValues = item.returnValues;
426
+ }
393
427
  }
394
428
 
395
- const collectedErrors = awaitingStepItems.flatMap(item =>
396
- item.errorsFound?.length > 0
397
- ? [`Error found in step ${item.awaitingStepId}`, ...item.errorsFound]
398
- : []
399
- );
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
+ }
400
438
 
401
- return {
439
+ const result = {
402
440
  isComplete: true,
403
- collectedAttributes,
441
+ collectedReturnValueByAwaitingStepId,
404
442
  collectedErrors,
405
443
  returnValues: sharedReturnValues,
406
- additionalAttributes: awaitingStepItems[0]?.additionalAttributes || null,
407
- ...(settings.checkIsAllError && {
408
- isAllError: awaitingStepItems.every(item => item.errorsFound?.length > 0)
409
- })
444
+ additionalAttributes: defaultAttrs
410
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;
411
454
  }
412
455
 
413
456
  /**
414
- * 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.
415
458
  * @async
416
459
  * @param {IzContext} _izContext - The Izara context.
417
460
  * @param {string} pendingStepId - The ID of the pending step.
@@ -422,7 +465,7 @@ export async function clearAllAwaitingSteps(_izContext, pendingStepId) {
422
465
  pendingStepId
423
466
  });
424
467
 
425
- const listPendingStepIds = await _queryAwaitingStepsByPendingStepId(
468
+ const awaitingStepItems = await _queryAwaitingStepsByPendingStepId(
426
469
  _izContext,
427
470
  pendingStepId
428
471
  );
@@ -436,7 +479,7 @@ export async function clearAllAwaitingSteps(_izContext, pendingStepId) {
436
479
  );
437
480
 
438
481
  await Promise.all(
439
- listPendingStepIds.flatMap(
482
+ awaitingStepItems.flatMap(
440
483
  ({ awaitingStepId, pendingStepId: itemPendingStepId }) => [
441
484
  dynamodbSharedLib.deleteItem(_izContext, tableSteps, {
442
485
  awaitingStepId,
@@ -454,12 +497,12 @@ export async function clearAllAwaitingSteps(_izContext, pendingStepId) {
454
497
  }
455
498
 
456
499
  /**
457
- * 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).
458
501
  * @async
459
502
  * @param {IzContext} _izContext - The Izara context.
460
503
  * @param {string} awaitingStepId - The ID of the awaiting step.
461
504
  * @param {string} pendingStepId - The ID of the pending step.
462
- * @param {Array<any>} [errorsFound=[]] - List of errors. If empty, the record is removed from byPending too.
505
+ * @param {Array<any>} [errorsFound=[]] - List of errors.
463
506
  * @returns {Promise<void>}
464
507
  */
465
508
  export async function removeAwaitingMultipleStep(
@@ -476,7 +519,8 @@ export async function removeAwaitingMultipleStep(
476
519
 
477
520
  if (!awaitingStepId || !pendingStepId) {
478
521
  throw new NoRetryError(
479
- '[Lib:removeAwaitingMultipleStep] awaitingStepId or pendingStepId required'
522
+ '[Lib:removeAwaitingMultipleStep]' +
523
+ ' awaitingStepId or pendingStepId required'
480
524
  );
481
525
  }
482
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