@salesforce/lds-drafts 1.100.3 → 1.100.5

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.
@@ -144,15 +144,19 @@ export interface UpdateQueueOperation {
144
144
  action: DraftAction<unknown, unknown>;
145
145
  }
146
146
  export type QueueOperation = UpdateQueueOperation | AddQueueOperation | DeleteQueueOperation;
147
+ export interface EnqueueResult<Data, Response> {
148
+ action: PendingDraftAction<Data>;
149
+ data: Response;
150
+ }
147
151
  export interface DraftQueue {
148
152
  /**
149
153
  * Enqueues a draft action into the DraftQueue
150
154
  * @param handlerId the id of the handler associated with the data
151
155
  * @param data the data the handler will use to create the draft action with
152
- * @returns A promise including the pending action created for the request
156
+ * @returns A promise including the pending action created for the request and the data associated with the action
153
157
  * @throws {Error} An error when a proper action handler is not found or conditions are not met to enqueue the action
154
158
  */
155
- enqueue<Response>(handlerId: string, data: unknown): Promise<PendingDraftAction<Response>>;
159
+ enqueue<Data, Response>(handlerId: string, data: Data): Promise<EnqueueResult<Data, Response>>;
156
160
  /**
157
161
  * add a new handler to the draft queue to process the data in the actions
158
162
  * @param id identifier to the handler
@@ -225,11 +229,5 @@ export interface DraftQueue {
225
229
  * @param metadata The metadata to set on the specified action
226
230
  */
227
231
  setMetadata(actionId: string, metadata: DraftActionMetadata): Promise<DraftAction<unknown, unknown>>;
228
- /**
229
- * Gets the data associated with the passed in action. This data will have drafts applied to it
230
- * @param action
231
- * @returns the data for the given action or undefined if the data no longer exists
232
- */
233
- getDataForAction<T>(action: DraftAction<T, unknown>): Promise<T | undefined>;
234
232
  }
235
233
  export {};
@@ -1,4 +1,4 @@
1
- import type { DraftQueue, DraftAction, CompletedDraftAction, PendingDraftAction, DraftQueueChangeListener, DraftActionMetadata } from './DraftQueue';
1
+ import type { DraftQueue, DraftAction, CompletedDraftAction, DraftQueueChangeListener, DraftActionMetadata, EnqueueResult } from './DraftQueue';
2
2
  import { ProcessActionResult, DraftQueueState } from './DraftQueue';
3
3
  import type { CustomActionExecutor } from './actionHandlers/CustomActionHandler';
4
4
  import type { ActionHandler } from './actionHandlers/ActionHandler';
@@ -16,13 +16,13 @@ export declare class DurableDraftQueue implements DraftQueue {
16
16
  private replacingAction?;
17
17
  private uploadingActionId?;
18
18
  private timeoutHandler;
19
+ private workerPool;
19
20
  private handlers;
20
21
  private getHandler;
21
22
  constructor(draftStore: DraftStore);
22
23
  addHandler<Data>(handler: ActionHandler<Data, unknown, unknown>): Promise<void>;
23
24
  removeHandler(id: string): Promise<void>;
24
25
  addCustomHandler(id: string, executor: CustomActionExecutor): Promise<void>;
25
- getDataForAction<T>(action: DraftAction<T, unknown>): Promise<T | undefined>;
26
26
  getQueueState(): DraftQueueState;
27
27
  startQueue(): Promise<void>;
28
28
  stopQueue(): Promise<void>;
@@ -31,7 +31,7 @@ export declare class DurableDraftQueue implements DraftQueue {
31
31
  */
32
32
  private stopQueueManually;
33
33
  getQueueActions<Data = unknown, Response = unknown>(): Promise<DraftAction<Data, Response>[]>;
34
- enqueue<Data>(handlerId: string, data: unknown): Promise<PendingDraftAction<Data>>;
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
37
  actionFailed(action: DraftAction<unknown, unknown>, retry: boolean): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import type { NetworkAdapter, ResourceRequest, Luvio, DurableStoreKeyMetadataMap } from '@luvio/engine';
2
- import type { DraftAction, CompletedDraftAction, QueueOperation, PendingDraftAction, DraftActionMetadata, DraftQueue } from '../DraftQueue';
2
+ import type { DraftAction, CompletedDraftAction, QueueOperation, PendingDraftAction, DraftActionMetadata, DraftQueue, EnqueueResult } from '../DraftQueue';
3
3
  import { ProcessActionResult } from '../DraftQueue';
4
4
  import type { ActionHandler, DraftIdAndKeyMapping, ReplacingActions } from './ActionHandler';
5
5
  export type ResponseIngestionEntry<T = unknown> = {
@@ -11,8 +11,11 @@ export declare abstract class AbstractResourceRequestActionHandler<ResponseType,
11
11
  protected readonly draftQueue: DraftQueue;
12
12
  protected readonly networkAdapter: NetworkAdapter;
13
13
  protected readonly getLuvio: () => Luvio;
14
+ ephemeralRedirects: {
15
+ [key: string]: string;
16
+ };
14
17
  constructor(draftQueue: DraftQueue, networkAdapter: NetworkAdapter, getLuvio: () => Luvio);
15
- enqueue(data: ResourceRequest): Promise<PendingDraftAction<ResourceRequest>>;
18
+ enqueue(data: ResourceRequest): Promise<EnqueueResult<ResourceRequest, ResponseType>>;
16
19
  handleAction(action: DraftAction<ResourceRequest, ResponseType>, actionCompleted: (action: CompletedDraftAction<ResourceRequest, ResponseType>) => Promise<void>, actionErrored: (action: DraftAction<ResourceRequest, ResponseType>, retry: boolean) => Promise<void>): Promise<ProcessActionResult>;
17
20
  buildPendingAction(request: ResourceRequest, queue: DraftAction<unknown, unknown>[]): Promise<PendingDraftAction<ResourceRequest>>;
18
21
  handleActionEnqueued(action: PendingDraftAction<ResourceRequest>): Promise<void>;
@@ -1,4 +1,4 @@
1
- import type { CompletedDraftAction, DraftAction, DraftActionMetadata, PendingDraftAction, ProcessActionResult, QueueOperation } from '../DraftQueue';
1
+ import type { CompletedDraftAction, DraftAction, DraftActionMetadata, EnqueueResult, PendingDraftAction, ProcessActionResult, QueueOperation } from '../DraftQueue';
2
2
  import type { DraftKeyMapping } from '../DraftIdMapping';
3
3
  export interface ReplacingActions<Response, Data> {
4
4
  original: DraftAction<Response, Data>;
@@ -36,7 +36,7 @@ export interface ActionHandler<Data, DraftMetadata, Type> {
36
36
  * Enqueues an action to the draft queue
37
37
  * @param data the data that will get added to the draft action
38
38
  */
39
- enqueue(data: Data): Promise<PendingDraftAction<Data>>;
39
+ enqueue(data: Data): Promise<EnqueueResult<Data, Type>>;
40
40
  /**
41
41
  * Used to build a PendingDraftAction that is specific to the type of Data and Response.
42
42
  * @param data
package/dist/ldsDrafts.js CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { HttpStatusCode, StoreKeyMap } from '@luvio/engine';
8
+ import { AsyncWorkerPool } from '@salesforce/lds-utils-adapters';
8
9
 
9
10
  var DraftActionStatus;
10
11
  (function (DraftActionStatus) {
@@ -190,14 +191,13 @@ const { isArray } = Array;
190
191
  function buildLuvioOverrideForDraftAdapters(luvio, handler, extractTargetIdFromCacheKey, options = {}) {
191
192
  // override this to create and enqueue a new draft action, and return synthetic response
192
193
  const dispatchResourceRequest = async function (resourceRequest, _context) {
193
- const action = await handler.enqueue(resourceRequest).catch((err) => {
194
+ const { data } = await handler.enqueue(resourceRequest).catch((err) => {
194
195
  throw transformErrorToDraftSynthesisError(err);
195
196
  });
196
- const record = await handler.getDataForAction(action);
197
- if (record === undefined) {
197
+ if (data === undefined) {
198
198
  return Promise.reject(createDraftSynthesisErrorResponse());
199
199
  }
200
- return createOkResponse(record);
200
+ return createOkResponse(data);
201
201
  };
202
202
  // override this to use an infinitely large ttl so the cache entry never expires
203
203
  const publishStoreMetadata = function (key, storeMetadataParams) {
@@ -424,6 +424,7 @@ class DurableDraftQueue {
424
424
  this.timeoutHandler = undefined;
425
425
  this.handlers = {};
426
426
  this.draftStore = draftStore;
427
+ this.workerPool = new AsyncWorkerPool(1);
427
428
  }
428
429
  addHandler(handler) {
429
430
  const id = handler.handlerId;
@@ -441,10 +442,6 @@ class DurableDraftQueue {
441
442
  const handler = customActionHandler(executor, id, this);
442
443
  return this.addHandler(handler);
443
444
  }
444
- async getDataForAction(action) {
445
- const handler = this.getHandler(action.handler);
446
- return handler.getDataForAction(action);
447
- }
448
445
  getQueueState() {
449
446
  return this.state;
450
447
  }
@@ -523,20 +520,26 @@ class DurableDraftQueue {
523
520
  });
524
521
  }
525
522
  async enqueue(handlerId, data) {
526
- let queue = await this.getQueueActions();
527
- const handler = this.getHandler(handlerId);
528
- const pendingAction = (await handler.buildPendingAction(data, queue));
529
- await this.draftStore.writeAction(pendingAction);
530
- queue = await this.getQueueActions();
531
- await this.notifyChangedListeners({
532
- type: DraftQueueEventType.ActionAdded,
533
- action: pendingAction,
523
+ return this.workerPool.push(async () => {
524
+ let queue = await this.getQueueActions();
525
+ const handler = this.getHandler(handlerId);
526
+ const pendingAction = (await handler.buildPendingAction(data, queue));
527
+ await this.draftStore.writeAction(pendingAction);
528
+ queue = await this.getQueueActions();
529
+ await this.notifyChangedListeners({
530
+ type: DraftQueueEventType.ActionAdded,
531
+ action: pendingAction,
532
+ });
533
+ await handler.handleActionEnqueued(pendingAction, queue);
534
+ if (this.state === DraftQueueState.Started) {
535
+ this.processNextAction();
536
+ }
537
+ const actionData = (await handler.getDataForAction(pendingAction));
538
+ return {
539
+ action: pendingAction,
540
+ data: actionData,
541
+ };
534
542
  });
535
- await handler.handleActionEnqueued(pendingAction, queue);
536
- if (this.state === DraftQueueState.Started) {
537
- this.processNextAction();
538
- }
539
- return pendingAction;
540
543
  }
541
544
  registerOnChangedListener(listener) {
542
545
  this.draftQueueChangedListeners.push(listener);
@@ -548,27 +551,29 @@ class DurableDraftQueue {
548
551
  };
549
552
  }
550
553
  async actionCompleted(action) {
551
- const handler = this.getHandler(action.handler);
552
- let queue = await this.getQueueActions();
553
- const queueOperations = handler.getQueueOperationsForCompletingDrafts(queue, action);
554
- const idAndKeyMappings = handler.getRedirectMappings(action);
555
- const keyMappings = idAndKeyMappings === undefined
556
- ? undefined
557
- : idAndKeyMappings.map((m) => {
558
- return { draftKey: m.draftKey, canonicalKey: m.canonicalKey };
554
+ return this.workerPool.push(async () => {
555
+ const handler = this.getHandler(action.handler);
556
+ let queue = await this.getQueueActions();
557
+ const queueOperations = handler.getQueueOperationsForCompletingDrafts(queue, action);
558
+ const idAndKeyMappings = handler.getRedirectMappings(action);
559
+ const keyMappings = idAndKeyMappings === undefined
560
+ ? undefined
561
+ : idAndKeyMappings.map((m) => {
562
+ return { draftKey: m.draftKey, canonicalKey: m.canonicalKey };
563
+ });
564
+ await this.draftStore.completeAction(queueOperations, keyMappings);
565
+ queue = await this.getQueueActions();
566
+ this.retryIntervalMilliseconds = 0;
567
+ this.uploadingActionId = undefined;
568
+ await handler.handleActionCompleted(action, queueOperations, queue, values(this.handlers));
569
+ await this.notifyChangedListeners({
570
+ type: DraftQueueEventType.ActionCompleted,
571
+ action,
559
572
  });
560
- await this.draftStore.completeAction(queueOperations, keyMappings);
561
- queue = await this.getQueueActions();
562
- this.retryIntervalMilliseconds = 0;
563
- this.uploadingActionId = undefined;
564
- await handler.handleActionCompleted(action, queueOperations, queue, values(this.handlers));
565
- await this.notifyChangedListeners({
566
- type: DraftQueueEventType.ActionCompleted,
567
- action,
573
+ if (this.state === DraftQueueState.Started) {
574
+ this.processNextAction();
575
+ }
568
576
  });
569
- if (this.state === DraftQueueState.Started) {
570
- this.processNextAction();
571
- }
572
577
  }
573
578
  async actionFailed(action, retry) {
574
579
  this.uploadingActionId = undefined;
@@ -1018,6 +1023,13 @@ class AbstractResourceRequestActionHandler {
1018
1023
  this.draftQueue = draftQueue;
1019
1024
  this.networkAdapter = networkAdapter;
1020
1025
  this.getLuvio = getLuvio;
1026
+ // NOTE[W-12567340]: This property stores in-memory mappings between draft
1027
+ // ids and canonical ids for the current session. Having a local copy of
1028
+ // these mappings is necessary to avoid a race condition between publishing
1029
+ // new mappings to the durable store and those mappings being loaded into
1030
+ // the luvio store redirect table, during which a new draft might be enqueued
1031
+ // which would not see a necessary mapping.
1032
+ this.ephemeralRedirects = {};
1021
1033
  }
1022
1034
  enqueue(data) {
1023
1035
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -1085,7 +1097,8 @@ class AbstractResourceRequestActionHandler {
1085
1097
  }
1086
1098
  getQueueOperationsForCompletingDrafts(queue, action) {
1087
1099
  const queueOperations = [];
1088
- if (action.data.method === 'post') {
1100
+ const redirects = this.getRedirectMappings(action);
1101
+ if (redirects !== undefined) {
1089
1102
  const { length } = queue;
1090
1103
  for (let i = 0; i < length; i++) {
1091
1104
  const queueAction = queue[i];
@@ -1095,69 +1108,64 @@ class AbstractResourceRequestActionHandler {
1095
1108
  continue;
1096
1109
  }
1097
1110
  if (isResourceRequestAction(queueAction)) {
1098
- const redirects = this.getRedirectMappings(action);
1099
- if (redirects !== undefined) {
1100
- let queueOperationMutated = false;
1101
- let updatedActionTag = undefined;
1102
- let updatedActionTargetId = undefined;
1103
- const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
1104
- let { basePath, body } = queueActionRequest;
1105
- let stringifiedBody = stringify(body);
1106
- // for each redirected ID/key we loop over the operation to see if it needs
1107
- // to be updated
1108
- for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
1109
- if (basePath.search(draftId) >= 0 ||
1110
- stringifiedBody.search(draftId) >= 0) {
1111
- basePath = basePath.replace(draftId, canonicalId);
1112
- stringifiedBody = stringifiedBody.replace(draftId, canonicalId);
1113
- queueOperationMutated = true;
1114
- }
1115
- // if the action is performed on a previous draft id, we need to replace the action
1116
- // with a new one at the updated canonical key
1117
- if (queueActionTag === draftKey) {
1118
- updatedActionTag = canonicalKey;
1119
- updatedActionTargetId = canonicalId;
1120
- }
1111
+ let queueOperationMutated = false;
1112
+ let updatedActionTag = undefined;
1113
+ let updatedActionTargetId = undefined;
1114
+ const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
1115
+ let { basePath, body } = queueActionRequest;
1116
+ let stringifiedBody = stringify(body);
1117
+ // for each redirected ID/key we loop over the operation to see if it needs
1118
+ // to be updated
1119
+ for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
1120
+ if (basePath.search(draftId) >= 0 || stringifiedBody.search(draftId) >= 0) {
1121
+ basePath = basePath.replace(draftId, canonicalId);
1122
+ stringifiedBody = stringifiedBody.replace(draftId, canonicalId);
1123
+ queueOperationMutated = true;
1121
1124
  }
1122
- if (queueOperationMutated) {
1123
- if (updatedActionTag !== undefined &&
1124
- updatedActionTargetId !== undefined) {
1125
- const updatedAction = {
1126
- ...queueAction,
1127
- tag: updatedActionTag,
1128
- targetId: updatedActionTargetId,
1129
- data: {
1130
- ...queueActionRequest,
1131
- basePath: basePath,
1132
- body: parse(stringifiedBody),
1133
- },
1134
- };
1135
- // item needs to be replaced with a new item at the new record key
1136
- queueOperations.push({
1137
- type: QueueOperationType.Delete,
1138
- id: queueActionId,
1139
- });
1140
- queueOperations.push({
1141
- type: QueueOperationType.Add,
1142
- action: updatedAction,
1143
- });
1144
- }
1145
- else {
1146
- const updatedAction = {
1147
- ...queueAction,
1148
- data: {
1149
- ...queueActionRequest,
1150
- basePath: basePath,
1151
- body: parse(stringifiedBody),
1152
- },
1153
- };
1154
- // item needs to be updated
1155
- queueOperations.push({
1156
- type: QueueOperationType.Update,
1157
- id: queueActionId,
1158
- action: updatedAction,
1159
- });
1160
- }
1125
+ // if the action is performed on a previous draft id, we need to replace the action
1126
+ // with a new one at the updated canonical key
1127
+ if (queueActionTag === draftKey) {
1128
+ updatedActionTag = canonicalKey;
1129
+ updatedActionTargetId = canonicalId;
1130
+ }
1131
+ }
1132
+ if (queueOperationMutated) {
1133
+ if (updatedActionTag !== undefined && updatedActionTargetId !== undefined) {
1134
+ const updatedAction = {
1135
+ ...queueAction,
1136
+ tag: updatedActionTag,
1137
+ targetId: updatedActionTargetId,
1138
+ data: {
1139
+ ...queueActionRequest,
1140
+ basePath: basePath,
1141
+ body: parse(stringifiedBody),
1142
+ },
1143
+ };
1144
+ // item needs to be replaced with a new item at the new record key
1145
+ queueOperations.push({
1146
+ type: QueueOperationType.Delete,
1147
+ id: queueActionId,
1148
+ });
1149
+ queueOperations.push({
1150
+ type: QueueOperationType.Add,
1151
+ action: updatedAction,
1152
+ });
1153
+ }
1154
+ else {
1155
+ const updatedAction = {
1156
+ ...queueAction,
1157
+ data: {
1158
+ ...queueActionRequest,
1159
+ basePath: basePath,
1160
+ body: parse(stringifiedBody),
1161
+ },
1162
+ };
1163
+ // item needs to be updated
1164
+ queueOperations.push({
1165
+ type: QueueOperationType.Update,
1166
+ id: queueActionId,
1167
+ action: updatedAction,
1168
+ });
1161
1169
  }
1162
1170
  }
1163
1171
  }
@@ -1177,6 +1185,9 @@ class AbstractResourceRequestActionHandler {
1177
1185
  const body = action.response.body;
1178
1186
  const canonicalId = this.getIdFromResponseBody(body);
1179
1187
  const draftId = action.targetId;
1188
+ if (draftId !== undefined && canonicalId !== undefined && draftId !== canonicalId) {
1189
+ this.ephemeralRedirects[draftId] = canonicalId;
1190
+ }
1180
1191
  return [
1181
1192
  {
1182
1193
  draftId,
@@ -1471,7 +1482,9 @@ class DraftManager {
1471
1482
  targetId,
1472
1483
  tag,
1473
1484
  })
1474
- .then(this.buildDraftQueueItem);
1485
+ .then((result) => {
1486
+ return this.buildDraftQueueItem(result.action);
1487
+ });
1475
1488
  }
1476
1489
  /**
1477
1490
  * Get the current state of each of the DraftActions in the DraftQueue
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-drafts",
3
- "version": "1.100.3",
3
+ "version": "1.100.5",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "LDS Drafts",
6
6
  "main": "dist/ldsDrafts.js",
@@ -25,6 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@luvio/engine": "0.135.4",
28
- "@luvio/environments": "0.135.4"
28
+ "@luvio/environments": "0.135.4",
29
+ "@salesforce/lds-utils-adapters": "^1.100.5"
29
30
  }
30
31
  }