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