@izara_project/izara-core-library-asynchronous-flow 1.0.40 → 1.0.41

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.40",
6
+ "version": "1.0.41",
7
7
  "description": "Shared asynchronous flow logic",
8
8
  "type": "module",
9
9
  "main": "index.js",
@@ -28,11 +28,48 @@ import { sqs } from '@izara_project/izara-core-library-external-request';
28
28
 
29
29
  import { identifierUuid } from '@izara_project/izara-shared-core';
30
30
 
31
+ /**
32
+ * Helper to safely join a prefix and an ID with a single underscore.
33
+ * Ensures no duplicate underscores between them.
34
+ * @param {string} id
35
+ * @param {string} [prefix='']
36
+ * @returns {string}
37
+ */
38
+ function _joinPrefixAndId(id, prefix = '') {
39
+ if (!prefix) return id;
40
+
41
+ const cleanPrefix = prefix.endsWith('_') ? prefix.slice(0, -1) : prefix;
42
+ const cleanId = id.startsWith('_') ? id.slice(1) : id;
43
+
44
+ return `${cleanPrefix}_${cleanId}`;
45
+ }
46
+
47
+ /**
48
+ * 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
+ * @param {string} partitionKey
54
+ * @param {string} [prefix='']
55
+ * @returns {string}
56
+ */
31
57
  function createAwaitingStepId(partitionKey, prefix = '') {
32
- return prefix + partitionKey;
58
+ return _joinPrefixAndId(partitionKey, prefix);
33
59
  }
60
+
61
+ /**
62
+ * 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
+ * @param {string} identifierId
68
+ * @param {string} [prefix='']
69
+ * @returns {string}
70
+ */
34
71
  function createPendingStepId(identifierId, prefix = '') {
35
- return prefix + identifierId;
72
+ return _joinPrefixAndId(identifierId, prefix);
36
73
  }
37
74
 
38
75
  /**
@@ -76,7 +113,7 @@ function createConcatenatedPendingStepId(parentId, childId) {
76
113
  * @returns {string} The parent Flow Id created.
77
114
  */
78
115
  function createParentFlowId(prefix = '') {
79
- return (prefix ? `${prefix}_` : '') + identifierUuid();
116
+ return _joinPrefixAndId(identifierUuid(), prefix);
80
117
  }
81
118
 
82
119
  /**
@@ -19,7 +19,10 @@ import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
19
19
  import { sns } from '@izara_project/izara-core-library-external-request';
20
20
  import snsSharedLib from '@izara_project/izara-core-library-sns';
21
21
 
22
- import { createParentFlowId } from './asyncFlowSharedLib.js';
22
+ import {
23
+ createParentFlowId,
24
+ createPendingStepId
25
+ } from './asyncFlowSharedLib.js';
23
26
 
24
27
  function _assertPendingStepId(pendingStepId) {
25
28
  if (!pendingStepId) {
@@ -97,15 +100,18 @@ async function _queryAwaitingStepsByPendingStepId(_izContext, pendingStepId) {
97
100
  * @async
98
101
  * @param {IzContext} _izContext - The Izara context.
99
102
  * @param {string} pendingStepId - The ID of the pending step that is waiting.
100
- * @param {Array<object>} [records=[]] - Array of step items containing awaitingStepId and optional attributes.
103
+ * @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.
101
109
  * @param {object} [options={}] - Optional configurations.
102
110
  * @param {string} [options.prefix=''] - Prefix to prepend to pendingStepId.
103
- * @param {object} [options.additionalAttributes={}] - Additional attributes to associate with each pending step record.
104
- * @param {object} [options.publishConfig=null] - SQS/SNS publish configuration if starting external tasks.
105
- * @param {Array<object>} options.publishConfig.messages - Messages to publish.
106
- * @param {object} options.publishConfig.flowType - Flow schema/service metadata.
107
- * @param {string} options.publishConfig.flowType.flowTag - Tag identifying the flow.
108
- * @param {string} options.publishConfig.flowType.serviceTag - Tag identifying the service.
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.
109
115
  * @returns {Promise<string|undefined>} The awaitingStepId of the first mapped record.
110
116
  */
111
117
  export async function createAwaitingMultipleSteps(
@@ -114,30 +120,20 @@ export async function createAwaitingMultipleSteps(
114
120
  records = [],
115
121
  options = {}
116
122
  ) {
117
- const {
118
- prefix = '',
119
- additionalAttributes = {},
120
- publishConfig = null
121
- } = options;
123
+ const { prefix = '', additionalAttributes = {}, flowType = null } = options;
122
124
 
123
125
  _izContext.logger.debug('[Lib:AsyncFlow:createAwaitingMultipleSteps] Input', {
124
126
  pendingStepId,
125
127
  options
126
128
  });
127
129
 
128
- const formattedPrefix = prefix
129
- ? prefix.endsWith('_')
130
- ? prefix
131
- : `${prefix}_`
132
- : '';
133
- const finalPendingStepId =
134
- formattedPrefix && !pendingStepId.startsWith(formattedPrefix)
135
- ? formattedPrefix + pendingStepId
136
- : pendingStepId;
130
+ const finalPendingStepId = createPendingStepId(pendingStepId, prefix);
131
+
132
+ const hasFlowPublish = flowType || records.some(r => r.flowType);
137
133
 
138
134
  let finalRecords = records;
139
135
 
140
- if (finalRecords.length === 0 && !publishConfig) {
136
+ if (finalRecords.length === 0 && !hasFlowPublish) {
141
137
  finalRecords = [
142
138
  {
143
139
  awaitingStepId: createParentFlowId(prefix),
@@ -146,28 +142,28 @@ export async function createAwaitingMultipleSteps(
146
142
  ];
147
143
  }
148
144
 
149
- if (publishConfig) {
150
- const { messages, flowType = {} } = publishConfig;
151
- const { flowTag, serviceTag } = flowType;
152
-
153
- if (!Array.isArray(messages)) {
154
- throw new NoRetryError('messages must be an array');
155
- }
156
-
157
- if (!flowTag || !serviceTag) {
158
- throw new NoRetryError('flowType must both flowTag serviceTag');
159
- }
145
+ if (hasFlowPublish) {
146
+ finalRecords = records.map(record => {
147
+ const recordFlowType = record.flowType || flowType;
160
148
 
161
- finalRecords = messages.map(message => {
162
- const parentFlowId = createParentFlowId(prefix);
149
+ if (!recordFlowType || !recordFlowType.flowTag || !recordFlowType.serviceTag) {
150
+ throw new NoRetryError('record.flowType must have both flowTag and serviceTag');
151
+ }
163
152
 
153
+ const awaitingStepId =
154
+ record.awaitingStepId || createParentFlowId(prefix);
164
155
  return {
165
- awaitingStepId: parentFlowId,
156
+ awaitingStepId,
166
157
  additionalAttributes,
167
158
  message: {
168
- ...message,
169
- parentFlowId
170
- }
159
+ ...record.message,
160
+ parentFlowId: awaitingStepId
161
+ },
162
+ topicArn: snsSharedLib.snsTopicArnByFlowSchema(
163
+ _izContext,
164
+ recordFlowType.flowTag,
165
+ recordFlowType.serviceTag
166
+ )
171
167
  };
172
168
  });
173
169
  }
@@ -194,19 +190,12 @@ export async function createAwaitingMultipleSteps(
194
190
  }
195
191
  );
196
192
 
197
- if (publishConfig) {
198
- const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
199
- _izContext,
200
- publishConfig.flowType.flowTag,
201
- publishConfig.flowType.serviceTag
202
- );
203
-
193
+ if (hasFlowPublish) {
204
194
  await Promise.all(
205
- finalRecords.map(({ message }) =>
195
+ finalRecords.map(({ message, topicArn }) =>
206
196
  sns.publishAsync(_izContext, {
207
197
  TopicArn: `${topicArn}_In`,
208
- Message: JSON.stringify(message),
209
- MessageAttributes: {}
198
+ Message: JSON.stringify(message)
210
199
  })
211
200
  )
212
201
  );