@izara_project/izara-core-library-asynchronous-flow 1.0.19 → 1.0.21
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/index.js +9 -5
- package/package.json +1 -1
- package/src/asyncFlowSharedLib.js +507 -0
- package/src/awaitingMultipleSteps.js +568 -0
- package/src/awaitingStep.js +244 -0
- package/src/AsyncFlowSharedLib.js +0 -1309
|
@@ -1,1309 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright (C) 2021 Sven Mason <http://izara.io>
|
|
3
|
-
|
|
4
|
-
This program is free software: you can redistribute it and/or modify
|
|
5
|
-
it under the terms of the GNU Affero General Public License as
|
|
6
|
-
published by the Free Software Foundation, either version 3 of the
|
|
7
|
-
License, or (at your option) any later version.
|
|
8
|
-
|
|
9
|
-
This program is distributed in the hope that it will be useful,
|
|
10
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
-
GNU Affero General Public License for more details.
|
|
13
|
-
|
|
14
|
-
You should have received a copy of the GNU Affero General Public License
|
|
15
|
-
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
'use strict';
|
|
19
|
-
|
|
20
|
-
// Core libraries
|
|
21
|
-
import { NoRetryError } from '@izara_project/izara-core-library-core';
|
|
22
|
-
import Logger from '@izara_project/izara-core-library-logger';
|
|
23
|
-
|
|
24
|
-
// Data and service interaction libraries
|
|
25
|
-
import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
|
|
26
|
-
import sqsSharedLib from '@izara_project/izara-core-library-sqs';
|
|
27
|
-
|
|
28
|
-
// External service interactions
|
|
29
|
-
import { sqs } from '@izara_project/izara-core-library-external-request';
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Create a field name for storing the unique-request id, with optional prefix.
|
|
34
|
-
* @param {string} prefix
|
|
35
|
-
* @param {string} [uniqueRequestIdFieldName='UniqueRequestId']
|
|
36
|
-
* @returns {string}
|
|
37
|
-
*/
|
|
38
|
-
function createFieldNameUniqueRequestId(
|
|
39
|
-
prefix,
|
|
40
|
-
uniqueRequestIdFieldName = 'UniqueRequestId'
|
|
41
|
-
) {
|
|
42
|
-
return prefix + uniqueRequestIdFieldName;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Create a concatenated awaitingStepId from composite parts.
|
|
47
|
-
* @param {string} partitionKey
|
|
48
|
-
* @param {string|number} sortId
|
|
49
|
-
* @param {string} [prefix=""]
|
|
50
|
-
* @returns {string}
|
|
51
|
-
*/
|
|
52
|
-
function createConcatenatedAwaitingStepId(
|
|
53
|
-
partitionKey,
|
|
54
|
-
sortId,
|
|
55
|
-
prefix = ""
|
|
56
|
-
) {
|
|
57
|
-
return prefix + partitionKey + "_" + sortId
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Create a concatenated pendingStepId from parent/child ids.
|
|
62
|
-
* @param {string} parentId
|
|
63
|
-
* @param {string} childId
|
|
64
|
-
* @returns {string}
|
|
65
|
-
*/
|
|
66
|
-
function createConcatenatedPendingStepId(
|
|
67
|
-
parentId,
|
|
68
|
-
childId
|
|
69
|
-
) {
|
|
70
|
-
return parentId + "_" + childId
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Create a simple awaitingStepId (prefix + partitionKey).
|
|
75
|
-
* @param {string} partitionKey
|
|
76
|
-
* @param {string} [prefix=""]
|
|
77
|
-
* @returns {string}
|
|
78
|
-
*/
|
|
79
|
-
function createAwaitingStepId(
|
|
80
|
-
partitionKey,
|
|
81
|
-
prefix = ""
|
|
82
|
-
) {
|
|
83
|
-
return prefix + partitionKey
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Create a simple pendingStepId (prefix + identifierId).
|
|
88
|
-
* @param {string} identifierId
|
|
89
|
-
* @param {string} [prefix=""]
|
|
90
|
-
* @returns {string}
|
|
91
|
-
*/
|
|
92
|
-
function createPendingStepId(
|
|
93
|
-
identifierId,
|
|
94
|
-
prefix = ""
|
|
95
|
-
) {
|
|
96
|
-
return prefix + identifierId
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Remove the known prefix from a pendingStepId and validate it is not equal to the prefix.
|
|
101
|
-
* If prefix is empty, the original ID is returned.
|
|
102
|
-
* @param {string} pendingStepId
|
|
103
|
-
* @param {string} [prefix=""]
|
|
104
|
-
* @returns {string}
|
|
105
|
-
* @throws {NoRetryError} If the stripped value equals the prefix (invalid)
|
|
106
|
-
*/
|
|
107
|
-
function explodePendingStepId(
|
|
108
|
-
pendingStepId,
|
|
109
|
-
prefix = ""
|
|
110
|
-
) {
|
|
111
|
-
if (prefix === "") {
|
|
112
|
-
return pendingStepId;
|
|
113
|
-
} else {
|
|
114
|
-
let identifierId = pendingStepId.slice(prefix.length)
|
|
115
|
-
// ** validate identifierId == prefix
|
|
116
|
-
if (identifierId == prefix) {
|
|
117
|
-
throw new NoRetryError("IdentifierId should not be like prefix.");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
Logger.debug("return explode:", identifierId);
|
|
121
|
-
return identifierId;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// copy from izara-core-library-trigger-cache
|
|
127
|
-
/**
|
|
128
|
-
* Build the field name that stores "cache complete" marker.
|
|
129
|
-
* @param {string} [prefix='cache']
|
|
130
|
-
* @returns {string}
|
|
131
|
-
*/
|
|
132
|
-
function createFieldNameCacheComplete(prefix = 'cache') {
|
|
133
|
-
return prefix + 'CacheComplete';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Build the field name that stores a cache "status".
|
|
138
|
-
* @param {string} [prefix='cache']
|
|
139
|
-
* @returns {string}
|
|
140
|
-
*/
|
|
141
|
-
function createFieldNameStatus(prefix = 'cache') {
|
|
142
|
-
return prefix + 'Status';
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
//====================================== AwaitingMultipleSteps
|
|
147
|
-
// AwaitingMultipleSteps is when a flow is awaiting multiple external flows before it can continue
|
|
148
|
-
// One awaitingStepId can have multiple pendingStepIds that are awaiting it
|
|
149
|
-
// One pendingStepId can have multiple awaitingStepIds it is awaiting
|
|
150
|
-
// logic can prepend any prefix to awaitingStepId if want to be share one table and differentiate different external step ids
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Create multiple awaiting records with optional per-record additional attributes.
|
|
154
|
-
* Writes to both "AwaitingMultipleSteps" and "AwaitingMultipleStepByPending".
|
|
155
|
-
* @async
|
|
156
|
-
* @param {IzContext} _izContext
|
|
157
|
-
* @param {AwaitingMultiInputRecord[]} records
|
|
158
|
-
* @param {string} pendingStepId
|
|
159
|
-
* @returns {Promise<void>}
|
|
160
|
-
*/
|
|
161
|
-
async function createAwaitingMultipleStepsWithAdditionalAttributes(
|
|
162
|
-
_izContext,
|
|
163
|
-
records, // array of object including properties: awaitingStepId / (optional) additionalAttributes
|
|
164
|
-
pendingStepId
|
|
165
|
-
) {
|
|
166
|
-
_izContext.logger.debug("Lib:createAwaitingMultipleStepsWithAdditionalAttributes", {
|
|
167
|
-
records: records,
|
|
168
|
-
pendingStepId: pendingStepId
|
|
169
|
-
});
|
|
170
|
-
let promiseArray = []
|
|
171
|
-
|
|
172
|
-
for (let record of records) {
|
|
173
|
-
|
|
174
|
-
let attributesWhenCreate = {
|
|
175
|
-
awaitingStepId: record.awaitingStepId,
|
|
176
|
-
pendingStepId: pendingStepId,
|
|
177
|
-
timestamp: Date.now(),
|
|
178
|
-
...record.additionalAttributes
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
promiseArray.push(
|
|
182
|
-
dynamodbSharedLib.putItem(
|
|
183
|
-
_izContext,
|
|
184
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleSteps"),
|
|
185
|
-
attributesWhenCreate
|
|
186
|
-
)
|
|
187
|
-
);
|
|
188
|
-
promiseArray.push(
|
|
189
|
-
dynamodbSharedLib.putItem(
|
|
190
|
-
_izContext,
|
|
191
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleStepByPending"),
|
|
192
|
-
{
|
|
193
|
-
pendingStepId: pendingStepId,
|
|
194
|
-
awaitingStepId: record.awaitingStepId,
|
|
195
|
-
timestamp: Date.now(),
|
|
196
|
-
}
|
|
197
|
-
));
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
await Promise.all(promiseArray);
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Create multiple awaiting records given an array of awaitingStepIds.
|
|
207
|
-
* Writes to both "AwaitingMultipleSteps" and "AwaitingMultipleStepByPending".
|
|
208
|
-
* @async
|
|
209
|
-
* @param {IzContext} _izContext
|
|
210
|
-
* @param {string[]} records - array of awaitingStepIds
|
|
211
|
-
* @param {string} pendingStepId
|
|
212
|
-
* @returns {Promise<void>}
|
|
213
|
-
*/
|
|
214
|
-
async function createAwaitingMultipleSteps(
|
|
215
|
-
_izContext,
|
|
216
|
-
records, // array of awaitingStepIds
|
|
217
|
-
pendingStepId
|
|
218
|
-
) {
|
|
219
|
-
_izContext.logger.debug("Lib:createAwaitingMultipleSteps", {
|
|
220
|
-
records: records,
|
|
221
|
-
pendingStepId: pendingStepId
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
// // 0001 ORG.
|
|
225
|
-
let promiseArray = []
|
|
226
|
-
|
|
227
|
-
for (let record of records) {
|
|
228
|
-
|
|
229
|
-
let attributesWhenCreate = {
|
|
230
|
-
awaitingStepId: record,
|
|
231
|
-
pendingStepId: pendingStepId,
|
|
232
|
-
timestamp: Date.now()
|
|
233
|
-
}
|
|
234
|
-
_izContext.logger.debug("putItem:AwaitingMultipleSteps +++++++", { awaitingStepId: attributesWhenCreate.awaitingStepId })
|
|
235
|
-
promiseArray.push(
|
|
236
|
-
dynamodbSharedLib.putItem(
|
|
237
|
-
_izContext,
|
|
238
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleSteps"),
|
|
239
|
-
attributesWhenCreate
|
|
240
|
-
)
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
_izContext.logger.debug("putItem:AwaitingMultipleStepByPending", { awaitingStepId: attributesWhenCreate.awaitingStepId })
|
|
245
|
-
promiseArray.push(
|
|
246
|
-
dynamodbSharedLib.putItem(
|
|
247
|
-
_izContext,
|
|
248
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleStepByPending"),
|
|
249
|
-
attributesWhenCreate
|
|
250
|
-
)
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
_izContext.logger.debug("check 0")
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
await Promise.all(promiseArray);
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Find all pendingStepIds that are waiting on a given awaitingStepId (multiple).
|
|
262
|
-
* @async
|
|
263
|
-
* @param {IzContext} _izContext
|
|
264
|
-
* @param {string} awaitingStepId
|
|
265
|
-
* @returns {Promise<string[]>}
|
|
266
|
-
*/
|
|
267
|
-
async function findPendingStepIdsAwaitingMultipleSteps(
|
|
268
|
-
_izContext,
|
|
269
|
-
awaitingStepId
|
|
270
|
-
) {
|
|
271
|
-
|
|
272
|
-
let pendingStepIds = [];
|
|
273
|
-
|
|
274
|
-
let listPendingStepIds = await dynamodbSharedLib.query(
|
|
275
|
-
_izContext,
|
|
276
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleSteps"),
|
|
277
|
-
{
|
|
278
|
-
awaitingStepId: awaitingStepId
|
|
279
|
-
}
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
|
|
283
|
-
pendingStepIds.push(listPendingStepIds.Items[idx].pendingStepId)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return pendingStepIds
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Return the raw items waiting on a given awaitingStepId (multiple).
|
|
291
|
-
* @async
|
|
292
|
-
* @param {IzContext} _izContext
|
|
293
|
-
* @param {string} awaitingStepId
|
|
294
|
-
* @returns {Promise<AwaitingMultipleStepsItem[]>}
|
|
295
|
-
*/
|
|
296
|
-
async function findPendingStepsAwaitingMultipleSteps(
|
|
297
|
-
_izContext,
|
|
298
|
-
awaitingStepId
|
|
299
|
-
) {
|
|
300
|
-
|
|
301
|
-
let listPendingSteps = await dynamodbSharedLib.query(
|
|
302
|
-
_izContext,
|
|
303
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleSteps"),
|
|
304
|
-
{
|
|
305
|
-
awaitingStepId: awaitingStepId
|
|
306
|
-
}
|
|
307
|
-
);
|
|
308
|
-
_izContext.logger.debug("Record of awaitingStepId", listPendingSteps);
|
|
309
|
-
return listPendingSteps.Items;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Fetch exactly one pending step for a given awaitingStepId (throws if not 1).
|
|
314
|
-
* @async
|
|
315
|
-
* @param {IzContext} _izContext
|
|
316
|
-
* @param {string} awaitingStepId
|
|
317
|
-
* @returns {Promise<AwaitingMultipleStepsItem>}
|
|
318
|
-
* @throws {NoRetryError} if there is not exactly one item
|
|
319
|
-
*/
|
|
320
|
-
async function findPendingStepAwaitingMultipleSteps(
|
|
321
|
-
_izContext,
|
|
322
|
-
awaitingStepId
|
|
323
|
-
) {
|
|
324
|
-
|
|
325
|
-
let listPendingSteps = await dynamodbSharedLib.query(
|
|
326
|
-
_izContext,
|
|
327
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleSteps"),
|
|
328
|
-
{
|
|
329
|
-
awaitingStepId: awaitingStepId
|
|
330
|
-
}
|
|
331
|
-
);
|
|
332
|
-
_izContext.logger.debug("Record of awaitingStepId", listPendingSteps)
|
|
333
|
-
|
|
334
|
-
if (listPendingSteps.Items.length !== 1) {
|
|
335
|
-
throw new NoRetryError("listPendingSteps not equals 1")
|
|
336
|
-
};
|
|
337
|
-
return listPendingSteps.Items[0];
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Use case:
|
|
341
|
-
// Use only after checkAllAwaitingStepsFinished
|
|
342
|
-
// Do NOT delete records in both tables (AwaitingMultipleSteps, AwaitingMultipleStepByPending) if AwaitingMultipleSteps is not yet finished
|
|
343
|
-
// Delete records from both tables only when checkAllAwaitingStepsFinished = true
|
|
344
|
-
// Example usage: flow validateCart lambda ProcessCartOrderFromTraversal
|
|
345
|
-
/**
|
|
346
|
-
* Given a pendingStepId, return all awaiting steps (by the ByPending table).
|
|
347
|
-
* Use only after confirming all awaiting steps are finished, and clean up both tables accordingly.
|
|
348
|
-
* @async
|
|
349
|
-
* @param {IzContext} _izContext
|
|
350
|
-
* @param {string} pendingStepId
|
|
351
|
-
* @returns {Promise<AwaitingMultipleStepByPendingItem[]>}
|
|
352
|
-
*/
|
|
353
|
-
async function findAwaitingMultipleStepByPending(
|
|
354
|
-
_izContext,
|
|
355
|
-
pendingStepId
|
|
356
|
-
) {
|
|
357
|
-
|
|
358
|
-
let listAwitingSteps = await dynamodbSharedLib.query(
|
|
359
|
-
_izContext,
|
|
360
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleStepByPending"),
|
|
361
|
-
{
|
|
362
|
-
pendingStepId: pendingStepId
|
|
363
|
-
}
|
|
364
|
-
);
|
|
365
|
-
_izContext.logger.debug("Record of AwaitingMultipleStepByPending", listAwitingSteps);
|
|
366
|
-
return listAwitingSteps.Items;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
//====================================== AwaitingStep //======================================
|
|
370
|
-
// AwaitingStep is when a flow is awaiting one external flow, and will continue it's flow once that one external flow is complete
|
|
371
|
-
// One awaitingStepId can have multiple pendingStepIds that are awaiting it
|
|
372
|
-
// logic can prepend any prefix to awaitingStepId if want to be share one table and differentiate different external step ids
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Create a single AwaitingStep record, optionally attaching additional attributes and callingFlowConfig.
|
|
376
|
-
* @async
|
|
377
|
-
* @param {IzContext} _izContext
|
|
378
|
-
* @param {string} awaitingStepId
|
|
379
|
-
* @param {string} pendingStepId
|
|
380
|
-
* @param {Attrs} [additionalAttributes={}]
|
|
381
|
-
* @param {Attrs} [callingFlowConfig={}]
|
|
382
|
-
* @returns {Promise<void>}
|
|
383
|
-
*/
|
|
384
|
-
async function createAwaitingStep(
|
|
385
|
-
_izContext,
|
|
386
|
-
awaitingStepId,
|
|
387
|
-
pendingStepId,
|
|
388
|
-
additionalAttributes = {}, // save in top level
|
|
389
|
-
callingFlowConfig = {} // optional
|
|
390
|
-
) {
|
|
391
|
-
console.log("Lib createAwaitingStep:", {
|
|
392
|
-
awaitingStepId,
|
|
393
|
-
pendingStepId,
|
|
394
|
-
additionalAttributes,
|
|
395
|
-
callingFlowConfig
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
let attributesWhenCreate = {
|
|
399
|
-
awaitingStepId: awaitingStepId,
|
|
400
|
-
pendingStepId: pendingStepId,
|
|
401
|
-
timestamp: Date.now(),
|
|
402
|
-
uniqueRequestId: _izContext.uniqueRequestId,
|
|
403
|
-
}
|
|
404
|
-
// loop if not empty
|
|
405
|
-
for (let additionalAttribute in additionalAttributes) {
|
|
406
|
-
attributesWhenCreate[additionalAttribute] = additionalAttributes[additionalAttribute] // each additionalAttributes
|
|
407
|
-
}
|
|
408
|
-
if (Object.keys(callingFlowConfig).length > 0) {
|
|
409
|
-
Object.assign(attributesWhenCreate, { callingFlowConfig })
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
await dynamodbSharedLib.putItem(
|
|
413
|
-
_izContext,
|
|
414
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingStep"),
|
|
415
|
-
attributesWhenCreate
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Find all pendingStepIds that are waiting on a given awaitingStepId (single).
|
|
421
|
-
* @async
|
|
422
|
-
* @param {IzContext} _izContext
|
|
423
|
-
* @param {string} awaitingStepId
|
|
424
|
-
* @returns {Promise<string[]>}
|
|
425
|
-
*/
|
|
426
|
-
async function findPendingStepIdsAwaitingStep(
|
|
427
|
-
_izContext,
|
|
428
|
-
awaitingStepId
|
|
429
|
-
) {
|
|
430
|
-
|
|
431
|
-
let pendingStepIds = [];
|
|
432
|
-
|
|
433
|
-
let listPendingStepIds = await dynamodbSharedLib.query(
|
|
434
|
-
_izContext,
|
|
435
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingStep"),
|
|
436
|
-
{
|
|
437
|
-
awaitingStepId: awaitingStepId
|
|
438
|
-
}
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
|
|
442
|
-
pendingStepIds.push(listPendingStepIds.Items[idx].pendingStepId)
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
return pendingStepIds
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Return the raw items waiting on a given awaitingStepId (single).
|
|
450
|
-
* @async
|
|
451
|
-
* @param {IzContext} _izContext
|
|
452
|
-
* @param {string} awaitingStepId
|
|
453
|
-
* @returns {Promise<AwaitingStepItem[]>}
|
|
454
|
-
*/
|
|
455
|
-
async function findPendingStepsAwaitingStep(
|
|
456
|
-
_izContext,
|
|
457
|
-
awaitingStepId
|
|
458
|
-
) {
|
|
459
|
-
|
|
460
|
-
let listPendingSteps = await dynamodbSharedLib.query(
|
|
461
|
-
_izContext,
|
|
462
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingStep"),
|
|
463
|
-
{
|
|
464
|
-
awaitingStepId: awaitingStepId
|
|
465
|
-
}
|
|
466
|
-
);
|
|
467
|
-
_izContext.logger.debug("Record of awaitingStepId", listPendingSteps);
|
|
468
|
-
return listPendingSteps.Items;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Fetch exactly one pending step for a given awaitingStepId (throws if not 1).
|
|
473
|
-
* @async
|
|
474
|
-
* @param {IzContext} _izContext
|
|
475
|
-
* @param {string} awaitingStepId
|
|
476
|
-
* @returns {Promise<AwaitingStepItem>}
|
|
477
|
-
* @throws {NoRetryError} if there is not exactly one item
|
|
478
|
-
*/
|
|
479
|
-
async function findPendingStepAwaitingStep(
|
|
480
|
-
_izContext,
|
|
481
|
-
awaitingStepId
|
|
482
|
-
) {
|
|
483
|
-
|
|
484
|
-
let listPendingSteps = await dynamodbSharedLib.query(
|
|
485
|
-
_izContext,
|
|
486
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingStep"),
|
|
487
|
-
{
|
|
488
|
-
awaitingStepId: awaitingStepId
|
|
489
|
-
}
|
|
490
|
-
);
|
|
491
|
-
_izContext.logger.debug("Record of awaitingStepId", listPendingSteps)
|
|
492
|
-
|
|
493
|
-
if (listPendingSteps.Items.length !== 1) {
|
|
494
|
-
throw new NoRetryError("listPendingSteps not equals 1")
|
|
495
|
-
};
|
|
496
|
-
return listPendingSteps.Items[0];
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// for get callingFlow save in awaitingStep and remove property callingFlow in awaitingStep
|
|
500
|
-
/**
|
|
501
|
-
* Get one awaiting step by id and also extract (and remove) its callingFlowConfig (if present).
|
|
502
|
-
* Returns a tuple: [AwaitingStepItemWithoutCallingFlow, callingFlowConfig|null].
|
|
503
|
-
* @async
|
|
504
|
-
* @param {IzContext} _izContext
|
|
505
|
-
* @param {string} awaitingStepId
|
|
506
|
-
* @returns {Promise<[AwaitingStepItem, Attrs|null]>}
|
|
507
|
-
*/
|
|
508
|
-
async function findPendingStepAwaitingStepWithCallingFlow(
|
|
509
|
-
_izContext,
|
|
510
|
-
awaitingStepId,
|
|
511
|
-
) {
|
|
512
|
-
|
|
513
|
-
let callingFlowConfig = null;
|
|
514
|
-
let awaitingStep = await findPendingStepAwaitingStep(
|
|
515
|
-
_izContext,
|
|
516
|
-
awaitingStepId
|
|
517
|
-
);
|
|
518
|
-
|
|
519
|
-
// callingFlowConfig.
|
|
520
|
-
// cleaning object awaitingStep not have callingFlowConfig after return.
|
|
521
|
-
if (awaitingStep.hasOwnProperty("callingFlowConfig")) {
|
|
522
|
-
callingFlowConfig = awaitingStep.callingFlowConfig
|
|
523
|
-
delete awaitingStep.callingFlowConfig
|
|
524
|
-
console.log("after delete awaitingStep", awaitingStep);
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
return [awaitingStep, callingFlowConfig]
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Mark current awaiting step as complete (in *_ByPending), then check if all awaiting steps are done.
|
|
532
|
-
* Returns true if all are finished; false otherwise.
|
|
533
|
-
* @async
|
|
534
|
-
* @param {IzContext} _izContext
|
|
535
|
-
* @param {string} pendingStepId
|
|
536
|
-
* @param {string|null} [currentAwaitingStepId=null]
|
|
537
|
-
* @param {any[]} [errorsFound=[]]
|
|
538
|
-
* @param {Attrs} [additionalAttributes={}]
|
|
539
|
-
* @returns {Promise<boolean>}
|
|
540
|
-
*/
|
|
541
|
-
async function checkAllAwaitingStepsFinished(
|
|
542
|
-
_izContext,
|
|
543
|
-
pendingStepId,
|
|
544
|
-
currentAwaitingStepId = null,
|
|
545
|
-
errorsFound = [],
|
|
546
|
-
additionalAttributes = {}
|
|
547
|
-
) {
|
|
548
|
-
|
|
549
|
-
let remaining = await checkAllAwaitingStepsFinishedWithError(
|
|
550
|
-
_izContext,
|
|
551
|
-
pendingStepId,
|
|
552
|
-
currentAwaitingStepId,
|
|
553
|
-
errorsFound,
|
|
554
|
-
additionalAttributes
|
|
555
|
-
);
|
|
556
|
-
return remaining;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* Same as checkAllAwaitingStepsFinished but writes errors/additionalAttributes to the *_ByPending row.
|
|
561
|
-
* @async
|
|
562
|
-
* @param {IzContext} _izContext
|
|
563
|
-
* @param {string} pendingStepId
|
|
564
|
-
* @param {string|null} [currentAwaitingStepId=null]
|
|
565
|
-
* @param {any[]} [errorsFound=[]]
|
|
566
|
-
* @param {Attrs} [additionalAttributes={}]
|
|
567
|
-
* @returns {Promise<boolean>}
|
|
568
|
-
*/
|
|
569
|
-
async function checkAllAwaitingStepsFinishedWithError(
|
|
570
|
-
_izContext,
|
|
571
|
-
pendingStepId,
|
|
572
|
-
currentAwaitingStepId = null,
|
|
573
|
-
errorsFound = [],
|
|
574
|
-
additionalAttributes = {}
|
|
575
|
-
) {
|
|
576
|
-
_izContext.logger.debug("Lib:checkAllAwaitingStepsFinishedWithError", {
|
|
577
|
-
pendingStepId: pendingStepId,
|
|
578
|
-
currentAwaitingStepId: currentAwaitingStepId,
|
|
579
|
-
errorsFound: errorsFound
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
if (currentAwaitingStepId) {
|
|
583
|
-
await dynamodbSharedLib.updateItem(
|
|
584
|
-
_izContext,
|
|
585
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleStepByPending"),
|
|
586
|
-
{
|
|
587
|
-
pendingStepId: pendingStepId,
|
|
588
|
-
awaitingStepId: currentAwaitingStepId
|
|
589
|
-
},
|
|
590
|
-
{
|
|
591
|
-
complete: true,
|
|
592
|
-
errorsFound: errorsFound,
|
|
593
|
-
...additionalAttributes
|
|
594
|
-
},
|
|
595
|
-
);
|
|
596
|
-
|
|
597
|
-
}
|
|
598
|
-
let listPendingStepIds = await dynamodbSharedLib.query(
|
|
599
|
-
_izContext,
|
|
600
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleStepByPending"),
|
|
601
|
-
{
|
|
602
|
-
pendingStepId: pendingStepId
|
|
603
|
-
},
|
|
604
|
-
{
|
|
605
|
-
limit: 50 // arbitrary limit so not pull too many results, need >2 because some others might be marked as "complete" = true
|
|
606
|
-
}
|
|
607
|
-
);
|
|
608
|
-
for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
|
|
609
|
-
|
|
610
|
-
// if have error found return errorsFound
|
|
611
|
-
// wrap function
|
|
612
|
-
|
|
613
|
-
if (currentAwaitingStepId && listPendingStepIds.Items[idx].awaitingStepId == currentAwaitingStepId) {
|
|
614
|
-
continue;
|
|
615
|
-
}
|
|
616
|
-
// if any record found not set to complete then there are steps not yet finished
|
|
617
|
-
if (!listPendingStepIds.Items[idx].complete) {
|
|
618
|
-
return false;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
return true; // all passed
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
// // mark currentAwaitingStepId as complete, this is done to protect against race condition in between checking all complete
|
|
626
|
-
// // and then removing the record, we need to let other requests know this record is complete and need to do this before
|
|
627
|
-
// // querying all remaining records, otherwise if have only two remaining requests they might both show as records remaining
|
|
628
|
-
// // (seeing each other) and not continue flow, then each deletes their record and no records remain to trigger next step.
|
|
629
|
-
// // can result in race condition where multiple requests see all records finished, code has to account for this, but should already do so for idempotence
|
|
630
|
-
// // add query that updates the currentAwaitingStepId to have attribute "complete" = true
|
|
631
|
-
// if (currentAwaitingStepId) {
|
|
632
|
-
// await dynamodbSharedLib.updateItem(
|
|
633
|
-
// _izContext,
|
|
634
|
-
// dynamodbSharedLib.tableName(tableName),
|
|
635
|
-
// {
|
|
636
|
-
// awaitingStepId: currentAwaitingStepId,
|
|
637
|
-
// pendingStepId: pendingStepId
|
|
638
|
-
// },
|
|
639
|
-
// {
|
|
640
|
-
// complete: true
|
|
641
|
-
// },
|
|
642
|
-
// )
|
|
643
|
-
// }
|
|
644
|
-
// let listPendingStepIds = await dynamodbSharedLib.query(
|
|
645
|
-
// _izContext,
|
|
646
|
-
// dynamodbSharedLib.tableName(tableName),
|
|
647
|
-
// {
|
|
648
|
-
// pendingStepId: pendingStepId
|
|
649
|
-
// },
|
|
650
|
-
// {
|
|
651
|
-
// indexName: dynamodbSharedLib.tableName(tableName + 'Index'),
|
|
652
|
-
// limit: 50 // arbitrary limit so not pull too many results, need >2 because some others might be marked as "complete" = true
|
|
653
|
-
// }
|
|
654
|
-
// );
|
|
655
|
-
// for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
|
|
656
|
-
|
|
657
|
-
// if (currentAwaitingStepId && listPendingStepIds.Items[idx].awaitingStepId == currentAwaitingStepId) {
|
|
658
|
-
// continue;
|
|
659
|
-
// }
|
|
660
|
-
// // if any record found not set to complete then there are steps not yet finished
|
|
661
|
-
// if (!listPendingStepIds.Items[idx].complete) {
|
|
662
|
-
// return false;
|
|
663
|
-
// }
|
|
664
|
-
// }
|
|
665
|
-
|
|
666
|
-
// return true;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
/**
|
|
670
|
-
* Remove all awaiting records for a given pendingStepId from both tables.
|
|
671
|
-
* @async
|
|
672
|
-
* @param {IzContext} _izContext
|
|
673
|
-
* @param {string} pendingStepId
|
|
674
|
-
* @returns {Promise<boolean>} true if delete flow processed
|
|
675
|
-
*/
|
|
676
|
-
async function clearAllAwaitingSteps(
|
|
677
|
-
_izContext,
|
|
678
|
-
pendingStepId
|
|
679
|
-
) {
|
|
680
|
-
// ----- query items from GSI table
|
|
681
|
-
let listPendingStepIds = await dynamodbSharedLib.query(
|
|
682
|
-
_izContext,
|
|
683
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleStepByPending"),
|
|
684
|
-
{
|
|
685
|
-
pendingStepId: pendingStepId
|
|
686
|
-
},
|
|
687
|
-
);
|
|
688
|
-
_izContext.logger.debug('listPendingStepIds', listPendingStepIds);
|
|
689
|
-
|
|
690
|
-
let promiseArray = []
|
|
691
|
-
|
|
692
|
-
for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
|
|
693
|
-
|
|
694
|
-
_izContext.logger.debug(`delete item in dynamodb AwaitingMultipleSteps`)
|
|
695
|
-
promiseArray.push(
|
|
696
|
-
dynamodbSharedLib.deleteItem(
|
|
697
|
-
_izContext,
|
|
698
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleSteps"),
|
|
699
|
-
{
|
|
700
|
-
awaitingStepId: listPendingStepIds.Items[idx].awaitingStepId,
|
|
701
|
-
pendingStepId: listPendingStepIds.Items[idx].pendingStepId
|
|
702
|
-
}
|
|
703
|
-
)
|
|
704
|
-
);
|
|
705
|
-
|
|
706
|
-
_izContext.logger.debug(`delete item in dynamodb AwaitingMultipleStepByPending`)
|
|
707
|
-
promiseArray.push(
|
|
708
|
-
dynamodbSharedLib.deleteItem(
|
|
709
|
-
_izContext,
|
|
710
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleStepByPending"),
|
|
711
|
-
{
|
|
712
|
-
pendingStepId: listPendingStepIds.Items[idx].pendingStepId,
|
|
713
|
-
awaitingStepId: listPendingStepIds.Items[idx].awaitingStepId
|
|
714
|
-
}
|
|
715
|
-
)
|
|
716
|
-
);
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
await Promise.all(promiseArray);
|
|
720
|
-
|
|
721
|
-
return true;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* Remove a single AwaitingStep record (single dependency table).
|
|
726
|
-
* @async
|
|
727
|
-
* @param {IzContext} _izContext
|
|
728
|
-
* @param {string} awaitingStepId
|
|
729
|
-
* @param {string} pendingStepId
|
|
730
|
-
* @returns {Promise<void>}
|
|
731
|
-
*/
|
|
732
|
-
async function removeAwaitingStep(
|
|
733
|
-
_izContext,
|
|
734
|
-
awaitingStepId,
|
|
735
|
-
pendingStepId
|
|
736
|
-
) {
|
|
737
|
-
|
|
738
|
-
await dynamodbSharedLib.deleteItem(
|
|
739
|
-
_izContext,
|
|
740
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingStep"),
|
|
741
|
-
{
|
|
742
|
-
awaitingStepId: awaitingStepId,
|
|
743
|
-
pendingStepId: pendingStepId
|
|
744
|
-
}
|
|
745
|
-
);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
/**
|
|
749
|
-
* Remove items for AwaitingMultipleSteps and (conditionally) AwaitingMultipleStepByPending.
|
|
750
|
-
* If errorsFound is empty, delete from ByPending table as well.
|
|
751
|
-
* @async
|
|
752
|
-
* @param {IzContext} _izContext
|
|
753
|
-
* @param {string} awaitingStepId
|
|
754
|
-
* @param {string} pendingStepId
|
|
755
|
-
* @param {any[]} [errorsFound=[]]
|
|
756
|
-
* @returns {Promise<void>}
|
|
757
|
-
*/
|
|
758
|
-
async function removeAwaitingMultipleStep(
|
|
759
|
-
_izContext,
|
|
760
|
-
awaitingStepId,
|
|
761
|
-
pendingStepId,
|
|
762
|
-
errorsFound = []
|
|
763
|
-
) {
|
|
764
|
-
_izContext.logger.debug("Lib:removeAwaitingMultipleStep", {
|
|
765
|
-
awaitingStepId: awaitingStepId,
|
|
766
|
-
pendingStepId: pendingStepId
|
|
767
|
-
})
|
|
768
|
-
|
|
769
|
-
let promiseArray = []
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
let keyValues = {
|
|
773
|
-
awaitingStepId: awaitingStepId,
|
|
774
|
-
pendingStepId: pendingStepId
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
let keyValuesForByPending = {
|
|
778
|
-
pendingStepId: pendingStepId,
|
|
779
|
-
awaitingStepId: awaitingStepId
|
|
780
|
-
}
|
|
781
|
-
_izContext.logger.debug('deleting ... ', {
|
|
782
|
-
keyValues,
|
|
783
|
-
keyValuesForByPending
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
_izContext.logger.debug(`delete item in dynamodb AwaitingMultipleSteps ==> awaitingStepId:${keyValues.awaitingStepId}`)
|
|
787
|
-
promiseArray.push(
|
|
788
|
-
dynamodbSharedLib.deleteItem(
|
|
789
|
-
_izContext,
|
|
790
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleSteps"),
|
|
791
|
-
keyValues
|
|
792
|
-
));
|
|
793
|
-
|
|
794
|
-
if (errorsFound.length == 0) {
|
|
795
|
-
_izContext.logger.debug(`delete item in dynamodb AwaitingMultipleStepByPending ==> awaitingStepId:${keyValues.awaitingStepId}`)
|
|
796
|
-
promiseArray.push(
|
|
797
|
-
dynamodbSharedLib.deleteItem(
|
|
798
|
-
_izContext,
|
|
799
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingMultipleStepByPending"),
|
|
800
|
-
keyValuesForByPending
|
|
801
|
-
));
|
|
802
|
-
}
|
|
803
|
-
await Promise.all(promiseArray);
|
|
804
|
-
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// only works with AwaitingStep, does not work with AwaitingMultipleSteps, need to make new function that checks conditional pass? If pass delete from byPending table too
|
|
808
|
-
/**
|
|
809
|
-
* Remove an AwaitingStep item only if its stored UniqueRequestId matches `checkUniqueRequestId`.
|
|
810
|
-
* Uses a conditional expression; will not error if the condition fails.
|
|
811
|
-
* @async
|
|
812
|
-
* @param {IzContext} _izContext
|
|
813
|
-
* @param {string} awaitingStepId
|
|
814
|
-
* @param {string} pendingStepId
|
|
815
|
-
* @param {string} checkUniqueRequestId
|
|
816
|
-
* @param {string} [prefix=""] - Optional prefix for UniqueRequestId field name
|
|
817
|
-
* @returns {Promise<void>}
|
|
818
|
-
*/
|
|
819
|
-
async function removeAwaitingStepWithCheckUniqueRequestId(
|
|
820
|
-
_izContext,
|
|
821
|
-
awaitingStepId,
|
|
822
|
-
pendingStepId,
|
|
823
|
-
checkUniqueRequestId,
|
|
824
|
-
prefix = ""
|
|
825
|
-
) {
|
|
826
|
-
|
|
827
|
-
// if logicalElements add queryElements.logicalElements
|
|
828
|
-
// set condition uniqueRequestId match with exists
|
|
829
|
-
const queryElementsCondition = {
|
|
830
|
-
additionalAttributes: {
|
|
831
|
-
checkUniqueRequestId: checkUniqueRequestId,
|
|
832
|
-
},
|
|
833
|
-
logicalElements: [
|
|
834
|
-
{
|
|
835
|
-
type: "logical",
|
|
836
|
-
comparison: "equals",
|
|
837
|
-
lhsAttribute: createFieldNameUniqueRequestId(prefix),
|
|
838
|
-
rhsReference: "checkUniqueRequestId"
|
|
839
|
-
},
|
|
840
|
-
]
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
await dynamodbSharedLib.deleteItem(
|
|
844
|
-
_izContext,
|
|
845
|
-
await dynamodbSharedLib.tableName(_izContext, "AwaitingStep"),
|
|
846
|
-
{
|
|
847
|
-
awaitingStepId: awaitingStepId,
|
|
848
|
-
pendingStepId: pendingStepId
|
|
849
|
-
},
|
|
850
|
-
queryElementsCondition,
|
|
851
|
-
{
|
|
852
|
-
errorOnConditionalExpNotPass: false
|
|
853
|
-
}
|
|
854
|
-
);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
//====================================== end AwaitingStep
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* Checks if record has uniqueRequestId set, if not set to this requests uniqueRequestId and continue to process
|
|
861
|
-
* if already set check if matches this request uniqueRequestId, if yes continue to process, if not then stop processing
|
|
862
|
-
* status: "process"|"stop"|"recordNotFound"
|
|
863
|
-
|
|
864
|
-
* if unable to set uniqueRequestId (because other thread updated it) throw NoRetryError
|
|
865
|
-
* conditional update existingRecord set uniqueRequestId = _izContext.uniqueRequestId
|
|
866
|
-
* conditional check uniqueRequestId still not exist in case another thread added
|
|
867
|
-
* if conditional not pass "continue" and try again
|
|
868
|
-
|
|
869
|
-
* @returns [string] [returnStatus, existingRecord]
|
|
870
|
-
*/
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
/**
|
|
874
|
-
* Coordinate unique-request processing over a record:
|
|
875
|
-
* - If no record: returns ["recordNotFound", {}]
|
|
876
|
-
* - If record exists but no uniqueRequestId: tries to set it via conditional update
|
|
877
|
-
* - If record has a different uniqueRequestId: returns ["stop", existingRecord]
|
|
878
|
-
* - If record has the same uniqueRequestId: returns ["process", existingRecord]
|
|
879
|
-
*
|
|
880
|
-
* Will loop up to 2 attempts to avoid races; throws on 3rd iteration.
|
|
881
|
-
*
|
|
882
|
-
* @async
|
|
883
|
-
* @param {IzContext} _izContext
|
|
884
|
-
* @param {string} tableName - Logical table name known by dynamodbSharedLib.tableName
|
|
885
|
-
* @param {DynamoKey} primaryKey - Primary key for the target record
|
|
886
|
-
* @param {string} [prefix=''] - Optional field-name prefix
|
|
887
|
-
* @param {string|null} [overwriteUniqueRequestId=null] - If provided, use this as the "current" unique request id
|
|
888
|
-
* @param {string} [uniqueRequestIdFieldName="UniqueRequestId"] - Base field name before prefix
|
|
889
|
-
* @returns {Promise<[UniqueRequestStatus, Attrs]>}
|
|
890
|
-
* @throws {NoRetryError} If unable to set uniqueRequestId after 2 tries
|
|
891
|
-
*/
|
|
892
|
-
async function checkUniqueRequestProcessing(
|
|
893
|
-
_izContext,
|
|
894
|
-
tableName,
|
|
895
|
-
primaryKey,
|
|
896
|
-
prefix = '',
|
|
897
|
-
overwriteUniqueRequestId = null,
|
|
898
|
-
uniqueRequestIdFieldName = "UniqueRequestId" // if set will check/add this fieldname with _izContext.uniqueRequestId value
|
|
899
|
-
) {
|
|
900
|
-
try {
|
|
901
|
-
|
|
902
|
-
_izContext.logger.debug('current uniqueRequestId:', _izContext.uniqueRequestId)
|
|
903
|
-
|
|
904
|
-
let uniqueRequestId = _izContext.uniqueRequestId;
|
|
905
|
-
if (overwriteUniqueRequestId) {
|
|
906
|
-
uniqueRequestId = overwriteUniqueRequestId;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
// add prefix
|
|
910
|
-
uniqueRequestIdFieldName = createFieldNameUniqueRequestId(
|
|
911
|
-
prefix,
|
|
912
|
-
uniqueRequestIdFieldName
|
|
913
|
-
);
|
|
914
|
-
|
|
915
|
-
//loop 3 times incase status changes, on 3rd iteration throw error (so checks 2 times)
|
|
916
|
-
for (let i = 1; i <= 3; i++) {
|
|
917
|
-
if (i == 3) {
|
|
918
|
-
throw new NoRetryError(`unable to set uniqueRequestId in table ${tableName}, record`, primaryKey);
|
|
919
|
-
}
|
|
920
|
-
//* get record from dynamo
|
|
921
|
-
let existingRecord = await dynamodbSharedLib.getItem(
|
|
922
|
-
_izContext,
|
|
923
|
-
await dynamodbSharedLib.tableName(_izContext, tableName),
|
|
924
|
-
primaryKey,
|
|
925
|
-
)
|
|
926
|
-
_izContext.logger.debug('existingRecord:', existingRecord);
|
|
927
|
-
|
|
928
|
-
let returnStatus = "process";
|
|
929
|
-
|
|
930
|
-
if (!existingRecord) {
|
|
931
|
-
// return ["recordNotFound", null]; // cannot return null, js cannot found object and throw error and dont know where it is?
|
|
932
|
-
return ["recordNotFound", {}];
|
|
933
|
-
|
|
934
|
-
} else if (!existingRecord[uniqueRequestIdFieldName]) {
|
|
935
|
-
// uniqueRequestId not yet exist
|
|
936
|
-
|
|
937
|
-
let updateAttributes = [
|
|
938
|
-
{
|
|
939
|
-
attributeName: uniqueRequestIdFieldName,
|
|
940
|
-
action: "set",
|
|
941
|
-
value: _izContext.uniqueRequestId,
|
|
942
|
-
},
|
|
943
|
-
]
|
|
944
|
-
//* conditional check uniqueRequestId still not exist in case another thread added
|
|
945
|
-
const queryElementsWhenUpdate = {
|
|
946
|
-
logicalElements: [
|
|
947
|
-
{
|
|
948
|
-
type: "function",
|
|
949
|
-
function: "attribute_not_exists",
|
|
950
|
-
attribute: uniqueRequestIdFieldName,
|
|
951
|
-
}
|
|
952
|
-
],
|
|
953
|
-
returnValues: 'ALL_NEW'
|
|
954
|
-
}
|
|
955
|
-
// // ----- main query ---------
|
|
956
|
-
let updateRecord = null;
|
|
957
|
-
try {
|
|
958
|
-
updateRecord = await dynamodbSharedLib.updateItem(
|
|
959
|
-
_izContext,
|
|
960
|
-
await dynamodbSharedLib.tableName(_izContext, tableName),
|
|
961
|
-
primaryKey,
|
|
962
|
-
updateAttributes,
|
|
963
|
-
queryElementsWhenUpdate,
|
|
964
|
-
{ complexAttributes: true }, // force to complex
|
|
965
|
-
)
|
|
966
|
-
} catch (err) { // catch error when put and handle errors.
|
|
967
|
-
_izContext.logger.debug('updateItem storedCache expired err', err);
|
|
968
|
-
if (err.name == 'ConditionalCheckFailedException') {
|
|
969
|
-
continue;
|
|
970
|
-
} else { // e.g. ProvisionedThroughputExceededException
|
|
971
|
-
// throw new Error('Unhandled Error when putItem', err) // TT, if throw will stop
|
|
972
|
-
throw (err) // TT, if throw will stop
|
|
973
|
-
}
|
|
974
|
-
} //end catch err
|
|
975
|
-
|
|
976
|
-
} else if (existingRecord[uniqueRequestIdFieldName] !== uniqueRequestId) {
|
|
977
|
-
|
|
978
|
-
// when another request/uniqueRequestId need to stop process
|
|
979
|
-
return ["stop", existingRecord];
|
|
980
|
-
|
|
981
|
-
} else if (existingRecord[uniqueRequestIdFieldName] == uniqueRequestId) {
|
|
982
|
-
|
|
983
|
-
// if existingRecord[uniqueRequestIdFieldName] == uniqueRequestId then return "process
|
|
984
|
-
return [returnStatus, existingRecord];
|
|
985
|
-
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
} // end loop 2 times
|
|
989
|
-
//should never get here
|
|
990
|
-
throw new NoRetryError('unexpected logic flow in checkUniqueRequestProcessing');
|
|
991
|
-
|
|
992
|
-
} catch (err) {
|
|
993
|
-
_izContext.logger.error('ERROR checkUniqueRequestProcessing:', err)
|
|
994
|
-
throw (err)
|
|
995
|
-
}
|
|
996
|
-
};
|
|
997
|
-
|
|
998
|
-
// ----- Helper funtion, common use case in triggerFlow ---------
|
|
999
|
-
/**
|
|
1000
|
-
* Convenience wrapper around checkAndGetTimeCacheComplete.
|
|
1001
|
-
* Returns [isSameOrUnset, uniqueRequestIdWhenComplete].
|
|
1002
|
-
* @async
|
|
1003
|
-
* @param {IzContext} _izContext
|
|
1004
|
-
* @param {string} fullMainTableName - Fully resolved table name (not logical)
|
|
1005
|
-
* @param {DynamoKey} keyValues
|
|
1006
|
-
* @param {number|string} timeCacheComplete - Caller’s expected cache mark
|
|
1007
|
-
* @param {string} prefix - Field prefix used in cache fields
|
|
1008
|
-
* @returns {Promise<[boolean, string|undefined]>}
|
|
1009
|
-
*/
|
|
1010
|
-
async function checkTimeCacheComplete(
|
|
1011
|
-
_izContext,
|
|
1012
|
-
fullMainTableName,
|
|
1013
|
-
keyValues,
|
|
1014
|
-
timeCacheComplete,
|
|
1015
|
-
prefix
|
|
1016
|
-
) {
|
|
1017
|
-
let [returnValue, newTimeCacheComplete, uniqueRequestId] = await checkAndGetTimeCacheComplete(
|
|
1018
|
-
_izContext,
|
|
1019
|
-
fullMainTableName,
|
|
1020
|
-
keyValues,
|
|
1021
|
-
timeCacheComplete,
|
|
1022
|
-
prefix
|
|
1023
|
-
);
|
|
1024
|
-
return [returnValue, uniqueRequestId];
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
/**
|
|
1028
|
-
* Read cache fields from a record and compare cacheComplete value to caller’s `timeCacheComplete`.
|
|
1029
|
-
* If stored differs (and is present), returns [false, storedValue].
|
|
1030
|
-
* Otherwise returns [true, storedValue, uniqueRequestId].
|
|
1031
|
-
* @async
|
|
1032
|
-
* @param {IzContext} _izContext
|
|
1033
|
-
* @param {string} fullMainTableName - Fully resolved table name (not logical)
|
|
1034
|
-
* @param {DynamoKey} keyValues
|
|
1035
|
-
* @param {number|string} timeCacheComplete
|
|
1036
|
-
* @param {string} prefix
|
|
1037
|
-
* @returns {Promise<[boolean, any, string|undefined]>}
|
|
1038
|
-
* @throws {NoRetryError} If record cannot be fetched
|
|
1039
|
-
*/
|
|
1040
|
-
async function checkAndGetTimeCacheComplete(
|
|
1041
|
-
_izContext,
|
|
1042
|
-
fullMainTableName,
|
|
1043
|
-
keyValues,
|
|
1044
|
-
timeCacheComplete,
|
|
1045
|
-
prefix
|
|
1046
|
-
) {
|
|
1047
|
-
_izContext.logger.debug('event checkTimeCacheComplete', {
|
|
1048
|
-
fullMainTableName: fullMainTableName,
|
|
1049
|
-
keyValues: keyValues,
|
|
1050
|
-
timeCacheComplete: timeCacheComplete,
|
|
1051
|
-
prefix: prefix
|
|
1052
|
-
});
|
|
1053
|
-
|
|
1054
|
-
let uniqueRequestIdFieldName = createFieldNameUniqueRequestId('cache');
|
|
1055
|
-
let cacheCompleteFieldName = createFieldNameCacheComplete(prefix);
|
|
1056
|
-
let statusFieldName = createFieldNameStatus(prefix);
|
|
1057
|
-
|
|
1058
|
-
let getTimeCacheComplete = await dynamodbSharedLib.getItem(
|
|
1059
|
-
_izContext,
|
|
1060
|
-
fullMainTableName,
|
|
1061
|
-
keyValues
|
|
1062
|
-
)
|
|
1063
|
-
_izContext.logger.debug(`getTimeCacheComplete from ${fullMainTableName}: `, getTimeCacheComplete);
|
|
1064
|
-
|
|
1065
|
-
if (!getTimeCacheComplete) {
|
|
1066
|
-
throw new NoRetryError(`cannot getTimeCacheComplete from ${fullMainTableName}`);
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// check if exist and match with initail create not changing in flow.
|
|
1070
|
-
// if (!getTimeCacheComplete[uniqueRequestIdFieldName]
|
|
1071
|
-
// // || !getTimeCacheComplete[cacheCompleteFieldName]
|
|
1072
|
-
// || (getTimeCacheComplete[cacheCompleteFieldName]
|
|
1073
|
-
// && getTimeCacheComplete[cacheCompleteFieldName] !== timeCacheComplete
|
|
1074
|
-
// && getTimeCacheComplete[statusFieldName] != "complete")
|
|
1075
|
-
// ) {
|
|
1076
|
-
// return [false, getTimeCacheComplete[cacheCompleteFieldName]];
|
|
1077
|
-
// } else {
|
|
1078
|
-
// return [true, getTimeCacheComplete[cacheCompleteFieldName], getTimeCacheComplete[uniqueRequestIdFieldName]];
|
|
1079
|
-
// }
|
|
1080
|
-
|
|
1081
|
-
if (getTimeCacheComplete[cacheCompleteFieldName]
|
|
1082
|
-
&& getTimeCacheComplete[cacheCompleteFieldName] !== timeCacheComplete
|
|
1083
|
-
) {
|
|
1084
|
-
return [false, getTimeCacheComplete[cacheCompleteFieldName]];
|
|
1085
|
-
} else {
|
|
1086
|
-
return [true, getTimeCacheComplete[cacheCompleteFieldName], getTimeCacheComplete[uniqueRequestIdFieldName]];
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
// ----- shared both stored cache and triggered cache, check uniqueRequestId changed ---------
|
|
1092
|
-
/**
|
|
1093
|
-
* Ensure the cache object’s "uniqueRequestIdCompleteFieldName" equals the given checkUniqueRequestId.
|
|
1094
|
-
* @async
|
|
1095
|
-
* @param {IzContext} _izContext
|
|
1096
|
-
* @param {string} fullMainTableName
|
|
1097
|
-
* @param {DynamoKey} keyValues
|
|
1098
|
-
* @param {string} checkUniqueRequestId
|
|
1099
|
-
* @param {string} uniqueRequestIdCompleteFieldName
|
|
1100
|
-
* @returns {Promise<boolean>}
|
|
1101
|
-
*/
|
|
1102
|
-
async function checkCacheUniqueRequestId(
|
|
1103
|
-
_izContext,
|
|
1104
|
-
fullMainTableName,
|
|
1105
|
-
keyValues,
|
|
1106
|
-
checkUniqueRequestId,
|
|
1107
|
-
uniqueRequestIdCompleteFieldName
|
|
1108
|
-
) {
|
|
1109
|
-
|
|
1110
|
-
let cacheObject = await dynamodbSharedLib.getItem(
|
|
1111
|
-
_izContext,
|
|
1112
|
-
fullMainTableName,
|
|
1113
|
-
keyValues
|
|
1114
|
-
)
|
|
1115
|
-
_izContext.logger.debug('cacheObject', cacheObject);
|
|
1116
|
-
|
|
1117
|
-
if (!cacheObject[uniqueRequestIdCompleteFieldName] || cacheObject[uniqueRequestIdCompleteFieldName] !== checkUniqueRequestId) {
|
|
1118
|
-
return false
|
|
1119
|
-
} else {
|
|
1120
|
-
return true
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
// use shared findPendingStepIdsAwaitingStep
|
|
1126
|
-
// module.exports.findPendingStepIdsAwaitingStepM = async (
|
|
1127
|
-
// _izContext,
|
|
1128
|
-
// awaitingStepId,
|
|
1129
|
-
// tableName = "AwaitingMultipleSteps"
|
|
1130
|
-
// ) => {
|
|
1131
|
-
|
|
1132
|
-
// let pendingStepIds = [];
|
|
1133
|
-
|
|
1134
|
-
// let listPendingStepIds = await dynamodbSharedLib.query(
|
|
1135
|
-
// _izContext,
|
|
1136
|
-
// dynamodbSharedLib.tableName(tableName),
|
|
1137
|
-
// {
|
|
1138
|
-
// awaitingStepId: awaitingStepId
|
|
1139
|
-
// }
|
|
1140
|
-
// );
|
|
1141
|
-
|
|
1142
|
-
// for (let idx = 0; idx < listPendingStepIds.Items.length; idx++) {
|
|
1143
|
-
// pendingStepIds.push(listPendingStepIds.Items[idx].pendingStepId)
|
|
1144
|
-
// }
|
|
1145
|
-
|
|
1146
|
-
// return pendingStepIds
|
|
1147
|
-
// }
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
// use shared removeAwaitingStep
|
|
1151
|
-
// module.exports.removeAwaitingStep = async (
|
|
1152
|
-
// _izContext,
|
|
1153
|
-
// awaitingStepId,
|
|
1154
|
-
// pendingStepId,
|
|
1155
|
-
// tableName = "AwaitingMultipleSteps"
|
|
1156
|
-
// ) => {
|
|
1157
|
-
|
|
1158
|
-
// await dynamodbSharedLib.deleteItem(
|
|
1159
|
-
// _izContext,
|
|
1160
|
-
// dynamodbSharedLib.tableName(tableName),
|
|
1161
|
-
// {
|
|
1162
|
-
// awaitingStepId: awaitingStepId,
|
|
1163
|
-
// pendingStepId: pendingStepId
|
|
1164
|
-
// }
|
|
1165
|
-
// );
|
|
1166
|
-
// }
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
//====================================== end AwaitingMultipleSteps
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
//====================================== end Multiple Lambda Invocations (Logic pagination of handling results)
|
|
1174
|
-
|
|
1175
|
-
/**
|
|
1176
|
-
* Validate and normalize a DynamoDB pagination startKey object.
|
|
1177
|
-
* Ensures partitionKeyFieldName/sortKeyFieldName are alphanumeric/underscore/hyphen.
|
|
1178
|
-
* If startKey is empty object, returns `null` for easier processing downstream.
|
|
1179
|
-
* @param {IzContext} _izContext
|
|
1180
|
-
* @param {Attrs|null|undefined} startKey
|
|
1181
|
-
* @param {string} partitionKeyFieldName
|
|
1182
|
-
* @param {string} sortKeyFieldName
|
|
1183
|
-
* @returns {Attrs|null}
|
|
1184
|
-
* @throws {NoRetryError} On invalid field names or malformed startKey
|
|
1185
|
-
*/
|
|
1186
|
-
function validateStartKeyParam(
|
|
1187
|
-
_izContext,
|
|
1188
|
-
startKey,
|
|
1189
|
-
partitionKeyFieldName,
|
|
1190
|
-
sortKeyFieldName
|
|
1191
|
-
) {
|
|
1192
|
-
const stringNotEmptyRegex = /^[A-Za-z0-9_-]+$/;
|
|
1193
|
-
|
|
1194
|
-
if (
|
|
1195
|
-
!partitionKeyFieldName || !sortKeyFieldName
|
|
1196
|
-
|| !stringNotEmptyRegex.test(partitionKeyFieldName)
|
|
1197
|
-
|| !stringNotEmptyRegex.test(sortKeyFieldName)
|
|
1198
|
-
) {
|
|
1199
|
-
throw new NoRetryError("validateStartKeyParam: Invalid partitionKeyFieldName or sortKeyFieldName");
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
if (startKey && Object.keys(startKey).length != 0) {
|
|
1203
|
-
if (!startKey[partitionKeyFieldName] || !startKey[sortKeyFieldName]) {
|
|
1204
|
-
throw new NoRetryError("validateStartKeyParam: Invalid startKey, missing partitionKeyFieldName or sortKeyFieldName");
|
|
1205
|
-
}
|
|
1206
|
-
} else {
|
|
1207
|
-
startKey = null; // if empty set to null so lib functions process correctly
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
return startKey;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
/**
|
|
1214
|
-
* For paginated multi-invocation flows: validates the current invocation count,
|
|
1215
|
-
* attaches the (optional) next startKey to the message, increments the count,
|
|
1216
|
-
* and re-enqueues the message to the specified SQS queue.
|
|
1217
|
-
* @async
|
|
1218
|
-
* @param {IzContext} _izContext
|
|
1219
|
-
* @param {Attrs} messageProperty - The message body object to re-dispatch
|
|
1220
|
-
* @param {Attrs} [passOnStartKey={}] - Optional startKey to pass along
|
|
1221
|
-
* @param {number} numberInvocation - Current invocation count
|
|
1222
|
-
* @param {string} queueName - Logical queue name resolvable via sqsSharedLib.sqsQueueUrl
|
|
1223
|
-
* @returns {Promise<void>}
|
|
1224
|
-
* @throws {NoRetryError} If `numberInvocation` exceeds internal safety limit
|
|
1225
|
-
*/
|
|
1226
|
-
async function validateMultipleInvocations(
|
|
1227
|
-
_izContext,
|
|
1228
|
-
messageProperty,
|
|
1229
|
-
passOnStartKey = {},
|
|
1230
|
-
numberInvocation,
|
|
1231
|
-
queueName
|
|
1232
|
-
) {
|
|
1233
|
-
_izContext.logger.debug("Lib:validateMultipleInvocations", {
|
|
1234
|
-
messageProperty: messageProperty,
|
|
1235
|
-
passOnStartKey: passOnStartKey,
|
|
1236
|
-
numberInvocation: numberInvocation,
|
|
1237
|
-
queueName: queueName
|
|
1238
|
-
});
|
|
1239
|
-
try {
|
|
1240
|
-
const maxProcessInvocationCountImport = 20
|
|
1241
|
-
if (numberInvocation >= maxProcessInvocationCountImport) {
|
|
1242
|
-
_izContext.logger.error("numberInvocation exceeds maxProcessInvocationCountImport limit");
|
|
1243
|
-
throw new NoRetryError("numberInvocation exceeds maxProcessInvocationCountImport limit");
|
|
1244
|
-
};
|
|
1245
|
-
|
|
1246
|
-
if (passOnStartKey) {
|
|
1247
|
-
messageProperty["startKey"] = passOnStartKey
|
|
1248
|
-
};
|
|
1249
|
-
messageProperty["numberInvocation"] = numberInvocation;
|
|
1250
|
-
|
|
1251
|
-
let messageReInvokeFunction = {
|
|
1252
|
-
MessageBody: JSON.stringify(
|
|
1253
|
-
messageProperty
|
|
1254
|
-
),
|
|
1255
|
-
QueueUrl: await sqsSharedLib.sqsQueueUrl(_izContext, queueName)
|
|
1256
|
-
};
|
|
1257
|
-
_izContext.logger.debug(`Send mesage to Dsq:${queueName} `, messageReInvokeFunction);
|
|
1258
|
-
await sqs.sendMessage(_izContext, messageReInvokeFunction);
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
} catch (err) {
|
|
1262
|
-
throw (err)
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
export default {
|
|
1267
|
-
// Helper functions
|
|
1268
|
-
createFieldNameUniqueRequestId,
|
|
1269
|
-
createConcatenatedAwaitingStepId,
|
|
1270
|
-
createConcatenatedPendingStepId,
|
|
1271
|
-
createAwaitingStepId,
|
|
1272
|
-
createPendingStepId,
|
|
1273
|
-
explodePendingStepId,
|
|
1274
|
-
createFieldNameCacheComplete,
|
|
1275
|
-
createFieldNameStatus,
|
|
1276
|
-
|
|
1277
|
-
// AwaitingMultipleSteps functions
|
|
1278
|
-
createAwaitingMultipleStepsWithAdditionalAttributes,
|
|
1279
|
-
createAwaitingMultipleSteps,
|
|
1280
|
-
checkAllAwaitingStepsFinishedWithError,
|
|
1281
|
-
checkAllAwaitingStepsFinished,
|
|
1282
|
-
clearAllAwaitingSteps,
|
|
1283
|
-
removeAwaitingMultipleStep,
|
|
1284
|
-
findPendingStepIdsAwaitingMultipleSteps,
|
|
1285
|
-
findPendingStepsAwaitingMultipleSteps,
|
|
1286
|
-
findPendingStepAwaitingMultipleSteps,
|
|
1287
|
-
findAwaitingMultipleStepByPending,
|
|
1288
|
-
|
|
1289
|
-
// AwaitingStep functions
|
|
1290
|
-
createAwaitingStep,
|
|
1291
|
-
findPendingStepIdsAwaitingStep,
|
|
1292
|
-
findPendingStepAwaitingStep,
|
|
1293
|
-
findPendingStepsAwaitingStep,
|
|
1294
|
-
findPendingStepAwaitingStepWithCallingFlow,
|
|
1295
|
-
removeAwaitingStep,
|
|
1296
|
-
removeAwaitingStepWithCheckUniqueRequestId,
|
|
1297
|
-
|
|
1298
|
-
// Unique Request Processing functions
|
|
1299
|
-
checkUniqueRequestProcessing,
|
|
1300
|
-
|
|
1301
|
-
// Cache functions
|
|
1302
|
-
checkTimeCacheComplete,
|
|
1303
|
-
checkAndGetTimeCacheComplete,
|
|
1304
|
-
checkCacheUniqueRequestId,
|
|
1305
|
-
|
|
1306
|
-
// Validation functions
|
|
1307
|
-
validateStartKeyParam,
|
|
1308
|
-
validateMultipleInvocations
|
|
1309
|
-
};
|