@salesforce/lds-drafts 1.315.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 +50 -519
- package/dist/types/DraftQueue.d.ts +12 -7
- package/dist/types/DraftStore.d.ts +1 -0
- package/dist/types/DurableDraftQueue.d.ts +1 -0
- package/dist/types/DurableDraftStore.d.ts +1 -0
- package/dist/types/main.d.ts +2 -3
- package/package.json +5 -2
- package/dist/types/actionHandlers/AbstractResourceRequestActionHandler.d.ts +0 -71
- package/dist/types/actionHandlers/IdempotentWrite.d.ts +0 -33
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) {
|
|
@@ -385,6 +384,11 @@ class DurableDraftQueue {
|
|
|
385
384
|
this.userState = DraftQueueState.Stopped;
|
|
386
385
|
this.uploadingActionId = undefined;
|
|
387
386
|
this.timeoutHandler = undefined;
|
|
387
|
+
this.logger = typeof __nimbus !== 'undefined' &&
|
|
388
|
+
__nimbus.plugins !== undefined &&
|
|
389
|
+
__nimbus.plugins.JSLoggerPlugin !== undefined
|
|
390
|
+
? __nimbus.plugins.JSLoggerPlugin
|
|
391
|
+
: undefined;
|
|
388
392
|
this.handlers = {};
|
|
389
393
|
this.draftStore = draftStore;
|
|
390
394
|
this.workerPool = new AsyncWorkerPool(1);
|
|
@@ -425,12 +429,13 @@ class DurableDraftQueue {
|
|
|
425
429
|
await this.notifyChangedListeners({
|
|
426
430
|
type: DraftQueueEventType.QueueStateChanged,
|
|
427
431
|
state: this.state,
|
|
432
|
+
draftCount: this.draftStore.getCount(),
|
|
428
433
|
});
|
|
429
434
|
const result = await this.processNextAction();
|
|
430
435
|
switch (result) {
|
|
431
436
|
case ProcessActionResult.BLOCKED_ON_ERROR:
|
|
432
437
|
this.state = DraftQueueState.Error;
|
|
433
|
-
return Promise.reject();
|
|
438
|
+
return Promise.reject('Unable to start queue - first action is in error state');
|
|
434
439
|
default:
|
|
435
440
|
return Promise.resolve();
|
|
436
441
|
}
|
|
@@ -441,16 +446,22 @@ class DurableDraftQueue {
|
|
|
441
446
|
// Do nothing if the queue state is already stopped
|
|
442
447
|
return Promise.resolve();
|
|
443
448
|
}
|
|
444
|
-
this.stopQueueManually();
|
|
449
|
+
this.stopQueueManually(false);
|
|
445
450
|
return this.notifyChangedListeners({
|
|
446
451
|
type: DraftQueueEventType.QueueStateChanged,
|
|
447
452
|
state: DraftQueueState.Stopped,
|
|
453
|
+
draftCount: this.draftStore.getCount(),
|
|
448
454
|
});
|
|
449
455
|
}
|
|
450
456
|
/**
|
|
451
457
|
* Used to stop the queue within DraftQueue without user interaction
|
|
452
458
|
*/
|
|
453
|
-
stopQueueManually() {
|
|
459
|
+
stopQueueManually(internalReason) {
|
|
460
|
+
if (this.logger) {
|
|
461
|
+
this.logger.logInfo(internalReason
|
|
462
|
+
? 'Draft queue stopped for internal reason'
|
|
463
|
+
: 'Draft queue stopped by app');
|
|
464
|
+
}
|
|
454
465
|
if (this.timeoutHandler) {
|
|
455
466
|
clearTimeout(this.timeoutHandler);
|
|
456
467
|
this.timeoutHandler = undefined;
|
|
@@ -460,9 +471,6 @@ class DurableDraftQueue {
|
|
|
460
471
|
async getQueueActions() {
|
|
461
472
|
const drafts = (await this.draftStore.getAllDrafts());
|
|
462
473
|
const queue = [];
|
|
463
|
-
if (drafts === undefined) {
|
|
464
|
-
return queue;
|
|
465
|
-
}
|
|
466
474
|
drafts.forEach((draft) => {
|
|
467
475
|
if (draft.id === this.uploadingActionId) {
|
|
468
476
|
draft.status = DraftActionStatus.Uploading;
|
|
@@ -493,6 +501,7 @@ class DurableDraftQueue {
|
|
|
493
501
|
await this.notifyChangedListeners({
|
|
494
502
|
type: DraftQueueEventType.ActionAdded,
|
|
495
503
|
action: pendingAction,
|
|
504
|
+
draftCount: this.draftStore.getCount(),
|
|
496
505
|
});
|
|
497
506
|
await handler.handleActionEnqueued(pendingAction, queue);
|
|
498
507
|
if (this.state === DraftQueueState.Started) {
|
|
@@ -525,6 +534,7 @@ class DurableDraftQueue {
|
|
|
525
534
|
await this.notifyChangedListeners({
|
|
526
535
|
type: DraftQueueEventType.ActionCompleted,
|
|
527
536
|
action,
|
|
537
|
+
draftCount: this.draftStore.getCount(),
|
|
528
538
|
});
|
|
529
539
|
if (this.state === DraftQueueState.Started) {
|
|
530
540
|
this.processNextAction();
|
|
@@ -574,6 +584,7 @@ class DurableDraftQueue {
|
|
|
574
584
|
await this.notifyChangedListeners({
|
|
575
585
|
type: DraftQueueEventType.ActionFailed,
|
|
576
586
|
action: action,
|
|
587
|
+
draftCount: this.draftStore.getCount(),
|
|
577
588
|
});
|
|
578
589
|
return ProcessActionResult.BLOCKED_ON_ERROR;
|
|
579
590
|
}
|
|
@@ -590,6 +601,7 @@ class DurableDraftQueue {
|
|
|
590
601
|
await this.notifyChangedListeners({
|
|
591
602
|
type: DraftQueueEventType.ActionUploading,
|
|
592
603
|
action: { ...action, status: DraftActionStatus.Uploading },
|
|
604
|
+
draftCount: this.draftStore.getCount(),
|
|
593
605
|
});
|
|
594
606
|
return this.handle(action);
|
|
595
607
|
}
|
|
@@ -611,6 +623,7 @@ class DurableDraftQueue {
|
|
|
611
623
|
return this.notifyChangedListeners({
|
|
612
624
|
type: DraftQueueEventType.ActionFailed,
|
|
613
625
|
action: errorAction,
|
|
626
|
+
draftCount: this.draftStore.getCount(),
|
|
614
627
|
});
|
|
615
628
|
}
|
|
616
629
|
async notifyChangedListeners(event) {
|
|
@@ -657,6 +670,7 @@ class DurableDraftQueue {
|
|
|
657
670
|
await this.notifyChangedListeners({
|
|
658
671
|
type: DraftQueueEventType.ActionDeleted,
|
|
659
672
|
action,
|
|
673
|
+
draftCount: this.draftStore.getCount(),
|
|
660
674
|
});
|
|
661
675
|
if (this.userState === DraftQueueState.Started &&
|
|
662
676
|
this.state !== DraftQueueState.Started &&
|
|
@@ -666,7 +680,7 @@ class DurableDraftQueue {
|
|
|
666
680
|
}
|
|
667
681
|
async updateDraftAction(action) {
|
|
668
682
|
// stop queue manually
|
|
669
|
-
this.stopQueueManually();
|
|
683
|
+
this.stopQueueManually(true);
|
|
670
684
|
const actionStatus = await this.statusOfAction(action.id);
|
|
671
685
|
if (actionStatus === DraftActionStatus.Uploading) {
|
|
672
686
|
return Promise.reject('cannot update an uploading action');
|
|
@@ -696,7 +710,7 @@ class DurableDraftQueue {
|
|
|
696
710
|
return this.replaceOrMergeActions(targetActionId, sourceActionId, true);
|
|
697
711
|
}
|
|
698
712
|
async retryAction(actionId) {
|
|
699
|
-
this.stopQueueManually();
|
|
713
|
+
this.stopQueueManually(true);
|
|
700
714
|
const actions = await this.getQueueActions();
|
|
701
715
|
const target = actions[0];
|
|
702
716
|
if (!target || target.id !== actionId) {
|
|
@@ -714,6 +728,7 @@ class DurableDraftQueue {
|
|
|
714
728
|
await this.notifyChangedListeners({
|
|
715
729
|
type: DraftQueueEventType.ActionUpdated,
|
|
716
730
|
action: pendingAction,
|
|
731
|
+
draftCount: this.draftStore.getCount(),
|
|
717
732
|
});
|
|
718
733
|
await this.startQueueSafe();
|
|
719
734
|
return pendingAction;
|
|
@@ -739,6 +754,7 @@ class DurableDraftQueue {
|
|
|
739
754
|
await this.notifyChangedListeners({
|
|
740
755
|
type: DraftQueueEventType.ActionUpdated,
|
|
741
756
|
action: action,
|
|
757
|
+
draftCount: this.draftStore.getCount(),
|
|
742
758
|
});
|
|
743
759
|
return action;
|
|
744
760
|
}
|
|
@@ -746,6 +762,7 @@ class DurableDraftQueue {
|
|
|
746
762
|
await this.notifyChangedListeners({
|
|
747
763
|
type: DraftQueueEventType.QueueStateChanged,
|
|
748
764
|
state: DraftQueueState.Waiting,
|
|
765
|
+
draftCount: this.draftStore.getCount(),
|
|
749
766
|
});
|
|
750
767
|
this.timeoutHandler = setTimeout(() => {
|
|
751
768
|
if (this.state !== DraftQueueState.Stopped) {
|
|
@@ -804,7 +821,7 @@ class DurableDraftQueue {
|
|
|
804
821
|
if (this.replacingAction !== undefined) {
|
|
805
822
|
throw Error('Cannot replace/merge actions while a replace/merge action operation is in progress.');
|
|
806
823
|
}
|
|
807
|
-
this.stopQueueManually();
|
|
824
|
+
this.stopQueueManually(true);
|
|
808
825
|
const promise = this.getActionsForReplaceOrMerge(targetActionId, sourceActionId).then(async ({ target, source }) => {
|
|
809
826
|
// put in a try/finally block so we don't leave this.replacingAction
|
|
810
827
|
// indefinitely set
|
|
@@ -820,6 +837,7 @@ class DurableDraftQueue {
|
|
|
820
837
|
await this.notifyChangedListeners({
|
|
821
838
|
type: DraftQueueEventType.ActionUpdated,
|
|
822
839
|
action: updatedTarget,
|
|
840
|
+
draftCount: this.draftStore.getCount(),
|
|
823
841
|
});
|
|
824
842
|
// remove the source from queue
|
|
825
843
|
await this.removeDraftAction(sourceActionId);
|
|
@@ -958,6 +976,9 @@ class DurableDraftStore {
|
|
|
958
976
|
};
|
|
959
977
|
return this.enqueueAction(action);
|
|
960
978
|
}
|
|
979
|
+
getCount() {
|
|
980
|
+
return keys(this.draftStore).length;
|
|
981
|
+
}
|
|
961
982
|
/**
|
|
962
983
|
* Runs a write operation against the draft store, if the initial
|
|
963
984
|
* revive is still in progress, the action gets enqueued to run once the
|
|
@@ -1001,23 +1022,15 @@ class DurableDraftStore {
|
|
|
1001
1022
|
for (let i = 0, len = keys$1.length; i < len; i++) {
|
|
1002
1023
|
const entry = durableEntries[keys$1[i]];
|
|
1003
1024
|
const action = entry.data;
|
|
1004
|
-
if (
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
return Promise.reject('Unexpected draft action version found in the durable store');
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
draftStore[action.id] = action;
|
|
1014
|
-
}
|
|
1015
|
-
else {
|
|
1016
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
1017
|
-
const err = new Error('Expected draft action to be defined in the durable store');
|
|
1018
|
-
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');
|
|
1019
1031
|
}
|
|
1020
1032
|
}
|
|
1033
|
+
draftStore[action.id] = action;
|
|
1021
1034
|
}
|
|
1022
1035
|
return this.runQueuedOperations();
|
|
1023
1036
|
})
|
|
@@ -1053,494 +1066,6 @@ class DurableDraftStore {
|
|
|
1053
1066
|
}
|
|
1054
1067
|
}
|
|
1055
1068
|
|
|
1056
|
-
const HTTP_HEADER_RETRY_AFTER = 'Retry-After';
|
|
1057
|
-
const HTTP_HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';
|
|
1058
|
-
const ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED = 'IDEMPOTENCY_FEATURE_NOT_ENABLED';
|
|
1059
|
-
const ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED = 'IDEMPOTENCY_NOT_SUPPORTED';
|
|
1060
|
-
const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = 'IDEMPOTENCY_KEY_USED_DIFFERENT_USER';
|
|
1061
|
-
const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = 'IDEMPOTENCY_CONCURRENT_REQUEST';
|
|
1062
|
-
const ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED = 'IDEMPOTENCY_KEY_ALREADY_USED';
|
|
1063
|
-
const ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR = 'IDEMPOTENCY_BACKEND_OPERATION_ERROR';
|
|
1064
|
-
/**
|
|
1065
|
-
* Get the retry after in milliseconds from the response headers, undefined if not specified.
|
|
1066
|
-
* The header could have two different format.
|
|
1067
|
-
* Retry-After: <http-date>, like Wed, 21 Oct 2015 07:28:00 GMT
|
|
1068
|
-
* Retry-After: <delay-seconds>, like 1.5s
|
|
1069
|
-
* @param headers http headers
|
|
1070
|
-
* @returns the time to delat in millisconds.
|
|
1071
|
-
*/
|
|
1072
|
-
function getRetryAfterInMs(headers) {
|
|
1073
|
-
const retryAfterHeader = headers && headers[HTTP_HEADER_RETRY_AFTER];
|
|
1074
|
-
if (retryAfterHeader === undefined) {
|
|
1075
|
-
return undefined;
|
|
1076
|
-
}
|
|
1077
|
-
const delayInSecond = parseFloat(retryAfterHeader);
|
|
1078
|
-
if (retryAfterHeader === delayInSecond.toString()) {
|
|
1079
|
-
return Math.round(delayInSecond * 1000);
|
|
1080
|
-
}
|
|
1081
|
-
const delayUntilDateTime = Date.parse(retryAfterHeader);
|
|
1082
|
-
if (isNaN(delayUntilDateTime)) {
|
|
1083
|
-
return undefined;
|
|
1084
|
-
}
|
|
1085
|
-
return delayUntilDateTime - Date.now();
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
const DEFAULT_FIELD_LAST_MODIFIED_DATE = 'LastModifiedDate';
|
|
1089
|
-
const DEFAULT_FIELD_CREATED_DATE = 'CreatedDate';
|
|
1090
|
-
class AbstractResourceRequestActionHandler {
|
|
1091
|
-
constructor(draftQueue, networkAdapter, getLuvio) {
|
|
1092
|
-
this.draftQueue = draftQueue;
|
|
1093
|
-
this.networkAdapter = networkAdapter;
|
|
1094
|
-
this.getLuvio = getLuvio;
|
|
1095
|
-
// NOTE[W-12567340]: This property stores in-memory mappings between draft
|
|
1096
|
-
// ids and canonical ids for the current session. Having a local copy of
|
|
1097
|
-
// these mappings is necessary to avoid a race condition between publishing
|
|
1098
|
-
// new mappings to the durable store and those mappings being loaded into
|
|
1099
|
-
// the luvio store redirect table, during which a new draft might be enqueued
|
|
1100
|
-
// which would not see a necessary mapping.
|
|
1101
|
-
this.ephemeralRedirects = {};
|
|
1102
|
-
// determined by Server setup.
|
|
1103
|
-
this.isIdempotencySupported = true;
|
|
1104
|
-
// idempotency write flag set by lds
|
|
1105
|
-
this.isLdsIdempotencyWriteDisabled = ldsIdempotencyWriteDisabled.isOpen({
|
|
1106
|
-
fallback: false,
|
|
1107
|
-
});
|
|
1108
|
-
this.isBackdatingEnabled = ldsBackdatingEnabled.isOpen({ fallback: false });
|
|
1109
|
-
}
|
|
1110
|
-
enqueue(data) {
|
|
1111
|
-
return this.draftQueue.enqueue(this.handlerId, data);
|
|
1112
|
-
}
|
|
1113
|
-
async handleAction(action, actionCompleted, actionErrored) {
|
|
1114
|
-
const { data: request } = action;
|
|
1115
|
-
// no context is stored in draft action
|
|
1116
|
-
try {
|
|
1117
|
-
const response = await this.networkAdapter(request, {});
|
|
1118
|
-
if (response.ok) {
|
|
1119
|
-
await actionCompleted({
|
|
1120
|
-
...action,
|
|
1121
|
-
response,
|
|
1122
|
-
status: DraftActionStatus.Completed,
|
|
1123
|
-
});
|
|
1124
|
-
return ProcessActionResult.ACTION_SUCCEEDED;
|
|
1125
|
-
}
|
|
1126
|
-
let shouldRetry = false;
|
|
1127
|
-
let retryDelayInMs = undefined;
|
|
1128
|
-
let actionDataChanged = false;
|
|
1129
|
-
let updatedAction = action;
|
|
1130
|
-
if (request && request.headers && request.headers[HTTP_HEADER_IDEMPOTENCY_KEY]) {
|
|
1131
|
-
const status = response.status;
|
|
1132
|
-
switch (status) {
|
|
1133
|
-
case 408 /* IdempotentWriteSpecificHttpStatusCode.RequestTimeout */:
|
|
1134
|
-
case 503 /* IdempotentWriteSpecificHttpStatusCode.ServiceUnavailable */:
|
|
1135
|
-
retryDelayInMs = getRetryAfterInMs(response.headers);
|
|
1136
|
-
shouldRetry = true;
|
|
1137
|
-
break;
|
|
1138
|
-
case HttpStatusCode.ServerError: {
|
|
1139
|
-
shouldRetry = true;
|
|
1140
|
-
if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR)) {
|
|
1141
|
-
this.isIdempotencySupported = false;
|
|
1142
|
-
retryDelayInMs = 0;
|
|
1143
|
-
actionDataChanged = true;
|
|
1144
|
-
}
|
|
1145
|
-
break;
|
|
1146
|
-
}
|
|
1147
|
-
case 409 /* IdempotentWriteSpecificHttpStatusCode.Conflict */: {
|
|
1148
|
-
if (this.isUiApiErrors(response.body)) {
|
|
1149
|
-
const errorCode = response.body[0].errorCode;
|
|
1150
|
-
if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER)) {
|
|
1151
|
-
retryDelayInMs = 0;
|
|
1152
|
-
actionDataChanged = true;
|
|
1153
|
-
}
|
|
1154
|
-
else if (errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST) {
|
|
1155
|
-
retryDelayInMs = getRetryAfterInMs(response.headers);
|
|
1156
|
-
}
|
|
1157
|
-
shouldRetry = true;
|
|
1158
|
-
}
|
|
1159
|
-
break;
|
|
1160
|
-
}
|
|
1161
|
-
case HttpStatusCode.BadRequest: {
|
|
1162
|
-
if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED, ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED)) {
|
|
1163
|
-
retryDelayInMs = 0;
|
|
1164
|
-
actionDataChanged = true;
|
|
1165
|
-
shouldRetry = true;
|
|
1166
|
-
}
|
|
1167
|
-
break;
|
|
1168
|
-
}
|
|
1169
|
-
case 422 /* IdempotentWriteSpecificHttpStatusCode.UnProcessableEntity */: {
|
|
1170
|
-
if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED)) {
|
|
1171
|
-
retryDelayInMs = 0;
|
|
1172
|
-
actionDataChanged = true;
|
|
1173
|
-
shouldRetry = true;
|
|
1174
|
-
}
|
|
1175
|
-
break;
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
if (this.isBackdatingEnabled &&
|
|
1180
|
-
response.status === HttpStatusCode.BadRequest &&
|
|
1181
|
-
this.isBackdatingError(response.body, action)) {
|
|
1182
|
-
updatedAction.timestamp = Date.now();
|
|
1183
|
-
updatedAction.data.body.fields = {
|
|
1184
|
-
...updatedAction.data.body.fields,
|
|
1185
|
-
LastModifiedDate: new Date(updatedAction.timestamp).toISOString(),
|
|
1186
|
-
};
|
|
1187
|
-
if (this.hasIdempotencySupport() &&
|
|
1188
|
-
updatedAction.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY]) {
|
|
1189
|
-
updatedAction.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
1190
|
-
}
|
|
1191
|
-
shouldRetry = true;
|
|
1192
|
-
actionDataChanged = true;
|
|
1193
|
-
}
|
|
1194
|
-
await actionErrored(shouldRetry
|
|
1195
|
-
? updatedAction
|
|
1196
|
-
: {
|
|
1197
|
-
...updatedAction,
|
|
1198
|
-
error: response,
|
|
1199
|
-
status: DraftActionStatus.Error,
|
|
1200
|
-
}, shouldRetry, retryDelayInMs, actionDataChanged);
|
|
1201
|
-
return ProcessActionResult.ACTION_ERRORED;
|
|
1202
|
-
}
|
|
1203
|
-
catch (e) {
|
|
1204
|
-
await actionErrored(action, true);
|
|
1205
|
-
return ProcessActionResult.NETWORK_ERROR;
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
// 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.
|
|
1209
|
-
handleIdempotencyServerError(responseBody, action, updateIdempotencyKey, ...targetErrorCodes) {
|
|
1210
|
-
if (this.isUiApiErrors(responseBody)) {
|
|
1211
|
-
const errorCode = responseBody[0].errorCode;
|
|
1212
|
-
if (targetErrorCodes.includes(errorCode)) {
|
|
1213
|
-
action.data.headers = action.data.headers || {};
|
|
1214
|
-
if (updateIdempotencyKey) {
|
|
1215
|
-
action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
1216
|
-
}
|
|
1217
|
-
else {
|
|
1218
|
-
delete action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY];
|
|
1219
|
-
}
|
|
1220
|
-
return true;
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
return false;
|
|
1224
|
-
}
|
|
1225
|
-
// 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.
|
|
1226
|
-
isUiApiErrors(body) {
|
|
1227
|
-
return body !== undefined && isArray(body) && body.length > 0 && body[0].errorCode;
|
|
1228
|
-
}
|
|
1229
|
-
isBackdatingError(body, action) {
|
|
1230
|
-
if (body.enhancedErrorType &&
|
|
1231
|
-
body.enhancedErrorType === 'RecordError' &&
|
|
1232
|
-
body.output &&
|
|
1233
|
-
body.output.errors &&
|
|
1234
|
-
isArray(body.output.errors) &&
|
|
1235
|
-
body.output.errors.length > 0 &&
|
|
1236
|
-
action.data.body &&
|
|
1237
|
-
action.data.body.fields &&
|
|
1238
|
-
action.data.body.fields[DEFAULT_FIELD_LAST_MODIFIED_DATE]) {
|
|
1239
|
-
return body.output.errors.some((error) => error.errorCode === 'CollisionDetectedException');
|
|
1240
|
-
}
|
|
1241
|
-
return false;
|
|
1242
|
-
}
|
|
1243
|
-
async buildPendingAction(request, queue) {
|
|
1244
|
-
const targetId = await this.getIdFromRequest(request);
|
|
1245
|
-
const tag = this.buildTagForTargetId(targetId);
|
|
1246
|
-
const handlerActions = queue.filter((x) => x.handler === this.handlerId);
|
|
1247
|
-
if (request.method === 'post' && actionsForTag(tag, handlerActions).length > 0) {
|
|
1248
|
-
return Promise.reject(new Error('Cannot enqueue a POST draft action with an existing tag'));
|
|
1249
|
-
}
|
|
1250
|
-
if (deleteActionsForTag(tag, handlerActions).length > 0) {
|
|
1251
|
-
return Promise.reject(new Error('Cannot enqueue a draft action for a deleted record'));
|
|
1252
|
-
}
|
|
1253
|
-
return {
|
|
1254
|
-
handler: this.handlerId,
|
|
1255
|
-
targetId,
|
|
1256
|
-
tag,
|
|
1257
|
-
data: request,
|
|
1258
|
-
status: DraftActionStatus.Pending,
|
|
1259
|
-
id: generateUniqueDraftActionId(queue.map((x) => x.id)),
|
|
1260
|
-
timestamp: Date.now(),
|
|
1261
|
-
metadata: {},
|
|
1262
|
-
version: '242.0.0',
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
async handleActionEnqueued(_action) { }
|
|
1266
|
-
handleActionRemoved(action) {
|
|
1267
|
-
return this.reingestRecord(action);
|
|
1268
|
-
}
|
|
1269
|
-
getQueueOperationsForCompletingDrafts(queue, action) {
|
|
1270
|
-
const queueOperations = [];
|
|
1271
|
-
const redirects = this.getRedirectMappings(action);
|
|
1272
|
-
if (redirects !== undefined) {
|
|
1273
|
-
const { length } = queue;
|
|
1274
|
-
for (let i = 0; i < length; i++) {
|
|
1275
|
-
const queueAction = queue[i];
|
|
1276
|
-
// if this queueAction is the action that is completing we can move on,
|
|
1277
|
-
// it is about to be deleted and won't have the draft ID in it
|
|
1278
|
-
if (queueAction.id === action.id) {
|
|
1279
|
-
continue;
|
|
1280
|
-
}
|
|
1281
|
-
if (isResourceRequestAction(queueAction)) {
|
|
1282
|
-
let queueOperationMutated = false;
|
|
1283
|
-
let updatedActionTag = undefined;
|
|
1284
|
-
let updatedActionTargetId = undefined;
|
|
1285
|
-
const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
|
|
1286
|
-
let { basePath, body } = queueActionRequest;
|
|
1287
|
-
let stringifiedBody = stringify(body);
|
|
1288
|
-
// for each redirected ID/key we loop over the operation to see if it needs
|
|
1289
|
-
// to be updated
|
|
1290
|
-
for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
|
|
1291
|
-
if (basePath.search(draftId) >= 0 || stringifiedBody.search(draftId) >= 0) {
|
|
1292
|
-
basePath = basePath.replace(draftId, canonicalId);
|
|
1293
|
-
stringifiedBody = stringifiedBody.replace(draftId, canonicalId);
|
|
1294
|
-
queueOperationMutated = true;
|
|
1295
|
-
}
|
|
1296
|
-
// if the action is performed on a previous draft id, we need to replace the action
|
|
1297
|
-
// with a new one at the updated canonical key
|
|
1298
|
-
if (queueActionTag === draftKey) {
|
|
1299
|
-
updatedActionTag = canonicalKey;
|
|
1300
|
-
updatedActionTargetId = canonicalId;
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
if (queueOperationMutated) {
|
|
1304
|
-
if (updatedActionTag !== undefined && updatedActionTargetId !== undefined) {
|
|
1305
|
-
const updatedAction = {
|
|
1306
|
-
...queueAction,
|
|
1307
|
-
tag: updatedActionTag,
|
|
1308
|
-
targetId: updatedActionTargetId,
|
|
1309
|
-
data: {
|
|
1310
|
-
...queueActionRequest,
|
|
1311
|
-
basePath: basePath,
|
|
1312
|
-
body: parse(stringifiedBody),
|
|
1313
|
-
},
|
|
1314
|
-
};
|
|
1315
|
-
// item needs to be replaced with a new item at the new record key
|
|
1316
|
-
queueOperations.push({
|
|
1317
|
-
type: QueueOperationType.Delete,
|
|
1318
|
-
id: queueActionId,
|
|
1319
|
-
});
|
|
1320
|
-
queueOperations.push({
|
|
1321
|
-
type: QueueOperationType.Add,
|
|
1322
|
-
action: updatedAction,
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
else {
|
|
1326
|
-
const updatedAction = {
|
|
1327
|
-
...queueAction,
|
|
1328
|
-
data: {
|
|
1329
|
-
...queueActionRequest,
|
|
1330
|
-
basePath: basePath,
|
|
1331
|
-
body: parse(stringifiedBody),
|
|
1332
|
-
},
|
|
1333
|
-
};
|
|
1334
|
-
// item needs to be updated
|
|
1335
|
-
queueOperations.push({
|
|
1336
|
-
type: QueueOperationType.Update,
|
|
1337
|
-
id: queueActionId,
|
|
1338
|
-
action: updatedAction,
|
|
1339
|
-
});
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
// delete completed action
|
|
1346
|
-
queueOperations.push({
|
|
1347
|
-
type: QueueOperationType.Delete,
|
|
1348
|
-
id: action.id,
|
|
1349
|
-
});
|
|
1350
|
-
return queueOperations;
|
|
1351
|
-
}
|
|
1352
|
-
getRedirectMappings(action) {
|
|
1353
|
-
if (action.data.method !== 'post') {
|
|
1354
|
-
return undefined;
|
|
1355
|
-
}
|
|
1356
|
-
const body = action.response.body;
|
|
1357
|
-
const canonicalId = this.getIdFromResponseBody(body);
|
|
1358
|
-
const draftId = action.targetId;
|
|
1359
|
-
if (draftId !== undefined && canonicalId !== undefined && draftId !== canonicalId) {
|
|
1360
|
-
this.ephemeralRedirects[draftId] = canonicalId;
|
|
1361
|
-
}
|
|
1362
|
-
return [
|
|
1363
|
-
{
|
|
1364
|
-
draftId,
|
|
1365
|
-
canonicalId,
|
|
1366
|
-
draftKey: this.buildTagForTargetId(draftId),
|
|
1367
|
-
canonicalKey: this.buildTagForTargetId(canonicalId),
|
|
1368
|
-
},
|
|
1369
|
-
];
|
|
1370
|
-
}
|
|
1371
|
-
async handleActionCompleted(action, queueOperations, allHandlers) {
|
|
1372
|
-
const { data: request, tag } = action;
|
|
1373
|
-
const { method } = request;
|
|
1374
|
-
if (method === 'delete') {
|
|
1375
|
-
return this.evictKey(tag);
|
|
1376
|
-
}
|
|
1377
|
-
const recordsToIngest = [];
|
|
1378
|
-
recordsToIngest.push({
|
|
1379
|
-
response: action.response.body,
|
|
1380
|
-
synchronousIngest: this.synchronousIngest.bind(this),
|
|
1381
|
-
buildCacheKeysForResponse: this.buildCacheKeysFromResponse.bind(this),
|
|
1382
|
-
});
|
|
1383
|
-
const recordsNeedingReplay = queueOperations.filter((x) => x.type === QueueOperationType.Update);
|
|
1384
|
-
for (const recordNeedingReplay of recordsNeedingReplay) {
|
|
1385
|
-
const { action } = recordNeedingReplay;
|
|
1386
|
-
if (isResourceRequestAction(action)) {
|
|
1387
|
-
// We can't assume the queue operation is for our handler, have to find the handler.
|
|
1388
|
-
const handler = allHandlers.find((h) => h.handlerId === action.handler);
|
|
1389
|
-
if (handler !== undefined) {
|
|
1390
|
-
const record = await handler.getDataForAction(action);
|
|
1391
|
-
if (record !== undefined) {
|
|
1392
|
-
recordsToIngest.push({
|
|
1393
|
-
response: record,
|
|
1394
|
-
synchronousIngest: handler.synchronousIngest.bind(handler),
|
|
1395
|
-
buildCacheKeysForResponse: handler.buildCacheKeysFromResponse.bind(handler),
|
|
1396
|
-
});
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
await this.ingestResponses(recordsToIngest, action);
|
|
1402
|
-
}
|
|
1403
|
-
handleReplaceAction(targetAction, sourceAction) {
|
|
1404
|
-
//reject if the action to replace is a POST action
|
|
1405
|
-
const pendingAction = targetAction;
|
|
1406
|
-
if (pendingAction.data.method === 'post') {
|
|
1407
|
-
throw Error('Cannot replace a POST action');
|
|
1408
|
-
}
|
|
1409
|
-
if (this.isActionOfType(targetAction) &&
|
|
1410
|
-
this.isActionOfType(sourceAction)) {
|
|
1411
|
-
targetAction.status = DraftActionStatus.Pending;
|
|
1412
|
-
targetAction.data = sourceAction.data;
|
|
1413
|
-
return targetAction;
|
|
1414
|
-
}
|
|
1415
|
-
else {
|
|
1416
|
-
throw Error('Incompatible Action types to replace one another');
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
mergeActions(targetAction, sourceAction) {
|
|
1420
|
-
const { id: targetId, data: targetData, metadata: targetMetadata, timestamp: targetTimestamp, } = targetAction;
|
|
1421
|
-
const { method: targetMethod, body: targetBody } = targetData;
|
|
1422
|
-
const { data: sourceData, metadata: sourceMetadata } = sourceAction;
|
|
1423
|
-
const { method: sourceMethod, body: sourceBody } = sourceData;
|
|
1424
|
-
if (targetMethod.toLowerCase() === 'delete' || sourceMethod.toLowerCase() === 'delete') {
|
|
1425
|
-
throw Error('Cannot merge DELETE actions.');
|
|
1426
|
-
}
|
|
1427
|
-
if (targetMethod.toLowerCase() === 'patch' && sourceMethod.toLowerCase() === 'post') {
|
|
1428
|
-
// overlaying a POST over a PATCH is not supported
|
|
1429
|
-
throw Error('Cannot merge a POST action over top of a PATCH action.');
|
|
1430
|
-
}
|
|
1431
|
-
// overlay top-level properties, maintain target's timestamp and id
|
|
1432
|
-
const merged = {
|
|
1433
|
-
...targetAction,
|
|
1434
|
-
...sourceAction,
|
|
1435
|
-
timestamp: targetTimestamp,
|
|
1436
|
-
id: targetId,
|
|
1437
|
-
};
|
|
1438
|
-
// overlay data
|
|
1439
|
-
// NOTE: we stick to the target's ResourceRequest properties (except body
|
|
1440
|
-
// which is merged) because we don't want to overwrite a POST with a PATCH
|
|
1441
|
-
// (all other cases should be fine or wouldn't have passed pre-requisites)
|
|
1442
|
-
merged.data = {
|
|
1443
|
-
...targetData,
|
|
1444
|
-
body: this.mergeRequestBody(targetBody, sourceBody),
|
|
1445
|
-
};
|
|
1446
|
-
// Updates Idempotency key if target has one
|
|
1447
|
-
if (targetData.headers && targetData.headers[HTTP_HEADER_IDEMPOTENCY_KEY]) {
|
|
1448
|
-
merged.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
1449
|
-
}
|
|
1450
|
-
// overlay metadata
|
|
1451
|
-
merged.metadata = { ...targetMetadata, ...sourceMetadata };
|
|
1452
|
-
// put status back to pending to auto upload if queue is active and targed is at the head.
|
|
1453
|
-
merged.status = DraftActionStatus.Pending;
|
|
1454
|
-
return merged;
|
|
1455
|
-
}
|
|
1456
|
-
shouldDeleteActionByTagOnRemoval(action) {
|
|
1457
|
-
return action.data.method === 'post';
|
|
1458
|
-
}
|
|
1459
|
-
updateMetadata(_existingMetadata, incomingMetadata) {
|
|
1460
|
-
return incomingMetadata;
|
|
1461
|
-
}
|
|
1462
|
-
isActionOfType(action) {
|
|
1463
|
-
return action.handler === this.handlerId;
|
|
1464
|
-
}
|
|
1465
|
-
async reingestRecord(action) {
|
|
1466
|
-
const record = await this.getDataForAction(action);
|
|
1467
|
-
if (record !== undefined) {
|
|
1468
|
-
await this.ingestResponses([
|
|
1469
|
-
{
|
|
1470
|
-
response: record,
|
|
1471
|
-
synchronousIngest: this.synchronousIngest.bind(this),
|
|
1472
|
-
buildCacheKeysForResponse: this.buildCacheKeysFromResponse.bind(this),
|
|
1473
|
-
},
|
|
1474
|
-
], action);
|
|
1475
|
-
}
|
|
1476
|
-
else {
|
|
1477
|
-
await this.evictKey(action.tag);
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
// Given an action for this handler this method should return the draft IDs.
|
|
1481
|
-
// Most of the time it will simply be the targetId, but certain handlers might
|
|
1482
|
-
// have multiple draft-created IDs so they would override this to return them.
|
|
1483
|
-
getDraftIdsFromAction(action) {
|
|
1484
|
-
return [action.targetId];
|
|
1485
|
-
}
|
|
1486
|
-
hasIdempotencySupport() {
|
|
1487
|
-
return this.isIdempotencySupported && !this.isLdsIdempotencyWriteDisabled;
|
|
1488
|
-
}
|
|
1489
|
-
async ingestResponses(responses, action) {
|
|
1490
|
-
const luvio = this.getLuvio();
|
|
1491
|
-
await luvio.handleSuccessResponse(() => {
|
|
1492
|
-
if (action.status === DraftActionStatus.Completed) {
|
|
1493
|
-
const mappings = this.getRedirectMappings(action);
|
|
1494
|
-
if (mappings) {
|
|
1495
|
-
mappings.forEach((mapping) => {
|
|
1496
|
-
luvio.storeRedirect(mapping.draftKey, mapping.canonicalKey);
|
|
1497
|
-
});
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
for (const entry of responses) {
|
|
1501
|
-
const { response, synchronousIngest } = entry;
|
|
1502
|
-
synchronousIngest(response, action);
|
|
1503
|
-
}
|
|
1504
|
-
return luvio.storeBroadcast();
|
|
1505
|
-
},
|
|
1506
|
-
// getTypeCacheKeysRecord uses the response, not the full path factory
|
|
1507
|
-
// so 2nd parameter will be unused
|
|
1508
|
-
() => {
|
|
1509
|
-
const keySet = new StoreKeyMap();
|
|
1510
|
-
for (const entry of responses) {
|
|
1511
|
-
const { response, buildCacheKeysForResponse } = entry;
|
|
1512
|
-
const set = buildCacheKeysForResponse(response);
|
|
1513
|
-
for (const key of set.keys()) {
|
|
1514
|
-
const value = set.get(key);
|
|
1515
|
-
if (value !== undefined) {
|
|
1516
|
-
keySet.set(key, value);
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
return keySet;
|
|
1521
|
-
});
|
|
1522
|
-
}
|
|
1523
|
-
async evictKey(key) {
|
|
1524
|
-
const luvio = this.getLuvio();
|
|
1525
|
-
await luvio.handleSuccessResponse(() => {
|
|
1526
|
-
luvio.storeEvict(key);
|
|
1527
|
-
return luvio.storeBroadcast();
|
|
1528
|
-
}, () => {
|
|
1529
|
-
return new StoreKeyMap();
|
|
1530
|
-
});
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
function actionsForTag(tag, queue) {
|
|
1534
|
-
return queue.filter((action) => action.tag === tag);
|
|
1535
|
-
}
|
|
1536
|
-
function deleteActionsForTag(tag, queue) {
|
|
1537
|
-
return queue.filter((action) => action.tag === tag && action.data.method === 'delete');
|
|
1538
|
-
}
|
|
1539
|
-
function isResourceRequestAction(action) {
|
|
1540
|
-
const dataAsAny = action.data;
|
|
1541
|
-
return (dataAsAny !== undefined && dataAsAny.method !== undefined && dataAsAny.body !== undefined);
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
1069
|
/**
|
|
1545
1070
|
* Denotes what kind of operation a DraftQueueItem represents.
|
|
1546
1071
|
*/
|
|
@@ -1761,16 +1286,17 @@ class DraftManager {
|
|
|
1761
1286
|
return Promise.reject('cannot edit incompatible action type or uploading actions');
|
|
1762
1287
|
}
|
|
1763
1288
|
action.data.body.fields = { ...action.data.body.fields, ...fields };
|
|
1289
|
+
action.status = DraftActionStatus.Pending;
|
|
1764
1290
|
await this.draftQueue.updateDraftAction(action);
|
|
1765
1291
|
return this.buildDraftQueueItem(action);
|
|
1766
1292
|
}
|
|
1767
1293
|
isValidFieldMap(fields) {
|
|
1768
1294
|
const keys$1 = keys(fields);
|
|
1769
|
-
const validTypes = ['string', 'number', '
|
|
1295
|
+
const validTypes = ['string', 'number', 'boolean'];
|
|
1770
1296
|
for (let i = 0; i < keys$1.length; i++) {
|
|
1771
1297
|
const key = keys$1[i];
|
|
1772
1298
|
const value = fields[key];
|
|
1773
|
-
if (!validTypes.includes(typeof value)) {
|
|
1299
|
+
if (!validTypes.includes(typeof value) && value !== null) {
|
|
1774
1300
|
return false;
|
|
1775
1301
|
}
|
|
1776
1302
|
}
|
|
@@ -1798,6 +1324,7 @@ class DraftManager {
|
|
|
1798
1324
|
}
|
|
1799
1325
|
const data = action.data;
|
|
1800
1326
|
data.body.fields = { ...data.body.fields, ...fields };
|
|
1327
|
+
action.status = DraftActionStatus.Pending;
|
|
1801
1328
|
await this.draftQueue.updateDraftAction(action);
|
|
1802
1329
|
return this.buildDraftQueueItem(action);
|
|
1803
1330
|
}
|
|
@@ -1914,6 +1441,10 @@ class DraftManager {
|
|
|
1914
1441
|
});
|
|
1915
1442
|
}
|
|
1916
1443
|
}
|
|
1444
|
+
function isResourceRequestAction(action) {
|
|
1445
|
+
const dataAsAny = action.data;
|
|
1446
|
+
return (dataAsAny !== undefined && dataAsAny.method !== undefined && dataAsAny.body !== undefined);
|
|
1447
|
+
}
|
|
1917
1448
|
|
|
1918
1449
|
function makeEnvironmentDraftAware(luvio, env, durableStore, handlers, draftQueue) {
|
|
1919
1450
|
const draftMetadata = {};
|
|
@@ -2005,4 +1536,4 @@ function makeEnvironmentDraftAware(luvio, env, durableStore, handlers, draftQueu
|
|
|
2005
1536
|
});
|
|
2006
1537
|
}
|
|
2007
1538
|
|
|
2008
|
-
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 };
|
|
@@ -103,31 +103,36 @@ export declare enum DraftQueueEventType {
|
|
|
103
103
|
*/
|
|
104
104
|
QueueStateChanged = "state"
|
|
105
105
|
}
|
|
106
|
-
export interface
|
|
106
|
+
export interface DraftQueueStats {
|
|
107
|
+
draftCount: number;
|
|
108
|
+
}
|
|
109
|
+
export interface DraftQueueAddEvent extends DraftQueueStats {
|
|
107
110
|
type: DraftQueueEventType.ActionAdded;
|
|
108
111
|
action: PendingDraftAction<unknown>;
|
|
109
112
|
}
|
|
110
|
-
export interface DraftQueueUploadingEvent {
|
|
113
|
+
export interface DraftQueueUploadingEvent extends DraftQueueStats {
|
|
111
114
|
type: DraftQueueEventType.ActionUploading;
|
|
112
115
|
action: UploadingDraftAction<unknown>;
|
|
116
|
+
draftCount: number;
|
|
113
117
|
}
|
|
114
|
-
export interface DraftQueueDeleteEvent {
|
|
118
|
+
export interface DraftQueueDeleteEvent extends DraftQueueStats {
|
|
115
119
|
type: DraftQueueEventType.ActionDeleted;
|
|
116
120
|
action: DraftAction<unknown, unknown>;
|
|
121
|
+
draftCount: number;
|
|
117
122
|
}
|
|
118
|
-
export interface DraftQueueCompleteEvent {
|
|
123
|
+
export interface DraftQueueCompleteEvent extends DraftQueueStats {
|
|
119
124
|
type: DraftQueueEventType.ActionCompleted;
|
|
120
125
|
action: CompletedDraftAction<unknown, unknown>;
|
|
121
126
|
}
|
|
122
|
-
export interface DraftQueueActionFailedEvent {
|
|
127
|
+
export interface DraftQueueActionFailedEvent extends DraftQueueStats {
|
|
123
128
|
type: DraftQueueEventType.ActionFailed;
|
|
124
129
|
action: ErrorDraftAction<unknown>;
|
|
125
130
|
}
|
|
126
|
-
export interface DraftQueueActionUpdatedEvent {
|
|
131
|
+
export interface DraftQueueActionUpdatedEvent extends DraftQueueStats {
|
|
127
132
|
type: DraftQueueEventType.ActionUpdated;
|
|
128
133
|
action: DraftAction<unknown, unknown>;
|
|
129
134
|
}
|
|
130
|
-
export interface DraftQueueStateChangedEvent {
|
|
135
|
+
export interface DraftQueueStateChangedEvent extends DraftQueueStats {
|
|
131
136
|
type: DraftQueueEventType.QueueStateChanged;
|
|
132
137
|
state: DraftQueueState;
|
|
133
138
|
}
|
|
@@ -21,6 +21,7 @@ export declare class DurableDraftStore implements DraftStore {
|
|
|
21
21
|
deleteDraft(id: string): Promise<void>;
|
|
22
22
|
deleteByTag(tag: string): Promise<void>;
|
|
23
23
|
completeAction(queueOperations: QueueOperation[]): Promise<void>;
|
|
24
|
+
getCount(): number;
|
|
24
25
|
/**
|
|
25
26
|
* Runs a write operation against the draft store, if the initial
|
|
26
27
|
* revive is still in progress, the action gets enqueued to run once the
|
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,7 +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
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@salesforce/nimbus-plugin-lds": "^1.317.0"
|
|
30
33
|
},
|
|
31
34
|
"volta": {
|
|
32
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;
|