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