@salesforce/lds-drafts 1.451.0 → 1.453.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
@@ -322,7 +322,7 @@ class DurableDraftQueue {
322
322
  }
323
323
  return handler;
324
324
  }
325
- constructor(draftStore) {
325
+ constructor(draftStore, instrumentation) {
326
326
  this.retryIntervalMilliseconds = 0;
327
327
  this.minimumRetryInterval = 250;
328
328
  this.maximumRetryInterval = 32000;
@@ -339,6 +339,7 @@ class DurableDraftQueue {
339
339
  this.handlers = {};
340
340
  this.draftStore = draftStore;
341
341
  this.workerPool = new AsyncWorkerPool(1);
342
+ this.instrumentation = instrumentation;
342
343
  }
343
344
  addHandler(handler) {
344
345
  const id = handler.handlerId;
@@ -429,15 +430,22 @@ class DurableDraftQueue {
429
430
  this.state = DraftQueueState.Stopped;
430
431
  }
431
432
  async getQueueActions() {
433
+ // W-23515682: time the whole call, including this queue's own internal
434
+ // this.getQueueActions() during a drain — the amplification signal an external wrapper
435
+ // cannot see. Measure-only; control flow below is unchanged.
436
+ const { instrumentation } = this;
437
+ const start = instrumentation?.onGetQueueActions !== undefined ? Date.now() : 0;
432
438
  const drafts = (await this.draftStore.getAllDrafts());
433
439
  const queue = [];
434
440
  drafts.forEach((draft) => {
435
441
  if (draft.id === this.uploadingActionId) {
436
- draft.status = DraftActionStatus.Uploading;
442
+ queue.push({ ...draft, status: DraftActionStatus.Uploading });
443
+ }
444
+ else {
445
+ queue.push(draft);
437
446
  }
438
- queue.push(draft);
439
447
  });
440
- return queue.sort((a, b) => {
448
+ const sorted = queue.sort((a, b) => {
441
449
  const aTime = parseInt(a.id, 10);
442
450
  const bTime = parseInt(b.id, 10);
443
451
  // safety check
@@ -449,6 +457,8 @@ class DurableDraftQueue {
449
457
  }
450
458
  return aTime - bTime;
451
459
  });
460
+ instrumentation?.onGetQueueActions?.(Date.now() - start, sorted.length);
461
+ return sorted;
452
462
  }
453
463
  async enqueue(handlerId, data, observabilityContext) {
454
464
  return this.workerPool.push({
@@ -597,6 +607,8 @@ class DurableDraftQueue {
597
607
  const listener = draftQueueChangedListeners[i];
598
608
  results.push(listener(event));
599
609
  }
610
+ // W-23515682: listener fan-out per event type. Measure-only.
611
+ this.instrumentation?.onNotifyChangedListeners?.(event.type, draftQueueLen);
600
612
  await Promise.all(results);
601
613
  }
602
614
  /**
@@ -844,36 +856,66 @@ function buildDraftDurableStoreKey(recordKey, draftActionId) {
844
856
  *
845
857
  */
846
858
  class DurableDraftStore {
847
- constructor(durableStore) {
859
+ constructor(durableStore, instrumentation, useSnapshot = true) {
848
860
  this.draftStore = {};
861
+ // Snapshot: holds deep clones of each draft, refreshed at write time.
862
+ // getAllDrafts serves from this snapshot with zero deep clones per read.
863
+ this.cleanSnapshot = {};
849
864
  // queue of writes that were made during the initial sync
850
865
  this.writeQueue = [];
851
866
  this.durableStore = durableStore;
867
+ this.instrumentation = instrumentation;
868
+ this.useSnapshot = useSnapshot;
852
869
  this.resyncDraftStore();
853
870
  }
871
+ // When no instrumentation is injected the promise is returned as-is (zero overhead).
872
+ timeDurableOp(op, segment, run) {
873
+ const onDurableOperation = this.instrumentation?.onDurableOperation;
874
+ if (onDurableOperation === undefined) {
875
+ return run();
876
+ }
877
+ const start = Date.now();
878
+ return run().finally(() => {
879
+ onDurableOperation(op, segment, Date.now() - start);
880
+ });
881
+ }
854
882
  writeAction(action) {
855
883
  const addAction = () => {
856
884
  const { id, tag } = action;
857
885
  this.draftStore[id] = action;
886
+ this.cleanSnapshot[id] = clone(action);
858
887
  const durableEntryKey = buildDraftDurableStoreKey(tag, id);
859
888
  const entry = {
860
889
  data: action,
861
890
  };
862
891
  const entries = { [durableEntryKey]: entry };
863
- return this.durableStore.setEntries(entries, DRAFT_SEGMENT);
892
+ return this.timeDurableOp('setEntries', DRAFT_SEGMENT, () => this.durableStore.setEntries(entries, DRAFT_SEGMENT));
864
893
  };
865
894
  return this.enqueueAction(addAction);
866
895
  }
867
896
  getAllDrafts() {
868
897
  const waitForOngoingSync = this.syncPromise || Promise.resolve();
869
898
  return waitForOngoingSync.then(() => {
870
- const { draftStore } = this;
871
- const keys$1 = keys(draftStore);
872
899
  const actionArray = [];
873
- for (let i = 0, len = keys$1.length; i < len; i++) {
874
- const key = keys$1[i];
875
- // clone draft so we don't expose the internal draft store
876
- actionArray.push(clone(draftStore[key]));
900
+ if (this.useSnapshot) {
901
+ const { cleanSnapshot } = this;
902
+ const keys$1 = keys(cleanSnapshot);
903
+ for (let i = 0, len = keys$1.length; i < len; i++) {
904
+ const key = keys$1[i];
905
+ // Return shallow spread of snapshot entries so top-level caller mutations
906
+ // (e.g., .status =) don't persist into the snapshot. The snapshot itself
907
+ // is a deep clone of draftStore, so no nested mutation reaches draftStore.
908
+ actionArray.push({ ...cleanSnapshot[key] });
909
+ }
910
+ }
911
+ else {
912
+ // Killswitch (W-23515682): original pre-fix behavior — deep-clone every draft on
913
+ // every read so the internal draft store is never exposed. O(N) clones per read.
914
+ const { draftStore } = this;
915
+ const keys$1 = keys(draftStore);
916
+ for (let i = 0, len = keys$1.length; i < len; i++) {
917
+ actionArray.push(clone(draftStore[keys$1[i]]));
918
+ }
877
919
  }
878
920
  return actionArray;
879
921
  });
@@ -883,8 +925,9 @@ class DurableDraftStore {
883
925
  const draft = this.draftStore[id];
884
926
  if (draft !== undefined) {
885
927
  delete this.draftStore[id];
928
+ delete this.cleanSnapshot[id];
886
929
  const durableKey = buildDraftDurableStoreKey(draft.tag, draft.id);
887
- return this.durableStore.evictEntries([durableKey], DRAFT_SEGMENT);
930
+ return this.timeDurableOp('evictEntries', DRAFT_SEGMENT, () => this.durableStore.evictEntries([durableKey], DRAFT_SEGMENT));
888
931
  }
889
932
  return Promise.resolve();
890
933
  };
@@ -900,10 +943,11 @@ class DurableDraftStore {
900
943
  const action = draftStore[key];
901
944
  if (action.tag === tag) {
902
945
  delete draftStore[action.id];
946
+ delete this.cleanSnapshot[action.id];
903
947
  durableKeys.push(buildDraftDurableStoreKey(action.tag, action.id));
904
948
  }
905
949
  }
906
- return this.durableStore.evictEntries(durableKeys, DRAFT_SEGMENT);
950
+ return this.timeDurableOp('evictEntries', DRAFT_SEGMENT, () => this.durableStore.evictEntries(durableKeys, DRAFT_SEGMENT));
907
951
  };
908
952
  return this.enqueueAction(deleteAction);
909
953
  }
@@ -917,6 +961,7 @@ class DurableDraftStore {
917
961
  const action = draftStore[operation.id];
918
962
  if (action !== undefined) {
919
963
  delete draftStore[operation.id];
964
+ delete this.cleanSnapshot[operation.id];
920
965
  const key = buildDraftDurableStoreKey(action.tag, action.id);
921
966
  durableStoreOperations.push({
922
967
  ids: [key],
@@ -929,6 +974,7 @@ class DurableDraftStore {
929
974
  const { action } = operation;
930
975
  const key = buildDraftDurableStoreKey(action.tag, action.id);
931
976
  draftStore[action.id] = action;
977
+ this.cleanSnapshot[action.id] = clone(action);
932
978
  durableStoreOperations.push({
933
979
  type: 'setEntries',
934
980
  segment: DRAFT_SEGMENT,
@@ -940,7 +986,7 @@ class DurableDraftStore {
940
986
  });
941
987
  }
942
988
  }
943
- return this.durableStore.batchOperations(durableStoreOperations);
989
+ return this.timeDurableOp('batchOperations', DRAFT_SEGMENT, () => this.durableStore.batchOperations(durableStoreOperations));
944
990
  };
945
991
  return this.enqueueAction(action);
946
992
  }
@@ -983,6 +1029,7 @@ class DurableDraftStore {
983
1029
  .then((durableEntries) => {
984
1030
  if (durableEntries === undefined) {
985
1031
  this.draftStore = {};
1032
+ this.cleanSnapshot = {};
986
1033
  return this.runQueuedOperations();
987
1034
  }
988
1035
  const { draftStore } = this;
@@ -999,6 +1046,7 @@ class DurableDraftStore {
999
1046
  }
1000
1047
  }
1001
1048
  draftStore[action.id] = action;
1049
+ this.cleanSnapshot[action.id] = clone(action);
1002
1050
  }
1003
1051
  return this.runQueuedOperations();
1004
1052
  })
@@ -0,0 +1,5 @@
1
+ export interface DraftQueueInstrumentation {
2
+ onGetQueueActions?(durationMs: number, actionCount: number): void;
3
+ onNotifyChangedListeners?(eventType: string, listenerCount: number): void;
4
+ onDurableOperation?(op: string, segment: string, durationMs: number): void;
5
+ }
@@ -3,6 +3,7 @@ import { ProcessActionResult, DraftQueueState } from './DraftQueue';
3
3
  import type { CustomActionExecutor } from './actionHandlers/CustomActionHandler';
4
4
  import type { ActionHandler } from './actionHandlers/ActionHandler';
5
5
  import type { DraftStore } from './DraftStore';
6
+ import type { DraftQueueInstrumentation } from './DraftQueueInstrumentation';
6
7
  import type { ObservabilityContext } from '@salesforce/nimbus-plugin-lds';
7
8
  export declare const DRAFT_SEGMENT = "DRAFT";
8
9
  /**
@@ -27,8 +28,9 @@ export declare class DurableDraftQueue implements DraftQueue {
27
28
  private logger;
28
29
  private workerPool;
29
30
  private handlers;
31
+ private instrumentation?;
30
32
  private getHandler;
31
- constructor(draftStore: DraftStore);
33
+ constructor(draftStore: DraftStore, instrumentation?: DraftQueueInstrumentation);
32
34
  addHandler<Data>(handler: ActionHandler<Data, unknown>): Promise<void>;
33
35
  removeHandler(id: string): Promise<void>;
34
36
  addCustomHandler(id: string, executor: CustomActionExecutor): Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import type { DurableStore } from '@luvio/environments';
2
2
  import type { DraftAction, QueueOperation } from './DraftQueue';
3
3
  import type { DraftStore } from './DraftStore';
4
+ import type { DraftQueueInstrumentation } from './DraftQueueInstrumentation';
4
5
  /**
5
6
  * Implements a write-through InMemoryStore for Drafts, storing all drafts in a
6
7
  * in-memory store with a write through to the DurableStore.
@@ -13,9 +14,13 @@ import type { DraftStore } from './DraftStore';
13
14
  export declare class DurableDraftStore implements DraftStore {
14
15
  private durableStore;
15
16
  private draftStore;
17
+ private cleanSnapshot;
16
18
  private syncPromise;
17
19
  private writeQueue;
18
- constructor(durableStore: DurableStore);
20
+ private instrumentation?;
21
+ private useSnapshot;
22
+ constructor(durableStore: DurableStore, instrumentation?: DraftQueueInstrumentation, useSnapshot?: boolean);
23
+ private timeDurableOp;
19
24
  writeAction(action: DraftAction<unknown, unknown>): Promise<void>;
20
25
  getAllDrafts<_R, _D>(): Promise<DraftAction<unknown, unknown>[]>;
21
26
  deleteDraft(id: string): Promise<void>;
@@ -2,6 +2,7 @@ export { DraftQueue, DraftQueueState, DraftAction, ErrorDraftAction, PendingDraf
2
2
  export { DurableDraftQueue, DRAFT_SEGMENT, DRAFT_ACTION_RETRY_COUNT_METADATA_KEY, } from './DurableDraftQueue';
3
3
  export { generateUniqueDraftActionId, uuidv4 } from './utils/id';
4
4
  export { DurableDraftStore } from './DurableDraftStore';
5
+ export type { DraftQueueInstrumentation } from './DraftQueueInstrumentation';
5
6
  export { DraftStore } from './DraftStore';
6
7
  export { DraftManager, DraftManagerState, DraftActionOperationType, DraftQueueItem, DraftQueueItemMetadata, } from './DraftManager';
7
8
  export { ActionHandler, ReplacingActions } from './actionHandlers/ActionHandler';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-drafts",
3
- "version": "1.451.0",
3
+ "version": "1.453.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.161.0",
28
28
  "@luvio/environments": "0.161.0",
29
- "@salesforce/lds-utils-adapters": "^1.451.0"
29
+ "@salesforce/lds-utils-adapters": "^1.453.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@salesforce/nimbus-plugin-lds": "^1.451.0"
32
+ "@salesforce/nimbus-plugin-lds": "^1.453.0"
33
33
  },
34
34
  "volta": {
35
35
  "extends": "../../package.json"