@izara_project/izara-core-library-lock-update 1.0.1 → 1.0.2
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 +11 -0
- package/package.json +19 -8
- package/src/constants/lockUpdateConstants.js +57 -0
- package/src/lockUpdateSharedLib.js +1078 -0
- package/src/validation/lockUpdateSchemaValidation.js +607 -0
|
@@ -0,0 +1,1078 @@
|
|
|
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
|
+
import { NoRetryError, validator } from '@izara_project/izara-core-library-core';
|
|
21
|
+
import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
|
|
22
|
+
import errorHandlingSharedLib from '@izara_project/izara-core-library-error-handling';
|
|
23
|
+
import { sns } from '@izara_project/izara-core-library-external-request';
|
|
24
|
+
import { identifiersObject } from '@izara_project/izara-core-library-service-schemas';
|
|
25
|
+
import snsSharedLib from '@izara_project/izara-core-library-sns';
|
|
26
|
+
import { getObjectSchema } from '@izara_project/izara-core-library-service-schemas';
|
|
27
|
+
import graphSharedLib from '@izara_project/izara-core-library-graph-service';
|
|
28
|
+
|
|
29
|
+
import lockUpdateConstants from './constants/lockUpdateConstants.js';
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
function _createAjvValidationSchema(objectSchema) {
|
|
34
|
+
const properties = {};
|
|
35
|
+
const identifierFieldNames = new Set();
|
|
36
|
+
|
|
37
|
+
// Collect identifier field names
|
|
38
|
+
for (const identifier of objectSchema.identifiers) {
|
|
39
|
+
if (identifier.fieldName) {
|
|
40
|
+
identifierFieldNames.add(identifier.fieldName);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (Array.isArray(identifier.fieldNames)) {
|
|
44
|
+
for (const fieldName of identifier.fieldNames) {
|
|
45
|
+
identifierFieldNames.add(fieldName);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Create properties
|
|
51
|
+
for (const [fieldName, fieldSchema] of Object.entries(
|
|
52
|
+
objectSchema.fieldNames
|
|
53
|
+
)) {
|
|
54
|
+
if (identifierFieldNames.has(fieldName)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
properties[fieldName] = {
|
|
59
|
+
type: fieldSchema.type
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Add identifier properties
|
|
64
|
+
for (const identifier of objectSchema.identifiers) {
|
|
65
|
+
if (identifier.name) {
|
|
66
|
+
properties[identifier.name] = {
|
|
67
|
+
type: 'string'
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (identifier.fieldName) {
|
|
72
|
+
properties[identifier.fieldName] = {
|
|
73
|
+
type: objectSchema.fieldNames[identifier.fieldName].type
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties,
|
|
81
|
+
additionalProperties: false
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function _handleConflictAndAddToQueue(
|
|
86
|
+
_izContext,
|
|
87
|
+
queueObjType,
|
|
88
|
+
queueIdentifiers,
|
|
89
|
+
flowType,
|
|
90
|
+
requestMessage,
|
|
91
|
+
lockTableName,
|
|
92
|
+
identifiersBase,
|
|
93
|
+
uniqueRequestId,
|
|
94
|
+
lockTag
|
|
95
|
+
) {
|
|
96
|
+
|
|
97
|
+
_izContext.logger.debug(
|
|
98
|
+
'Function: _handleConflictAndAddToQueue',
|
|
99
|
+
{
|
|
100
|
+
queueObjType,
|
|
101
|
+
queueIdentifiers,
|
|
102
|
+
flowType,
|
|
103
|
+
requestMessage,
|
|
104
|
+
lockTableName,
|
|
105
|
+
identifiersBase,
|
|
106
|
+
uniqueRequestId,
|
|
107
|
+
lockTag
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const queueSchemaResult =
|
|
112
|
+
await getObjectSchema.getObjSchemaS3WithCache(
|
|
113
|
+
_izContext,
|
|
114
|
+
queueObjType
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
queueSchemaResult.errorsFound.length > 0 ||
|
|
119
|
+
!queueSchemaResult.result
|
|
120
|
+
) {
|
|
121
|
+
throw new NoRetryError(
|
|
122
|
+
JSON.stringify(queueSchemaResult.errorsFound)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const queueObjectSchema = queueSchemaResult.result;
|
|
127
|
+
|
|
128
|
+
const queueStorageResource = getDynamoDbStorageResource(
|
|
129
|
+
_izContext,
|
|
130
|
+
queueObjectSchema
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const queueTableName = dynamodbSharedLib.tableName(
|
|
134
|
+
_izContext,
|
|
135
|
+
queueStorageResource.tableName,
|
|
136
|
+
queueStorageResource.serviceTag
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const queueDynamoDbIdentifiers =
|
|
140
|
+
await identifiersObject.dynamoDbIdentifiersByStorageResource(
|
|
141
|
+
_izContext,
|
|
142
|
+
queueObjectSchema,
|
|
143
|
+
queueStorageResource,
|
|
144
|
+
queueIdentifiers
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Save the queue record only when overwriteUniqueRequestId is not provided.
|
|
148
|
+
// If overwriteUniqueRequestId is provided, the request is already being processed
|
|
149
|
+
// through the existing record, so skip creating a new queue record.
|
|
150
|
+
if (
|
|
151
|
+
!requestMessage.hasOwnProperty('overwriteUniqueRequestId') ||
|
|
152
|
+
requestMessage.overwriteUniqueRequestId === ''
|
|
153
|
+
) {
|
|
154
|
+
|
|
155
|
+
const queueRecordObjInstance = {
|
|
156
|
+
...queueDynamoDbIdentifiers,
|
|
157
|
+
flowType,
|
|
158
|
+
messageBody: requestMessage
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const queueValidationSchema = _createAjvValidationSchema(
|
|
162
|
+
queueObjectSchema
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const queueValidationResult = validator.validateObject(
|
|
166
|
+
queueValidationSchema,
|
|
167
|
+
queueRecordObjInstance
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (queueValidationResult.pass !== true) {
|
|
171
|
+
throw new NoRetryError(queueValidationResult.error);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const conditionCheckElements = {
|
|
175
|
+
logicalElements: dynamodbSharedLib.generateNotExistLogicalElements(
|
|
176
|
+
queueDynamoDbIdentifiers,
|
|
177
|
+
),
|
|
178
|
+
returnValues: 'ALL_NEW'
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
await dynamodbSharedLib.putItem(
|
|
182
|
+
_izContext,
|
|
183
|
+
queueTableName,
|
|
184
|
+
queueRecordObjInstance,
|
|
185
|
+
conditionCheckElements
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const {
|
|
190
|
+
fieldNameUniqueRequestId,
|
|
191
|
+
fieldNameLockStatus
|
|
192
|
+
} = createLockFieldNames(lockTag)
|
|
193
|
+
|
|
194
|
+
const queryElementsWhenAcquireLock = {
|
|
195
|
+
logicalElements: [
|
|
196
|
+
{
|
|
197
|
+
type: 'function',
|
|
198
|
+
function: 'attribute_not_exists',
|
|
199
|
+
attribute: fieldNameUniqueRequestId
|
|
200
|
+
}
|
|
201
|
+
],
|
|
202
|
+
returnValues: 'ALL_NEW'
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const reCheckLock = await dynamodbSharedLib.updateItem(
|
|
207
|
+
_izContext,
|
|
208
|
+
lockTableName,
|
|
209
|
+
identifiersBase,
|
|
210
|
+
{
|
|
211
|
+
[fieldNameUniqueRequestId]: uniqueRequestId,
|
|
212
|
+
[fieldNameLockStatus]: true
|
|
213
|
+
},
|
|
214
|
+
queryElementsWhenAcquireLock,
|
|
215
|
+
{
|
|
216
|
+
errorOnConditionalExpNotPass: true
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return ['process', reCheckLock];
|
|
221
|
+
} catch (error) {
|
|
222
|
+
// Another request still owns the lock.
|
|
223
|
+
return ['stop', null];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
function getDynamoDbStorageResource(
|
|
229
|
+
_izContext,
|
|
230
|
+
objectSchema,
|
|
231
|
+
) {
|
|
232
|
+
for (const storageResource of Object.values(
|
|
233
|
+
objectSchema.storageResources
|
|
234
|
+
)) {
|
|
235
|
+
if (storageResource.storageType !== 'dynamoDB') {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!storageResource.tableName) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return storageResource;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
throw new NoRetryError(
|
|
247
|
+
`DynamoDB storage resource with tableName not found for objType: `
|
|
248
|
+
+ `${JSON.stringify(objectSchema.objectType)}`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function createLockFieldNames(lockTag) {
|
|
253
|
+
const fieldNameUniqueRequestId =
|
|
254
|
+
lockUpdateConstants.createUniqueRequestIdFieldName(lockTag);
|
|
255
|
+
|
|
256
|
+
const fieldNameLockStatus =
|
|
257
|
+
lockUpdateConstants.createLockStatusFieldName(lockTag);
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
fieldNameUniqueRequestId,
|
|
261
|
+
fieldNameLockStatus
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
function getAddOnConfig(
|
|
267
|
+
_izContext,
|
|
268
|
+
mainObjectSchema,
|
|
269
|
+
) {
|
|
270
|
+
let lockTag = null;
|
|
271
|
+
let storageResourceTag = null;
|
|
272
|
+
|
|
273
|
+
if (Array.isArray(mainObjectSchema.addOnDataStructure)) {
|
|
274
|
+
for (const addOnData of mainObjectSchema.addOnDataStructure) {
|
|
275
|
+
if (
|
|
276
|
+
addOnData.type !== 'reserveQuantity' &&
|
|
277
|
+
addOnData.type !== 'lockUpdate'
|
|
278
|
+
) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let currentLockUpdateTag = null;
|
|
283
|
+
|
|
284
|
+
if (addOnData.type === 'reserveQuantity') {
|
|
285
|
+
currentLockUpdateTag = addOnData.reserveQuantityTag;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (addOnData.type === 'lockUpdate') {
|
|
289
|
+
currentLockUpdateTag = addOnData.lockUpdateTag;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!currentLockUpdateTag) {
|
|
293
|
+
throw new NoRetryError(`Missing lock update tag for add-on type '${addOnData.type}'`,);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
lockTag = currentLockUpdateTag;
|
|
297
|
+
storageResourceTag = addOnData.storageResourceTag;
|
|
298
|
+
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!lockTag) {
|
|
304
|
+
throw new NoRetryError(`
|
|
305
|
+
Add-on type 'reserveQuantity' or 'lockUpdate' not found for objType '${JSON.stringify(mainObjectSchema.objectType)}'`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
lockTag,
|
|
311
|
+
storageResourceTag
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Checks the main object's lock status and determines whether to process,
|
|
321
|
+
* finalize, or queue the request.
|
|
322
|
+
*
|
|
323
|
+
* @param {Object} _izContext - Execution context.
|
|
324
|
+
* @param {Object} mainObjType - Main object type used to resolve storage configuration.
|
|
325
|
+
* @param {Object} identifiers - Identifiers of the main object record.
|
|
326
|
+
* @param {Object} queueIdentifiers - Identifiers used to construct the queue record's keys.
|
|
327
|
+
* @param {Object} flowType - Flow type used to identify the request's processing flow.
|
|
328
|
+
* @param {string} uniqueRequestId - Unique identifier for the request.
|
|
329
|
+
* @param {Object} requestMessage - Request message to enqueue when the lock is unavailable.
|
|
330
|
+
* @param {Object} errorParam - Found errors and service-level error tags.
|
|
331
|
+
* @returns {Promise<Array|Object>} Processing result and associated record
|
|
332
|
+
* data, or an object containing errorParam as foundErrors.
|
|
333
|
+
*/
|
|
334
|
+
async function checkRequestWithLock(
|
|
335
|
+
_izContext,
|
|
336
|
+
mainObjType,
|
|
337
|
+
identifiers,
|
|
338
|
+
queueIdentifiers,
|
|
339
|
+
flowType,
|
|
340
|
+
uniqueRequestId,
|
|
341
|
+
requestMessage,
|
|
342
|
+
errorParams
|
|
343
|
+
) {
|
|
344
|
+
try {
|
|
345
|
+
_izContext.logger.debug('Function: checkRequestWithLock', {
|
|
346
|
+
mainObjType,
|
|
347
|
+
identifiers,
|
|
348
|
+
queueIdentifiers,
|
|
349
|
+
flowType,
|
|
350
|
+
uniqueRequestId,
|
|
351
|
+
requestMessage
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const schemaResult = await getObjectSchema.getObjSchemaS3WithCache(
|
|
355
|
+
_izContext,
|
|
356
|
+
mainObjType
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
if (schemaResult.errorsFound.length > 0 || !schemaResult.result) {
|
|
360
|
+
throw new NoRetryError(
|
|
361
|
+
JSON.stringify(schemaResult.errorsFound)
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const mainObjectSchema = schemaResult.result;
|
|
366
|
+
|
|
367
|
+
const { lockTag, storageResourceTag } =
|
|
368
|
+
getAddOnConfig(
|
|
369
|
+
_izContext,
|
|
370
|
+
mainObjectSchema,
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const mainObjectStorage = mainObjectSchema.storageResources[storageResourceTag];
|
|
374
|
+
|
|
375
|
+
const lockFieldNames = createLockFieldNames(lockTag);
|
|
376
|
+
|
|
377
|
+
const fieldNameUniqueRequestId = lockFieldNames.fieldNameUniqueRequestId;
|
|
378
|
+
const fieldNameLockStatus = lockFieldNames.fieldNameLockStatus;
|
|
379
|
+
|
|
380
|
+
const lockTableName = dynamodbSharedLib.tableName(
|
|
381
|
+
_izContext,
|
|
382
|
+
mainObjectStorage.tableName,
|
|
383
|
+
mainObjectStorage.serviceTag
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
const identifiersBase = await identifiersObject.identifiersBaseFromIdentifiers(
|
|
387
|
+
_izContext,
|
|
388
|
+
mainObjType,
|
|
389
|
+
identifiers
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
const lockRecord = await dynamodbSharedLib.getItem(
|
|
393
|
+
_izContext,
|
|
394
|
+
lockTableName,
|
|
395
|
+
identifiersBase
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
if (!lockRecord) {
|
|
399
|
+
throw new NoRetryError(
|
|
400
|
+
`Lock record not found. mainObjType: ${JSON.stringify(mainObjType)}, ` +
|
|
401
|
+
`identifiersBase: ${JSON.stringify(identifiersBase)}`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const lockUniqueRequestId = lockRecord[fieldNameUniqueRequestId];
|
|
406
|
+
const statusProcessing = lockRecord[fieldNameLockStatus];
|
|
407
|
+
|
|
408
|
+
_izContext.logger.debug('Lock status', {
|
|
409
|
+
lockRecord,
|
|
410
|
+
[fieldNameUniqueRequestId]: lockUniqueRequestId,
|
|
411
|
+
[fieldNameLockStatus]: statusProcessing,
|
|
412
|
+
uniqueRequestId
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
if (
|
|
416
|
+
lockUniqueRequestId === undefined &&
|
|
417
|
+
!statusProcessing
|
|
418
|
+
) {
|
|
419
|
+
|
|
420
|
+
const validationErrors = graphSharedLib.checkPropertiesValidWithSchema(
|
|
421
|
+
_izContext,
|
|
422
|
+
mainObjectSchema,
|
|
423
|
+
{
|
|
424
|
+
[fieldNameUniqueRequestId]: uniqueRequestId,
|
|
425
|
+
[fieldNameLockStatus]: true
|
|
426
|
+
},
|
|
427
|
+
errorParams
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
if (validationErrors.length > 0) {
|
|
431
|
+
throw new NoRetryError(validationErrors.join('; '));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
const queryElementsWhenAcquireLock = {
|
|
436
|
+
additionalAttributes: {
|
|
437
|
+
currentLockStatus: false
|
|
438
|
+
},
|
|
439
|
+
logicalElements: [
|
|
440
|
+
{
|
|
441
|
+
type: 'function',
|
|
442
|
+
function: 'attribute_not_exists',
|
|
443
|
+
attribute: fieldNameUniqueRequestId
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
type: 'logicalOperator',
|
|
447
|
+
operator: 'and'
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
type: 'group',
|
|
451
|
+
elements: [
|
|
452
|
+
{
|
|
453
|
+
type: 'function',
|
|
454
|
+
function: 'attribute_not_exists',
|
|
455
|
+
attribute: fieldNameLockStatus
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
type: 'logicalOperator',
|
|
459
|
+
operator: 'or'
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
type: 'logical',
|
|
463
|
+
comparison: 'equals',
|
|
464
|
+
lhsAttribute: fieldNameLockStatus,
|
|
465
|
+
rhsReference: 'currentLockStatus'
|
|
466
|
+
}
|
|
467
|
+
]
|
|
468
|
+
}
|
|
469
|
+
],
|
|
470
|
+
returnValues: 'ALL_NEW'
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const updatedLock = await dynamodbSharedLib.updateItem(
|
|
474
|
+
_izContext,
|
|
475
|
+
lockTableName,
|
|
476
|
+
identifiersBase,
|
|
477
|
+
{
|
|
478
|
+
[fieldNameUniqueRequestId]: uniqueRequestId,
|
|
479
|
+
[fieldNameLockStatus]: true
|
|
480
|
+
},
|
|
481
|
+
queryElementsWhenAcquireLock,
|
|
482
|
+
{
|
|
483
|
+
errorOnConditionalExpNotPass: true
|
|
484
|
+
}
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
return ['process', updatedLock];
|
|
488
|
+
} catch (error) {
|
|
489
|
+
|
|
490
|
+
if (error.message !== 'ConditionalExpNotPass') {
|
|
491
|
+
throw new NoRetryError(
|
|
492
|
+
'updateItem failed: ' + error.message,
|
|
493
|
+
error
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return await _handleConflictAndAddToQueue(
|
|
498
|
+
_izContext,
|
|
499
|
+
queueObjType,
|
|
500
|
+
queueIdentifiers,
|
|
501
|
+
flowType,
|
|
502
|
+
requestMessage,
|
|
503
|
+
lockTableName,
|
|
504
|
+
identifiersBase,
|
|
505
|
+
uniqueRequestId,
|
|
506
|
+
lockTag
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
} else if (lockUniqueRequestId === uniqueRequestId) {
|
|
510
|
+
|
|
511
|
+
if (statusProcessing === true) {
|
|
512
|
+
return ['process', lockRecord];
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (statusProcessing === false) {
|
|
516
|
+
return ['finalize', lockRecord];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
throw new NoRetryError(
|
|
520
|
+
`Invalid processing status: ${statusProcessing}, identifiers: ${JSON.stringify(identifiers)}`,
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
} else {
|
|
524
|
+
return await _handleConflictAndAddToQueue(
|
|
525
|
+
_izContext,
|
|
526
|
+
queueTableName,
|
|
527
|
+
queueIdentifiers,
|
|
528
|
+
flowType,
|
|
529
|
+
requestMessage,
|
|
530
|
+
lockTableName,
|
|
531
|
+
identifiersBase,
|
|
532
|
+
uniqueRequestId,
|
|
533
|
+
fieldNameUniqueRequestId,
|
|
534
|
+
fieldNameLockStatus,
|
|
535
|
+
// errorParam
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
} catch (error) {
|
|
539
|
+
_izContext.logger.error('Error in checkRequestWithLock:', {
|
|
540
|
+
errorMessage: error.message
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Updates the main object record only when the lock belongs to the current
|
|
549
|
+
* request, then releases the lock.
|
|
550
|
+
*
|
|
551
|
+
* @param {Object} _izContext - Execution context.
|
|
552
|
+
* @param {string} mainObjType - Main object type used to resolve storage configuration.
|
|
553
|
+
* @param {Object} identifiers - Identifiers of the main object record.
|
|
554
|
+
* @param {string} checkUniqueRequestId - Unique request ID used to validate the lock.
|
|
555
|
+
* @param {Object} additionalAttributes - Additional attributes to update on the main object record.
|
|
556
|
+
* @param {Object} errorParam - Found errors and service-level error tags.
|
|
557
|
+
* @returns {Promise<Object>} Updated main object record.
|
|
558
|
+
*/
|
|
559
|
+
async function updateMainRecordWithLock(
|
|
560
|
+
_izContext,
|
|
561
|
+
mainObjType,
|
|
562
|
+
identifiers,
|
|
563
|
+
checkUniqueRequestId,
|
|
564
|
+
errorParam,
|
|
565
|
+
additionalAttributes = {},
|
|
566
|
+
) {
|
|
567
|
+
const schemaResult = await getObjectSchema.getObjSchemaS3WithCache(
|
|
568
|
+
_izContext,
|
|
569
|
+
mainObjType
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
if (schemaResult.errorsFound.length > 0 || !schemaResult.result) {
|
|
573
|
+
throw new NoRetryError(
|
|
574
|
+
JSON.stringify(schemaResult.errorsFound)
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const mainObjectSchema = schemaResult.result;
|
|
579
|
+
|
|
580
|
+
const {
|
|
581
|
+
lockTag,
|
|
582
|
+
storageResourceTag
|
|
583
|
+
} = getAddOnConfig(
|
|
584
|
+
_izContext,
|
|
585
|
+
mainObjectSchema
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
const mainObjectStorage =
|
|
589
|
+
mainObjectSchema.storageResources[storageResourceTag];
|
|
590
|
+
|
|
591
|
+
const mainTableName = dynamodbSharedLib.tableName(
|
|
592
|
+
_izContext,
|
|
593
|
+
mainObjectStorage.tableName,
|
|
594
|
+
mainObjectStorage.serviceTag
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
const {
|
|
598
|
+
fieldNameUniqueRequestId,
|
|
599
|
+
fieldNameLockStatus
|
|
600
|
+
} = createLockFieldNames(lockTag);
|
|
601
|
+
|
|
602
|
+
// Validate additional attributes and lock release together.
|
|
603
|
+
const propertiesToValidate = Object.assign(
|
|
604
|
+
{},
|
|
605
|
+
additionalAttributes,
|
|
606
|
+
{
|
|
607
|
+
[fieldNameLockStatus]: false
|
|
608
|
+
}
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
const validationErrors = graphSharedLib.checkPropertiesValidWithSchema(
|
|
612
|
+
_izContext,
|
|
613
|
+
mainObjectSchema,
|
|
614
|
+
propertiesToValidate,
|
|
615
|
+
errorParam
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
if (
|
|
619
|
+
Array.isArray(validationErrors) &&
|
|
620
|
+
validationErrors.length > 0
|
|
621
|
+
) {
|
|
622
|
+
throw new NoRetryError(validationErrors.join('; '));
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const updateAttributes = [];
|
|
626
|
+
|
|
627
|
+
for (const attributeName of Object.keys(propertiesToValidate)) {
|
|
628
|
+
updateAttributes.push({
|
|
629
|
+
attributeName: attributeName,
|
|
630
|
+
action: 'set',
|
|
631
|
+
value: propertiesToValidate[attributeName]
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const queryElementsWhenUpdateMainRecord = {
|
|
636
|
+
additionalAttributes: {
|
|
637
|
+
currentUniqueRequestId: checkUniqueRequestId
|
|
638
|
+
},
|
|
639
|
+
logicalElements: [
|
|
640
|
+
{
|
|
641
|
+
type: 'logical',
|
|
642
|
+
comparison: 'equals',
|
|
643
|
+
lhsAttribute: fieldNameUniqueRequestId,
|
|
644
|
+
rhsReference: 'currentUniqueRequestId'
|
|
645
|
+
}
|
|
646
|
+
],
|
|
647
|
+
returnValues: 'ALL_NEW'
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
try {
|
|
651
|
+
return await dynamodbSharedLib.updateItem(
|
|
652
|
+
_izContext,
|
|
653
|
+
mainTableName,
|
|
654
|
+
identifiers,
|
|
655
|
+
updateAttributes,
|
|
656
|
+
queryElementsWhenUpdateMainRecord,
|
|
657
|
+
{
|
|
658
|
+
complexAttributes: true,
|
|
659
|
+
errorOnConditionalExpNotPass: true
|
|
660
|
+
}
|
|
661
|
+
);
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (error.message === 'ConditionalExpNotPass') {
|
|
664
|
+
throw new NoRetryError(
|
|
665
|
+
'Cannot update main record because lock does not belong to current request.'
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
throw error;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
function getLockTagFromObjectType(objectType) {
|
|
675
|
+
const suffixes = ['History', 'Queue'];
|
|
676
|
+
|
|
677
|
+
for (const suffix of suffixes) {
|
|
678
|
+
if (objectType.endsWith(suffix)) {
|
|
679
|
+
return objectType.slice(0, -suffix.length);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
throw new NoRetryError(`Invalid lock objectType: ${objectType}`);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Ensures a lock history record exists for the supplied identifiers.
|
|
688
|
+
*
|
|
689
|
+
* Creates a history record if it does not exist.
|
|
690
|
+
* Returns the existing record if it already exists.
|
|
691
|
+
*
|
|
692
|
+
* @param {Object} _izContext - Execution context.
|
|
693
|
+
* @param {string} historyObjType - History object type.
|
|
694
|
+
* @param {Object} historyIdentifiers - Identifiers of the history record.
|
|
695
|
+
* @param {string} targetStatus - Status stored in the history record.
|
|
696
|
+
* @param {Object} errorParams - Error handling parameters.
|
|
697
|
+
* @param {Object} additionalAttributes - Additional attributes to store.
|
|
698
|
+
* @param {Object} queryElements - Additional query elements for the operation.
|
|
699
|
+
* @returns {Promise<Object>} The created or existing history record.
|
|
700
|
+
*/
|
|
701
|
+
async function ensureHistory(
|
|
702
|
+
_izContext,
|
|
703
|
+
historyObjType,
|
|
704
|
+
historyIdentifiers,
|
|
705
|
+
targetStatus,
|
|
706
|
+
errorParams,
|
|
707
|
+
additionalAttributes = {},
|
|
708
|
+
queryElements = {},
|
|
709
|
+
) {
|
|
710
|
+
|
|
711
|
+
const schemaResult = await getObjectSchema.getObjSchemaS3WithCache(
|
|
712
|
+
_izContext,
|
|
713
|
+
historyObjType
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
if (schemaResult.errorsFound.length > 0 || !schemaResult.result) {
|
|
717
|
+
throw new NoRetryError(
|
|
718
|
+
JSON.stringify(schemaResult.errorsFound)
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const historyObjectSchema = schemaResult.result;
|
|
723
|
+
|
|
724
|
+
const historyStorageResource = getDynamoDbStorageResource(
|
|
725
|
+
_izContext,
|
|
726
|
+
historyObjectSchema
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
const historyTableName = dynamodbSharedLib.tableName(
|
|
730
|
+
_izContext,
|
|
731
|
+
historyStorageResource.tableName,
|
|
732
|
+
historyStorageResource.serviceTag
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
const lockTag = getLockTagFromObjectType(historyObjectSchema.objectType);
|
|
736
|
+
|
|
737
|
+
const fieldNameStatus =
|
|
738
|
+
lockUpdateConstants.createStatusFieldName(lockTag);
|
|
739
|
+
|
|
740
|
+
const propertiesToValidate = {
|
|
741
|
+
[fieldNameStatus]: targetStatus
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
if (additionalAttributes && typeof additionalAttributes === 'object') {
|
|
745
|
+
for (const attributeName of Object.keys(additionalAttributes)) {
|
|
746
|
+
propertiesToValidate[attributeName] = additionalAttributes[attributeName];
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const validationErrors = graphSharedLib.checkPropertiesValidWithSchema(
|
|
751
|
+
_izContext,
|
|
752
|
+
historyObjectSchema,
|
|
753
|
+
propertiesToValidate,
|
|
754
|
+
errorParams
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
if (Array.isArray(validationErrors) && validationErrors.length > 0) {
|
|
758
|
+
throw new NoRetryError(validationErrors.join('; '));
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const updateAttributes = [
|
|
762
|
+
{
|
|
763
|
+
attributeName: fieldNameStatus,
|
|
764
|
+
action: 'set',
|
|
765
|
+
value: targetStatus
|
|
766
|
+
}
|
|
767
|
+
];
|
|
768
|
+
|
|
769
|
+
if (additionalAttributes && typeof additionalAttributes === 'object') {
|
|
770
|
+
for (const [attributeName, value] of Object.entries(additionalAttributes)) {
|
|
771
|
+
updateAttributes.push({
|
|
772
|
+
attributeName,
|
|
773
|
+
action: 'set',
|
|
774
|
+
value
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const identifiersBase = await identifiersObject.dynamoDbIdentifiersByStorageResource(
|
|
780
|
+
_izContext,
|
|
781
|
+
historyObjectSchema,
|
|
782
|
+
historyStorageResource,
|
|
783
|
+
historyIdentifiers,
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
try {
|
|
787
|
+
return await dynamodbSharedLib.updateItem(
|
|
788
|
+
_izContext,
|
|
789
|
+
historyTableName,
|
|
790
|
+
identifiersBase,
|
|
791
|
+
updateAttributes,
|
|
792
|
+
queryElements,
|
|
793
|
+
{
|
|
794
|
+
complexAttributes: true,
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
} catch (error) {
|
|
798
|
+
throw error;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Removes the current request from the lock queue and publishes the next
|
|
804
|
+
* waiting request to its original flow, if one exists.
|
|
805
|
+
*
|
|
806
|
+
* @param {Object} _izContext - Execution context.
|
|
807
|
+
* @param {string} queueObjType - Queue object type.
|
|
808
|
+
* @param {Object} identifiers - Identifiers of the current queue record.
|
|
809
|
+
* @returns {Promise<Object|null>} SNS publish result, or null if the queue is empty.
|
|
810
|
+
*/
|
|
811
|
+
async function publishNextWaitingRequest(
|
|
812
|
+
_izContext,
|
|
813
|
+
queueObjType,
|
|
814
|
+
identifiers
|
|
815
|
+
) {
|
|
816
|
+
// Get Queue ObjectSchema
|
|
817
|
+
const queueSchemaResult = await getObjectSchema.getObjSchemaS3WithCache(
|
|
818
|
+
_izContext,
|
|
819
|
+
queueObjType
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
if (
|
|
823
|
+
queueSchemaResult.errorsFound.length > 0 ||
|
|
824
|
+
!queueSchemaResult.result
|
|
825
|
+
) {
|
|
826
|
+
throw new NoRetryError(
|
|
827
|
+
`Unable to get Queue ObjectSchema '${queueObjType}': ` +
|
|
828
|
+
JSON.stringify(queueSchemaResult.errorsFound)
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const queueObjectSchema = queueSchemaResult.result;
|
|
833
|
+
|
|
834
|
+
// Get Queue DynamoDB Table
|
|
835
|
+
const queueStorageResource = getDynamoDbStorageResource(
|
|
836
|
+
_izContext,
|
|
837
|
+
queueObjectSchema
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
const queueTableName = dynamodbSharedLib.tableName(
|
|
841
|
+
_izContext,
|
|
842
|
+
queueStorageResource.tableName,
|
|
843
|
+
queueStorageResource.serviceTag
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
// Convert identifiers to ObjInstanceBase
|
|
847
|
+
const identifiersBase = await identifiersObject.identifiersBaseFromIdentifiers(
|
|
848
|
+
_izContext,
|
|
849
|
+
queueObjType,
|
|
850
|
+
identifiers
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
// Get Partition Key
|
|
854
|
+
const partitionKeyValue = {};
|
|
855
|
+
let hasSortKey = false;
|
|
856
|
+
|
|
857
|
+
for (const identifier of queueObjectSchema.identifiers) {
|
|
858
|
+
if (identifier.type === 'partitionKey') {
|
|
859
|
+
let partitionKeyFieldName = identifier.fieldName;
|
|
860
|
+
|
|
861
|
+
if (
|
|
862
|
+
!Object.prototype.hasOwnProperty.call(
|
|
863
|
+
identifiersBase,
|
|
864
|
+
partitionKeyFieldName
|
|
865
|
+
)
|
|
866
|
+
) {
|
|
867
|
+
partitionKeyFieldName = identifier.name;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (
|
|
871
|
+
!partitionKeyFieldName ||
|
|
872
|
+
!Object.prototype.hasOwnProperty.call(
|
|
873
|
+
identifiersBase,
|
|
874
|
+
partitionKeyFieldName
|
|
875
|
+
)
|
|
876
|
+
) {
|
|
877
|
+
throw new NoRetryError(
|
|
878
|
+
`Missing partition key in identifiersBase for Queue '${queueObjType}'`
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
partitionKeyValue[partitionKeyFieldName] = identifiersBase[partitionKeyFieldName];
|
|
883
|
+
|
|
884
|
+
} else if (identifier.type === 'sortKey') {
|
|
885
|
+
if (!identifier.name) {
|
|
886
|
+
throw new NoRetryError(
|
|
887
|
+
`Queue schema '${queueObjType}' has an invalid sortKey identifier`
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
hasSortKey = true;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (!hasSortKey) {
|
|
896
|
+
throw new NoRetryError(
|
|
897
|
+
`Queue schema '${queueObjType}' is missing sortKey`
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Delete Current Queue Record
|
|
902
|
+
await dynamodbSharedLib.deleteItem(
|
|
903
|
+
_izContext,
|
|
904
|
+
queueTableName,
|
|
905
|
+
identifiersBase
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
// Get Next Waiting Request
|
|
909
|
+
const queryResult = await dynamodbSharedLib.query(
|
|
910
|
+
_izContext,
|
|
911
|
+
queueTableName,
|
|
912
|
+
partitionKeyValue,
|
|
913
|
+
{ limit: 1 }
|
|
914
|
+
);
|
|
915
|
+
|
|
916
|
+
if (queryResult.Items.length === 0) {
|
|
917
|
+
_izContext.logger.debug('Lock queue is empty, there is no next message');
|
|
918
|
+
|
|
919
|
+
return null;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
const nextQueueRecord = queryResult.Items[0];
|
|
923
|
+
|
|
924
|
+
// Convert Queue ObjInstanceBase to ObjInstance
|
|
925
|
+
const nextQueueObjInstance =
|
|
926
|
+
await identifiersObject.objInstanceFromObjInstanceBase(
|
|
927
|
+
_izContext,
|
|
928
|
+
queueObjType,
|
|
929
|
+
nextQueueRecord
|
|
930
|
+
);
|
|
931
|
+
|
|
932
|
+
const flowType = nextQueueObjInstance.flowType;
|
|
933
|
+
const messageBody = nextQueueObjInstance.messageBody;
|
|
934
|
+
const nextUniqueRequestId = nextQueueObjInstance.uniqueRequestId;
|
|
935
|
+
|
|
936
|
+
// Prepare Next Request Message
|
|
937
|
+
const message = {
|
|
938
|
+
...messageBody,
|
|
939
|
+
overwriteUniqueRequestId: nextUniqueRequestId
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
// Publish Next Request to Original Flow
|
|
943
|
+
const topicName = `${flowType.flowTag}_In`;
|
|
944
|
+
|
|
945
|
+
const topicArn = snsSharedLib.snsTopicArnByFlowSchema(
|
|
946
|
+
_izContext,
|
|
947
|
+
topicName,
|
|
948
|
+
flowType.serviceTag
|
|
949
|
+
);
|
|
950
|
+
|
|
951
|
+
const snsPublishParams = {
|
|
952
|
+
Message: JSON.stringify(message),
|
|
953
|
+
TopicArn: topicArn
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
return sns.publishAsync(_izContext, snsPublishParams);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Safely removes the uniqueRequestId lock only if its current value matches
|
|
962
|
+
* the expected uniqueRequestId.
|
|
963
|
+
*
|
|
964
|
+
* @param {Object} _izContext - Execution context containing the logger.
|
|
965
|
+
* @param {string} mainObjType - Main object type.
|
|
966
|
+
* @param {Object} identifiersBase - Identifiers of the main record.
|
|
967
|
+
* @param {string} uniqueRequestId - Expected uniqueRequestId value for the lock.
|
|
968
|
+
* @returns {Promise<Object>} Updated main record.
|
|
969
|
+
*/
|
|
970
|
+
async function removeUniqueRequestIdSafe(
|
|
971
|
+
_izContext,
|
|
972
|
+
mainObjType,
|
|
973
|
+
identifiersBase,
|
|
974
|
+
uniqueRequestId,
|
|
975
|
+
) {
|
|
976
|
+
|
|
977
|
+
const schemaResult = await getObjectSchema.getObjSchemaS3WithCache(
|
|
978
|
+
_izContext,
|
|
979
|
+
mainObjType
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
if (schemaResult.errorsFound.length > 0 || !schemaResult.result) {
|
|
983
|
+
throw new NoRetryError(
|
|
984
|
+
JSON.stringify(schemaResult.errorsFound)
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const mainObjectSchema = schemaResult.result;
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
const { lockTag, storageResourceTag } = getAddOnConfig(
|
|
992
|
+
_izContext,
|
|
993
|
+
mainObjectSchema,
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
const { fieldNameUniqueRequestId } = createLockFieldNames(lockTag);
|
|
997
|
+
|
|
998
|
+
if (!mainObjectSchema.fieldNames || !Object.prototype.hasOwnProperty.call(mainObjectSchema.fieldNames, fieldNameUniqueRequestId)) {
|
|
999
|
+
throw new NoRetryError(
|
|
1000
|
+
`Field '${fieldNameUniqueRequestId}' not found in schema '${mainObjectSchema.objectType}'`
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const mainObjectStorage = mainObjectSchema.storageResources[storageResourceTag];
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
const lockTableName = dynamodbSharedLib.tableName(
|
|
1008
|
+
_izContext,
|
|
1009
|
+
mainObjectStorage.tableName,
|
|
1010
|
+
mainObjectStorage.serviceTag
|
|
1011
|
+
);
|
|
1012
|
+
|
|
1013
|
+
const queryElements = {
|
|
1014
|
+
additionalAttributes: {
|
|
1015
|
+
currentUniqueRequestId: uniqueRequestId
|
|
1016
|
+
},
|
|
1017
|
+
logicalElements: [
|
|
1018
|
+
{
|
|
1019
|
+
type: 'logical',
|
|
1020
|
+
comparison: 'equals',
|
|
1021
|
+
lhsAttribute: fieldNameUniqueRequestId,
|
|
1022
|
+
rhsReference: 'currentUniqueRequestId'
|
|
1023
|
+
}
|
|
1024
|
+
],
|
|
1025
|
+
returnValues: 'ALL_NEW'
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
try {
|
|
1029
|
+
await dynamodbSharedLib.updateItem(
|
|
1030
|
+
_izContext,
|
|
1031
|
+
lockTableName,
|
|
1032
|
+
identifiersBase,
|
|
1033
|
+
[
|
|
1034
|
+
{
|
|
1035
|
+
attributeName: fieldNameUniqueRequestId,
|
|
1036
|
+
action: 'remove'
|
|
1037
|
+
}
|
|
1038
|
+
],
|
|
1039
|
+
queryElements,
|
|
1040
|
+
{
|
|
1041
|
+
complexAttributes: true,
|
|
1042
|
+
errorOnConditionalExpNotPass: true
|
|
1043
|
+
}
|
|
1044
|
+
);
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
if (error.message === 'ConditionalExpNotPass') {
|
|
1047
|
+
_izContext.logger.error(
|
|
1048
|
+
'Failed to remove uniqueRequestId because it did not match',
|
|
1049
|
+
{
|
|
1050
|
+
tableName: lockTableName,
|
|
1051
|
+
identifiers: identifiersBase,
|
|
1052
|
+
uniqueRequestId
|
|
1053
|
+
}
|
|
1054
|
+
);
|
|
1055
|
+
|
|
1056
|
+
throw new NoRetryError(
|
|
1057
|
+
'Cannot remove uniqueRequestId because it does not match the current request.'
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
throw error;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
export {
|
|
1065
|
+
// 1. Configuration and utilities
|
|
1066
|
+
getAddOnConfig,
|
|
1067
|
+
getDynamoDbStorageResource,
|
|
1068
|
+
createLockFieldNames,
|
|
1069
|
+
|
|
1070
|
+
// 2. Lock management
|
|
1071
|
+
checkRequestWithLock,
|
|
1072
|
+
updateMainRecordWithLock,
|
|
1073
|
+
ensureHistory,
|
|
1074
|
+
removeUniqueRequestIdSafe,
|
|
1075
|
+
|
|
1076
|
+
// 3. Queue management
|
|
1077
|
+
publishNextWaitingRequest
|
|
1078
|
+
};
|