@salesforce/lds-drafts 1.316.0 → 1.317.0
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
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
* For full license text, see the LICENSE.txt file
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { HttpStatusCode
|
|
8
|
-
import { AsyncWorkerPool
|
|
9
|
-
|
|
10
|
-
import ldsBackdatingEnabled from '@salesforce/gate/lds.backdatingEnabled';
|
|
7
|
+
import { HttpStatusCode } from '@luvio/engine';
|
|
8
|
+
import { AsyncWorkerPool } from '@salesforce/lds-utils-adapters';
|
|
9
|
+
export { uuidv4 } from '@salesforce/lds-utils-adapters';
|
|
11
10
|
|
|
12
11
|
var DraftActionStatus;
|
|
13
12
|
(function (DraftActionStatus) {
|
|
@@ -472,9 +471,6 @@ class DurableDraftQueue {
|
|
|
472
471
|
async getQueueActions() {
|
|
473
472
|
const drafts = (await this.draftStore.getAllDrafts());
|
|
474
473
|
const queue = [];
|
|
475
|
-
if (drafts === undefined) {
|
|
476
|
-
return queue;
|
|
477
|
-
}
|
|
478
474
|
drafts.forEach((draft) => {
|
|
479
475
|
if (draft.id === this.uploadingActionId) {
|
|
480
476
|
draft.status = DraftActionStatus.Uploading;
|
|
@@ -1026,23 +1022,15 @@ class DurableDraftStore {
|
|
|
1026
1022
|
for (let i = 0, len = keys$1.length; i < len; i++) {
|
|
1027
1023
|
const entry = durableEntries[keys$1[i]];
|
|
1028
1024
|
const action = entry.data;
|
|
1029
|
-
if (
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
return Promise.reject('Unexpected draft action version found in the durable store');
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
draftStore[action.id] = action;
|
|
1039
|
-
}
|
|
1040
|
-
else {
|
|
1041
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
1042
|
-
const err = new Error('Expected draft action to be defined in the durable store');
|
|
1043
|
-
return Promise.reject(err);
|
|
1025
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1026
|
+
// the `version` property was introduced in 242, we should assert version
|
|
1027
|
+
// exists once we are sure there are no durable stores that contain
|
|
1028
|
+
// versionless actions
|
|
1029
|
+
if (action.version && action.version !== '242.0.0') {
|
|
1030
|
+
return Promise.reject('Unexpected draft action version found in the durable store');
|
|
1044
1031
|
}
|
|
1045
1032
|
}
|
|
1033
|
+
draftStore[action.id] = action;
|
|
1046
1034
|
}
|
|
1047
1035
|
return this.runQueuedOperations();
|
|
1048
1036
|
})
|
|
@@ -1078,494 +1066,6 @@ class DurableDraftStore {
|
|
|
1078
1066
|
}
|
|
1079
1067
|
}
|
|
1080
1068
|
|
|
1081
|
-
const HTTP_HEADER_RETRY_AFTER = 'Retry-After';
|
|
1082
|
-
const HTTP_HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';
|
|
1083
|
-
const ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED = 'IDEMPOTENCY_FEATURE_NOT_ENABLED';
|
|
1084
|
-
const ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED = 'IDEMPOTENCY_NOT_SUPPORTED';
|
|
1085
|
-
const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = 'IDEMPOTENCY_KEY_USED_DIFFERENT_USER';
|
|
1086
|
-
const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = 'IDEMPOTENCY_CONCURRENT_REQUEST';
|
|
1087
|
-
const ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED = 'IDEMPOTENCY_KEY_ALREADY_USED';
|
|
1088
|
-
const ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR = 'IDEMPOTENCY_BACKEND_OPERATION_ERROR';
|
|
1089
|
-
/**
|
|
1090
|
-
* Get the retry after in milliseconds from the response headers, undefined if not specified.
|
|
1091
|
-
* The header could have two different format.
|
|
1092
|
-
* Retry-After: <http-date>, like Wed, 21 Oct 2015 07:28:00 GMT
|
|
1093
|
-
* Retry-After: <delay-seconds>, like 1.5s
|
|
1094
|
-
* @param headers http headers
|
|
1095
|
-
* @returns the time to delat in millisconds.
|
|
1096
|
-
*/
|
|
1097
|
-
function getRetryAfterInMs(headers) {
|
|
1098
|
-
const retryAfterHeader = headers && headers[HTTP_HEADER_RETRY_AFTER];
|
|
1099
|
-
if (retryAfterHeader === undefined) {
|
|
1100
|
-
return undefined;
|
|
1101
|
-
}
|
|
1102
|
-
const delayInSecond = parseFloat(retryAfterHeader);
|
|
1103
|
-
if (retryAfterHeader === delayInSecond.toString()) {
|
|
1104
|
-
return Math.round(delayInSecond * 1000);
|
|
1105
|
-
}
|
|
1106
|
-
const delayUntilDateTime = Date.parse(retryAfterHeader);
|
|
1107
|
-
if (isNaN(delayUntilDateTime)) {
|
|
1108
|
-
return undefined;
|
|
1109
|
-
}
|
|
1110
|
-
return delayUntilDateTime - Date.now();
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
const DEFAULT_FIELD_LAST_MODIFIED_DATE = 'LastModifiedDate';
|
|
1114
|
-
const DEFAULT_FIELD_CREATED_DATE = 'CreatedDate';
|
|
1115
|
-
class AbstractResourceRequestActionHandler {
|
|
1116
|
-
constructor(draftQueue, networkAdapter, getLuvio) {
|
|
1117
|
-
this.draftQueue = draftQueue;
|
|
1118
|
-
this.networkAdapter = networkAdapter;
|
|
1119
|
-
this.getLuvio = getLuvio;
|
|
1120
|
-
// NOTE[W-12567340]: This property stores in-memory mappings between draft
|
|
1121
|
-
// ids and canonical ids for the current session. Having a local copy of
|
|
1122
|
-
// these mappings is necessary to avoid a race condition between publishing
|
|
1123
|
-
// new mappings to the durable store and those mappings being loaded into
|
|
1124
|
-
// the luvio store redirect table, during which a new draft might be enqueued
|
|
1125
|
-
// which would not see a necessary mapping.
|
|
1126
|
-
this.ephemeralRedirects = {};
|
|
1127
|
-
// determined by Server setup.
|
|
1128
|
-
this.isIdempotencySupported = true;
|
|
1129
|
-
// idempotency write flag set by lds
|
|
1130
|
-
this.isLdsIdempotencyWriteDisabled = ldsIdempotencyWriteDisabled.isOpen({
|
|
1131
|
-
fallback: false,
|
|
1132
|
-
});
|
|
1133
|
-
this.isBackdatingEnabled = ldsBackdatingEnabled.isOpen({ fallback: false });
|
|
1134
|
-
}
|
|
1135
|
-
enqueue(data) {
|
|
1136
|
-
return this.draftQueue.enqueue(this.handlerId, data);
|
|
1137
|
-
}
|
|
1138
|
-
async handleAction(action, actionCompleted, actionErrored) {
|
|
1139
|
-
const { data: request } = action;
|
|
1140
|
-
// no context is stored in draft action
|
|
1141
|
-
try {
|
|
1142
|
-
const response = await this.networkAdapter(request, {});
|
|
1143
|
-
if (response.ok) {
|
|
1144
|
-
await actionCompleted({
|
|
1145
|
-
...action,
|
|
1146
|
-
response,
|
|
1147
|
-
status: DraftActionStatus.Completed,
|
|
1148
|
-
});
|
|
1149
|
-
return ProcessActionResult.ACTION_SUCCEEDED;
|
|
1150
|
-
}
|
|
1151
|
-
let shouldRetry = false;
|
|
1152
|
-
let retryDelayInMs = undefined;
|
|
1153
|
-
let actionDataChanged = false;
|
|
1154
|
-
let updatedAction = action;
|
|
1155
|
-
if (request && request.headers && request.headers[HTTP_HEADER_IDEMPOTENCY_KEY]) {
|
|
1156
|
-
const status = response.status;
|
|
1157
|
-
switch (status) {
|
|
1158
|
-
case 408 /* IdempotentWriteSpecificHttpStatusCode.RequestTimeout */:
|
|
1159
|
-
case 503 /* IdempotentWriteSpecificHttpStatusCode.ServiceUnavailable */:
|
|
1160
|
-
retryDelayInMs = getRetryAfterInMs(response.headers);
|
|
1161
|
-
shouldRetry = true;
|
|
1162
|
-
break;
|
|
1163
|
-
case HttpStatusCode.ServerError: {
|
|
1164
|
-
shouldRetry = true;
|
|
1165
|
-
if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR)) {
|
|
1166
|
-
this.isIdempotencySupported = false;
|
|
1167
|
-
retryDelayInMs = 0;
|
|
1168
|
-
actionDataChanged = true;
|
|
1169
|
-
}
|
|
1170
|
-
break;
|
|
1171
|
-
}
|
|
1172
|
-
case 409 /* IdempotentWriteSpecificHttpStatusCode.Conflict */: {
|
|
1173
|
-
if (this.isUiApiErrors(response.body)) {
|
|
1174
|
-
const errorCode = response.body[0].errorCode;
|
|
1175
|
-
if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER)) {
|
|
1176
|
-
retryDelayInMs = 0;
|
|
1177
|
-
actionDataChanged = true;
|
|
1178
|
-
}
|
|
1179
|
-
else if (errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST) {
|
|
1180
|
-
retryDelayInMs = getRetryAfterInMs(response.headers);
|
|
1181
|
-
}
|
|
1182
|
-
shouldRetry = true;
|
|
1183
|
-
}
|
|
1184
|
-
break;
|
|
1185
|
-
}
|
|
1186
|
-
case HttpStatusCode.BadRequest: {
|
|
1187
|
-
if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED, ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED)) {
|
|
1188
|
-
retryDelayInMs = 0;
|
|
1189
|
-
actionDataChanged = true;
|
|
1190
|
-
shouldRetry = true;
|
|
1191
|
-
}
|
|
1192
|
-
break;
|
|
1193
|
-
}
|
|
1194
|
-
case 422 /* IdempotentWriteSpecificHttpStatusCode.UnProcessableEntity */: {
|
|
1195
|
-
if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED)) {
|
|
1196
|
-
retryDelayInMs = 0;
|
|
1197
|
-
actionDataChanged = true;
|
|
1198
|
-
shouldRetry = true;
|
|
1199
|
-
}
|
|
1200
|
-
break;
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
if (this.isBackdatingEnabled &&
|
|
1205
|
-
response.status === HttpStatusCode.BadRequest &&
|
|
1206
|
-
this.isBackdatingError(response.body, action)) {
|
|
1207
|
-
updatedAction.timestamp = Date.now();
|
|
1208
|
-
updatedAction.data.body.fields = {
|
|
1209
|
-
...updatedAction.data.body.fields,
|
|
1210
|
-
LastModifiedDate: new Date(updatedAction.timestamp).toISOString(),
|
|
1211
|
-
};
|
|
1212
|
-
if (this.hasIdempotencySupport() &&
|
|
1213
|
-
updatedAction.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY]) {
|
|
1214
|
-
updatedAction.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
1215
|
-
}
|
|
1216
|
-
shouldRetry = true;
|
|
1217
|
-
actionDataChanged = true;
|
|
1218
|
-
}
|
|
1219
|
-
await actionErrored(shouldRetry
|
|
1220
|
-
? updatedAction
|
|
1221
|
-
: {
|
|
1222
|
-
...updatedAction,
|
|
1223
|
-
error: response,
|
|
1224
|
-
status: DraftActionStatus.Error,
|
|
1225
|
-
}, shouldRetry, retryDelayInMs, actionDataChanged);
|
|
1226
|
-
return ProcessActionResult.ACTION_ERRORED;
|
|
1227
|
-
}
|
|
1228
|
-
catch (e) {
|
|
1229
|
-
await actionErrored(action, true);
|
|
1230
|
-
return ProcessActionResult.NETWORK_ERROR;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
// 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.
|
|
1234
|
-
handleIdempotencyServerError(responseBody, action, updateIdempotencyKey, ...targetErrorCodes) {
|
|
1235
|
-
if (this.isUiApiErrors(responseBody)) {
|
|
1236
|
-
const errorCode = responseBody[0].errorCode;
|
|
1237
|
-
if (targetErrorCodes.includes(errorCode)) {
|
|
1238
|
-
action.data.headers = action.data.headers || {};
|
|
1239
|
-
if (updateIdempotencyKey) {
|
|
1240
|
-
action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
1241
|
-
}
|
|
1242
|
-
else {
|
|
1243
|
-
delete action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY];
|
|
1244
|
-
}
|
|
1245
|
-
return true;
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
return false;
|
|
1249
|
-
}
|
|
1250
|
-
// 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.
|
|
1251
|
-
isUiApiErrors(body) {
|
|
1252
|
-
return body !== undefined && isArray(body) && body.length > 0 && body[0].errorCode;
|
|
1253
|
-
}
|
|
1254
|
-
isBackdatingError(body, action) {
|
|
1255
|
-
if (body.enhancedErrorType &&
|
|
1256
|
-
body.enhancedErrorType === 'RecordError' &&
|
|
1257
|
-
body.output &&
|
|
1258
|
-
body.output.errors &&
|
|
1259
|
-
isArray(body.output.errors) &&
|
|
1260
|
-
body.output.errors.length > 0 &&
|
|
1261
|
-
action.data.body &&
|
|
1262
|
-
action.data.body.fields &&
|
|
1263
|
-
action.data.body.fields[DEFAULT_FIELD_LAST_MODIFIED_DATE]) {
|
|
1264
|
-
return body.output.errors.some((error) => error.errorCode === 'CollisionDetectedException');
|
|
1265
|
-
}
|
|
1266
|
-
return false;
|
|
1267
|
-
}
|
|
1268
|
-
async buildPendingAction(request, queue) {
|
|
1269
|
-
const targetId = await this.getIdFromRequest(request);
|
|
1270
|
-
const tag = this.buildTagForTargetId(targetId);
|
|
1271
|
-
const handlerActions = queue.filter((x) => x.handler === this.handlerId);
|
|
1272
|
-
if (request.method === 'post' && actionsForTag(tag, handlerActions).length > 0) {
|
|
1273
|
-
return Promise.reject(new Error('Cannot enqueue a POST draft action with an existing tag'));
|
|
1274
|
-
}
|
|
1275
|
-
if (deleteActionsForTag(tag, handlerActions).length > 0) {
|
|
1276
|
-
return Promise.reject(new Error('Cannot enqueue a draft action for a deleted record'));
|
|
1277
|
-
}
|
|
1278
|
-
return {
|
|
1279
|
-
handler: this.handlerId,
|
|
1280
|
-
targetId,
|
|
1281
|
-
tag,
|
|
1282
|
-
data: request,
|
|
1283
|
-
status: DraftActionStatus.Pending,
|
|
1284
|
-
id: generateUniqueDraftActionId(queue.map((x) => x.id)),
|
|
1285
|
-
timestamp: Date.now(),
|
|
1286
|
-
metadata: {},
|
|
1287
|
-
version: '242.0.0',
|
|
1288
|
-
};
|
|
1289
|
-
}
|
|
1290
|
-
async handleActionEnqueued(_action) { }
|
|
1291
|
-
handleActionRemoved(action) {
|
|
1292
|
-
return this.reingestRecord(action);
|
|
1293
|
-
}
|
|
1294
|
-
getQueueOperationsForCompletingDrafts(queue, action) {
|
|
1295
|
-
const queueOperations = [];
|
|
1296
|
-
const redirects = this.getRedirectMappings(action);
|
|
1297
|
-
if (redirects !== undefined) {
|
|
1298
|
-
const { length } = queue;
|
|
1299
|
-
for (let i = 0; i < length; i++) {
|
|
1300
|
-
const queueAction = queue[i];
|
|
1301
|
-
// if this queueAction is the action that is completing we can move on,
|
|
1302
|
-
// it is about to be deleted and won't have the draft ID in it
|
|
1303
|
-
if (queueAction.id === action.id) {
|
|
1304
|
-
continue;
|
|
1305
|
-
}
|
|
1306
|
-
if (isResourceRequestAction(queueAction)) {
|
|
1307
|
-
let queueOperationMutated = false;
|
|
1308
|
-
let updatedActionTag = undefined;
|
|
1309
|
-
let updatedActionTargetId = undefined;
|
|
1310
|
-
const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
|
|
1311
|
-
let { basePath, body } = queueActionRequest;
|
|
1312
|
-
let stringifiedBody = stringify(body);
|
|
1313
|
-
// for each redirected ID/key we loop over the operation to see if it needs
|
|
1314
|
-
// to be updated
|
|
1315
|
-
for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
|
|
1316
|
-
if (basePath.search(draftId) >= 0 || stringifiedBody.search(draftId) >= 0) {
|
|
1317
|
-
basePath = basePath.replace(draftId, canonicalId);
|
|
1318
|
-
stringifiedBody = stringifiedBody.replace(draftId, canonicalId);
|
|
1319
|
-
queueOperationMutated = true;
|
|
1320
|
-
}
|
|
1321
|
-
// if the action is performed on a previous draft id, we need to replace the action
|
|
1322
|
-
// with a new one at the updated canonical key
|
|
1323
|
-
if (queueActionTag === draftKey) {
|
|
1324
|
-
updatedActionTag = canonicalKey;
|
|
1325
|
-
updatedActionTargetId = canonicalId;
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
if (queueOperationMutated) {
|
|
1329
|
-
if (updatedActionTag !== undefined && updatedActionTargetId !== undefined) {
|
|
1330
|
-
const updatedAction = {
|
|
1331
|
-
...queueAction,
|
|
1332
|
-
tag: updatedActionTag,
|
|
1333
|
-
targetId: updatedActionTargetId,
|
|
1334
|
-
data: {
|
|
1335
|
-
...queueActionRequest,
|
|
1336
|
-
basePath: basePath,
|
|
1337
|
-
body: parse(stringifiedBody),
|
|
1338
|
-
},
|
|
1339
|
-
};
|
|
1340
|
-
// item needs to be replaced with a new item at the new record key
|
|
1341
|
-
queueOperations.push({
|
|
1342
|
-
type: QueueOperationType.Delete,
|
|
1343
|
-
id: queueActionId,
|
|
1344
|
-
});
|
|
1345
|
-
queueOperations.push({
|
|
1346
|
-
type: QueueOperationType.Add,
|
|
1347
|
-
action: updatedAction,
|
|
1348
|
-
});
|
|
1349
|
-
}
|
|
1350
|
-
else {
|
|
1351
|
-
const updatedAction = {
|
|
1352
|
-
...queueAction,
|
|
1353
|
-
data: {
|
|
1354
|
-
...queueActionRequest,
|
|
1355
|
-
basePath: basePath,
|
|
1356
|
-
body: parse(stringifiedBody),
|
|
1357
|
-
},
|
|
1358
|
-
};
|
|
1359
|
-
// item needs to be updated
|
|
1360
|
-
queueOperations.push({
|
|
1361
|
-
type: QueueOperationType.Update,
|
|
1362
|
-
id: queueActionId,
|
|
1363
|
-
action: updatedAction,
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
// delete completed action
|
|
1371
|
-
queueOperations.push({
|
|
1372
|
-
type: QueueOperationType.Delete,
|
|
1373
|
-
id: action.id,
|
|
1374
|
-
});
|
|
1375
|
-
return queueOperations;
|
|
1376
|
-
}
|
|
1377
|
-
getRedirectMappings(action) {
|
|
1378
|
-
if (action.data.method !== 'post') {
|
|
1379
|
-
return undefined;
|
|
1380
|
-
}
|
|
1381
|
-
const body = action.response.body;
|
|
1382
|
-
const canonicalId = this.getIdFromResponseBody(body);
|
|
1383
|
-
const draftId = action.targetId;
|
|
1384
|
-
if (draftId !== undefined && canonicalId !== undefined && draftId !== canonicalId) {
|
|
1385
|
-
this.ephemeralRedirects[draftId] = canonicalId;
|
|
1386
|
-
}
|
|
1387
|
-
return [
|
|
1388
|
-
{
|
|
1389
|
-
draftId,
|
|
1390
|
-
canonicalId,
|
|
1391
|
-
draftKey: this.buildTagForTargetId(draftId),
|
|
1392
|
-
canonicalKey: this.buildTagForTargetId(canonicalId),
|
|
1393
|
-
},
|
|
1394
|
-
];
|
|
1395
|
-
}
|
|
1396
|
-
async handleActionCompleted(action, queueOperations, allHandlers) {
|
|
1397
|
-
const { data: request, tag } = action;
|
|
1398
|
-
const { method } = request;
|
|
1399
|
-
if (method === 'delete') {
|
|
1400
|
-
return this.evictKey(tag);
|
|
1401
|
-
}
|
|
1402
|
-
const recordsToIngest = [];
|
|
1403
|
-
recordsToIngest.push({
|
|
1404
|
-
response: action.response.body,
|
|
1405
|
-
synchronousIngest: this.synchronousIngest.bind(this),
|
|
1406
|
-
buildCacheKeysForResponse: this.buildCacheKeysFromResponse.bind(this),
|
|
1407
|
-
});
|
|
1408
|
-
const recordsNeedingReplay = queueOperations.filter((x) => x.type === QueueOperationType.Update);
|
|
1409
|
-
for (const recordNeedingReplay of recordsNeedingReplay) {
|
|
1410
|
-
const { action } = recordNeedingReplay;
|
|
1411
|
-
if (isResourceRequestAction(action)) {
|
|
1412
|
-
// We can't assume the queue operation is for our handler, have to find the handler.
|
|
1413
|
-
const handler = allHandlers.find((h) => h.handlerId === action.handler);
|
|
1414
|
-
if (handler !== undefined) {
|
|
1415
|
-
const record = await handler.getDataForAction(action);
|
|
1416
|
-
if (record !== undefined) {
|
|
1417
|
-
recordsToIngest.push({
|
|
1418
|
-
response: record,
|
|
1419
|
-
synchronousIngest: handler.synchronousIngest.bind(handler),
|
|
1420
|
-
buildCacheKeysForResponse: handler.buildCacheKeysFromResponse.bind(handler),
|
|
1421
|
-
});
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
await this.ingestResponses(recordsToIngest, action);
|
|
1427
|
-
}
|
|
1428
|
-
handleReplaceAction(targetAction, sourceAction) {
|
|
1429
|
-
//reject if the action to replace is a POST action
|
|
1430
|
-
const pendingAction = targetAction;
|
|
1431
|
-
if (pendingAction.data.method === 'post') {
|
|
1432
|
-
throw Error('Cannot replace a POST action');
|
|
1433
|
-
}
|
|
1434
|
-
if (this.isActionOfType(targetAction) &&
|
|
1435
|
-
this.isActionOfType(sourceAction)) {
|
|
1436
|
-
targetAction.status = DraftActionStatus.Pending;
|
|
1437
|
-
targetAction.data = sourceAction.data;
|
|
1438
|
-
return targetAction;
|
|
1439
|
-
}
|
|
1440
|
-
else {
|
|
1441
|
-
throw Error('Incompatible Action types to replace one another');
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
mergeActions(targetAction, sourceAction) {
|
|
1445
|
-
const { id: targetId, data: targetData, metadata: targetMetadata, timestamp: targetTimestamp, } = targetAction;
|
|
1446
|
-
const { method: targetMethod, body: targetBody } = targetData;
|
|
1447
|
-
const { data: sourceData, metadata: sourceMetadata } = sourceAction;
|
|
1448
|
-
const { method: sourceMethod, body: sourceBody } = sourceData;
|
|
1449
|
-
if (targetMethod.toLowerCase() === 'delete' || sourceMethod.toLowerCase() === 'delete') {
|
|
1450
|
-
throw Error('Cannot merge DELETE actions.');
|
|
1451
|
-
}
|
|
1452
|
-
if (targetMethod.toLowerCase() === 'patch' && sourceMethod.toLowerCase() === 'post') {
|
|
1453
|
-
// overlaying a POST over a PATCH is not supported
|
|
1454
|
-
throw Error('Cannot merge a POST action over top of a PATCH action.');
|
|
1455
|
-
}
|
|
1456
|
-
// overlay top-level properties, maintain target's timestamp and id
|
|
1457
|
-
const merged = {
|
|
1458
|
-
...targetAction,
|
|
1459
|
-
...sourceAction,
|
|
1460
|
-
timestamp: targetTimestamp,
|
|
1461
|
-
id: targetId,
|
|
1462
|
-
};
|
|
1463
|
-
// overlay data
|
|
1464
|
-
// NOTE: we stick to the target's ResourceRequest properties (except body
|
|
1465
|
-
// which is merged) because we don't want to overwrite a POST with a PATCH
|
|
1466
|
-
// (all other cases should be fine or wouldn't have passed pre-requisites)
|
|
1467
|
-
merged.data = {
|
|
1468
|
-
...targetData,
|
|
1469
|
-
body: this.mergeRequestBody(targetBody, sourceBody),
|
|
1470
|
-
};
|
|
1471
|
-
// Updates Idempotency key if target has one
|
|
1472
|
-
if (targetData.headers && targetData.headers[HTTP_HEADER_IDEMPOTENCY_KEY]) {
|
|
1473
|
-
merged.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
1474
|
-
}
|
|
1475
|
-
// overlay metadata
|
|
1476
|
-
merged.metadata = { ...targetMetadata, ...sourceMetadata };
|
|
1477
|
-
// put status back to pending to auto upload if queue is active and targed is at the head.
|
|
1478
|
-
merged.status = DraftActionStatus.Pending;
|
|
1479
|
-
return merged;
|
|
1480
|
-
}
|
|
1481
|
-
shouldDeleteActionByTagOnRemoval(action) {
|
|
1482
|
-
return action.data.method === 'post';
|
|
1483
|
-
}
|
|
1484
|
-
updateMetadata(_existingMetadata, incomingMetadata) {
|
|
1485
|
-
return incomingMetadata;
|
|
1486
|
-
}
|
|
1487
|
-
isActionOfType(action) {
|
|
1488
|
-
return action.handler === this.handlerId;
|
|
1489
|
-
}
|
|
1490
|
-
async reingestRecord(action) {
|
|
1491
|
-
const record = await this.getDataForAction(action);
|
|
1492
|
-
if (record !== undefined) {
|
|
1493
|
-
await this.ingestResponses([
|
|
1494
|
-
{
|
|
1495
|
-
response: record,
|
|
1496
|
-
synchronousIngest: this.synchronousIngest.bind(this),
|
|
1497
|
-
buildCacheKeysForResponse: this.buildCacheKeysFromResponse.bind(this),
|
|
1498
|
-
},
|
|
1499
|
-
], action);
|
|
1500
|
-
}
|
|
1501
|
-
else {
|
|
1502
|
-
await this.evictKey(action.tag);
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
// Given an action for this handler this method should return the draft IDs.
|
|
1506
|
-
// Most of the time it will simply be the targetId, but certain handlers might
|
|
1507
|
-
// have multiple draft-created IDs so they would override this to return them.
|
|
1508
|
-
getDraftIdsFromAction(action) {
|
|
1509
|
-
return [action.targetId];
|
|
1510
|
-
}
|
|
1511
|
-
hasIdempotencySupport() {
|
|
1512
|
-
return this.isIdempotencySupported && !this.isLdsIdempotencyWriteDisabled;
|
|
1513
|
-
}
|
|
1514
|
-
async ingestResponses(responses, action) {
|
|
1515
|
-
const luvio = this.getLuvio();
|
|
1516
|
-
await luvio.handleSuccessResponse(() => {
|
|
1517
|
-
if (action.status === DraftActionStatus.Completed) {
|
|
1518
|
-
const mappings = this.getRedirectMappings(action);
|
|
1519
|
-
if (mappings) {
|
|
1520
|
-
mappings.forEach((mapping) => {
|
|
1521
|
-
luvio.storeRedirect(mapping.draftKey, mapping.canonicalKey);
|
|
1522
|
-
});
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
for (const entry of responses) {
|
|
1526
|
-
const { response, synchronousIngest } = entry;
|
|
1527
|
-
synchronousIngest(response, action);
|
|
1528
|
-
}
|
|
1529
|
-
return luvio.storeBroadcast();
|
|
1530
|
-
},
|
|
1531
|
-
// getTypeCacheKeysRecord uses the response, not the full path factory
|
|
1532
|
-
// so 2nd parameter will be unused
|
|
1533
|
-
() => {
|
|
1534
|
-
const keySet = new StoreKeyMap();
|
|
1535
|
-
for (const entry of responses) {
|
|
1536
|
-
const { response, buildCacheKeysForResponse } = entry;
|
|
1537
|
-
const set = buildCacheKeysForResponse(response);
|
|
1538
|
-
for (const key of set.keys()) {
|
|
1539
|
-
const value = set.get(key);
|
|
1540
|
-
if (value !== undefined) {
|
|
1541
|
-
keySet.set(key, value);
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
return keySet;
|
|
1546
|
-
});
|
|
1547
|
-
}
|
|
1548
|
-
async evictKey(key) {
|
|
1549
|
-
const luvio = this.getLuvio();
|
|
1550
|
-
await luvio.handleSuccessResponse(() => {
|
|
1551
|
-
luvio.storeEvict(key);
|
|
1552
|
-
return luvio.storeBroadcast();
|
|
1553
|
-
}, () => {
|
|
1554
|
-
return new StoreKeyMap();
|
|
1555
|
-
});
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
function actionsForTag(tag, queue) {
|
|
1559
|
-
return queue.filter((action) => action.tag === tag);
|
|
1560
|
-
}
|
|
1561
|
-
function deleteActionsForTag(tag, queue) {
|
|
1562
|
-
return queue.filter((action) => action.tag === tag && action.data.method === 'delete');
|
|
1563
|
-
}
|
|
1564
|
-
function isResourceRequestAction(action) {
|
|
1565
|
-
const dataAsAny = action.data;
|
|
1566
|
-
return (dataAsAny !== undefined && dataAsAny.method !== undefined && dataAsAny.body !== undefined);
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
1069
|
/**
|
|
1570
1070
|
* Denotes what kind of operation a DraftQueueItem represents.
|
|
1571
1071
|
*/
|
|
@@ -1941,6 +1441,10 @@ class DraftManager {
|
|
|
1941
1441
|
});
|
|
1942
1442
|
}
|
|
1943
1443
|
}
|
|
1444
|
+
function isResourceRequestAction(action) {
|
|
1445
|
+
const dataAsAny = action.data;
|
|
1446
|
+
return (dataAsAny !== undefined && dataAsAny.method !== undefined && dataAsAny.body !== undefined);
|
|
1447
|
+
}
|
|
1944
1448
|
|
|
1945
1449
|
function makeEnvironmentDraftAware(luvio, env, durableStore, handlers, draftQueue) {
|
|
1946
1450
|
const draftMetadata = {};
|
|
@@ -2032,4 +1536,4 @@ function makeEnvironmentDraftAware(luvio, env, durableStore, handlers, draftQueu
|
|
|
2032
1536
|
});
|
|
2033
1537
|
}
|
|
2034
1538
|
|
|
2035
|
-
export {
|
|
1539
|
+
export { CustomActionResultType, DRAFT_ERROR_CODE, DRAFT_ID_MAPPINGS_SEGMENT, DRAFT_SEGMENT, DraftActionOperationType, DraftActionStatus, DraftErrorFetchResponse, DraftFetchResponse, DraftManager, DraftQueueEventType, DraftQueueState, DraftSynthesisError, DurableDraftQueue, DurableDraftStore, ProcessActionResult, QueueOperationType, createBadRequestResponse, createDeletedResponse, createDraftSynthesisErrorResponse, createInternalErrorResponse, createNotFoundResponse, createOkResponse, generateUniqueDraftActionId, isDraftSynthesisError, makeEnvironmentDraftAware, transformErrorToDraftSynthesisError };
|
package/dist/types/main.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
export { DraftQueue, DraftQueueState, DraftAction, ErrorDraftAction, PendingDraftAction, CompletedDraftAction, DraftActionStatus, ProcessActionResult, DraftQueueChangeListener, DraftActionMetadata, DraftQueueEventType, QueueOperation, } from './DraftQueue';
|
|
1
|
+
export { DraftQueue, DraftQueueState, DraftAction, ErrorDraftAction, PendingDraftAction, CompletedDraftAction, DraftActionStatus, ProcessActionResult, DraftQueueChangeListener, DraftActionMetadata, DraftQueueEventType, QueueOperation, UpdateQueueOperation, QueueOperationType, } from './DraftQueue';
|
|
2
2
|
export { DRAFT_ID_MAPPINGS_SEGMENT, DraftKeyMapping } from './DraftIdMapping';
|
|
3
3
|
export { DurableDraftQueue, DRAFT_SEGMENT } from './DurableDraftQueue';
|
|
4
|
-
export { generateUniqueDraftActionId } from './utils/id';
|
|
4
|
+
export { generateUniqueDraftActionId, uuidv4 } from './utils/id';
|
|
5
5
|
export { DurableDraftStore } from './DurableDraftStore';
|
|
6
6
|
export { DraftStore } from './DraftStore';
|
|
7
7
|
export { DraftManager, DraftManagerState, DraftActionOperationType, DraftQueueItem, DraftQueueItemMetadata, } from './DraftManager';
|
|
8
8
|
export { ActionHandler, ReplacingActions, DraftIdAndKeyMapping, } from './actionHandlers/ActionHandler';
|
|
9
9
|
export type { CustomActionResult } from './actionHandlers/CustomActionHandler';
|
|
10
10
|
export { CustomActionResultType, CustomActionExecutor } from './actionHandlers/CustomActionHandler';
|
|
11
|
-
export { AbstractResourceRequestActionHandler, ResponseIngestionEntry, DEFAULT_FIELD_CREATED_DATE, DEFAULT_FIELD_LAST_MODIFIED_DATE, } from './actionHandlers/AbstractResourceRequestActionHandler';
|
|
12
11
|
export { makeEnvironmentDraftAware } from './makeEnvironmentDraftAware';
|
|
13
12
|
export type { DraftAwareEnvironment } from './makeEnvironmentDraftAware';
|
|
14
13
|
export * from './DraftFetchResponse';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/lds-drafts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.317.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"description": "LDS Drafts",
|
|
6
6
|
"main": "dist/ldsDrafts.js",
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@luvio/engine": "0.156.4",
|
|
28
28
|
"@luvio/environments": "0.156.4",
|
|
29
|
-
"@salesforce/lds-utils-adapters": "^1.
|
|
29
|
+
"@salesforce/lds-utils-adapters": "^1.317.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@salesforce/nimbus-plugin-lds": "^1.
|
|
32
|
+
"@salesforce/nimbus-plugin-lds": "^1.317.0"
|
|
33
33
|
},
|
|
34
34
|
"volta": {
|
|
35
35
|
"extends": "../../package.json"
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { NetworkAdapter, ResourceRequest, Luvio, DurableStoreKeyMetadataMap } from '@luvio/engine';
|
|
2
|
-
import type { DraftAction, CompletedDraftAction, QueueOperation, PendingDraftAction, DraftActionMetadata, DraftQueue } from '../DraftQueue';
|
|
3
|
-
import { ProcessActionResult } from '../DraftQueue';
|
|
4
|
-
import type { ActionHandler, DraftIdAndKeyMapping } from './ActionHandler';
|
|
5
|
-
export type ResponseIngestionEntry<T = unknown> = {
|
|
6
|
-
response: T;
|
|
7
|
-
synchronousIngest: AbstractResourceRequestActionHandler<T, unknown>['synchronousIngest'];
|
|
8
|
-
buildCacheKeysForResponse: AbstractResourceRequestActionHandler<T, unknown>['buildCacheKeysFromResponse'];
|
|
9
|
-
};
|
|
10
|
-
export declare const DEFAULT_FIELD_LAST_MODIFIED_DATE = "LastModifiedDate";
|
|
11
|
-
export declare const DEFAULT_FIELD_CREATED_DATE = "CreatedDate";
|
|
12
|
-
export declare abstract class AbstractResourceRequestActionHandler<ResponseType, DraftMetadata> implements ActionHandler<ResourceRequest, DraftMetadata, ResponseType> {
|
|
13
|
-
protected readonly draftQueue: DraftQueue;
|
|
14
|
-
protected readonly networkAdapter: NetworkAdapter;
|
|
15
|
-
protected readonly getLuvio: () => Luvio;
|
|
16
|
-
ephemeralRedirects: {
|
|
17
|
-
[key: string]: string;
|
|
18
|
-
};
|
|
19
|
-
isIdempotencySupported: boolean;
|
|
20
|
-
isLdsIdempotencyWriteDisabled: boolean;
|
|
21
|
-
isBackdatingEnabled: boolean;
|
|
22
|
-
constructor(draftQueue: DraftQueue, networkAdapter: NetworkAdapter, getLuvio: () => Luvio);
|
|
23
|
-
enqueue(data: ResourceRequest): Promise<PendingDraftAction<ResourceRequest>>;
|
|
24
|
-
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>;
|
|
25
|
-
handleIdempotencyServerError(responseBody: any, action: DraftAction<ResourceRequest, ResponseType>, updateIdempotencyKey: boolean, ...targetErrorCodes: string[]): boolean;
|
|
26
|
-
isUiApiErrors(body: any): boolean;
|
|
27
|
-
isBackdatingError(body: any, action: DraftAction<ResourceRequest, ResponseType>): any;
|
|
28
|
-
buildPendingAction(request: ResourceRequest, queue: DraftAction<unknown, unknown>[]): Promise<PendingDraftAction<ResourceRequest>>;
|
|
29
|
-
handleActionEnqueued(_action: PendingDraftAction<ResourceRequest>): Promise<void>;
|
|
30
|
-
handleActionRemoved(action: DraftAction<ResourceRequest, ResponseType>): Promise<void>;
|
|
31
|
-
getQueueOperationsForCompletingDrafts(queue: DraftAction<unknown, unknown>[], action: CompletedDraftAction<ResourceRequest, ResponseType>): QueueOperation[];
|
|
32
|
-
getRedirectMappings(action: CompletedDraftAction<ResourceRequest, ResponseType>): DraftIdAndKeyMapping[] | undefined;
|
|
33
|
-
handleActionCompleted(action: CompletedDraftAction<ResourceRequest, ResponseType>, queueOperations: QueueOperation[], allHandlers: ActionHandler<unknown, unknown, unknown>[]): Promise<void>;
|
|
34
|
-
handleReplaceAction(targetAction: DraftAction<ResourceRequest, ResponseType>, sourceAction: DraftAction<ResourceRequest, ResponseType>): DraftAction<ResourceRequest, ResponseType>;
|
|
35
|
-
mergeActions(targetAction: DraftAction<ResourceRequest, ResponseType>, sourceAction: DraftAction<ResourceRequest, ResponseType>): DraftAction<ResourceRequest, ResponseType>;
|
|
36
|
-
shouldDeleteActionByTagOnRemoval(action: DraftAction<ResourceRequest, ResponseType>): boolean;
|
|
37
|
-
updateMetadata(_existingMetadata: DraftActionMetadata, incomingMetadata: DraftActionMetadata): DraftActionMetadata;
|
|
38
|
-
private isActionOfType;
|
|
39
|
-
protected reingestRecord(action: DraftAction<ResourceRequest, ResponseType>): Promise<void>;
|
|
40
|
-
getDraftIdsFromAction(action: DraftAction<ResourceRequest, ResponseType>): string[];
|
|
41
|
-
hasIdempotencySupport(): boolean;
|
|
42
|
-
ingestResponses(responses: ResponseIngestionEntry[], action: DraftAction<ResourceRequest, ResponseType>): Promise<void>;
|
|
43
|
-
evictKey(key: string): Promise<void>;
|
|
44
|
-
abstract handlerId: string;
|
|
45
|
-
abstract canHandlePublish(key: string): boolean;
|
|
46
|
-
abstract canRepresentationContainDraftMetadata(representationName: string): boolean;
|
|
47
|
-
/**
|
|
48
|
-
* Extracts or synthesizes an id from a given resource request
|
|
49
|
-
* @throws if the id cannot be determined or synthesized
|
|
50
|
-
* @param request the request to get the id from
|
|
51
|
-
*/
|
|
52
|
-
abstract getIdFromRequest(request: ResourceRequest): Promise<string>;
|
|
53
|
-
abstract getIdFromResponseBody(responseBody: ResponseType): string;
|
|
54
|
-
abstract buildTagForTargetId(id: string): string;
|
|
55
|
-
abstract buildCacheKeysFromResponse(response: ResponseType): DurableStoreKeyMetadataMap;
|
|
56
|
-
abstract synchronousIngest(response: ResponseType, action: DraftAction<ResourceRequest, ResponseType>): void;
|
|
57
|
-
abstract getDataForAction(action: DraftAction<ResourceRequest, ResponseType>): Promise<ResponseType | undefined>;
|
|
58
|
-
abstract getDraftMetadata(key: string): Promise<DraftMetadata | undefined>;
|
|
59
|
-
abstract applyDraftsToIncomingData(key: string, data: unknown, draftMetadata: DraftMetadata | undefined, publishFn: (key: string, data: any) => void): void;
|
|
60
|
-
/**
|
|
61
|
-
* Returns true if the given targetId is a for a draft-created record
|
|
62
|
-
*
|
|
63
|
-
* @param targetId the targetId to check
|
|
64
|
-
*/
|
|
65
|
-
abstract isDraftId(targetId: string): boolean;
|
|
66
|
-
/**
|
|
67
|
-
* Overlay the sourceBody over top of the targetBody
|
|
68
|
-
*/
|
|
69
|
-
abstract mergeRequestBody<T = unknown>(targetBody: T, sourceBody: T): T;
|
|
70
|
-
}
|
|
71
|
-
export declare function isResourceRequestAction(action: DraftAction<unknown, unknown>): action is DraftAction<ResourceRequest, unknown>;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { HttpStatusCode, Headers } from '@luvio/engine';
|
|
2
|
-
export interface UIApiError {
|
|
3
|
-
errorCode: string;
|
|
4
|
-
message: string | null;
|
|
5
|
-
}
|
|
6
|
-
export declare const enum IdempotentWriteSpecificHttpStatusCode {
|
|
7
|
-
RequestTimeout = 408,
|
|
8
|
-
Conflict = 409,
|
|
9
|
-
BadGateway = 502,
|
|
10
|
-
ServiceUnavailable = 503,
|
|
11
|
-
UnProcessableEntity = 422
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* The http status code which could be returned from idempotent write.
|
|
15
|
-
*/
|
|
16
|
-
export type IdempotentWriteHttpStatusCode = IdempotentWriteSpecificHttpStatusCode | HttpStatusCode;
|
|
17
|
-
export declare const HTTP_HEADER_RETRY_AFTER = "Retry-After";
|
|
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";
|
|
21
|
-
export declare const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = "IDEMPOTENCY_KEY_USED_DIFFERENT_USER";
|
|
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";
|
|
25
|
-
/**
|
|
26
|
-
* Get the retry after in milliseconds from the response headers, undefined if not specified.
|
|
27
|
-
* The header could have two different format.
|
|
28
|
-
* Retry-After: <http-date>, like Wed, 21 Oct 2015 07:28:00 GMT
|
|
29
|
-
* Retry-After: <delay-seconds>, like 1.5s
|
|
30
|
-
* @param headers http headers
|
|
31
|
-
* @returns the time to delat in millisconds.
|
|
32
|
-
*/
|
|
33
|
-
export declare function getRetryAfterInMs(headers?: Headers): number | undefined;
|