@salesforce/lds-drafts 1.172.0 → 1.174.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 +85 -14
- package/dist/types/DraftAwareAdapter.d.ts +0 -1
- package/dist/types/DurableDraftQueue.d.ts +2 -1
- package/dist/types/actionHandlers/AbstractResourceRequestActionHandler.d.ts +1 -1
- package/dist/types/actionHandlers/ActionHandler.d.ts +1 -1
- package/dist/types/actionHandlers/IdempotentWrite.d.ts +28 -0
- package/package.json +1 -1
package/dist/ldsDrafts.js
CHANGED
|
@@ -234,13 +234,40 @@ function uuidv4() {
|
|
|
234
234
|
return uuid.join('');
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
const
|
|
237
|
+
const HTTP_HEADER_RETRY_AFTER = 'Retry-After';
|
|
238
|
+
const HTTP_HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';
|
|
239
|
+
const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = 'IDEMPOTENCY_KEY_USED_DIFFERENT_USER';
|
|
240
|
+
const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = 'IDEMPOTENCY_CONCURRENT_REQUEST';
|
|
241
|
+
/**
|
|
242
|
+
* Get the retry after in milliseconds from the response headers, undefined if not specified.
|
|
243
|
+
* The header could have two different format.
|
|
244
|
+
* Retry-After: <http-date>, like Wed, 21 Oct 2015 07:28:00 GMT
|
|
245
|
+
* Retry-After: <delay-seconds>, like 1.5s
|
|
246
|
+
* @param headers http headers
|
|
247
|
+
* @returns the time to delat in millisconds.
|
|
248
|
+
*/
|
|
249
|
+
function getRetryAfterInMs(headers) {
|
|
250
|
+
const retryAfterHeader = headers && headers[HTTP_HEADER_RETRY_AFTER];
|
|
251
|
+
if (retryAfterHeader === undefined) {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
const delayInSecond = parseFloat(retryAfterHeader);
|
|
255
|
+
if (retryAfterHeader === delayInSecond.toString()) {
|
|
256
|
+
return Math.round(delayInSecond * 1000);
|
|
257
|
+
}
|
|
258
|
+
const delayUntilDateTime = Date.parse(retryAfterHeader);
|
|
259
|
+
if (isNaN(delayUntilDateTime)) {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
return delayUntilDateTime - Date.now();
|
|
263
|
+
}
|
|
264
|
+
|
|
238
265
|
function buildLuvioOverrideForDraftAdapters(luvio, handler, extractTargetIdFromCacheKey, options = {}) {
|
|
239
266
|
// override this to create and enqueue a new draft action, and return synthetic response
|
|
240
267
|
const dispatchResourceRequest = async function (resourceRequest, _context) {
|
|
241
268
|
const resourceRequestCopy = clone(resourceRequest);
|
|
242
269
|
resourceRequestCopy.headers = resourceRequestCopy.headers || {};
|
|
243
|
-
resourceRequestCopy.headers[
|
|
270
|
+
resourceRequestCopy.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
244
271
|
const { data } = await handler.enqueue(resourceRequestCopy).catch((err) => {
|
|
245
272
|
throw transformErrorToDraftSynthesisError(err);
|
|
246
273
|
});
|
|
@@ -603,11 +630,19 @@ class DurableDraftQueue {
|
|
|
603
630
|
},
|
|
604
631
|
});
|
|
605
632
|
}
|
|
606
|
-
async actionFailed(action, retry) {
|
|
633
|
+
async actionFailed(action, retry, retryDelayInMs, actionDataChanged) {
|
|
634
|
+
if (actionDataChanged === true) {
|
|
635
|
+
await this.draftStore.writeAction({
|
|
636
|
+
...action,
|
|
637
|
+
status: DraftActionStatus.Pending,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
607
640
|
this.uploadingActionId = undefined;
|
|
608
641
|
if (retry && this.state !== DraftQueueState.Stopped) {
|
|
609
642
|
this.state = DraftQueueState.Waiting;
|
|
610
|
-
|
|
643
|
+
return retryDelayInMs !== undefined
|
|
644
|
+
? this.scheduleRetryWithSpecifiedDelay(retryDelayInMs)
|
|
645
|
+
: this.scheduleRetry();
|
|
611
646
|
}
|
|
612
647
|
else if (isDraftError(action)) {
|
|
613
648
|
return this.handleServerError(action, action.error);
|
|
@@ -753,14 +788,17 @@ class DurableDraftQueue {
|
|
|
753
788
|
});
|
|
754
789
|
return action;
|
|
755
790
|
}
|
|
756
|
-
|
|
757
|
-
const newInterval = this.retryIntervalMilliseconds * 2;
|
|
758
|
-
this.retryIntervalMilliseconds = Math.min(Math.max(newInterval, this.minimumRetryInterval), this.maximumRetryInterval);
|
|
791
|
+
scheduleRetryWithSpecifiedDelay(retryDelayInMs) {
|
|
759
792
|
this.timeoutHandler = setTimeout(() => {
|
|
760
793
|
if (this.state !== DraftQueueState.Stopped) {
|
|
761
794
|
this.processNextAction();
|
|
762
795
|
}
|
|
763
|
-
},
|
|
796
|
+
}, retryDelayInMs);
|
|
797
|
+
}
|
|
798
|
+
scheduleRetry() {
|
|
799
|
+
const newInterval = this.retryIntervalMilliseconds * 2;
|
|
800
|
+
this.retryIntervalMilliseconds = Math.min(Math.max(newInterval, this.minimumRetryInterval), this.maximumRetryInterval);
|
|
801
|
+
this.scheduleRetryWithSpecifiedDelay(this.retryIntervalMilliseconds);
|
|
764
802
|
}
|
|
765
803
|
async getActionsForReplaceOrMerge(targetActionId, sourceActionId) {
|
|
766
804
|
const actions = await this.getQueueActions();
|
|
@@ -1076,14 +1114,47 @@ class AbstractResourceRequestActionHandler {
|
|
|
1076
1114
|
});
|
|
1077
1115
|
return ProcessActionResult.ACTION_SUCCEEDED;
|
|
1078
1116
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1117
|
+
let shouldRetry = false;
|
|
1118
|
+
let retryDelayInMs = undefined;
|
|
1119
|
+
let actionDataChanged = false;
|
|
1120
|
+
let updatedAction = action;
|
|
1121
|
+
if (request && request.headers && request.headers[HTTP_HEADER_IDEMPOTENCY_KEY]) {
|
|
1122
|
+
const status = response.status;
|
|
1123
|
+
switch (status) {
|
|
1124
|
+
case 408 /* IdempotentWriteSpecificHttpStatusCode.RequestTimeout */:
|
|
1125
|
+
case 503 /* IdempotentWriteSpecificHttpStatusCode.ServiceUnavailable */:
|
|
1126
|
+
retryDelayInMs = getRetryAfterInMs(response.headers);
|
|
1127
|
+
shouldRetry = true;
|
|
1128
|
+
break;
|
|
1129
|
+
case HttpStatusCode.ServerError:
|
|
1130
|
+
shouldRetry = true;
|
|
1131
|
+
break;
|
|
1132
|
+
case 409 /* IdempotentWriteSpecificHttpStatusCode.Conflict */: {
|
|
1133
|
+
const errorCode = JSON.parse(response.body)[0].errorCode;
|
|
1134
|
+
if (errorCode === ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER) {
|
|
1135
|
+
updatedAction.data.headers = updatedAction.data.headers || {};
|
|
1136
|
+
updatedAction.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
1137
|
+
retryDelayInMs = 0;
|
|
1138
|
+
actionDataChanged = true;
|
|
1139
|
+
}
|
|
1140
|
+
else if (errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST) {
|
|
1141
|
+
retryDelayInMs = getRetryAfterInMs(response.headers);
|
|
1142
|
+
}
|
|
1143
|
+
shouldRetry = true;
|
|
1144
|
+
break;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
await actionErrored(shouldRetry
|
|
1149
|
+
? updatedAction
|
|
1150
|
+
: {
|
|
1151
|
+
...updatedAction,
|
|
1152
|
+
error: response,
|
|
1153
|
+
status: DraftActionStatus.Error,
|
|
1154
|
+
}, shouldRetry, retryDelayInMs, actionDataChanged);
|
|
1084
1155
|
return ProcessActionResult.ACTION_ERRORED;
|
|
1085
1156
|
}
|
|
1086
|
-
catch (
|
|
1157
|
+
catch (e) {
|
|
1087
1158
|
await actionErrored(action, true);
|
|
1088
1159
|
return ProcessActionResult.NETWORK_ERROR;
|
|
1089
1160
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { DispatchResourceRequestContext, Luvio, Snapshot } from '@luvio/engine';
|
|
2
2
|
import type { AbstractResourceRequestActionHandler } from './actionHandlers/AbstractResourceRequestActionHandler';
|
|
3
3
|
export type AdapterBuildNetworkSnapshot<Config, Response> = (luvio: Luvio, config: Config, dispatchResourceRequestContext?: DispatchResourceRequestContext) => Promise<Snapshot<Response>>;
|
|
4
|
-
export declare const IdempotencyKey = "Idempotency-Key";
|
|
5
4
|
export declare function buildLuvioOverrideForDraftAdapters<ResponseType = unknown, DraftMetadata = unknown>(luvio: Luvio, handler: AbstractResourceRequestActionHandler<ResponseType, DraftMetadata>, extractTargetIdFromCacheKey: (cacheKey: string) => string | undefined, options?: {
|
|
6
5
|
forDeleteAdapter?: boolean;
|
|
7
6
|
}): Luvio;
|
|
@@ -34,7 +34,7 @@ export declare class DurableDraftQueue implements DraftQueue {
|
|
|
34
34
|
enqueue<Data, Response>(handlerId: string, data: unknown): Promise<EnqueueResult<Data, Response>>;
|
|
35
35
|
registerOnChangedListener(listener: DraftQueueChangeListener): () => Promise<void>;
|
|
36
36
|
actionCompleted(action: CompletedDraftAction<unknown, unknown>): Promise<void>;
|
|
37
|
-
actionFailed(action: DraftAction<unknown, unknown>, retry: boolean): Promise<void>;
|
|
37
|
+
actionFailed(action: DraftAction<unknown, unknown>, retry: boolean, retryDelayInMs?: number, actionDataChanged?: boolean): Promise<void>;
|
|
38
38
|
handle(action: DraftAction<unknown, unknown>): Promise<ProcessActionResult>;
|
|
39
39
|
processNextAction(): Promise<ProcessActionResult>;
|
|
40
40
|
private handleServerError;
|
|
@@ -48,6 +48,7 @@ export declare class DurableDraftQueue implements DraftQueue {
|
|
|
48
48
|
replaceAction<Data, Response>(targetActionId: string, sourceActionId: string): Promise<DraftAction<Data, Response>>;
|
|
49
49
|
mergeActions<Data, Response>(targetActionId: string, sourceActionId: string): Promise<DraftAction<Data, Response>>;
|
|
50
50
|
setMetadata(actionId: string, metadata: DraftActionMetadata): Promise<DraftAction<unknown, unknown>>;
|
|
51
|
+
private scheduleRetryWithSpecifiedDelay;
|
|
51
52
|
private scheduleRetry;
|
|
52
53
|
private getActionsForReplaceOrMerge;
|
|
53
54
|
private assertReplaceOrMergePrerequisites;
|
|
@@ -16,7 +16,7 @@ export declare abstract class AbstractResourceRequestActionHandler<ResponseType,
|
|
|
16
16
|
};
|
|
17
17
|
constructor(draftQueue: DraftQueue, networkAdapter: NetworkAdapter, getLuvio: () => Luvio);
|
|
18
18
|
enqueue(data: ResourceRequest): Promise<EnqueueResult<ResourceRequest, ResponseType>>;
|
|
19
|
-
handleAction(action: DraftAction<ResourceRequest, ResponseType>, actionCompleted: (action: CompletedDraftAction<ResourceRequest, ResponseType>) => Promise<void>, actionErrored: (action: DraftAction<ResourceRequest, ResponseType>, retry: boolean) => Promise<void>): Promise<ProcessActionResult>;
|
|
19
|
+
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>;
|
|
20
20
|
buildPendingAction(request: ResourceRequest, queue: DraftAction<unknown, unknown>[]): Promise<PendingDraftAction<ResourceRequest>>;
|
|
21
21
|
handleActionEnqueued(action: PendingDraftAction<ResourceRequest>): Promise<void>;
|
|
22
22
|
handleActionRemoved(action: DraftAction<ResourceRequest, ResponseType>): Promise<void>;
|
|
@@ -57,7 +57,7 @@ export interface ActionHandler<Data, DraftMetadata, Type> {
|
|
|
57
57
|
* @param actionCompleted callback function when the action completes successfully
|
|
58
58
|
* @param actionErrored callback function invoked when the action errors
|
|
59
59
|
*/
|
|
60
|
-
handleAction(action: DraftAction<Data, Type>, actionCompleted: (action: CompletedDraftAction<Data, Type>) => Promise<void>, actionErrored: (action: DraftAction<Data, Type>, retry: boolean) => Promise<void>): Promise<ProcessActionResult>;
|
|
60
|
+
handleAction(action: DraftAction<Data, Type>, actionCompleted: (action: CompletedDraftAction<Data, Type>) => Promise<void>, actionErrored: (action: DraftAction<Data, Type>, retry: boolean, retryDelayInMs?: number, actionDataChanged?: boolean) => Promise<void>): Promise<ProcessActionResult>;
|
|
61
61
|
/**
|
|
62
62
|
* Invoked by the draft queue after an action for this handler is removed from the queue
|
|
63
63
|
* @param action: the removed action
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The http status code which could be returned from idempotent write.
|
|
14
|
+
*/
|
|
15
|
+
export type IdempotentWriteHttpStatusCode = IdempotentWriteSpecificHttpStatusCode | HttpStatusCode;
|
|
16
|
+
export declare const HTTP_HEADER_RETRY_AFTER = "Retry-After";
|
|
17
|
+
export declare const HTTP_HEADER_IDEMPOTENCY_KEY = "Idempotency-Key";
|
|
18
|
+
export declare const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = "IDEMPOTENCY_KEY_USED_DIFFERENT_USER";
|
|
19
|
+
export declare const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = "IDEMPOTENCY_CONCURRENT_REQUEST";
|
|
20
|
+
/**
|
|
21
|
+
* Get the retry after in milliseconds from the response headers, undefined if not specified.
|
|
22
|
+
* The header could have two different format.
|
|
23
|
+
* Retry-After: <http-date>, like Wed, 21 Oct 2015 07:28:00 GMT
|
|
24
|
+
* Retry-After: <delay-seconds>, like 1.5s
|
|
25
|
+
* @param headers http headers
|
|
26
|
+
* @returns the time to delat in millisconds.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getRetryAfterInMs(headers?: Headers): number | undefined;
|