@salesforce/lds-drafts 1.229.0-dev1 → 1.229.0-dev10
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/dist/ldsDrafts.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { HttpStatusCode, StoreKeyMap } from '@luvio/engine';
|
|
8
8
|
import { AsyncWorkerPool } from '@salesforce/lds-utils-adapters';
|
|
9
|
+
import ldsIdempotencyWriteDisabled from '@salesforce/gate/lds.idempotencyWriteDisabled';
|
|
9
10
|
|
|
10
11
|
var DraftActionStatus;
|
|
11
12
|
(function (DraftActionStatus) {
|
|
@@ -236,8 +237,12 @@ function uuidv4() {
|
|
|
236
237
|
|
|
237
238
|
const HTTP_HEADER_RETRY_AFTER = 'Retry-After';
|
|
238
239
|
const HTTP_HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';
|
|
240
|
+
const ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED = 'IDEMPOTENCY_FEATURE_NOT_ENABLED';
|
|
241
|
+
const ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED = 'IDEMPOTENCY_NOT_SUPPORTED';
|
|
239
242
|
const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = 'IDEMPOTENCY_KEY_USED_DIFFERENT_USER';
|
|
240
243
|
const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = 'IDEMPOTENCY_CONCURRENT_REQUEST';
|
|
244
|
+
const ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED = 'IDEMPOTENCY_KEY_ALREADY_USED';
|
|
245
|
+
const ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR = 'IDEMPOTENCY_BACKEND_OPERATION_ERROR';
|
|
241
246
|
/**
|
|
242
247
|
* Get the retry after in milliseconds from the response headers, undefined if not specified.
|
|
243
248
|
* The header could have two different format.
|
|
@@ -267,7 +272,9 @@ function buildLuvioOverrideForDraftAdapters(luvio, handler, extractTargetIdFromC
|
|
|
267
272
|
const dispatchResourceRequest = async function (resourceRequest, _context) {
|
|
268
273
|
const resourceRequestCopy = clone(resourceRequest);
|
|
269
274
|
resourceRequestCopy.headers = resourceRequestCopy.headers || {};
|
|
270
|
-
|
|
275
|
+
if (handler.hasIdempotencySupport()) {
|
|
276
|
+
resourceRequestCopy.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
277
|
+
}
|
|
271
278
|
// enable return extra fields for record creation and record update http call
|
|
272
279
|
if (resourceRequest.basePath === '/ui-api/records' &&
|
|
273
280
|
(resourceRequest.method === 'post' || resourceRequest.method === 'patch')) {
|
|
@@ -1103,6 +1110,12 @@ class AbstractResourceRequestActionHandler {
|
|
|
1103
1110
|
// the luvio store redirect table, during which a new draft might be enqueued
|
|
1104
1111
|
// which would not see a necessary mapping.
|
|
1105
1112
|
this.ephemeralRedirects = {};
|
|
1113
|
+
// determined by Server setup.
|
|
1114
|
+
this.isIdempotencySupported = true;
|
|
1115
|
+
// idempotency write flag set by lds
|
|
1116
|
+
this.isLdsIdempotencyWriteDisabled = ldsIdempotencyWriteDisabled.isOpen({
|
|
1117
|
+
fallback: false,
|
|
1118
|
+
});
|
|
1106
1119
|
}
|
|
1107
1120
|
enqueue(data) {
|
|
1108
1121
|
return this.draftQueue.enqueue(this.handlerId, data);
|
|
@@ -1132,21 +1145,43 @@ class AbstractResourceRequestActionHandler {
|
|
|
1132
1145
|
retryDelayInMs = getRetryAfterInMs(response.headers);
|
|
1133
1146
|
shouldRetry = true;
|
|
1134
1147
|
break;
|
|
1135
|
-
case HttpStatusCode.ServerError:
|
|
1148
|
+
case HttpStatusCode.ServerError: {
|
|
1136
1149
|
shouldRetry = true;
|
|
1150
|
+
if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR)) {
|
|
1151
|
+
this.isIdempotencySupported = false;
|
|
1152
|
+
retryDelayInMs = 0;
|
|
1153
|
+
actionDataChanged = true;
|
|
1154
|
+
}
|
|
1137
1155
|
break;
|
|
1156
|
+
}
|
|
1138
1157
|
case 409 /* IdempotentWriteSpecificHttpStatusCode.Conflict */: {
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1158
|
+
if (this.isUiApiErrors(response.body)) {
|
|
1159
|
+
const errorCode = response.body[0].errorCode;
|
|
1160
|
+
if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER)) {
|
|
1161
|
+
retryDelayInMs = 0;
|
|
1162
|
+
actionDataChanged = true;
|
|
1163
|
+
}
|
|
1164
|
+
else if (errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST) {
|
|
1165
|
+
retryDelayInMs = getRetryAfterInMs(response.headers);
|
|
1166
|
+
}
|
|
1167
|
+
shouldRetry = true;
|
|
1168
|
+
}
|
|
1169
|
+
break;
|
|
1170
|
+
}
|
|
1171
|
+
case HttpStatusCode.BadRequest: {
|
|
1172
|
+
if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED, ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED)) {
|
|
1143
1173
|
retryDelayInMs = 0;
|
|
1144
1174
|
actionDataChanged = true;
|
|
1175
|
+
shouldRetry = true;
|
|
1145
1176
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
case 422 /* IdempotentWriteSpecificHttpStatusCode.UnProcessableEntity */: {
|
|
1180
|
+
if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED)) {
|
|
1181
|
+
retryDelayInMs = 0;
|
|
1182
|
+
actionDataChanged = true;
|
|
1183
|
+
shouldRetry = true;
|
|
1148
1184
|
}
|
|
1149
|
-
shouldRetry = true;
|
|
1150
1185
|
break;
|
|
1151
1186
|
}
|
|
1152
1187
|
}
|
|
@@ -1165,6 +1200,27 @@ class AbstractResourceRequestActionHandler {
|
|
|
1165
1200
|
return ProcessActionResult.NETWORK_ERROR;
|
|
1166
1201
|
}
|
|
1167
1202
|
}
|
|
1203
|
+
// true if response is an idempotency server error. updates or deletes idempotency key if the reponse is idempotency related error. Idempotency related error is in format of UiApiError array.
|
|
1204
|
+
handleIdempotencyServerError(responseBody, action, updateIdempotencyKey, ...targetErrorCodes) {
|
|
1205
|
+
if (this.isUiApiErrors(responseBody)) {
|
|
1206
|
+
const errorCode = responseBody[0].errorCode;
|
|
1207
|
+
if (targetErrorCodes.includes(errorCode)) {
|
|
1208
|
+
action.data.headers = action.data.headers || {};
|
|
1209
|
+
if (updateIdempotencyKey) {
|
|
1210
|
+
action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
1211
|
+
}
|
|
1212
|
+
else {
|
|
1213
|
+
delete action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY];
|
|
1214
|
+
}
|
|
1215
|
+
return true;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return false;
|
|
1219
|
+
}
|
|
1220
|
+
// checks if the body is an array of UiApiError. Sometimes the body has `enhancedErrorType` field as an error indicator(one example is the field validation failure). In such case Action being processed updates to an Error Action.
|
|
1221
|
+
isUiApiErrors(body) {
|
|
1222
|
+
return body !== undefined && Array.isArray(body) && body.length > 0 && body[0].errorCode;
|
|
1223
|
+
}
|
|
1168
1224
|
async buildPendingAction(request, queue) {
|
|
1169
1225
|
const targetId = await this.getIdFromRequest(request);
|
|
1170
1226
|
if (targetId === undefined) {
|
|
@@ -1378,6 +1434,10 @@ class AbstractResourceRequestActionHandler {
|
|
|
1378
1434
|
...targetData,
|
|
1379
1435
|
body: this.mergeRequestBody(targetBody, sourceBody),
|
|
1380
1436
|
};
|
|
1437
|
+
// Updates Idempotency key if target has one
|
|
1438
|
+
if (targetData.headers && targetData.headers[HTTP_HEADER_IDEMPOTENCY_KEY]) {
|
|
1439
|
+
merged.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
1440
|
+
}
|
|
1381
1441
|
// overlay metadata
|
|
1382
1442
|
merged.metadata = { ...targetMetadata, ...sourceMetadata };
|
|
1383
1443
|
// put status back to pending to auto upload if queue is active and targed is at the head.
|
|
@@ -1414,6 +1474,9 @@ class AbstractResourceRequestActionHandler {
|
|
|
1414
1474
|
getDraftIdsFromAction(action) {
|
|
1415
1475
|
return [action.targetId];
|
|
1416
1476
|
}
|
|
1477
|
+
hasIdempotencySupport() {
|
|
1478
|
+
return this.isIdempotencySupported && !this.isLdsIdempotencyWriteDisabled;
|
|
1479
|
+
}
|
|
1417
1480
|
async ingestResponses(responses, action) {
|
|
1418
1481
|
const luvio = this.getLuvio();
|
|
1419
1482
|
await luvio.handleSuccessResponse(() => {
|
|
@@ -14,9 +14,13 @@ export declare abstract class AbstractResourceRequestActionHandler<ResponseType,
|
|
|
14
14
|
ephemeralRedirects: {
|
|
15
15
|
[key: string]: string;
|
|
16
16
|
};
|
|
17
|
+
isIdempotencySupported: boolean;
|
|
18
|
+
isLdsIdempotencyWriteDisabled: boolean;
|
|
17
19
|
constructor(draftQueue: DraftQueue, networkAdapter: NetworkAdapter, getLuvio: () => Luvio);
|
|
18
20
|
enqueue(data: ResourceRequest): Promise<EnqueueResult<ResourceRequest, ResponseType>>;
|
|
19
21
|
handleAction(action: DraftAction<ResourceRequest, ResponseType>, actionCompleted: (action: CompletedDraftAction<ResourceRequest, ResponseType>) => Promise<void>, actionErrored: (action: DraftAction<ResourceRequest, ResponseType>, retry: boolean, retryDelayInMs?: number, actionDataChanged?: boolean) => Promise<void>): Promise<ProcessActionResult>;
|
|
22
|
+
handleIdempotencyServerError(responseBody: any, action: DraftAction<ResourceRequest, ResponseType>, updateIdempotencyKey: boolean, ...targetErrorCodes: string[]): boolean;
|
|
23
|
+
isUiApiErrors(body: any): boolean;
|
|
20
24
|
buildPendingAction(request: ResourceRequest, queue: DraftAction<unknown, unknown>[]): Promise<PendingDraftAction<ResourceRequest>>;
|
|
21
25
|
handleActionEnqueued(action: PendingDraftAction<ResourceRequest>): Promise<void>;
|
|
22
26
|
handleActionRemoved(action: DraftAction<ResourceRequest, ResponseType>): Promise<void>;
|
|
@@ -30,6 +34,7 @@ export declare abstract class AbstractResourceRequestActionHandler<ResponseType,
|
|
|
30
34
|
private isActionOfType;
|
|
31
35
|
protected reingestRecord(action: DraftAction<ResourceRequest, ResponseType>): Promise<void>;
|
|
32
36
|
getDraftIdsFromAction(action: DraftAction<ResourceRequest, ResponseType>): string[];
|
|
37
|
+
hasIdempotencySupport(): boolean;
|
|
33
38
|
ingestResponses(responses: ResponseIngestionEntry[], action: DraftAction<ResourceRequest, ResponseType>): Promise<void>;
|
|
34
39
|
evictKey(key: string): Promise<void>;
|
|
35
40
|
abstract handlerId: string;
|
|
@@ -7,7 +7,8 @@ export declare const enum IdempotentWriteSpecificHttpStatusCode {
|
|
|
7
7
|
RequestTimeout = 408,
|
|
8
8
|
Conflict = 409,
|
|
9
9
|
BadGateway = 502,
|
|
10
|
-
ServiceUnavailable = 503
|
|
10
|
+
ServiceUnavailable = 503,
|
|
11
|
+
UnProcessableEntity = 422
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
14
|
* The http status code which could be returned from idempotent write.
|
|
@@ -15,8 +16,12 @@ export declare const enum IdempotentWriteSpecificHttpStatusCode {
|
|
|
15
16
|
export type IdempotentWriteHttpStatusCode = IdempotentWriteSpecificHttpStatusCode | HttpStatusCode;
|
|
16
17
|
export declare const HTTP_HEADER_RETRY_AFTER = "Retry-After";
|
|
17
18
|
export declare const HTTP_HEADER_IDEMPOTENCY_KEY = "Idempotency-Key";
|
|
19
|
+
export declare const ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED = "IDEMPOTENCY_FEATURE_NOT_ENABLED";
|
|
20
|
+
export declare const ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED = "IDEMPOTENCY_NOT_SUPPORTED";
|
|
18
21
|
export declare const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = "IDEMPOTENCY_KEY_USED_DIFFERENT_USER";
|
|
19
22
|
export declare const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = "IDEMPOTENCY_CONCURRENT_REQUEST";
|
|
23
|
+
export declare const ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED = "IDEMPOTENCY_KEY_ALREADY_USED";
|
|
24
|
+
export declare const ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR = "IDEMPOTENCY_BACKEND_OPERATION_ERROR";
|
|
20
25
|
/**
|
|
21
26
|
* Get the retry after in milliseconds from the response headers, undefined if not specified.
|
|
22
27
|
* The header could have two different format.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/lds-drafts",
|
|
3
|
-
"version": "1.229.0-
|
|
3
|
+
"version": "1.229.0-dev10",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"description": "LDS Drafts",
|
|
6
6
|
"main": "dist/ldsDrafts.js",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"release:corejar": "yarn build && ../core-build/scripts/core.js --adapter=lds-drafts"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@luvio/engine": "0.
|
|
28
|
-
"@luvio/environments": "0.
|
|
29
|
-
"@salesforce/lds-utils-adapters": "1.229.0-
|
|
27
|
+
"@luvio/engine": "0.146.0-dev5",
|
|
28
|
+
"@luvio/environments": "0.146.0-dev5",
|
|
29
|
+
"@salesforce/lds-utils-adapters": "1.229.0-dev10"
|
|
30
30
|
}
|
|
31
31
|
}
|