@salesforce/lds-worker-api 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.
@@ -1373,4 +1373,4 @@ if (process.env.NODE_ENV !== 'production') {
1373
1373
  }
1374
1374
 
1375
1375
  export { createPrimingSession, draftManager, draftQueue, evictCacheRecordsByIds, evictExpiredCacheEntries, executeAdapter, executeMutatingAdapter, getImperativeAdapterNames, importLuvioAdapterModule, importOneStoreAdapterModule, invokeAdapter, invokeAdapterWithDraftToMerge, invokeAdapterWithDraftToReplace, invokeAdapterWithMetadata, nimbusDraftQueue, setMetadataTTL, setUiApiRecordTTL, stopEviction, subscribeToAdapter };
1376
- // version: 1.451.0-b776d3eb45
1376
+ // version: 1.453.0-725e09cf25
@@ -4276,7 +4276,7 @@ function withDefaultLuvio(callback) {
4276
4276
  }
4277
4277
  callbacks.push(callback);
4278
4278
  }
4279
- // version: 1.451.0-b776d3eb45
4279
+ // version: 1.453.0-725e09cf25
4280
4280
 
4281
4281
  // TODO [TD-0081508]: once that TD is fulfilled we can probably change this file
4282
4282
  function instrumentAdapter$1(createFunction, _metadata) {
@@ -5320,7 +5320,7 @@ function createGraphQLWireAdapterConstructor(luvio, adapter, metadata, astResolv
5320
5320
  const { apiFamily, name } = metadata;
5321
5321
  return createGraphQLWireAdapterConstructor$1(adapter, `${apiFamily}.${name}`, luvio, astResolver);
5322
5322
  }
5323
- // version: 1.451.0-b776d3eb45
5323
+ // version: 1.453.0-725e09cf25
5324
5324
 
5325
5325
  function isSupportedEntity(_objectApiName) {
5326
5326
  return true;
@@ -14373,7 +14373,7 @@ function coerceFormFactor$1(form) {
14373
14373
  return undefined;
14374
14374
  }
14375
14375
 
14376
- const VERSION$10$2 = "9859bc4e735e831effdc3df4b3b3e415";
14376
+ const VERSION$10$2 = "3f49d751896cf66e6e29788d8880e2cc";
14377
14377
  const RepresentationType$G = 'PlatformActionRepresentation';
14378
14378
  function keyBuilder$1P$1(luvio, config) {
14379
14379
  return keyPrefix$3 + '::' + RepresentationType$G + ':' + config.externalId + ':' + (config.relatedSourceObject === null ? '' : config.relatedSourceObject) + ':' + (config.relatedListRecordId === null ? '' : config.relatedListRecordId);
@@ -14433,10 +14433,6 @@ const select$1G$2 = function PlatformActionRepresentationSelect() {
14433
14433
  name: 'lwcComponent',
14434
14434
  kind: 'Scalar'
14435
14435
  },
14436
- {
14437
- name: 'pageDeveloperName',
14438
- kind: 'Scalar'
14439
- },
14440
14436
  {
14441
14437
  name: 'primaryColor',
14442
14438
  kind: 'Scalar'
@@ -14465,14 +14461,6 @@ const select$1G$2 = function PlatformActionRepresentationSelect() {
14465
14461
  name: 'targetObject',
14466
14462
  kind: 'Scalar'
14467
14463
  },
14468
- {
14469
- name: 'targetParentField',
14470
- kind: 'Scalar'
14471
- },
14472
- {
14473
- name: 'targetRecordTypeId',
14474
- kind: 'Scalar'
14475
- },
14476
14464
  {
14477
14465
  name: 'targetUrl',
14478
14466
  kind: 'Scalar'
@@ -14545,11 +14533,6 @@ function equals$S(existing, incoming) {
14545
14533
  if (!(existing_lwcComponent === incoming_lwcComponent)) {
14546
14534
  return false;
14547
14535
  }
14548
- const existing_pageDeveloperName = existing.pageDeveloperName;
14549
- const incoming_pageDeveloperName = incoming.pageDeveloperName;
14550
- if (!(existing_pageDeveloperName === incoming_pageDeveloperName)) {
14551
- return false;
14552
- }
14553
14536
  const existing_primaryColor = existing.primaryColor;
14554
14537
  const incoming_primaryColor = incoming.primaryColor;
14555
14538
  if (!(existing_primaryColor === incoming_primaryColor)) {
@@ -14580,16 +14563,6 @@ function equals$S(existing, incoming) {
14580
14563
  if (!(existing_targetObject === incoming_targetObject)) {
14581
14564
  return false;
14582
14565
  }
14583
- const existing_targetParentField = existing.targetParentField;
14584
- const incoming_targetParentField = incoming.targetParentField;
14585
- if (!(existing_targetParentField === incoming_targetParentField)) {
14586
- return false;
14587
- }
14588
- const existing_targetRecordTypeId = existing.targetRecordTypeId;
14589
- const incoming_targetRecordTypeId = incoming.targetRecordTypeId;
14590
- if (!(existing_targetRecordTypeId === incoming_targetRecordTypeId)) {
14591
- return false;
14592
- }
14593
14566
  const existing_targetUrl = existing.targetUrl;
14594
14567
  const incoming_targetUrl = incoming.targetUrl;
14595
14568
  if (!(existing_targetUrl === incoming_targetUrl)) {
@@ -32574,7 +32547,7 @@ withDefaultLuvio((luvio) => {
32574
32547
  throttle(60, 60000, setupNotifyAllListRecordUpdateAvailable(luvio));
32575
32548
  throttle(60, 60000, setupNotifyAllListInfoSummaryUpdateAvailable(luvio));
32576
32549
  });
32577
- // version: 1.451.0-231643dd9a
32550
+ // version: 1.453.0-e6dc0c039a
32578
32551
 
32579
32552
  var draftQueueMaxRetryAttemptsGate = {
32580
32553
  isOpen: function (e) {
@@ -32585,6 +32558,15 @@ var draftQueueMaxRetryAttemptsGate = {
32585
32558
  },
32586
32559
  };
32587
32560
 
32561
+ var draftQueueRedirectScanGate = {
32562
+ isOpen: function (e) {
32563
+ return e.fallback;
32564
+ },
32565
+ hasError: function () {
32566
+ return !0;
32567
+ },
32568
+ };
32569
+
32588
32570
  var rotateIdempotencyKeyOn400Killswitch = {
32589
32571
  isOpen: function (e) {
32590
32572
  return e.fallback;
@@ -44130,6 +44112,15 @@ var graphqlPartialEmitParity = {
44130
44112
  },
44131
44113
  };
44132
44114
 
44115
+ var draftQueueStoreSnapshotGate = {
44116
+ isOpen: function (e) {
44117
+ return e.fallback;
44118
+ },
44119
+ hasError: function () {
44120
+ return !0;
44121
+ },
44122
+ };
44123
+
44133
44124
  var ldsAdapterO11yLoggingGate = {
44134
44125
  isOpen: function (e) {
44135
44126
  return e.fallback;
@@ -45919,6 +45910,15 @@ function resolveAndValidateGraphQLDocument(query, operationName, options) {
45919
45910
  return ok$3(document);
45920
45911
  }
45921
45912
 
45913
+ var draftQueueSideEffectMapGate = {
45914
+ isOpen: function (e) {
45915
+ return e.fallback;
45916
+ },
45917
+ hasError: function () {
45918
+ return !0;
45919
+ },
45920
+ };
45921
+
45922
45922
  var productConsumedSideEffects = {
45923
45923
  isOpen: function (e) {
45924
45924
  return e.fallback;
@@ -48241,7 +48241,7 @@ class DurableDraftQueue {
48241
48241
  }
48242
48242
  return handler;
48243
48243
  }
48244
- constructor(draftStore) {
48244
+ constructor(draftStore, instrumentation) {
48245
48245
  this.retryIntervalMilliseconds = 0;
48246
48246
  this.minimumRetryInterval = 250;
48247
48247
  this.maximumRetryInterval = 32000;
@@ -48258,6 +48258,7 @@ class DurableDraftQueue {
48258
48258
  this.handlers = {};
48259
48259
  this.draftStore = draftStore;
48260
48260
  this.workerPool = new AsyncWorkerPool(1);
48261
+ this.instrumentation = instrumentation;
48261
48262
  }
48262
48263
  addHandler(handler) {
48263
48264
  const id = handler.handlerId;
@@ -48348,15 +48349,22 @@ class DurableDraftQueue {
48348
48349
  this.state = DraftQueueState.Stopped;
48349
48350
  }
48350
48351
  async getQueueActions() {
48352
+ // W-23515682: time the whole call, including this queue's own internal
48353
+ // this.getQueueActions() during a drain — the amplification signal an external wrapper
48354
+ // cannot see. Measure-only; control flow below is unchanged.
48355
+ const { instrumentation } = this;
48356
+ const start = instrumentation?.onGetQueueActions !== undefined ? Date.now() : 0;
48351
48357
  const drafts = (await this.draftStore.getAllDrafts());
48352
48358
  const queue = [];
48353
48359
  drafts.forEach((draft) => {
48354
48360
  if (draft.id === this.uploadingActionId) {
48355
- draft.status = DraftActionStatus.Uploading;
48361
+ queue.push({ ...draft, status: DraftActionStatus.Uploading });
48362
+ }
48363
+ else {
48364
+ queue.push(draft);
48356
48365
  }
48357
- queue.push(draft);
48358
48366
  });
48359
- return queue.sort((a, b) => {
48367
+ const sorted = queue.sort((a, b) => {
48360
48368
  const aTime = parseInt(a.id, 10);
48361
48369
  const bTime = parseInt(b.id, 10);
48362
48370
  // safety check
@@ -48368,6 +48376,8 @@ class DurableDraftQueue {
48368
48376
  }
48369
48377
  return aTime - bTime;
48370
48378
  });
48379
+ instrumentation?.onGetQueueActions?.(Date.now() - start, sorted.length);
48380
+ return sorted;
48371
48381
  }
48372
48382
  async enqueue(handlerId, data, observabilityContext) {
48373
48383
  return this.workerPool.push({
@@ -48516,6 +48526,8 @@ class DurableDraftQueue {
48516
48526
  const listener = draftQueueChangedListeners[i];
48517
48527
  results.push(listener(event));
48518
48528
  }
48529
+ // W-23515682: listener fan-out per event type. Measure-only.
48530
+ this.instrumentation?.onNotifyChangedListeners?.(event.type, draftQueueLen);
48519
48531
  await Promise.all(results);
48520
48532
  }
48521
48533
  /**
@@ -48763,36 +48775,66 @@ function buildDraftDurableStoreKey(recordKey, draftActionId) {
48763
48775
  *
48764
48776
  */
48765
48777
  class DurableDraftStore {
48766
- constructor(durableStore) {
48778
+ constructor(durableStore, instrumentation, useSnapshot = true) {
48767
48779
  this.draftStore = {};
48780
+ // Snapshot: holds deep clones of each draft, refreshed at write time.
48781
+ // getAllDrafts serves from this snapshot with zero deep clones per read.
48782
+ this.cleanSnapshot = {};
48768
48783
  // queue of writes that were made during the initial sync
48769
48784
  this.writeQueue = [];
48770
48785
  this.durableStore = durableStore;
48786
+ this.instrumentation = instrumentation;
48787
+ this.useSnapshot = useSnapshot;
48771
48788
  this.resyncDraftStore();
48772
48789
  }
48790
+ // When no instrumentation is injected the promise is returned as-is (zero overhead).
48791
+ timeDurableOp(op, segment, run) {
48792
+ const onDurableOperation = this.instrumentation?.onDurableOperation;
48793
+ if (onDurableOperation === undefined) {
48794
+ return run();
48795
+ }
48796
+ const start = Date.now();
48797
+ return run().finally(() => {
48798
+ onDurableOperation(op, segment, Date.now() - start);
48799
+ });
48800
+ }
48773
48801
  writeAction(action) {
48774
48802
  const addAction = () => {
48775
48803
  const { id, tag } = action;
48776
48804
  this.draftStore[id] = action;
48805
+ this.cleanSnapshot[id] = clone$1(action);
48777
48806
  const durableEntryKey = buildDraftDurableStoreKey(tag, id);
48778
48807
  const entry = {
48779
48808
  data: action,
48780
48809
  };
48781
48810
  const entries = { [durableEntryKey]: entry };
48782
- return this.durableStore.setEntries(entries, DRAFT_SEGMENT);
48811
+ return this.timeDurableOp('setEntries', DRAFT_SEGMENT, () => this.durableStore.setEntries(entries, DRAFT_SEGMENT));
48783
48812
  };
48784
48813
  return this.enqueueAction(addAction);
48785
48814
  }
48786
48815
  getAllDrafts() {
48787
48816
  const waitForOngoingSync = this.syncPromise || Promise.resolve();
48788
48817
  return waitForOngoingSync.then(() => {
48789
- const { draftStore } = this;
48790
- const keys$1 = keys$4(draftStore);
48791
48818
  const actionArray = [];
48792
- for (let i = 0, len = keys$1.length; i < len; i++) {
48793
- const key = keys$1[i];
48794
- // clone draft so we don't expose the internal draft store
48795
- actionArray.push(clone$1(draftStore[key]));
48819
+ if (this.useSnapshot) {
48820
+ const { cleanSnapshot } = this;
48821
+ const keys$1 = keys$4(cleanSnapshot);
48822
+ for (let i = 0, len = keys$1.length; i < len; i++) {
48823
+ const key = keys$1[i];
48824
+ // Return shallow spread of snapshot entries so top-level caller mutations
48825
+ // (e.g., .status =) don't persist into the snapshot. The snapshot itself
48826
+ // is a deep clone of draftStore, so no nested mutation reaches draftStore.
48827
+ actionArray.push({ ...cleanSnapshot[key] });
48828
+ }
48829
+ }
48830
+ else {
48831
+ // Killswitch (W-23515682): original pre-fix behavior — deep-clone every draft on
48832
+ // every read so the internal draft store is never exposed. O(N) clones per read.
48833
+ const { draftStore } = this;
48834
+ const keys$1 = keys$4(draftStore);
48835
+ for (let i = 0, len = keys$1.length; i < len; i++) {
48836
+ actionArray.push(clone$1(draftStore[keys$1[i]]));
48837
+ }
48796
48838
  }
48797
48839
  return actionArray;
48798
48840
  });
@@ -48802,8 +48844,9 @@ class DurableDraftStore {
48802
48844
  const draft = this.draftStore[id];
48803
48845
  if (draft !== undefined) {
48804
48846
  delete this.draftStore[id];
48847
+ delete this.cleanSnapshot[id];
48805
48848
  const durableKey = buildDraftDurableStoreKey(draft.tag, draft.id);
48806
- return this.durableStore.evictEntries([durableKey], DRAFT_SEGMENT);
48849
+ return this.timeDurableOp('evictEntries', DRAFT_SEGMENT, () => this.durableStore.evictEntries([durableKey], DRAFT_SEGMENT));
48807
48850
  }
48808
48851
  return Promise.resolve();
48809
48852
  };
@@ -48819,10 +48862,11 @@ class DurableDraftStore {
48819
48862
  const action = draftStore[key];
48820
48863
  if (action.tag === tag) {
48821
48864
  delete draftStore[action.id];
48865
+ delete this.cleanSnapshot[action.id];
48822
48866
  durableKeys.push(buildDraftDurableStoreKey(action.tag, action.id));
48823
48867
  }
48824
48868
  }
48825
- return this.durableStore.evictEntries(durableKeys, DRAFT_SEGMENT);
48869
+ return this.timeDurableOp('evictEntries', DRAFT_SEGMENT, () => this.durableStore.evictEntries(durableKeys, DRAFT_SEGMENT));
48826
48870
  };
48827
48871
  return this.enqueueAction(deleteAction);
48828
48872
  }
@@ -48836,6 +48880,7 @@ class DurableDraftStore {
48836
48880
  const action = draftStore[operation.id];
48837
48881
  if (action !== undefined) {
48838
48882
  delete draftStore[operation.id];
48883
+ delete this.cleanSnapshot[operation.id];
48839
48884
  const key = buildDraftDurableStoreKey(action.tag, action.id);
48840
48885
  durableStoreOperations.push({
48841
48886
  ids: [key],
@@ -48848,6 +48893,7 @@ class DurableDraftStore {
48848
48893
  const { action } = operation;
48849
48894
  const key = buildDraftDurableStoreKey(action.tag, action.id);
48850
48895
  draftStore[action.id] = action;
48896
+ this.cleanSnapshot[action.id] = clone$1(action);
48851
48897
  durableStoreOperations.push({
48852
48898
  type: 'setEntries',
48853
48899
  segment: DRAFT_SEGMENT,
@@ -48859,7 +48905,7 @@ class DurableDraftStore {
48859
48905
  });
48860
48906
  }
48861
48907
  }
48862
- return this.durableStore.batchOperations(durableStoreOperations);
48908
+ return this.timeDurableOp('batchOperations', DRAFT_SEGMENT, () => this.durableStore.batchOperations(durableStoreOperations));
48863
48909
  };
48864
48910
  return this.enqueueAction(action);
48865
48911
  }
@@ -48902,6 +48948,7 @@ class DurableDraftStore {
48902
48948
  .then((durableEntries) => {
48903
48949
  if (durableEntries === undefined) {
48904
48950
  this.draftStore = {};
48951
+ this.cleanSnapshot = {};
48905
48952
  return this.runQueuedOperations();
48906
48953
  }
48907
48954
  const { draftStore } = this;
@@ -48910,6 +48957,7 @@ class DurableDraftStore {
48910
48957
  const entry = durableEntries[keys$1[i]];
48911
48958
  const action = entry.data;
48912
48959
  draftStore[action.id] = action;
48960
+ this.cleanSnapshot[action.id] = clone$1(action);
48913
48961
  }
48914
48962
  return this.runQueuedOperations();
48915
48963
  })
@@ -80134,6 +80182,14 @@ const DRAFT_QUEUE_TOTAL_MERGE_ACTIONS_CALLS = 'draft-queue-total-mergeActions-ca
80134
80182
  // Used to triage the WOLI duplicate-record investigation in Splunk — correlate by draftId
80135
80183
  // with the other draft-queue events. @W-23428669
80136
80184
  const DRAFT_QUEUE_IDEMPOTENCY_KEY_ROTATED = 'draft-queue-idempotency-key-rotated';
80185
+ // Hotspot-attribution telemetry (@W-23515682). Operational counts/durations only — no
80186
+ // draft content, field values, or server record ids. These attribute the offline-drain wall-clock
80187
+ // to the O(N^2) hotspots (getQueueActions amplification, listener fan-out, durable-op latency).
80188
+ const DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT = 'draft-queue-get-queue-actions-count';
80189
+ const DRAFT_QUEUE_GET_QUEUE_ACTIONS_DURATION = 'draft-queue-get-queue-actions-duration';
80190
+ const DRAFT_QUEUE_NOTIFY_CHANGED_LISTENERS_COUNT = 'draft-queue-notify-changed-listeners-count';
80191
+ const DRAFT_QUEUE_DURABLE_OP_COUNT = 'draft-queue-durable-op-count';
80192
+ const DRAFT_QUEUE_DURABLE_OP_DURATION = 'draft-queue-durable-op-duration';
80137
80193
  /** Content Document */
80138
80194
  const CREATE_CONTENT_DOCUMENT_AND_VERSION_TOTAL_SYNTHESIZE_CALLS = 'content-document-version-total-synthesize-calls';
80139
80195
  const CREATE_CONTENT_DOCUMENT_AND_VERSION_DRAFT_SYNTHESIZE_ERROR = 'create-content-document-version-draft-synthesize-error';
@@ -80226,6 +80282,33 @@ function reportDraftActionEvent(state, draftCount, message) {
80226
80282
  break;
80227
80283
  }
80228
80284
  }
80285
+ /**
80286
+ * @W-23515682: count (amplification — queue re-reads per drain) + duration histogram (the
80287
+ * pre/post signal for the getAllDrafts clone-on-write fix). actionCount is queue depth, never content.
80288
+ */
80289
+ function reportDraftQueueGetQueueActions(durationMs, actionCount) {
80290
+ const depthBuckets = [1, 2, 5, 10, 20, 50, 100, 250, 500];
80291
+ ldsMobileInstrumentation$2.incrementCounter(DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT);
80292
+ ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_GET_QUEUE_ACTIONS_DURATION, durationMs);
80293
+ ldsMobileInstrumentation$2.bucketValue(DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT + '-depth', actionCount, depthBuckets);
80294
+ }
80295
+ /**
80296
+ * @W-23515682: count per event type — listener fan-out / amplification per event.
80297
+ */
80298
+ function reportDraftQueueNotifyChangedListeners(eventType, listenerCount) {
80299
+ ldsMobileInstrumentation$2.incrementCounter(DRAFT_QUEUE_NOTIFY_CHANGED_LISTENERS_COUNT, listenerCount, undefined, { eventType });
80300
+ }
80301
+ /**
80302
+ * @W-23515682: count + duration histogram by operation kind + segment — attributes the
80303
+ * client-CPU-vs-durable split of per-action processing time.
80304
+ */
80305
+ function reportDraftQueueDurableOperation(op, segment, durationMs) {
80306
+ ldsMobileInstrumentation$2.incrementCounter(DRAFT_QUEUE_DURABLE_OP_COUNT, 1, undefined, {
80307
+ op,
80308
+ segment,
80309
+ });
80310
+ ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_DURABLE_OP_DURATION, durationMs);
80311
+ }
80229
80312
  /**
80230
80313
  * Reports an exception thrown while uploading a draft action. The thrown error is
80231
80314
  * otherwise swallowed by the upload handler's retry path, leaving the failure
@@ -80709,14 +80792,45 @@ class AbstractResourceRequestActionHandler {
80709
80792
  let updatedActionTargetId = undefined;
80710
80793
  const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
80711
80794
  let { basePath, body } = queueActionRequest;
80795
+ // Literal indexOf pre-check (no RegExp coercion, unlike the old String.search) +
80796
+ // a replace RegExp built only when a reference is present — the win over the old scan.
80712
80797
  let stringifiedBody = stringify$5(body);
80798
+ // Read the killswitch once for the whole scan (W-23515682) — avoids a
80799
+ // per-redirect gate lookup in this hot loop.
80800
+ const useOptimizedScan = draftQueueRedirectScanGate.isOpen({ fallback: true });
80713
80801
  // for each redirected ID/key we loop over the operation to see if it needs
80714
80802
  // to be updated
80715
80803
  for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
80716
- if (basePath.search(draftId) >= 0 || stringifiedBody.search(draftId) >= 0) {
80717
- basePath = basePath.replace(new RegExp(draftId, 'g'), canonicalId);
80718
- stringifiedBody = stringifiedBody.replace(new RegExp(draftId, 'g'), canonicalId);
80719
- queueOperationMutated = true;
80804
+ if (useOptimizedScan) {
80805
+ // basePath AND body are checked independently: a queued action can carry
80806
+ // the draft id in either or BOTH, and each occurrence must be rewritten.
80807
+ const basePathNeedsUpdate = basePath.indexOf(draftId) >= 0;
80808
+ const bodyNeedsUpdate = stringifiedBody.indexOf(draftId) >= 0;
80809
+ if (basePathNeedsUpdate || bodyNeedsUpdate) {
80810
+ // Escape metacharacters so the draft id matches literally. Salesforce
80811
+ // ids carry no regex metacharacters today, so this is defensive against
80812
+ // a future id-format change silently corrupting unrelated ids. W-23515682.
80813
+ const escapedDraftId = draftId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
80814
+ const replaceRegex = new RegExp(escapedDraftId, 'g');
80815
+ if (basePathNeedsUpdate) {
80816
+ basePath = basePath.replace(replaceRegex, canonicalId);
80817
+ }
80818
+ if (bodyNeedsUpdate) {
80819
+ stringifiedBody = stringifiedBody.replace(replaceRegex, canonicalId);
80820
+ }
80821
+ queueOperationMutated = true;
80822
+ }
80823
+ }
80824
+ else {
80825
+ // Killswitch closed (W-23515682): original pre-fix scan — String.search
80826
+ // (coerces draftId to a RegExp) + a raw (unescaped) replace RegExp applied
80827
+ // to basePath and body together.
80828
+ if (basePath.search(draftId) >= 0 ||
80829
+ stringifiedBody.search(draftId) >= 0) {
80830
+ basePath = basePath.replace(new RegExp(draftId, 'g'), canonicalId);
80831
+ stringifiedBody = stringifiedBody.replace(new RegExp(draftId, 'g'), canonicalId);
80832
+ queueOperationMutated = true;
80833
+ }
80720
80834
  }
80721
80835
  // if the action is performed on a previous draft id, we need to replace the action
80722
80836
  // with a new one at the updated canonical key
@@ -90289,6 +90403,20 @@ function obtainFailureMessage(error) {
90289
90403
  }
90290
90404
  }
90291
90405
 
90406
+ // Mobile impl of the shared DraftQueueInstrumentation seam (@W-23515682). Forwards to
90407
+ // ldsMobileInstrumentation via metrics.ts; injected at construction (DraftQueueFactory).
90408
+ const draftQueueInstrumentation = {
90409
+ onGetQueueActions(durationMs, actionCount) {
90410
+ reportDraftQueueGetQueueActions(durationMs, actionCount);
90411
+ },
90412
+ onNotifyChangedListeners(eventType, listenerCount) {
90413
+ reportDraftQueueNotifyChangedListeners(eventType, listenerCount);
90414
+ },
90415
+ onDurableOperation(op, segment, durationMs) {
90416
+ reportDraftQueueDurableOperation(op, segment, durationMs);
90417
+ },
90418
+ };
90419
+
90292
90420
  // so eslint doesn't complain about nimbus
90293
90421
  /* global __nimbus */
90294
90422
  function buildLdsDraftQueue(durableStore) {
@@ -90297,7 +90425,8 @@ function buildLdsDraftQueue(durableStore) {
90297
90425
  __nimbus.plugins.LdsDraftQueue !== undefined) {
90298
90426
  return new NimbusDraftQueue();
90299
90427
  }
90300
- const draftQueue = new DurableDraftQueue(new DurableDraftStore(durableStore));
90428
+ const useSnapshot = draftQueueStoreSnapshotGate.isOpen({ fallback: true });
90429
+ const draftQueue = new DurableDraftQueue(new DurableDraftStore(durableStore, draftQueueInstrumentation, useSnapshot), draftQueueInstrumentation);
90301
90430
  return instrumentDraftQueue(draftQueue);
90302
90431
  }
90303
90432
 
@@ -93628,18 +93757,34 @@ class SideEffectStore {
93628
93757
  this.initialize();
93629
93758
  }
93630
93759
  async addEffects(effects) {
93760
+ const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
93631
93761
  const durableEntries = {};
93632
93762
  effects.forEach((effect) => {
93633
93763
  durableEntries[effect.uniqueId] = { data: effect };
93634
93764
  this.allEffects.set(effect.uniqueId, effect);
93765
+ if (incremental) {
93766
+ this.addEffectToKeyMap(effect);
93767
+ }
93635
93768
  });
93636
- this.regenerateKeyToEffectMap();
93769
+ // Killswitch (W-23515682): closed → original O(N) full rebuild on every add.
93770
+ if (!incremental) {
93771
+ this.regenerateKeyToEffectMap();
93772
+ }
93637
93773
  await this.durableStore.setEntries(durableEntries, SIDE_EFFECT_SEGMENT);
93638
93774
  }
93639
93775
  async removeEffects(effects) {
93776
+ const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
93640
93777
  const effectKeys = effects.map((e) => e.uniqueId);
93641
- effects.forEach((k) => this.allEffects.delete(k.uniqueId));
93642
- this.regenerateKeyToEffectMap();
93778
+ effects.forEach((effect) => {
93779
+ this.allEffects.delete(effect.uniqueId);
93780
+ if (incremental) {
93781
+ this.removeEffectFromKeyMap(effect);
93782
+ }
93783
+ });
93784
+ // Killswitch (W-23515682): closed → original O(N) full rebuild on every remove.
93785
+ if (!incremental) {
93786
+ this.regenerateKeyToEffectMap();
93787
+ }
93643
93788
  await this.durableStore.evictEntries(effectKeys, SIDE_EFFECT_SEGMENT);
93644
93789
  }
93645
93790
  getEffects(key) {
@@ -93674,6 +93819,39 @@ class SideEffectStore {
93674
93819
  });
93675
93820
  this.regenerateKeyToEffectMap();
93676
93821
  }
93822
+ addEffectToKeyMap(effect) {
93823
+ const recordEffects = this.keyToEffectMap.get(effect.key);
93824
+ if (recordEffects) {
93825
+ // Effects are keyed by uniqueId (mirroring allEffects.set / regenerateKeyToEffectMap):
93826
+ // a re-add of the same uniqueId must REPLACE the resident entry in place, not append a
93827
+ // duplicate. The action-completion replay path (setSideEffectsForActions on still-queued
93828
+ // actions) re-adds resident effects with no preceding remove; without this dedup a
93829
+ // record-field-increment would sit in the array twice and be applied twice (e.g. a
93830
+ // ProductConsumed QuantityOnHand -=5 becomes -=10). W-23515682.
93831
+ const index = recordEffects.findIndex((e) => e.uniqueId === effect.uniqueId);
93832
+ if (index !== -1) {
93833
+ recordEffects[index] = effect;
93834
+ }
93835
+ else {
93836
+ recordEffects.push(effect);
93837
+ }
93838
+ }
93839
+ else {
93840
+ this.keyToEffectMap.set(effect.key, [effect]);
93841
+ }
93842
+ }
93843
+ removeEffectFromKeyMap(effect) {
93844
+ const recordEffects = this.keyToEffectMap.get(effect.key);
93845
+ if (recordEffects) {
93846
+ const index = recordEffects.findIndex((e) => e.uniqueId === effect.uniqueId);
93847
+ if (index !== -1) {
93848
+ recordEffects.splice(index, 1);
93849
+ if (recordEffects.length === 0) {
93850
+ this.keyToEffectMap.delete(effect.key);
93851
+ }
93852
+ }
93853
+ }
93854
+ }
93677
93855
  regenerateKeyToEffectMap() {
93678
93856
  this.keyToEffectMap.clear();
93679
93857
  this.allEffects.forEach((effect) => {
@@ -96823,7 +97001,7 @@ function buildServiceDescriptor$b(luvio) {
96823
97001
  },
96824
97002
  };
96825
97003
  }
96826
- // version: 1.451.0-b776d3eb45
97004
+ // version: 1.453.0-725e09cf25
96827
97005
 
96828
97006
  /**
96829
97007
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -96849,7 +97027,7 @@ function buildServiceDescriptor$a(notifyRecordUpdateAvailable, getNormalizedLuvi
96849
97027
  },
96850
97028
  };
96851
97029
  }
96852
- // version: 1.451.0-b776d3eb45
97030
+ // version: 1.453.0-725e09cf25
96853
97031
 
96854
97032
  function findExecutableOperation$1(input) {
96855
97033
  const operations = input.query.definitions.filter(
@@ -99558,7 +99736,7 @@ register$1({
99558
99736
  id: '@salesforce/lds-network-adapter',
99559
99737
  instrument: instrument$2,
99560
99738
  });
99561
- // version: 1.451.0-b776d3eb45
99739
+ // version: 1.453.0-725e09cf25
99562
99740
 
99563
99741
  const { create: create$2, keys: keys$2 } = Object;
99564
99742
  const { stringify, parse } = JSON;
@@ -107093,7 +107271,7 @@ function registerCallback(cb) {
107093
107271
  cb(graphql_v1_import, graphql_imperative$1, graphql_imperative_legacy_v1_import, graphql_state_manager, useOneStoreGraphQL);
107094
107272
  }
107095
107273
  }
107096
- // version: 1.451.0-231643dd9a
107274
+ // version: 1.453.0-e6dc0c039a
107097
107275
 
107098
107276
  function createFragmentMap(documentNode) {
107099
107277
  const fragments = {};
@@ -136336,7 +136514,7 @@ register$1({
136336
136514
  configuration: { ...configurationForGraphQLAdapters$1 },
136337
136515
  instrument: instrument$1,
136338
136516
  });
136339
- // version: 1.451.0-231643dd9a
136517
+ // version: 1.453.0-e6dc0c039a
136340
136518
 
136341
136519
  // On core the unstable adapters are re-exported with different names,
136342
136520
  // we want to match them here.
@@ -136488,7 +136666,7 @@ withDefaultLuvio((luvio) => {
136488
136666
  unstable_graphQL_imperative = createImperativeAdapter(luvio, createInstrumentedAdapter(ldsAdapter, adapterMetadata), adapterMetadata);
136489
136667
  graphQLImperative = ldsAdapter;
136490
136668
  });
136491
- // version: 1.451.0-231643dd9a
136669
+ // version: 1.453.0-e6dc0c039a
136492
136670
 
136493
136671
  var gqlApi = /*#__PURE__*/Object.freeze({
136494
136672
  __proto__: null,
@@ -137287,7 +137465,7 @@ const callbacks$1 = [];
137287
137465
  function register(r) {
137288
137466
  callbacks$1.forEach((callback) => callback(r));
137289
137467
  }
137290
- // version: 1.451.0-b776d3eb45
137468
+ // version: 1.453.0-725e09cf25
137291
137469
 
137292
137470
  /**
137293
137471
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -138576,4 +138754,4 @@ const { luvio } = getRuntime();
138576
138754
  setDefaultLuvio({ luvio });
138577
138755
 
138578
138756
  export { createPrimingSession, draftManager, draftQueue, evictCacheRecordsByIds, evictExpiredCacheEntries, executeAdapter, executeMutatingAdapter, getImperativeAdapterNames, importLuvioAdapterModule, importOneStoreAdapterModule, invokeAdapter, invokeAdapterWithDraftToMerge, invokeAdapterWithDraftToReplace, invokeAdapterWithMetadata, nimbusDraftQueue, registerReportObserver, setMetadataTTL, setUiApiRecordTTL, stopEviction, subscribeToAdapter };
138579
- // version: 1.451.0-b776d3eb45
138757
+ // version: 1.453.0-725e09cf25
@@ -4282,7 +4282,7 @@
4282
4282
  }
4283
4283
  callbacks.push(callback);
4284
4284
  }
4285
- // version: 1.451.0-b776d3eb45
4285
+ // version: 1.453.0-725e09cf25
4286
4286
 
4287
4287
  // TODO [TD-0081508]: once that TD is fulfilled we can probably change this file
4288
4288
  function instrumentAdapter$1(createFunction, _metadata) {
@@ -5326,7 +5326,7 @@
5326
5326
  const { apiFamily, name } = metadata;
5327
5327
  return createGraphQLWireAdapterConstructor$1(adapter, `${apiFamily}.${name}`, luvio, astResolver);
5328
5328
  }
5329
- // version: 1.451.0-b776d3eb45
5329
+ // version: 1.453.0-725e09cf25
5330
5330
 
5331
5331
  function isSupportedEntity(_objectApiName) {
5332
5332
  return true;
@@ -14379,7 +14379,7 @@
14379
14379
  return undefined;
14380
14380
  }
14381
14381
 
14382
- const VERSION$10$2 = "9859bc4e735e831effdc3df4b3b3e415";
14382
+ const VERSION$10$2 = "3f49d751896cf66e6e29788d8880e2cc";
14383
14383
  const RepresentationType$G = 'PlatformActionRepresentation';
14384
14384
  function keyBuilder$1P$1(luvio, config) {
14385
14385
  return keyPrefix$3 + '::' + RepresentationType$G + ':' + config.externalId + ':' + (config.relatedSourceObject === null ? '' : config.relatedSourceObject) + ':' + (config.relatedListRecordId === null ? '' : config.relatedListRecordId);
@@ -14439,10 +14439,6 @@
14439
14439
  name: 'lwcComponent',
14440
14440
  kind: 'Scalar'
14441
14441
  },
14442
- {
14443
- name: 'pageDeveloperName',
14444
- kind: 'Scalar'
14445
- },
14446
14442
  {
14447
14443
  name: 'primaryColor',
14448
14444
  kind: 'Scalar'
@@ -14471,14 +14467,6 @@
14471
14467
  name: 'targetObject',
14472
14468
  kind: 'Scalar'
14473
14469
  },
14474
- {
14475
- name: 'targetParentField',
14476
- kind: 'Scalar'
14477
- },
14478
- {
14479
- name: 'targetRecordTypeId',
14480
- kind: 'Scalar'
14481
- },
14482
14470
  {
14483
14471
  name: 'targetUrl',
14484
14472
  kind: 'Scalar'
@@ -14551,11 +14539,6 @@
14551
14539
  if (!(existing_lwcComponent === incoming_lwcComponent)) {
14552
14540
  return false;
14553
14541
  }
14554
- const existing_pageDeveloperName = existing.pageDeveloperName;
14555
- const incoming_pageDeveloperName = incoming.pageDeveloperName;
14556
- if (!(existing_pageDeveloperName === incoming_pageDeveloperName)) {
14557
- return false;
14558
- }
14559
14542
  const existing_primaryColor = existing.primaryColor;
14560
14543
  const incoming_primaryColor = incoming.primaryColor;
14561
14544
  if (!(existing_primaryColor === incoming_primaryColor)) {
@@ -14586,16 +14569,6 @@
14586
14569
  if (!(existing_targetObject === incoming_targetObject)) {
14587
14570
  return false;
14588
14571
  }
14589
- const existing_targetParentField = existing.targetParentField;
14590
- const incoming_targetParentField = incoming.targetParentField;
14591
- if (!(existing_targetParentField === incoming_targetParentField)) {
14592
- return false;
14593
- }
14594
- const existing_targetRecordTypeId = existing.targetRecordTypeId;
14595
- const incoming_targetRecordTypeId = incoming.targetRecordTypeId;
14596
- if (!(existing_targetRecordTypeId === incoming_targetRecordTypeId)) {
14597
- return false;
14598
- }
14599
14572
  const existing_targetUrl = existing.targetUrl;
14600
14573
  const incoming_targetUrl = incoming.targetUrl;
14601
14574
  if (!(existing_targetUrl === incoming_targetUrl)) {
@@ -32580,7 +32553,7 @@
32580
32553
  throttle(60, 60000, setupNotifyAllListRecordUpdateAvailable(luvio));
32581
32554
  throttle(60, 60000, setupNotifyAllListInfoSummaryUpdateAvailable(luvio));
32582
32555
  });
32583
- // version: 1.451.0-231643dd9a
32556
+ // version: 1.453.0-e6dc0c039a
32584
32557
 
32585
32558
  var draftQueueMaxRetryAttemptsGate = {
32586
32559
  isOpen: function (e) {
@@ -32591,6 +32564,15 @@
32591
32564
  },
32592
32565
  };
32593
32566
 
32567
+ var draftQueueRedirectScanGate = {
32568
+ isOpen: function (e) {
32569
+ return e.fallback;
32570
+ },
32571
+ hasError: function () {
32572
+ return !0;
32573
+ },
32574
+ };
32575
+
32594
32576
  var rotateIdempotencyKeyOn400Killswitch = {
32595
32577
  isOpen: function (e) {
32596
32578
  return e.fallback;
@@ -44136,6 +44118,15 @@
44136
44118
  },
44137
44119
  };
44138
44120
 
44121
+ var draftQueueStoreSnapshotGate = {
44122
+ isOpen: function (e) {
44123
+ return e.fallback;
44124
+ },
44125
+ hasError: function () {
44126
+ return !0;
44127
+ },
44128
+ };
44129
+
44139
44130
  var ldsAdapterO11yLoggingGate = {
44140
44131
  isOpen: function (e) {
44141
44132
  return e.fallback;
@@ -45925,6 +45916,15 @@
45925
45916
  return ok$3(document);
45926
45917
  }
45927
45918
 
45919
+ var draftQueueSideEffectMapGate = {
45920
+ isOpen: function (e) {
45921
+ return e.fallback;
45922
+ },
45923
+ hasError: function () {
45924
+ return !0;
45925
+ },
45926
+ };
45927
+
45928
45928
  var productConsumedSideEffects = {
45929
45929
  isOpen: function (e) {
45930
45930
  return e.fallback;
@@ -48247,7 +48247,7 @@
48247
48247
  }
48248
48248
  return handler;
48249
48249
  }
48250
- constructor(draftStore) {
48250
+ constructor(draftStore, instrumentation) {
48251
48251
  this.retryIntervalMilliseconds = 0;
48252
48252
  this.minimumRetryInterval = 250;
48253
48253
  this.maximumRetryInterval = 32000;
@@ -48264,6 +48264,7 @@
48264
48264
  this.handlers = {};
48265
48265
  this.draftStore = draftStore;
48266
48266
  this.workerPool = new AsyncWorkerPool(1);
48267
+ this.instrumentation = instrumentation;
48267
48268
  }
48268
48269
  addHandler(handler) {
48269
48270
  const id = handler.handlerId;
@@ -48354,15 +48355,22 @@
48354
48355
  this.state = DraftQueueState.Stopped;
48355
48356
  }
48356
48357
  async getQueueActions() {
48358
+ // W-23515682: time the whole call, including this queue's own internal
48359
+ // this.getQueueActions() during a drain — the amplification signal an external wrapper
48360
+ // cannot see. Measure-only; control flow below is unchanged.
48361
+ const { instrumentation } = this;
48362
+ const start = instrumentation?.onGetQueueActions !== undefined ? Date.now() : 0;
48357
48363
  const drafts = (await this.draftStore.getAllDrafts());
48358
48364
  const queue = [];
48359
48365
  drafts.forEach((draft) => {
48360
48366
  if (draft.id === this.uploadingActionId) {
48361
- draft.status = DraftActionStatus.Uploading;
48367
+ queue.push({ ...draft, status: DraftActionStatus.Uploading });
48368
+ }
48369
+ else {
48370
+ queue.push(draft);
48362
48371
  }
48363
- queue.push(draft);
48364
48372
  });
48365
- return queue.sort((a, b) => {
48373
+ const sorted = queue.sort((a, b) => {
48366
48374
  const aTime = parseInt(a.id, 10);
48367
48375
  const bTime = parseInt(b.id, 10);
48368
48376
  // safety check
@@ -48374,6 +48382,8 @@
48374
48382
  }
48375
48383
  return aTime - bTime;
48376
48384
  });
48385
+ instrumentation?.onGetQueueActions?.(Date.now() - start, sorted.length);
48386
+ return sorted;
48377
48387
  }
48378
48388
  async enqueue(handlerId, data, observabilityContext) {
48379
48389
  return this.workerPool.push({
@@ -48522,6 +48532,8 @@
48522
48532
  const listener = draftQueueChangedListeners[i];
48523
48533
  results.push(listener(event));
48524
48534
  }
48535
+ // W-23515682: listener fan-out per event type. Measure-only.
48536
+ this.instrumentation?.onNotifyChangedListeners?.(event.type, draftQueueLen);
48525
48537
  await Promise.all(results);
48526
48538
  }
48527
48539
  /**
@@ -48769,36 +48781,66 @@
48769
48781
  *
48770
48782
  */
48771
48783
  class DurableDraftStore {
48772
- constructor(durableStore) {
48784
+ constructor(durableStore, instrumentation, useSnapshot = true) {
48773
48785
  this.draftStore = {};
48786
+ // Snapshot: holds deep clones of each draft, refreshed at write time.
48787
+ // getAllDrafts serves from this snapshot with zero deep clones per read.
48788
+ this.cleanSnapshot = {};
48774
48789
  // queue of writes that were made during the initial sync
48775
48790
  this.writeQueue = [];
48776
48791
  this.durableStore = durableStore;
48792
+ this.instrumentation = instrumentation;
48793
+ this.useSnapshot = useSnapshot;
48777
48794
  this.resyncDraftStore();
48778
48795
  }
48796
+ // When no instrumentation is injected the promise is returned as-is (zero overhead).
48797
+ timeDurableOp(op, segment, run) {
48798
+ const onDurableOperation = this.instrumentation?.onDurableOperation;
48799
+ if (onDurableOperation === undefined) {
48800
+ return run();
48801
+ }
48802
+ const start = Date.now();
48803
+ return run().finally(() => {
48804
+ onDurableOperation(op, segment, Date.now() - start);
48805
+ });
48806
+ }
48779
48807
  writeAction(action) {
48780
48808
  const addAction = () => {
48781
48809
  const { id, tag } = action;
48782
48810
  this.draftStore[id] = action;
48811
+ this.cleanSnapshot[id] = clone$1(action);
48783
48812
  const durableEntryKey = buildDraftDurableStoreKey(tag, id);
48784
48813
  const entry = {
48785
48814
  data: action,
48786
48815
  };
48787
48816
  const entries = { [durableEntryKey]: entry };
48788
- return this.durableStore.setEntries(entries, DRAFT_SEGMENT);
48817
+ return this.timeDurableOp('setEntries', DRAFT_SEGMENT, () => this.durableStore.setEntries(entries, DRAFT_SEGMENT));
48789
48818
  };
48790
48819
  return this.enqueueAction(addAction);
48791
48820
  }
48792
48821
  getAllDrafts() {
48793
48822
  const waitForOngoingSync = this.syncPromise || Promise.resolve();
48794
48823
  return waitForOngoingSync.then(() => {
48795
- const { draftStore } = this;
48796
- const keys$1 = keys$4(draftStore);
48797
48824
  const actionArray = [];
48798
- for (let i = 0, len = keys$1.length; i < len; i++) {
48799
- const key = keys$1[i];
48800
- // clone draft so we don't expose the internal draft store
48801
- actionArray.push(clone$1(draftStore[key]));
48825
+ if (this.useSnapshot) {
48826
+ const { cleanSnapshot } = this;
48827
+ const keys$1 = keys$4(cleanSnapshot);
48828
+ for (let i = 0, len = keys$1.length; i < len; i++) {
48829
+ const key = keys$1[i];
48830
+ // Return shallow spread of snapshot entries so top-level caller mutations
48831
+ // (e.g., .status =) don't persist into the snapshot. The snapshot itself
48832
+ // is a deep clone of draftStore, so no nested mutation reaches draftStore.
48833
+ actionArray.push({ ...cleanSnapshot[key] });
48834
+ }
48835
+ }
48836
+ else {
48837
+ // Killswitch (W-23515682): original pre-fix behavior — deep-clone every draft on
48838
+ // every read so the internal draft store is never exposed. O(N) clones per read.
48839
+ const { draftStore } = this;
48840
+ const keys$1 = keys$4(draftStore);
48841
+ for (let i = 0, len = keys$1.length; i < len; i++) {
48842
+ actionArray.push(clone$1(draftStore[keys$1[i]]));
48843
+ }
48802
48844
  }
48803
48845
  return actionArray;
48804
48846
  });
@@ -48808,8 +48850,9 @@
48808
48850
  const draft = this.draftStore[id];
48809
48851
  if (draft !== undefined) {
48810
48852
  delete this.draftStore[id];
48853
+ delete this.cleanSnapshot[id];
48811
48854
  const durableKey = buildDraftDurableStoreKey(draft.tag, draft.id);
48812
- return this.durableStore.evictEntries([durableKey], DRAFT_SEGMENT);
48855
+ return this.timeDurableOp('evictEntries', DRAFT_SEGMENT, () => this.durableStore.evictEntries([durableKey], DRAFT_SEGMENT));
48813
48856
  }
48814
48857
  return Promise.resolve();
48815
48858
  };
@@ -48825,10 +48868,11 @@
48825
48868
  const action = draftStore[key];
48826
48869
  if (action.tag === tag) {
48827
48870
  delete draftStore[action.id];
48871
+ delete this.cleanSnapshot[action.id];
48828
48872
  durableKeys.push(buildDraftDurableStoreKey(action.tag, action.id));
48829
48873
  }
48830
48874
  }
48831
- return this.durableStore.evictEntries(durableKeys, DRAFT_SEGMENT);
48875
+ return this.timeDurableOp('evictEntries', DRAFT_SEGMENT, () => this.durableStore.evictEntries(durableKeys, DRAFT_SEGMENT));
48832
48876
  };
48833
48877
  return this.enqueueAction(deleteAction);
48834
48878
  }
@@ -48842,6 +48886,7 @@
48842
48886
  const action = draftStore[operation.id];
48843
48887
  if (action !== undefined) {
48844
48888
  delete draftStore[operation.id];
48889
+ delete this.cleanSnapshot[operation.id];
48845
48890
  const key = buildDraftDurableStoreKey(action.tag, action.id);
48846
48891
  durableStoreOperations.push({
48847
48892
  ids: [key],
@@ -48854,6 +48899,7 @@
48854
48899
  const { action } = operation;
48855
48900
  const key = buildDraftDurableStoreKey(action.tag, action.id);
48856
48901
  draftStore[action.id] = action;
48902
+ this.cleanSnapshot[action.id] = clone$1(action);
48857
48903
  durableStoreOperations.push({
48858
48904
  type: 'setEntries',
48859
48905
  segment: DRAFT_SEGMENT,
@@ -48865,7 +48911,7 @@
48865
48911
  });
48866
48912
  }
48867
48913
  }
48868
- return this.durableStore.batchOperations(durableStoreOperations);
48914
+ return this.timeDurableOp('batchOperations', DRAFT_SEGMENT, () => this.durableStore.batchOperations(durableStoreOperations));
48869
48915
  };
48870
48916
  return this.enqueueAction(action);
48871
48917
  }
@@ -48908,6 +48954,7 @@
48908
48954
  .then((durableEntries) => {
48909
48955
  if (durableEntries === undefined) {
48910
48956
  this.draftStore = {};
48957
+ this.cleanSnapshot = {};
48911
48958
  return this.runQueuedOperations();
48912
48959
  }
48913
48960
  const { draftStore } = this;
@@ -48916,6 +48963,7 @@
48916
48963
  const entry = durableEntries[keys$1[i]];
48917
48964
  const action = entry.data;
48918
48965
  draftStore[action.id] = action;
48966
+ this.cleanSnapshot[action.id] = clone$1(action);
48919
48967
  }
48920
48968
  return this.runQueuedOperations();
48921
48969
  })
@@ -80140,6 +80188,14 @@
80140
80188
  // Used to triage the WOLI duplicate-record investigation in Splunk — correlate by draftId
80141
80189
  // with the other draft-queue events. @W-23428669
80142
80190
  const DRAFT_QUEUE_IDEMPOTENCY_KEY_ROTATED = 'draft-queue-idempotency-key-rotated';
80191
+ // Hotspot-attribution telemetry (@W-23515682). Operational counts/durations only — no
80192
+ // draft content, field values, or server record ids. These attribute the offline-drain wall-clock
80193
+ // to the O(N^2) hotspots (getQueueActions amplification, listener fan-out, durable-op latency).
80194
+ const DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT = 'draft-queue-get-queue-actions-count';
80195
+ const DRAFT_QUEUE_GET_QUEUE_ACTIONS_DURATION = 'draft-queue-get-queue-actions-duration';
80196
+ const DRAFT_QUEUE_NOTIFY_CHANGED_LISTENERS_COUNT = 'draft-queue-notify-changed-listeners-count';
80197
+ const DRAFT_QUEUE_DURABLE_OP_COUNT = 'draft-queue-durable-op-count';
80198
+ const DRAFT_QUEUE_DURABLE_OP_DURATION = 'draft-queue-durable-op-duration';
80143
80199
  /** Content Document */
80144
80200
  const CREATE_CONTENT_DOCUMENT_AND_VERSION_TOTAL_SYNTHESIZE_CALLS = 'content-document-version-total-synthesize-calls';
80145
80201
  const CREATE_CONTENT_DOCUMENT_AND_VERSION_DRAFT_SYNTHESIZE_ERROR = 'create-content-document-version-draft-synthesize-error';
@@ -80232,6 +80288,33 @@
80232
80288
  break;
80233
80289
  }
80234
80290
  }
80291
+ /**
80292
+ * @W-23515682: count (amplification — queue re-reads per drain) + duration histogram (the
80293
+ * pre/post signal for the getAllDrafts clone-on-write fix). actionCount is queue depth, never content.
80294
+ */
80295
+ function reportDraftQueueGetQueueActions(durationMs, actionCount) {
80296
+ const depthBuckets = [1, 2, 5, 10, 20, 50, 100, 250, 500];
80297
+ ldsMobileInstrumentation$2.incrementCounter(DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT);
80298
+ ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_GET_QUEUE_ACTIONS_DURATION, durationMs);
80299
+ ldsMobileInstrumentation$2.bucketValue(DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT + '-depth', actionCount, depthBuckets);
80300
+ }
80301
+ /**
80302
+ * @W-23515682: count per event type — listener fan-out / amplification per event.
80303
+ */
80304
+ function reportDraftQueueNotifyChangedListeners(eventType, listenerCount) {
80305
+ ldsMobileInstrumentation$2.incrementCounter(DRAFT_QUEUE_NOTIFY_CHANGED_LISTENERS_COUNT, listenerCount, undefined, { eventType });
80306
+ }
80307
+ /**
80308
+ * @W-23515682: count + duration histogram by operation kind + segment — attributes the
80309
+ * client-CPU-vs-durable split of per-action processing time.
80310
+ */
80311
+ function reportDraftQueueDurableOperation(op, segment, durationMs) {
80312
+ ldsMobileInstrumentation$2.incrementCounter(DRAFT_QUEUE_DURABLE_OP_COUNT, 1, undefined, {
80313
+ op,
80314
+ segment,
80315
+ });
80316
+ ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_DURABLE_OP_DURATION, durationMs);
80317
+ }
80235
80318
  /**
80236
80319
  * Reports an exception thrown while uploading a draft action. The thrown error is
80237
80320
  * otherwise swallowed by the upload handler's retry path, leaving the failure
@@ -80715,14 +80798,45 @@
80715
80798
  let updatedActionTargetId = undefined;
80716
80799
  const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
80717
80800
  let { basePath, body } = queueActionRequest;
80801
+ // Literal indexOf pre-check (no RegExp coercion, unlike the old String.search) +
80802
+ // a replace RegExp built only when a reference is present — the win over the old scan.
80718
80803
  let stringifiedBody = stringify$5(body);
80804
+ // Read the killswitch once for the whole scan (W-23515682) — avoids a
80805
+ // per-redirect gate lookup in this hot loop.
80806
+ const useOptimizedScan = draftQueueRedirectScanGate.isOpen({ fallback: true });
80719
80807
  // for each redirected ID/key we loop over the operation to see if it needs
80720
80808
  // to be updated
80721
80809
  for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
80722
- if (basePath.search(draftId) >= 0 || stringifiedBody.search(draftId) >= 0) {
80723
- basePath = basePath.replace(new RegExp(draftId, 'g'), canonicalId);
80724
- stringifiedBody = stringifiedBody.replace(new RegExp(draftId, 'g'), canonicalId);
80725
- queueOperationMutated = true;
80810
+ if (useOptimizedScan) {
80811
+ // basePath AND body are checked independently: a queued action can carry
80812
+ // the draft id in either or BOTH, and each occurrence must be rewritten.
80813
+ const basePathNeedsUpdate = basePath.indexOf(draftId) >= 0;
80814
+ const bodyNeedsUpdate = stringifiedBody.indexOf(draftId) >= 0;
80815
+ if (basePathNeedsUpdate || bodyNeedsUpdate) {
80816
+ // Escape metacharacters so the draft id matches literally. Salesforce
80817
+ // ids carry no regex metacharacters today, so this is defensive against
80818
+ // a future id-format change silently corrupting unrelated ids. W-23515682.
80819
+ const escapedDraftId = draftId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
80820
+ const replaceRegex = new RegExp(escapedDraftId, 'g');
80821
+ if (basePathNeedsUpdate) {
80822
+ basePath = basePath.replace(replaceRegex, canonicalId);
80823
+ }
80824
+ if (bodyNeedsUpdate) {
80825
+ stringifiedBody = stringifiedBody.replace(replaceRegex, canonicalId);
80826
+ }
80827
+ queueOperationMutated = true;
80828
+ }
80829
+ }
80830
+ else {
80831
+ // Killswitch closed (W-23515682): original pre-fix scan — String.search
80832
+ // (coerces draftId to a RegExp) + a raw (unescaped) replace RegExp applied
80833
+ // to basePath and body together.
80834
+ if (basePath.search(draftId) >= 0 ||
80835
+ stringifiedBody.search(draftId) >= 0) {
80836
+ basePath = basePath.replace(new RegExp(draftId, 'g'), canonicalId);
80837
+ stringifiedBody = stringifiedBody.replace(new RegExp(draftId, 'g'), canonicalId);
80838
+ queueOperationMutated = true;
80839
+ }
80726
80840
  }
80727
80841
  // if the action is performed on a previous draft id, we need to replace the action
80728
80842
  // with a new one at the updated canonical key
@@ -90295,6 +90409,20 @@
90295
90409
  }
90296
90410
  }
90297
90411
 
90412
+ // Mobile impl of the shared DraftQueueInstrumentation seam (@W-23515682). Forwards to
90413
+ // ldsMobileInstrumentation via metrics.ts; injected at construction (DraftQueueFactory).
90414
+ const draftQueueInstrumentation = {
90415
+ onGetQueueActions(durationMs, actionCount) {
90416
+ reportDraftQueueGetQueueActions(durationMs, actionCount);
90417
+ },
90418
+ onNotifyChangedListeners(eventType, listenerCount) {
90419
+ reportDraftQueueNotifyChangedListeners(eventType, listenerCount);
90420
+ },
90421
+ onDurableOperation(op, segment, durationMs) {
90422
+ reportDraftQueueDurableOperation(op, segment, durationMs);
90423
+ },
90424
+ };
90425
+
90298
90426
  // so eslint doesn't complain about nimbus
90299
90427
  /* global __nimbus */
90300
90428
  function buildLdsDraftQueue(durableStore) {
@@ -90303,7 +90431,8 @@
90303
90431
  __nimbus.plugins.LdsDraftQueue !== undefined) {
90304
90432
  return new NimbusDraftQueue();
90305
90433
  }
90306
- const draftQueue = new DurableDraftQueue(new DurableDraftStore(durableStore));
90434
+ const useSnapshot = draftQueueStoreSnapshotGate.isOpen({ fallback: true });
90435
+ const draftQueue = new DurableDraftQueue(new DurableDraftStore(durableStore, draftQueueInstrumentation, useSnapshot), draftQueueInstrumentation);
90307
90436
  return instrumentDraftQueue(draftQueue);
90308
90437
  }
90309
90438
 
@@ -93634,18 +93763,34 @@
93634
93763
  this.initialize();
93635
93764
  }
93636
93765
  async addEffects(effects) {
93766
+ const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
93637
93767
  const durableEntries = {};
93638
93768
  effects.forEach((effect) => {
93639
93769
  durableEntries[effect.uniqueId] = { data: effect };
93640
93770
  this.allEffects.set(effect.uniqueId, effect);
93771
+ if (incremental) {
93772
+ this.addEffectToKeyMap(effect);
93773
+ }
93641
93774
  });
93642
- this.regenerateKeyToEffectMap();
93775
+ // Killswitch (W-23515682): closed → original O(N) full rebuild on every add.
93776
+ if (!incremental) {
93777
+ this.regenerateKeyToEffectMap();
93778
+ }
93643
93779
  await this.durableStore.setEntries(durableEntries, SIDE_EFFECT_SEGMENT);
93644
93780
  }
93645
93781
  async removeEffects(effects) {
93782
+ const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
93646
93783
  const effectKeys = effects.map((e) => e.uniqueId);
93647
- effects.forEach((k) => this.allEffects.delete(k.uniqueId));
93648
- this.regenerateKeyToEffectMap();
93784
+ effects.forEach((effect) => {
93785
+ this.allEffects.delete(effect.uniqueId);
93786
+ if (incremental) {
93787
+ this.removeEffectFromKeyMap(effect);
93788
+ }
93789
+ });
93790
+ // Killswitch (W-23515682): closed → original O(N) full rebuild on every remove.
93791
+ if (!incremental) {
93792
+ this.regenerateKeyToEffectMap();
93793
+ }
93649
93794
  await this.durableStore.evictEntries(effectKeys, SIDE_EFFECT_SEGMENT);
93650
93795
  }
93651
93796
  getEffects(key) {
@@ -93680,6 +93825,39 @@
93680
93825
  });
93681
93826
  this.regenerateKeyToEffectMap();
93682
93827
  }
93828
+ addEffectToKeyMap(effect) {
93829
+ const recordEffects = this.keyToEffectMap.get(effect.key);
93830
+ if (recordEffects) {
93831
+ // Effects are keyed by uniqueId (mirroring allEffects.set / regenerateKeyToEffectMap):
93832
+ // a re-add of the same uniqueId must REPLACE the resident entry in place, not append a
93833
+ // duplicate. The action-completion replay path (setSideEffectsForActions on still-queued
93834
+ // actions) re-adds resident effects with no preceding remove; without this dedup a
93835
+ // record-field-increment would sit in the array twice and be applied twice (e.g. a
93836
+ // ProductConsumed QuantityOnHand -=5 becomes -=10). W-23515682.
93837
+ const index = recordEffects.findIndex((e) => e.uniqueId === effect.uniqueId);
93838
+ if (index !== -1) {
93839
+ recordEffects[index] = effect;
93840
+ }
93841
+ else {
93842
+ recordEffects.push(effect);
93843
+ }
93844
+ }
93845
+ else {
93846
+ this.keyToEffectMap.set(effect.key, [effect]);
93847
+ }
93848
+ }
93849
+ removeEffectFromKeyMap(effect) {
93850
+ const recordEffects = this.keyToEffectMap.get(effect.key);
93851
+ if (recordEffects) {
93852
+ const index = recordEffects.findIndex((e) => e.uniqueId === effect.uniqueId);
93853
+ if (index !== -1) {
93854
+ recordEffects.splice(index, 1);
93855
+ if (recordEffects.length === 0) {
93856
+ this.keyToEffectMap.delete(effect.key);
93857
+ }
93858
+ }
93859
+ }
93860
+ }
93683
93861
  regenerateKeyToEffectMap() {
93684
93862
  this.keyToEffectMap.clear();
93685
93863
  this.allEffects.forEach((effect) => {
@@ -96829,7 +97007,7 @@
96829
97007
  },
96830
97008
  };
96831
97009
  }
96832
- // version: 1.451.0-b776d3eb45
97010
+ // version: 1.453.0-725e09cf25
96833
97011
 
96834
97012
  /**
96835
97013
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -96855,7 +97033,7 @@
96855
97033
  },
96856
97034
  };
96857
97035
  }
96858
- // version: 1.451.0-b776d3eb45
97036
+ // version: 1.453.0-725e09cf25
96859
97037
 
96860
97038
  function findExecutableOperation$1(input) {
96861
97039
  const operations = input.query.definitions.filter(
@@ -99564,7 +99742,7 @@
99564
99742
  id: '@salesforce/lds-network-adapter',
99565
99743
  instrument: instrument$2,
99566
99744
  });
99567
- // version: 1.451.0-b776d3eb45
99745
+ // version: 1.453.0-725e09cf25
99568
99746
 
99569
99747
  const { create: create$2, keys: keys$2 } = Object;
99570
99748
  const { stringify, parse } = JSON;
@@ -107099,7 +107277,7 @@
107099
107277
  cb(graphql_v1_import, graphql_imperative$1, graphql_imperative_legacy_v1_import, graphql_state_manager, useOneStoreGraphQL);
107100
107278
  }
107101
107279
  }
107102
- // version: 1.451.0-231643dd9a
107280
+ // version: 1.453.0-e6dc0c039a
107103
107281
 
107104
107282
  function createFragmentMap(documentNode) {
107105
107283
  const fragments = {};
@@ -136342,7 +136520,7 @@
136342
136520
  configuration: { ...configurationForGraphQLAdapters$1 },
136343
136521
  instrument: instrument$1,
136344
136522
  });
136345
- // version: 1.451.0-231643dd9a
136523
+ // version: 1.453.0-e6dc0c039a
136346
136524
 
136347
136525
  // On core the unstable adapters are re-exported with different names,
136348
136526
  // we want to match them here.
@@ -136494,7 +136672,7 @@
136494
136672
  unstable_graphQL_imperative = createImperativeAdapter(luvio, createInstrumentedAdapter(ldsAdapter, adapterMetadata), adapterMetadata);
136495
136673
  graphQLImperative = ldsAdapter;
136496
136674
  });
136497
- // version: 1.451.0-231643dd9a
136675
+ // version: 1.453.0-e6dc0c039a
136498
136676
 
136499
136677
  var gqlApi = /*#__PURE__*/Object.freeze({
136500
136678
  __proto__: null,
@@ -137293,7 +137471,7 @@
137293
137471
  function register(r) {
137294
137472
  callbacks$1.forEach((callback) => callback(r));
137295
137473
  }
137296
- // version: 1.451.0-b776d3eb45
137474
+ // version: 1.453.0-725e09cf25
137297
137475
 
137298
137476
  /**
137299
137477
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -138603,4 +138781,4 @@
138603
138781
  exports.subscribeToAdapter = subscribeToAdapter;
138604
138782
 
138605
138783
  }));
138606
- // version: 1.451.0-b776d3eb45
138784
+ // version: 1.453.0-725e09cf25
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-worker-api",
3
- "version": "1.451.0",
3
+ "version": "1.453.0",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "",
6
6
  "main": "dist/standalone/es/lds-worker-api.js",
@@ -35,14 +35,14 @@
35
35
  },
36
36
  "devDependencies": {
37
37
  "@oat-sa/rollup-plugin-wildcard-external": "^1.0.0",
38
- "@salesforce/lds-adapters-graphql": "^1.451.0",
39
- "@salesforce/lds-adapters-uiapi": "^1.451.0",
40
- "@salesforce/lds-default-luvio": "^1.451.0",
41
- "@salesforce/lds-drafts": "^1.451.0",
42
- "@salesforce/lds-graphql-parser": "^1.451.0",
43
- "@salesforce/lds-luvio-engine": "^1.451.0",
44
- "@salesforce/lds-runtime-mobile": "^1.451.0",
45
- "@salesforce/nimbus-plugin-lds": "^1.451.0",
38
+ "@salesforce/lds-adapters-graphql": "^1.453.0",
39
+ "@salesforce/lds-adapters-uiapi": "^1.453.0",
40
+ "@salesforce/lds-default-luvio": "^1.453.0",
41
+ "@salesforce/lds-drafts": "^1.453.0",
42
+ "@salesforce/lds-graphql-parser": "^1.453.0",
43
+ "@salesforce/lds-luvio-engine": "^1.453.0",
44
+ "@salesforce/lds-runtime-mobile": "^1.453.0",
45
+ "@salesforce/nimbus-plugin-lds": "^1.453.0",
46
46
  "ajv": "^8.11.0",
47
47
  "glob": "^7.1.5",
48
48
  "nimbus-types": "^2.0.0-alpha1",