@salesforce/lds-runtime-mobile 1.451.0-dev2 → 1.452.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/main.js +207 -26
- package/dist/types/drafts/sideEffects/SideEffectStore.d.ts +2 -0
- package/dist/types/instrumentation/draftQueueInstrumentation.d.ts +2 -0
- package/dist/types/instrumentation/metrics.d.ts +14 -0
- package/package.json +16 -16
- package/sfdc/main.js +207 -26
- package/sfdc/types/drafts/sideEffects/SideEffectStore.d.ts +2 -0
- package/sfdc/types/instrumentation/draftQueueInstrumentation.d.ts +2 -0
- package/sfdc/types/instrumentation/metrics.d.ts +14 -0
package/dist/main.js
CHANGED
|
@@ -20,6 +20,7 @@ import { setupInstrumentation, instrumentAdapter as instrumentAdapter$1, instrum
|
|
|
20
20
|
import { HttpStatusCode as HttpStatusCode$1, setBypassDeepFreeze, StoreKeySet, StringKeyInMemoryStore, Reader, serializeStructuredKey, deepFreeze as deepFreeze$1, emitAdapterEvent, ingestShape, coerceConfig as coerceConfig$1, typeCheckConfig as typeCheckConfig$h, createResourceParams as createResourceParams$h, StoreKeyMap, buildNetworkSnapshotCachePolicy as buildNetworkSnapshotCachePolicy$f, resolveLink, createCustomAdapterEventEmitter, isFileReference, Environment, Luvio, InMemoryStore } from '@luvio/engine';
|
|
21
21
|
import { isSupportedEntity, configuration, getObjectInfoAdapterFactory, RECORD_ID_PREFIX, RECORD_FIELDS_KEY_JUNCTION, isStoreKeyRecordViewEntity, extractRecordIdFromStoreKey, buildRecordRepKeyFromId, keyBuilderRecord, RecordRepresentationTTL, keyBuilderQuickActionExecutionRepresentation, ingestQuickActionExecutionRepresentation, getRecordId18 as getRecordId18$1, getRecordsAdapterFactory as getRecordsAdapterFactory$1, RecordRepresentationRepresentationType, ObjectInfoRepresentationType, getObjectInfosAdapterFactory, getObjectInfoDirectoryAdapterFactory, UiApiNamespace, RecordRepresentationType, RecordRepresentationVersion } from '@salesforce/lds-adapters-uiapi';
|
|
22
22
|
import draftQueueMaxRetryAttemptsGate from '@salesforce/gate/lmr.draft-queue-max-retry-attempts';
|
|
23
|
+
import draftQueueRedirectScanGate from '@salesforce/gate/lmr.draft-queue-redirect-scan';
|
|
23
24
|
import rotateIdempotencyKeyOn400Killswitch from '@salesforce/gate/lds.idempotency-key-on-400-killswitch';
|
|
24
25
|
import { getInstrumentation, idleDetector } from 'o11y/client';
|
|
25
26
|
import allowUpdatesForNonCachedRecords from '@salesforce/gate/lmr.allowUpdatesForNonCachedRecords';
|
|
@@ -30,6 +31,7 @@ import FIRST_DAY_OF_WEEK from '@salesforce/i18n/firstDayOfWeek';
|
|
|
30
31
|
import { entityFetchedGraphqlMetadataSchema, entityFetchedMetadataSchema } from 'o11y_schema/sf_lightningsdk';
|
|
31
32
|
import graphqQueryFieldLimit from '@salesforce/gate/lmr.graphqQueryFieldLimit';
|
|
32
33
|
import graphqlPartialEmitParity from '@salesforce/gate/lmr.graphqlPartialEmitParity';
|
|
34
|
+
import draftQueueStoreSnapshotGate from '@salesforce/gate/lmr.draft-queue-store-snapshot';
|
|
33
35
|
import { pdpEventSchema } from 'o11y_schema/sf_pdp';
|
|
34
36
|
import { instrument as instrument$1 } from '@salesforce/lds-bindings';
|
|
35
37
|
import ldsAdapterO11yLoggingGate from '@salesforce/gate/lmr.ldsAdapterO11yLogging';
|
|
@@ -49,6 +51,7 @@ import useOneStore from '@salesforce/gate/lmr.useOneStore';
|
|
|
49
51
|
import { setServices } from '@conduit-client/service-provisioner/v1';
|
|
50
52
|
import '@conduit-client/type-normalization/v1';
|
|
51
53
|
import { Kind as Kind$2, visit as visit$2, print as print$1, resolveAndValidateGraphQLConfig, toGraphQLErrorResponse } from '@conduit-client/onestore-graphql-parser/v1';
|
|
54
|
+
import draftQueueSideEffectMapGate from '@salesforce/gate/lmr.draft-queue-sideeffect-map';
|
|
52
55
|
import productConsumedSideEffects from '@salesforce/gate/com.salesforce.fieldservice.vanStockLDSBypass256';
|
|
53
56
|
import reviveOnlyRequestedFields from '@salesforce/gate/lmr.reviveOnlyRequestedFields';
|
|
54
57
|
|
|
@@ -2362,7 +2365,7 @@ class DurableDraftQueue {
|
|
|
2362
2365
|
}
|
|
2363
2366
|
return handler;
|
|
2364
2367
|
}
|
|
2365
|
-
constructor(draftStore) {
|
|
2368
|
+
constructor(draftStore, instrumentation) {
|
|
2366
2369
|
this.retryIntervalMilliseconds = 0;
|
|
2367
2370
|
this.minimumRetryInterval = 250;
|
|
2368
2371
|
this.maximumRetryInterval = 32000;
|
|
@@ -2379,6 +2382,7 @@ class DurableDraftQueue {
|
|
|
2379
2382
|
this.handlers = {};
|
|
2380
2383
|
this.draftStore = draftStore;
|
|
2381
2384
|
this.workerPool = new AsyncWorkerPool(1);
|
|
2385
|
+
this.instrumentation = instrumentation;
|
|
2382
2386
|
}
|
|
2383
2387
|
addHandler(handler) {
|
|
2384
2388
|
const id = handler.handlerId;
|
|
@@ -2469,15 +2473,22 @@ class DurableDraftQueue {
|
|
|
2469
2473
|
this.state = DraftQueueState.Stopped;
|
|
2470
2474
|
}
|
|
2471
2475
|
async getQueueActions() {
|
|
2476
|
+
// W-23515682: time the whole call, including this queue's own internal
|
|
2477
|
+
// this.getQueueActions() during a drain — the amplification signal an external wrapper
|
|
2478
|
+
// cannot see. Measure-only; control flow below is unchanged.
|
|
2479
|
+
const { instrumentation } = this;
|
|
2480
|
+
const start = instrumentation?.onGetQueueActions !== undefined ? Date.now() : 0;
|
|
2472
2481
|
const drafts = (await this.draftStore.getAllDrafts());
|
|
2473
2482
|
const queue = [];
|
|
2474
2483
|
drafts.forEach((draft) => {
|
|
2475
2484
|
if (draft.id === this.uploadingActionId) {
|
|
2476
|
-
draft
|
|
2485
|
+
queue.push({ ...draft, status: DraftActionStatus.Uploading });
|
|
2486
|
+
}
|
|
2487
|
+
else {
|
|
2488
|
+
queue.push(draft);
|
|
2477
2489
|
}
|
|
2478
|
-
queue.push(draft);
|
|
2479
2490
|
});
|
|
2480
|
-
|
|
2491
|
+
const sorted = queue.sort((a, b) => {
|
|
2481
2492
|
const aTime = parseInt(a.id, 10);
|
|
2482
2493
|
const bTime = parseInt(b.id, 10);
|
|
2483
2494
|
// safety check
|
|
@@ -2489,6 +2500,8 @@ class DurableDraftQueue {
|
|
|
2489
2500
|
}
|
|
2490
2501
|
return aTime - bTime;
|
|
2491
2502
|
});
|
|
2503
|
+
instrumentation?.onGetQueueActions?.(Date.now() - start, sorted.length);
|
|
2504
|
+
return sorted;
|
|
2492
2505
|
}
|
|
2493
2506
|
async enqueue(handlerId, data, observabilityContext) {
|
|
2494
2507
|
return this.workerPool.push({
|
|
@@ -2637,6 +2650,8 @@ class DurableDraftQueue {
|
|
|
2637
2650
|
const listener = draftQueueChangedListeners[i];
|
|
2638
2651
|
results.push(listener(event));
|
|
2639
2652
|
}
|
|
2653
|
+
// W-23515682: listener fan-out per event type. Measure-only.
|
|
2654
|
+
this.instrumentation?.onNotifyChangedListeners?.(event.type, draftQueueLen);
|
|
2640
2655
|
await Promise.all(results);
|
|
2641
2656
|
}
|
|
2642
2657
|
/**
|
|
@@ -2884,36 +2899,66 @@ function buildDraftDurableStoreKey(recordKey, draftActionId) {
|
|
|
2884
2899
|
*
|
|
2885
2900
|
*/
|
|
2886
2901
|
class DurableDraftStore {
|
|
2887
|
-
constructor(durableStore) {
|
|
2902
|
+
constructor(durableStore, instrumentation, useSnapshot = true) {
|
|
2888
2903
|
this.draftStore = {};
|
|
2904
|
+
// Snapshot: holds deep clones of each draft, refreshed at write time.
|
|
2905
|
+
// getAllDrafts serves from this snapshot with zero deep clones per read.
|
|
2906
|
+
this.cleanSnapshot = {};
|
|
2889
2907
|
// queue of writes that were made during the initial sync
|
|
2890
2908
|
this.writeQueue = [];
|
|
2891
2909
|
this.durableStore = durableStore;
|
|
2910
|
+
this.instrumentation = instrumentation;
|
|
2911
|
+
this.useSnapshot = useSnapshot;
|
|
2892
2912
|
this.resyncDraftStore();
|
|
2893
2913
|
}
|
|
2914
|
+
// When no instrumentation is injected the promise is returned as-is (zero overhead).
|
|
2915
|
+
timeDurableOp(op, segment, run) {
|
|
2916
|
+
const onDurableOperation = this.instrumentation?.onDurableOperation;
|
|
2917
|
+
if (onDurableOperation === undefined) {
|
|
2918
|
+
return run();
|
|
2919
|
+
}
|
|
2920
|
+
const start = Date.now();
|
|
2921
|
+
return run().finally(() => {
|
|
2922
|
+
onDurableOperation(op, segment, Date.now() - start);
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2894
2925
|
writeAction(action) {
|
|
2895
2926
|
const addAction = () => {
|
|
2896
2927
|
const { id, tag } = action;
|
|
2897
2928
|
this.draftStore[id] = action;
|
|
2929
|
+
this.cleanSnapshot[id] = clone$1(action);
|
|
2898
2930
|
const durableEntryKey = buildDraftDurableStoreKey(tag, id);
|
|
2899
2931
|
const entry = {
|
|
2900
2932
|
data: action,
|
|
2901
2933
|
};
|
|
2902
2934
|
const entries = { [durableEntryKey]: entry };
|
|
2903
|
-
return this.durableStore.setEntries(entries, DRAFT_SEGMENT);
|
|
2935
|
+
return this.timeDurableOp('setEntries', DRAFT_SEGMENT, () => this.durableStore.setEntries(entries, DRAFT_SEGMENT));
|
|
2904
2936
|
};
|
|
2905
2937
|
return this.enqueueAction(addAction);
|
|
2906
2938
|
}
|
|
2907
2939
|
getAllDrafts() {
|
|
2908
2940
|
const waitForOngoingSync = this.syncPromise || Promise.resolve();
|
|
2909
2941
|
return waitForOngoingSync.then(() => {
|
|
2910
|
-
const { draftStore } = this;
|
|
2911
|
-
const keys$1 = keys$4(draftStore);
|
|
2912
2942
|
const actionArray = [];
|
|
2913
|
-
|
|
2914
|
-
const
|
|
2915
|
-
|
|
2916
|
-
|
|
2943
|
+
if (this.useSnapshot) {
|
|
2944
|
+
const { cleanSnapshot } = this;
|
|
2945
|
+
const keys$1 = keys$4(cleanSnapshot);
|
|
2946
|
+
for (let i = 0, len = keys$1.length; i < len; i++) {
|
|
2947
|
+
const key = keys$1[i];
|
|
2948
|
+
// Return shallow spread of snapshot entries so top-level caller mutations
|
|
2949
|
+
// (e.g., .status =) don't persist into the snapshot. The snapshot itself
|
|
2950
|
+
// is a deep clone of draftStore, so no nested mutation reaches draftStore.
|
|
2951
|
+
actionArray.push({ ...cleanSnapshot[key] });
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
else {
|
|
2955
|
+
// Killswitch (W-23515682): original pre-fix behavior — deep-clone every draft on
|
|
2956
|
+
// every read so the internal draft store is never exposed. O(N) clones per read.
|
|
2957
|
+
const { draftStore } = this;
|
|
2958
|
+
const keys$1 = keys$4(draftStore);
|
|
2959
|
+
for (let i = 0, len = keys$1.length; i < len; i++) {
|
|
2960
|
+
actionArray.push(clone$1(draftStore[keys$1[i]]));
|
|
2961
|
+
}
|
|
2917
2962
|
}
|
|
2918
2963
|
return actionArray;
|
|
2919
2964
|
});
|
|
@@ -2923,8 +2968,9 @@ class DurableDraftStore {
|
|
|
2923
2968
|
const draft = this.draftStore[id];
|
|
2924
2969
|
if (draft !== undefined) {
|
|
2925
2970
|
delete this.draftStore[id];
|
|
2971
|
+
delete this.cleanSnapshot[id];
|
|
2926
2972
|
const durableKey = buildDraftDurableStoreKey(draft.tag, draft.id);
|
|
2927
|
-
return this.durableStore.evictEntries([durableKey], DRAFT_SEGMENT);
|
|
2973
|
+
return this.timeDurableOp('evictEntries', DRAFT_SEGMENT, () => this.durableStore.evictEntries([durableKey], DRAFT_SEGMENT));
|
|
2928
2974
|
}
|
|
2929
2975
|
return Promise.resolve();
|
|
2930
2976
|
};
|
|
@@ -2940,10 +2986,11 @@ class DurableDraftStore {
|
|
|
2940
2986
|
const action = draftStore[key];
|
|
2941
2987
|
if (action.tag === tag) {
|
|
2942
2988
|
delete draftStore[action.id];
|
|
2989
|
+
delete this.cleanSnapshot[action.id];
|
|
2943
2990
|
durableKeys.push(buildDraftDurableStoreKey(action.tag, action.id));
|
|
2944
2991
|
}
|
|
2945
2992
|
}
|
|
2946
|
-
return this.durableStore.evictEntries(durableKeys, DRAFT_SEGMENT);
|
|
2993
|
+
return this.timeDurableOp('evictEntries', DRAFT_SEGMENT, () => this.durableStore.evictEntries(durableKeys, DRAFT_SEGMENT));
|
|
2947
2994
|
};
|
|
2948
2995
|
return this.enqueueAction(deleteAction);
|
|
2949
2996
|
}
|
|
@@ -2957,6 +3004,7 @@ class DurableDraftStore {
|
|
|
2957
3004
|
const action = draftStore[operation.id];
|
|
2958
3005
|
if (action !== undefined) {
|
|
2959
3006
|
delete draftStore[operation.id];
|
|
3007
|
+
delete this.cleanSnapshot[operation.id];
|
|
2960
3008
|
const key = buildDraftDurableStoreKey(action.tag, action.id);
|
|
2961
3009
|
durableStoreOperations.push({
|
|
2962
3010
|
ids: [key],
|
|
@@ -2969,6 +3017,7 @@ class DurableDraftStore {
|
|
|
2969
3017
|
const { action } = operation;
|
|
2970
3018
|
const key = buildDraftDurableStoreKey(action.tag, action.id);
|
|
2971
3019
|
draftStore[action.id] = action;
|
|
3020
|
+
this.cleanSnapshot[action.id] = clone$1(action);
|
|
2972
3021
|
durableStoreOperations.push({
|
|
2973
3022
|
type: 'setEntries',
|
|
2974
3023
|
segment: DRAFT_SEGMENT,
|
|
@@ -2980,7 +3029,7 @@ class DurableDraftStore {
|
|
|
2980
3029
|
});
|
|
2981
3030
|
}
|
|
2982
3031
|
}
|
|
2983
|
-
return this.durableStore.batchOperations(durableStoreOperations);
|
|
3032
|
+
return this.timeDurableOp('batchOperations', DRAFT_SEGMENT, () => this.durableStore.batchOperations(durableStoreOperations));
|
|
2984
3033
|
};
|
|
2985
3034
|
return this.enqueueAction(action);
|
|
2986
3035
|
}
|
|
@@ -3023,6 +3072,7 @@ class DurableDraftStore {
|
|
|
3023
3072
|
.then((durableEntries) => {
|
|
3024
3073
|
if (durableEntries === undefined) {
|
|
3025
3074
|
this.draftStore = {};
|
|
3075
|
+
this.cleanSnapshot = {};
|
|
3026
3076
|
return this.runQueuedOperations();
|
|
3027
3077
|
}
|
|
3028
3078
|
const { draftStore } = this;
|
|
@@ -3039,6 +3089,7 @@ class DurableDraftStore {
|
|
|
3039
3089
|
}
|
|
3040
3090
|
}
|
|
3041
3091
|
draftStore[action.id] = action;
|
|
3092
|
+
this.cleanSnapshot[action.id] = clone$1(action);
|
|
3042
3093
|
}
|
|
3043
3094
|
return this.runQueuedOperations();
|
|
3044
3095
|
})
|
|
@@ -42277,6 +42328,14 @@ const DRAFT_QUEUE_TOTAL_MERGE_ACTIONS_CALLS = 'draft-queue-total-mergeActions-ca
|
|
|
42277
42328
|
// Used to triage the WOLI duplicate-record investigation in Splunk — correlate by draftId
|
|
42278
42329
|
// with the other draft-queue events. @W-23428669
|
|
42279
42330
|
const DRAFT_QUEUE_IDEMPOTENCY_KEY_ROTATED = 'draft-queue-idempotency-key-rotated';
|
|
42331
|
+
// Hotspot-attribution telemetry (@W-23515682). Operational counts/durations only — no
|
|
42332
|
+
// draft content, field values, or server record ids. These attribute the offline-drain wall-clock
|
|
42333
|
+
// to the O(N^2) hotspots (getQueueActions amplification, listener fan-out, durable-op latency).
|
|
42334
|
+
const DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT = 'draft-queue-get-queue-actions-count';
|
|
42335
|
+
const DRAFT_QUEUE_GET_QUEUE_ACTIONS_DURATION = 'draft-queue-get-queue-actions-duration';
|
|
42336
|
+
const DRAFT_QUEUE_NOTIFY_CHANGED_LISTENERS_COUNT = 'draft-queue-notify-changed-listeners-count';
|
|
42337
|
+
const DRAFT_QUEUE_DURABLE_OP_COUNT = 'draft-queue-durable-op-count';
|
|
42338
|
+
const DRAFT_QUEUE_DURABLE_OP_DURATION = 'draft-queue-durable-op-duration';
|
|
42280
42339
|
/** Content Document */
|
|
42281
42340
|
const CREATE_CONTENT_DOCUMENT_AND_VERSION_TOTAL_SYNTHESIZE_CALLS = 'content-document-version-total-synthesize-calls';
|
|
42282
42341
|
const CREATE_CONTENT_DOCUMENT_AND_VERSION_DRAFT_SYNTHESIZE_ERROR = 'create-content-document-version-draft-synthesize-error';
|
|
@@ -42369,6 +42428,33 @@ function reportDraftActionEvent(state, draftCount, message) {
|
|
|
42369
42428
|
break;
|
|
42370
42429
|
}
|
|
42371
42430
|
}
|
|
42431
|
+
/**
|
|
42432
|
+
* @W-23515682: count (amplification — queue re-reads per drain) + duration histogram (the
|
|
42433
|
+
* pre/post signal for the getAllDrafts clone-on-write fix). actionCount is queue depth, never content.
|
|
42434
|
+
*/
|
|
42435
|
+
function reportDraftQueueGetQueueActions(durationMs, actionCount) {
|
|
42436
|
+
const depthBuckets = [1, 2, 5, 10, 20, 50, 100, 250, 500];
|
|
42437
|
+
ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT);
|
|
42438
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_GET_QUEUE_ACTIONS_DURATION, durationMs);
|
|
42439
|
+
ldsMobileInstrumentation.bucketValue(DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT + '-depth', actionCount, depthBuckets);
|
|
42440
|
+
}
|
|
42441
|
+
/**
|
|
42442
|
+
* @W-23515682: count per event type — listener fan-out / amplification per event.
|
|
42443
|
+
*/
|
|
42444
|
+
function reportDraftQueueNotifyChangedListeners(eventType, listenerCount) {
|
|
42445
|
+
ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_NOTIFY_CHANGED_LISTENERS_COUNT, listenerCount, undefined, { eventType });
|
|
42446
|
+
}
|
|
42447
|
+
/**
|
|
42448
|
+
* @W-23515682: count + duration histogram by operation kind + segment — attributes the
|
|
42449
|
+
* client-CPU-vs-durable split of per-action processing time.
|
|
42450
|
+
*/
|
|
42451
|
+
function reportDraftQueueDurableOperation(op, segment, durationMs) {
|
|
42452
|
+
ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_DURABLE_OP_COUNT, 1, undefined, {
|
|
42453
|
+
op,
|
|
42454
|
+
segment,
|
|
42455
|
+
});
|
|
42456
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_DURABLE_OP_DURATION, durationMs);
|
|
42457
|
+
}
|
|
42372
42458
|
/**
|
|
42373
42459
|
* Reports an exception thrown while uploading a draft action. The thrown error is
|
|
42374
42460
|
* otherwise swallowed by the upload handler's retry path, leaving the failure
|
|
@@ -42852,14 +42938,45 @@ class AbstractResourceRequestActionHandler {
|
|
|
42852
42938
|
let updatedActionTargetId = undefined;
|
|
42853
42939
|
const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
|
|
42854
42940
|
let { basePath, body } = queueActionRequest;
|
|
42941
|
+
// Literal indexOf pre-check (no RegExp coercion, unlike the old String.search) +
|
|
42942
|
+
// a replace RegExp built only when a reference is present — the win over the old scan.
|
|
42855
42943
|
let stringifiedBody = stringify$5(body);
|
|
42944
|
+
// Read the killswitch once for the whole scan (W-23515682) — avoids a
|
|
42945
|
+
// per-redirect gate lookup in this hot loop.
|
|
42946
|
+
const useOptimizedScan = draftQueueRedirectScanGate.isOpen({ fallback: true });
|
|
42856
42947
|
// for each redirected ID/key we loop over the operation to see if it needs
|
|
42857
42948
|
// to be updated
|
|
42858
42949
|
for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
|
|
42859
|
-
if (
|
|
42860
|
-
basePath
|
|
42861
|
-
|
|
42862
|
-
|
|
42950
|
+
if (useOptimizedScan) {
|
|
42951
|
+
// basePath AND body are checked independently: a queued action can carry
|
|
42952
|
+
// the draft id in either or BOTH, and each occurrence must be rewritten.
|
|
42953
|
+
const basePathNeedsUpdate = basePath.indexOf(draftId) >= 0;
|
|
42954
|
+
const bodyNeedsUpdate = stringifiedBody.indexOf(draftId) >= 0;
|
|
42955
|
+
if (basePathNeedsUpdate || bodyNeedsUpdate) {
|
|
42956
|
+
// Escape metacharacters so the draft id matches literally. Salesforce
|
|
42957
|
+
// ids carry no regex metacharacters today, so this is defensive against
|
|
42958
|
+
// a future id-format change silently corrupting unrelated ids. W-23515682.
|
|
42959
|
+
const escapedDraftId = draftId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
42960
|
+
const replaceRegex = new RegExp(escapedDraftId, 'g');
|
|
42961
|
+
if (basePathNeedsUpdate) {
|
|
42962
|
+
basePath = basePath.replace(replaceRegex, canonicalId);
|
|
42963
|
+
}
|
|
42964
|
+
if (bodyNeedsUpdate) {
|
|
42965
|
+
stringifiedBody = stringifiedBody.replace(replaceRegex, canonicalId);
|
|
42966
|
+
}
|
|
42967
|
+
queueOperationMutated = true;
|
|
42968
|
+
}
|
|
42969
|
+
}
|
|
42970
|
+
else {
|
|
42971
|
+
// Killswitch closed (W-23515682): original pre-fix scan — String.search
|
|
42972
|
+
// (coerces draftId to a RegExp) + a raw (unescaped) replace RegExp applied
|
|
42973
|
+
// to basePath and body together.
|
|
42974
|
+
if (basePath.search(draftId) >= 0 ||
|
|
42975
|
+
stringifiedBody.search(draftId) >= 0) {
|
|
42976
|
+
basePath = basePath.replace(new RegExp(draftId, 'g'), canonicalId);
|
|
42977
|
+
stringifiedBody = stringifiedBody.replace(new RegExp(draftId, 'g'), canonicalId);
|
|
42978
|
+
queueOperationMutated = true;
|
|
42979
|
+
}
|
|
42863
42980
|
}
|
|
42864
42981
|
// if the action is performed on a previous draft id, we need to replace the action
|
|
42865
42982
|
// with a new one at the updated canonical key
|
|
@@ -52438,6 +52555,20 @@ function obtainFailureMessage(error) {
|
|
|
52438
52555
|
}
|
|
52439
52556
|
}
|
|
52440
52557
|
|
|
52558
|
+
// Mobile impl of the shared DraftQueueInstrumentation seam (@W-23515682). Forwards to
|
|
52559
|
+
// ldsMobileInstrumentation via metrics.ts; injected at construction (DraftQueueFactory).
|
|
52560
|
+
const draftQueueInstrumentation = {
|
|
52561
|
+
onGetQueueActions(durationMs, actionCount) {
|
|
52562
|
+
reportDraftQueueGetQueueActions(durationMs, actionCount);
|
|
52563
|
+
},
|
|
52564
|
+
onNotifyChangedListeners(eventType, listenerCount) {
|
|
52565
|
+
reportDraftQueueNotifyChangedListeners(eventType, listenerCount);
|
|
52566
|
+
},
|
|
52567
|
+
onDurableOperation(op, segment, durationMs) {
|
|
52568
|
+
reportDraftQueueDurableOperation(op, segment, durationMs);
|
|
52569
|
+
},
|
|
52570
|
+
};
|
|
52571
|
+
|
|
52441
52572
|
// so eslint doesn't complain about nimbus
|
|
52442
52573
|
/* global __nimbus */
|
|
52443
52574
|
function buildLdsDraftQueue(durableStore) {
|
|
@@ -52446,7 +52577,8 @@ function buildLdsDraftQueue(durableStore) {
|
|
|
52446
52577
|
__nimbus.plugins.LdsDraftQueue !== undefined) {
|
|
52447
52578
|
return new NimbusDraftQueue();
|
|
52448
52579
|
}
|
|
52449
|
-
const
|
|
52580
|
+
const useSnapshot = draftQueueStoreSnapshotGate.isOpen({ fallback: true });
|
|
52581
|
+
const draftQueue = new DurableDraftQueue(new DurableDraftStore(durableStore, draftQueueInstrumentation, useSnapshot), draftQueueInstrumentation);
|
|
52450
52582
|
return instrumentDraftQueue(draftQueue);
|
|
52451
52583
|
}
|
|
52452
52584
|
|
|
@@ -55925,18 +56057,34 @@ class SideEffectStore {
|
|
|
55925
56057
|
this.initialize();
|
|
55926
56058
|
}
|
|
55927
56059
|
async addEffects(effects) {
|
|
56060
|
+
const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
|
|
55928
56061
|
const durableEntries = {};
|
|
55929
56062
|
effects.forEach((effect) => {
|
|
55930
56063
|
durableEntries[effect.uniqueId] = { data: effect };
|
|
55931
56064
|
this.allEffects.set(effect.uniqueId, effect);
|
|
56065
|
+
if (incremental) {
|
|
56066
|
+
this.addEffectToKeyMap(effect);
|
|
56067
|
+
}
|
|
55932
56068
|
});
|
|
55933
|
-
|
|
56069
|
+
// Killswitch (W-23515682): closed → original O(N) full rebuild on every add.
|
|
56070
|
+
if (!incremental) {
|
|
56071
|
+
this.regenerateKeyToEffectMap();
|
|
56072
|
+
}
|
|
55934
56073
|
await this.durableStore.setEntries(durableEntries, SIDE_EFFECT_SEGMENT);
|
|
55935
56074
|
}
|
|
55936
56075
|
async removeEffects(effects) {
|
|
56076
|
+
const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
|
|
55937
56077
|
const effectKeys = effects.map((e) => e.uniqueId);
|
|
55938
|
-
effects.forEach((
|
|
55939
|
-
|
|
56078
|
+
effects.forEach((effect) => {
|
|
56079
|
+
this.allEffects.delete(effect.uniqueId);
|
|
56080
|
+
if (incremental) {
|
|
56081
|
+
this.removeEffectFromKeyMap(effect);
|
|
56082
|
+
}
|
|
56083
|
+
});
|
|
56084
|
+
// Killswitch (W-23515682): closed → original O(N) full rebuild on every remove.
|
|
56085
|
+
if (!incremental) {
|
|
56086
|
+
this.regenerateKeyToEffectMap();
|
|
56087
|
+
}
|
|
55940
56088
|
await this.durableStore.evictEntries(effectKeys, SIDE_EFFECT_SEGMENT);
|
|
55941
56089
|
}
|
|
55942
56090
|
getEffects(key) {
|
|
@@ -55971,6 +56119,39 @@ class SideEffectStore {
|
|
|
55971
56119
|
});
|
|
55972
56120
|
this.regenerateKeyToEffectMap();
|
|
55973
56121
|
}
|
|
56122
|
+
addEffectToKeyMap(effect) {
|
|
56123
|
+
const recordEffects = this.keyToEffectMap.get(effect.key);
|
|
56124
|
+
if (recordEffects) {
|
|
56125
|
+
// Effects are keyed by uniqueId (mirroring allEffects.set / regenerateKeyToEffectMap):
|
|
56126
|
+
// a re-add of the same uniqueId must REPLACE the resident entry in place, not append a
|
|
56127
|
+
// duplicate. The action-completion replay path (setSideEffectsForActions on still-queued
|
|
56128
|
+
// actions) re-adds resident effects with no preceding remove; without this dedup a
|
|
56129
|
+
// record-field-increment would sit in the array twice and be applied twice (e.g. a
|
|
56130
|
+
// ProductConsumed QuantityOnHand -=5 becomes -=10). W-23515682.
|
|
56131
|
+
const index = recordEffects.findIndex((e) => e.uniqueId === effect.uniqueId);
|
|
56132
|
+
if (index !== -1) {
|
|
56133
|
+
recordEffects[index] = effect;
|
|
56134
|
+
}
|
|
56135
|
+
else {
|
|
56136
|
+
recordEffects.push(effect);
|
|
56137
|
+
}
|
|
56138
|
+
}
|
|
56139
|
+
else {
|
|
56140
|
+
this.keyToEffectMap.set(effect.key, [effect]);
|
|
56141
|
+
}
|
|
56142
|
+
}
|
|
56143
|
+
removeEffectFromKeyMap(effect) {
|
|
56144
|
+
const recordEffects = this.keyToEffectMap.get(effect.key);
|
|
56145
|
+
if (recordEffects) {
|
|
56146
|
+
const index = recordEffects.findIndex((e) => e.uniqueId === effect.uniqueId);
|
|
56147
|
+
if (index !== -1) {
|
|
56148
|
+
recordEffects.splice(index, 1);
|
|
56149
|
+
if (recordEffects.length === 0) {
|
|
56150
|
+
this.keyToEffectMap.delete(effect.key);
|
|
56151
|
+
}
|
|
56152
|
+
}
|
|
56153
|
+
}
|
|
56154
|
+
}
|
|
55974
56155
|
regenerateKeyToEffectMap() {
|
|
55975
56156
|
this.keyToEffectMap.clear();
|
|
55976
56157
|
this.allEffects.forEach((effect) => {
|
|
@@ -59137,7 +59318,7 @@ function buildServiceDescriptor$b(luvio) {
|
|
|
59137
59318
|
},
|
|
59138
59319
|
};
|
|
59139
59320
|
}
|
|
59140
|
-
// version: 1.
|
|
59321
|
+
// version: 1.452.0-bcd113b7fd
|
|
59141
59322
|
|
|
59142
59323
|
/**
|
|
59143
59324
|
* Copyright (c) 2022, Salesforce, Inc.,
|
|
@@ -59163,7 +59344,7 @@ function buildServiceDescriptor$a(notifyRecordUpdateAvailable, getNormalizedLuvi
|
|
|
59163
59344
|
},
|
|
59164
59345
|
};
|
|
59165
59346
|
}
|
|
59166
|
-
// version: 1.
|
|
59347
|
+
// version: 1.452.0-bcd113b7fd
|
|
59167
59348
|
|
|
59168
59349
|
function findExecutableOperation(input) {
|
|
59169
59350
|
const operations = input.query.definitions.filter(
|
|
@@ -61875,4 +62056,4 @@ register({
|
|
|
61875
62056
|
});
|
|
61876
62057
|
|
|
61877
62058
|
export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, ingest$1o as ingestDenormalizedRecordRepresentation, initializeOneStore, registerReportObserver, reportGraphqlQueryParseError };
|
|
61878
|
-
// version: 1.
|
|
62059
|
+
// version: 1.452.0-bcd113b7fd
|
|
@@ -10,6 +10,20 @@ export declare function reportGraphqlAdapterError(errorCode: string): void;
|
|
|
10
10
|
export declare function reportGraphqlQueryInstrumentation(data: QueryInstrumentation): void;
|
|
11
11
|
export declare function incrementGraphQLRefreshUndfined(): void;
|
|
12
12
|
export declare function reportDraftActionEvent(state: 'added' | 'uploading' | 'completed' | 'deleted' | 'updated' | 'failed', draftCount: number, message?: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* @W-23515682: count (amplification — queue re-reads per drain) + duration histogram (the
|
|
15
|
+
* pre/post signal for the getAllDrafts clone-on-write fix). actionCount is queue depth, never content.
|
|
16
|
+
*/
|
|
17
|
+
export declare function reportDraftQueueGetQueueActions(durationMs: number, actionCount: number): void;
|
|
18
|
+
/**
|
|
19
|
+
* @W-23515682: count per event type — listener fan-out / amplification per event.
|
|
20
|
+
*/
|
|
21
|
+
export declare function reportDraftQueueNotifyChangedListeners(eventType: string, listenerCount: number): void;
|
|
22
|
+
/**
|
|
23
|
+
* @W-23515682: count + duration histogram by operation kind + segment — attributes the
|
|
24
|
+
* client-CPU-vs-durable split of per-action processing time.
|
|
25
|
+
*/
|
|
26
|
+
export declare function reportDraftQueueDurableOperation(op: string, segment: string, durationMs: number): void;
|
|
13
27
|
/**
|
|
14
28
|
* Reports an exception thrown while uploading a draft action. The thrown error is
|
|
15
29
|
* otherwise swallowed by the upload handler's retry path, leaving the failure
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/lds-runtime-mobile",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.452.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"description": "LDS runtime for mobile/hybrid environments.",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"@conduit-client/service-bindings-imperative": "3.26.0",
|
|
36
36
|
"@conduit-client/service-bindings-lwc": "3.26.0",
|
|
37
37
|
"@conduit-client/service-provisioner": "3.26.0",
|
|
38
|
-
"@salesforce/lds-adapters-uiapi": "^1.
|
|
39
|
-
"@salesforce/lds-bindings": "^1.
|
|
40
|
-
"@salesforce/lds-instrumentation": "^1.
|
|
41
|
-
"@salesforce/lds-luvio-service": "^1.
|
|
42
|
-
"@salesforce/lds-luvio-uiapi-records-service": "^1.
|
|
38
|
+
"@salesforce/lds-adapters-uiapi": "^1.452.0",
|
|
39
|
+
"@salesforce/lds-bindings": "^1.452.0",
|
|
40
|
+
"@salesforce/lds-instrumentation": "^1.452.0",
|
|
41
|
+
"@salesforce/lds-luvio-service": "^1.452.0",
|
|
42
|
+
"@salesforce/lds-luvio-uiapi-records-service": "^1.452.0",
|
|
43
43
|
"@salesforce/user": "0.0.21",
|
|
44
44
|
"o11y": "250.7.0",
|
|
45
45
|
"o11y_schema": "256.126.0"
|
|
@@ -59,16 +59,16 @@
|
|
|
59
59
|
"@conduit-client/service-pubsub": "3.26.0",
|
|
60
60
|
"@conduit-client/service-store": "3.26.0",
|
|
61
61
|
"@conduit-client/utils": "3.26.0",
|
|
62
|
-
"@salesforce/lds-adapters-graphql": "^1.
|
|
63
|
-
"@salesforce/lds-drafts": "^1.
|
|
64
|
-
"@salesforce/lds-durable-records": "^1.
|
|
65
|
-
"@salesforce/lds-network-adapter": "^1.
|
|
66
|
-
"@salesforce/lds-network-nimbus": "^1.
|
|
67
|
-
"@salesforce/lds-store-binary": "^1.
|
|
68
|
-
"@salesforce/lds-store-nimbus": "^1.
|
|
69
|
-
"@salesforce/lds-store-sql": "^1.
|
|
70
|
-
"@salesforce/lds-utils-adapters": "^1.
|
|
71
|
-
"@salesforce/nimbus-plugin-lds": "^1.
|
|
62
|
+
"@salesforce/lds-adapters-graphql": "^1.452.0",
|
|
63
|
+
"@salesforce/lds-drafts": "^1.452.0",
|
|
64
|
+
"@salesforce/lds-durable-records": "^1.452.0",
|
|
65
|
+
"@salesforce/lds-network-adapter": "^1.452.0",
|
|
66
|
+
"@salesforce/lds-network-nimbus": "^1.452.0",
|
|
67
|
+
"@salesforce/lds-store-binary": "^1.452.0",
|
|
68
|
+
"@salesforce/lds-store-nimbus": "^1.452.0",
|
|
69
|
+
"@salesforce/lds-store-sql": "^1.452.0",
|
|
70
|
+
"@salesforce/lds-utils-adapters": "^1.452.0",
|
|
71
|
+
"@salesforce/nimbus-plugin-lds": "^1.452.0",
|
|
72
72
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
73
73
|
"wait-for-expect": "^3.0.2"
|
|
74
74
|
},
|
package/sfdc/main.js
CHANGED
|
@@ -20,6 +20,7 @@ import { setupInstrumentation, instrumentAdapter as instrumentAdapter$1, instrum
|
|
|
20
20
|
import { HttpStatusCode as HttpStatusCode$1, setBypassDeepFreeze, StoreKeySet, StringKeyInMemoryStore, Reader, serializeStructuredKey, deepFreeze as deepFreeze$1, emitAdapterEvent, ingestShape, coerceConfig as coerceConfig$1, typeCheckConfig as typeCheckConfig$h, createResourceParams as createResourceParams$h, StoreKeyMap, buildNetworkSnapshotCachePolicy as buildNetworkSnapshotCachePolicy$f, resolveLink, createCustomAdapterEventEmitter, isFileReference, Environment, Luvio, InMemoryStore } from 'force/luvioEngine';
|
|
21
21
|
import { isSupportedEntity, configuration, getObjectInfoAdapterFactory, RECORD_ID_PREFIX, RECORD_FIELDS_KEY_JUNCTION, isStoreKeyRecordViewEntity, extractRecordIdFromStoreKey, buildRecordRepKeyFromId, keyBuilderRecord, RecordRepresentationTTL, keyBuilderQuickActionExecutionRepresentation, ingestQuickActionExecutionRepresentation, getRecordId18 as getRecordId18$1, getRecordsAdapterFactory as getRecordsAdapterFactory$1, RecordRepresentationRepresentationType, ObjectInfoRepresentationType, getObjectInfosAdapterFactory, getObjectInfoDirectoryAdapterFactory, UiApiNamespace, RecordRepresentationType, RecordRepresentationVersion } from 'force/ldsAdaptersUiapi';
|
|
22
22
|
import draftQueueMaxRetryAttemptsGate from '@salesforce/gate/lmr.draft-queue-max-retry-attempts';
|
|
23
|
+
import draftQueueRedirectScanGate from '@salesforce/gate/lmr.draft-queue-redirect-scan';
|
|
23
24
|
import rotateIdempotencyKeyOn400Killswitch from '@salesforce/gate/lds.idempotency-key-on-400-killswitch';
|
|
24
25
|
import { getInstrumentation, idleDetector } from 'o11y/client';
|
|
25
26
|
import allowUpdatesForNonCachedRecords from '@salesforce/gate/lmr.allowUpdatesForNonCachedRecords';
|
|
@@ -30,6 +31,7 @@ import FIRST_DAY_OF_WEEK from '@salesforce/i18n/firstDayOfWeek';
|
|
|
30
31
|
import { entityFetchedGraphqlMetadataSchema, entityFetchedMetadataSchema } from 'o11y_schema/sf_lightningsdk';
|
|
31
32
|
import graphqQueryFieldLimit from '@salesforce/gate/lmr.graphqQueryFieldLimit';
|
|
32
33
|
import graphqlPartialEmitParity from '@salesforce/gate/lmr.graphqlPartialEmitParity';
|
|
34
|
+
import draftQueueStoreSnapshotGate from '@salesforce/gate/lmr.draft-queue-store-snapshot';
|
|
33
35
|
import { pdpEventSchema } from 'o11y_schema/sf_pdp';
|
|
34
36
|
import { instrument as instrument$1 } from 'force/ldsBindings';
|
|
35
37
|
import ldsAdapterO11yLoggingGate from '@salesforce/gate/lmr.ldsAdapterO11yLogging';
|
|
@@ -49,6 +51,7 @@ import useOneStore from '@salesforce/gate/lmr.useOneStore';
|
|
|
49
51
|
import { setServices } from 'force/luvioServiceProvisioner1';
|
|
50
52
|
import 'force/luvioTypeNormalization1';
|
|
51
53
|
import { Kind as Kind$2, visit as visit$2, print as print$1, resolveAndValidateGraphQLConfig, toGraphQLErrorResponse } from 'force/luvioOnestoreGraphqlParser';
|
|
54
|
+
import draftQueueSideEffectMapGate from '@salesforce/gate/lmr.draft-queue-sideeffect-map';
|
|
52
55
|
import productConsumedSideEffects from '@salesforce/gate/com.salesforce.fieldservice.vanStockLDSBypass256';
|
|
53
56
|
import reviveOnlyRequestedFields from '@salesforce/gate/lmr.reviveOnlyRequestedFields';
|
|
54
57
|
|
|
@@ -2362,7 +2365,7 @@ class DurableDraftQueue {
|
|
|
2362
2365
|
}
|
|
2363
2366
|
return handler;
|
|
2364
2367
|
}
|
|
2365
|
-
constructor(draftStore) {
|
|
2368
|
+
constructor(draftStore, instrumentation) {
|
|
2366
2369
|
this.retryIntervalMilliseconds = 0;
|
|
2367
2370
|
this.minimumRetryInterval = 250;
|
|
2368
2371
|
this.maximumRetryInterval = 32000;
|
|
@@ -2379,6 +2382,7 @@ class DurableDraftQueue {
|
|
|
2379
2382
|
this.handlers = {};
|
|
2380
2383
|
this.draftStore = draftStore;
|
|
2381
2384
|
this.workerPool = new AsyncWorkerPool(1);
|
|
2385
|
+
this.instrumentation = instrumentation;
|
|
2382
2386
|
}
|
|
2383
2387
|
addHandler(handler) {
|
|
2384
2388
|
const id = handler.handlerId;
|
|
@@ -2469,15 +2473,22 @@ class DurableDraftQueue {
|
|
|
2469
2473
|
this.state = DraftQueueState.Stopped;
|
|
2470
2474
|
}
|
|
2471
2475
|
async getQueueActions() {
|
|
2476
|
+
// W-23515682: time the whole call, including this queue's own internal
|
|
2477
|
+
// this.getQueueActions() during a drain — the amplification signal an external wrapper
|
|
2478
|
+
// cannot see. Measure-only; control flow below is unchanged.
|
|
2479
|
+
const { instrumentation } = this;
|
|
2480
|
+
const start = instrumentation?.onGetQueueActions !== undefined ? Date.now() : 0;
|
|
2472
2481
|
const drafts = (await this.draftStore.getAllDrafts());
|
|
2473
2482
|
const queue = [];
|
|
2474
2483
|
drafts.forEach((draft) => {
|
|
2475
2484
|
if (draft.id === this.uploadingActionId) {
|
|
2476
|
-
draft
|
|
2485
|
+
queue.push({ ...draft, status: DraftActionStatus.Uploading });
|
|
2486
|
+
}
|
|
2487
|
+
else {
|
|
2488
|
+
queue.push(draft);
|
|
2477
2489
|
}
|
|
2478
|
-
queue.push(draft);
|
|
2479
2490
|
});
|
|
2480
|
-
|
|
2491
|
+
const sorted = queue.sort((a, b) => {
|
|
2481
2492
|
const aTime = parseInt(a.id, 10);
|
|
2482
2493
|
const bTime = parseInt(b.id, 10);
|
|
2483
2494
|
// safety check
|
|
@@ -2489,6 +2500,8 @@ class DurableDraftQueue {
|
|
|
2489
2500
|
}
|
|
2490
2501
|
return aTime - bTime;
|
|
2491
2502
|
});
|
|
2503
|
+
instrumentation?.onGetQueueActions?.(Date.now() - start, sorted.length);
|
|
2504
|
+
return sorted;
|
|
2492
2505
|
}
|
|
2493
2506
|
async enqueue(handlerId, data, observabilityContext) {
|
|
2494
2507
|
return this.workerPool.push({
|
|
@@ -2637,6 +2650,8 @@ class DurableDraftQueue {
|
|
|
2637
2650
|
const listener = draftQueueChangedListeners[i];
|
|
2638
2651
|
results.push(listener(event));
|
|
2639
2652
|
}
|
|
2653
|
+
// W-23515682: listener fan-out per event type. Measure-only.
|
|
2654
|
+
this.instrumentation?.onNotifyChangedListeners?.(event.type, draftQueueLen);
|
|
2640
2655
|
await Promise.all(results);
|
|
2641
2656
|
}
|
|
2642
2657
|
/**
|
|
@@ -2884,36 +2899,66 @@ function buildDraftDurableStoreKey(recordKey, draftActionId) {
|
|
|
2884
2899
|
*
|
|
2885
2900
|
*/
|
|
2886
2901
|
class DurableDraftStore {
|
|
2887
|
-
constructor(durableStore) {
|
|
2902
|
+
constructor(durableStore, instrumentation, useSnapshot = true) {
|
|
2888
2903
|
this.draftStore = {};
|
|
2904
|
+
// Snapshot: holds deep clones of each draft, refreshed at write time.
|
|
2905
|
+
// getAllDrafts serves from this snapshot with zero deep clones per read.
|
|
2906
|
+
this.cleanSnapshot = {};
|
|
2889
2907
|
// queue of writes that were made during the initial sync
|
|
2890
2908
|
this.writeQueue = [];
|
|
2891
2909
|
this.durableStore = durableStore;
|
|
2910
|
+
this.instrumentation = instrumentation;
|
|
2911
|
+
this.useSnapshot = useSnapshot;
|
|
2892
2912
|
this.resyncDraftStore();
|
|
2893
2913
|
}
|
|
2914
|
+
// When no instrumentation is injected the promise is returned as-is (zero overhead).
|
|
2915
|
+
timeDurableOp(op, segment, run) {
|
|
2916
|
+
const onDurableOperation = this.instrumentation?.onDurableOperation;
|
|
2917
|
+
if (onDurableOperation === undefined) {
|
|
2918
|
+
return run();
|
|
2919
|
+
}
|
|
2920
|
+
const start = Date.now();
|
|
2921
|
+
return run().finally(() => {
|
|
2922
|
+
onDurableOperation(op, segment, Date.now() - start);
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2894
2925
|
writeAction(action) {
|
|
2895
2926
|
const addAction = () => {
|
|
2896
2927
|
const { id, tag } = action;
|
|
2897
2928
|
this.draftStore[id] = action;
|
|
2929
|
+
this.cleanSnapshot[id] = clone$1(action);
|
|
2898
2930
|
const durableEntryKey = buildDraftDurableStoreKey(tag, id);
|
|
2899
2931
|
const entry = {
|
|
2900
2932
|
data: action,
|
|
2901
2933
|
};
|
|
2902
2934
|
const entries = { [durableEntryKey]: entry };
|
|
2903
|
-
return this.durableStore.setEntries(entries, DRAFT_SEGMENT);
|
|
2935
|
+
return this.timeDurableOp('setEntries', DRAFT_SEGMENT, () => this.durableStore.setEntries(entries, DRAFT_SEGMENT));
|
|
2904
2936
|
};
|
|
2905
2937
|
return this.enqueueAction(addAction);
|
|
2906
2938
|
}
|
|
2907
2939
|
getAllDrafts() {
|
|
2908
2940
|
const waitForOngoingSync = this.syncPromise || Promise.resolve();
|
|
2909
2941
|
return waitForOngoingSync.then(() => {
|
|
2910
|
-
const { draftStore } = this;
|
|
2911
|
-
const keys$1 = keys$4(draftStore);
|
|
2912
2942
|
const actionArray = [];
|
|
2913
|
-
|
|
2914
|
-
const
|
|
2915
|
-
|
|
2916
|
-
|
|
2943
|
+
if (this.useSnapshot) {
|
|
2944
|
+
const { cleanSnapshot } = this;
|
|
2945
|
+
const keys$1 = keys$4(cleanSnapshot);
|
|
2946
|
+
for (let i = 0, len = keys$1.length; i < len; i++) {
|
|
2947
|
+
const key = keys$1[i];
|
|
2948
|
+
// Return shallow spread of snapshot entries so top-level caller mutations
|
|
2949
|
+
// (e.g., .status =) don't persist into the snapshot. The snapshot itself
|
|
2950
|
+
// is a deep clone of draftStore, so no nested mutation reaches draftStore.
|
|
2951
|
+
actionArray.push({ ...cleanSnapshot[key] });
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
else {
|
|
2955
|
+
// Killswitch (W-23515682): original pre-fix behavior — deep-clone every draft on
|
|
2956
|
+
// every read so the internal draft store is never exposed. O(N) clones per read.
|
|
2957
|
+
const { draftStore } = this;
|
|
2958
|
+
const keys$1 = keys$4(draftStore);
|
|
2959
|
+
for (let i = 0, len = keys$1.length; i < len; i++) {
|
|
2960
|
+
actionArray.push(clone$1(draftStore[keys$1[i]]));
|
|
2961
|
+
}
|
|
2917
2962
|
}
|
|
2918
2963
|
return actionArray;
|
|
2919
2964
|
});
|
|
@@ -2923,8 +2968,9 @@ class DurableDraftStore {
|
|
|
2923
2968
|
const draft = this.draftStore[id];
|
|
2924
2969
|
if (draft !== undefined) {
|
|
2925
2970
|
delete this.draftStore[id];
|
|
2971
|
+
delete this.cleanSnapshot[id];
|
|
2926
2972
|
const durableKey = buildDraftDurableStoreKey(draft.tag, draft.id);
|
|
2927
|
-
return this.durableStore.evictEntries([durableKey], DRAFT_SEGMENT);
|
|
2973
|
+
return this.timeDurableOp('evictEntries', DRAFT_SEGMENT, () => this.durableStore.evictEntries([durableKey], DRAFT_SEGMENT));
|
|
2928
2974
|
}
|
|
2929
2975
|
return Promise.resolve();
|
|
2930
2976
|
};
|
|
@@ -2940,10 +2986,11 @@ class DurableDraftStore {
|
|
|
2940
2986
|
const action = draftStore[key];
|
|
2941
2987
|
if (action.tag === tag) {
|
|
2942
2988
|
delete draftStore[action.id];
|
|
2989
|
+
delete this.cleanSnapshot[action.id];
|
|
2943
2990
|
durableKeys.push(buildDraftDurableStoreKey(action.tag, action.id));
|
|
2944
2991
|
}
|
|
2945
2992
|
}
|
|
2946
|
-
return this.durableStore.evictEntries(durableKeys, DRAFT_SEGMENT);
|
|
2993
|
+
return this.timeDurableOp('evictEntries', DRAFT_SEGMENT, () => this.durableStore.evictEntries(durableKeys, DRAFT_SEGMENT));
|
|
2947
2994
|
};
|
|
2948
2995
|
return this.enqueueAction(deleteAction);
|
|
2949
2996
|
}
|
|
@@ -2957,6 +3004,7 @@ class DurableDraftStore {
|
|
|
2957
3004
|
const action = draftStore[operation.id];
|
|
2958
3005
|
if (action !== undefined) {
|
|
2959
3006
|
delete draftStore[operation.id];
|
|
3007
|
+
delete this.cleanSnapshot[operation.id];
|
|
2960
3008
|
const key = buildDraftDurableStoreKey(action.tag, action.id);
|
|
2961
3009
|
durableStoreOperations.push({
|
|
2962
3010
|
ids: [key],
|
|
@@ -2969,6 +3017,7 @@ class DurableDraftStore {
|
|
|
2969
3017
|
const { action } = operation;
|
|
2970
3018
|
const key = buildDraftDurableStoreKey(action.tag, action.id);
|
|
2971
3019
|
draftStore[action.id] = action;
|
|
3020
|
+
this.cleanSnapshot[action.id] = clone$1(action);
|
|
2972
3021
|
durableStoreOperations.push({
|
|
2973
3022
|
type: 'setEntries',
|
|
2974
3023
|
segment: DRAFT_SEGMENT,
|
|
@@ -2980,7 +3029,7 @@ class DurableDraftStore {
|
|
|
2980
3029
|
});
|
|
2981
3030
|
}
|
|
2982
3031
|
}
|
|
2983
|
-
return this.durableStore.batchOperations(durableStoreOperations);
|
|
3032
|
+
return this.timeDurableOp('batchOperations', DRAFT_SEGMENT, () => this.durableStore.batchOperations(durableStoreOperations));
|
|
2984
3033
|
};
|
|
2985
3034
|
return this.enqueueAction(action);
|
|
2986
3035
|
}
|
|
@@ -3023,6 +3072,7 @@ class DurableDraftStore {
|
|
|
3023
3072
|
.then((durableEntries) => {
|
|
3024
3073
|
if (durableEntries === undefined) {
|
|
3025
3074
|
this.draftStore = {};
|
|
3075
|
+
this.cleanSnapshot = {};
|
|
3026
3076
|
return this.runQueuedOperations();
|
|
3027
3077
|
}
|
|
3028
3078
|
const { draftStore } = this;
|
|
@@ -3039,6 +3089,7 @@ class DurableDraftStore {
|
|
|
3039
3089
|
}
|
|
3040
3090
|
}
|
|
3041
3091
|
draftStore[action.id] = action;
|
|
3092
|
+
this.cleanSnapshot[action.id] = clone$1(action);
|
|
3042
3093
|
}
|
|
3043
3094
|
return this.runQueuedOperations();
|
|
3044
3095
|
})
|
|
@@ -42277,6 +42328,14 @@ const DRAFT_QUEUE_TOTAL_MERGE_ACTIONS_CALLS = 'draft-queue-total-mergeActions-ca
|
|
|
42277
42328
|
// Used to triage the WOLI duplicate-record investigation in Splunk — correlate by draftId
|
|
42278
42329
|
// with the other draft-queue events. @W-23428669
|
|
42279
42330
|
const DRAFT_QUEUE_IDEMPOTENCY_KEY_ROTATED = 'draft-queue-idempotency-key-rotated';
|
|
42331
|
+
// Hotspot-attribution telemetry (@W-23515682). Operational counts/durations only — no
|
|
42332
|
+
// draft content, field values, or server record ids. These attribute the offline-drain wall-clock
|
|
42333
|
+
// to the O(N^2) hotspots (getQueueActions amplification, listener fan-out, durable-op latency).
|
|
42334
|
+
const DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT = 'draft-queue-get-queue-actions-count';
|
|
42335
|
+
const DRAFT_QUEUE_GET_QUEUE_ACTIONS_DURATION = 'draft-queue-get-queue-actions-duration';
|
|
42336
|
+
const DRAFT_QUEUE_NOTIFY_CHANGED_LISTENERS_COUNT = 'draft-queue-notify-changed-listeners-count';
|
|
42337
|
+
const DRAFT_QUEUE_DURABLE_OP_COUNT = 'draft-queue-durable-op-count';
|
|
42338
|
+
const DRAFT_QUEUE_DURABLE_OP_DURATION = 'draft-queue-durable-op-duration';
|
|
42280
42339
|
/** Content Document */
|
|
42281
42340
|
const CREATE_CONTENT_DOCUMENT_AND_VERSION_TOTAL_SYNTHESIZE_CALLS = 'content-document-version-total-synthesize-calls';
|
|
42282
42341
|
const CREATE_CONTENT_DOCUMENT_AND_VERSION_DRAFT_SYNTHESIZE_ERROR = 'create-content-document-version-draft-synthesize-error';
|
|
@@ -42369,6 +42428,33 @@ function reportDraftActionEvent(state, draftCount, message) {
|
|
|
42369
42428
|
break;
|
|
42370
42429
|
}
|
|
42371
42430
|
}
|
|
42431
|
+
/**
|
|
42432
|
+
* @W-23515682: count (amplification — queue re-reads per drain) + duration histogram (the
|
|
42433
|
+
* pre/post signal for the getAllDrafts clone-on-write fix). actionCount is queue depth, never content.
|
|
42434
|
+
*/
|
|
42435
|
+
function reportDraftQueueGetQueueActions(durationMs, actionCount) {
|
|
42436
|
+
const depthBuckets = [1, 2, 5, 10, 20, 50, 100, 250, 500];
|
|
42437
|
+
ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT);
|
|
42438
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_GET_QUEUE_ACTIONS_DURATION, durationMs);
|
|
42439
|
+
ldsMobileInstrumentation.bucketValue(DRAFT_QUEUE_GET_QUEUE_ACTIONS_COUNT + '-depth', actionCount, depthBuckets);
|
|
42440
|
+
}
|
|
42441
|
+
/**
|
|
42442
|
+
* @W-23515682: count per event type — listener fan-out / amplification per event.
|
|
42443
|
+
*/
|
|
42444
|
+
function reportDraftQueueNotifyChangedListeners(eventType, listenerCount) {
|
|
42445
|
+
ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_NOTIFY_CHANGED_LISTENERS_COUNT, listenerCount, undefined, { eventType });
|
|
42446
|
+
}
|
|
42447
|
+
/**
|
|
42448
|
+
* @W-23515682: count + duration histogram by operation kind + segment — attributes the
|
|
42449
|
+
* client-CPU-vs-durable split of per-action processing time.
|
|
42450
|
+
*/
|
|
42451
|
+
function reportDraftQueueDurableOperation(op, segment, durationMs) {
|
|
42452
|
+
ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_DURABLE_OP_COUNT, 1, undefined, {
|
|
42453
|
+
op,
|
|
42454
|
+
segment,
|
|
42455
|
+
});
|
|
42456
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_DURABLE_OP_DURATION, durationMs);
|
|
42457
|
+
}
|
|
42372
42458
|
/**
|
|
42373
42459
|
* Reports an exception thrown while uploading a draft action. The thrown error is
|
|
42374
42460
|
* otherwise swallowed by the upload handler's retry path, leaving the failure
|
|
@@ -42852,14 +42938,45 @@ class AbstractResourceRequestActionHandler {
|
|
|
42852
42938
|
let updatedActionTargetId = undefined;
|
|
42853
42939
|
const { tag: queueActionTag, data: queueActionRequest, id: queueActionId, } = queueAction;
|
|
42854
42940
|
let { basePath, body } = queueActionRequest;
|
|
42941
|
+
// Literal indexOf pre-check (no RegExp coercion, unlike the old String.search) +
|
|
42942
|
+
// a replace RegExp built only when a reference is present — the win over the old scan.
|
|
42855
42943
|
let stringifiedBody = stringify$5(body);
|
|
42944
|
+
// Read the killswitch once for the whole scan (W-23515682) — avoids a
|
|
42945
|
+
// per-redirect gate lookup in this hot loop.
|
|
42946
|
+
const useOptimizedScan = draftQueueRedirectScanGate.isOpen({ fallback: true });
|
|
42856
42947
|
// for each redirected ID/key we loop over the operation to see if it needs
|
|
42857
42948
|
// to be updated
|
|
42858
42949
|
for (const { draftId, draftKey, canonicalId, canonicalKey } of redirects) {
|
|
42859
|
-
if (
|
|
42860
|
-
basePath
|
|
42861
|
-
|
|
42862
|
-
|
|
42950
|
+
if (useOptimizedScan) {
|
|
42951
|
+
// basePath AND body are checked independently: a queued action can carry
|
|
42952
|
+
// the draft id in either or BOTH, and each occurrence must be rewritten.
|
|
42953
|
+
const basePathNeedsUpdate = basePath.indexOf(draftId) >= 0;
|
|
42954
|
+
const bodyNeedsUpdate = stringifiedBody.indexOf(draftId) >= 0;
|
|
42955
|
+
if (basePathNeedsUpdate || bodyNeedsUpdate) {
|
|
42956
|
+
// Escape metacharacters so the draft id matches literally. Salesforce
|
|
42957
|
+
// ids carry no regex metacharacters today, so this is defensive against
|
|
42958
|
+
// a future id-format change silently corrupting unrelated ids. W-23515682.
|
|
42959
|
+
const escapedDraftId = draftId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
42960
|
+
const replaceRegex = new RegExp(escapedDraftId, 'g');
|
|
42961
|
+
if (basePathNeedsUpdate) {
|
|
42962
|
+
basePath = basePath.replace(replaceRegex, canonicalId);
|
|
42963
|
+
}
|
|
42964
|
+
if (bodyNeedsUpdate) {
|
|
42965
|
+
stringifiedBody = stringifiedBody.replace(replaceRegex, canonicalId);
|
|
42966
|
+
}
|
|
42967
|
+
queueOperationMutated = true;
|
|
42968
|
+
}
|
|
42969
|
+
}
|
|
42970
|
+
else {
|
|
42971
|
+
// Killswitch closed (W-23515682): original pre-fix scan — String.search
|
|
42972
|
+
// (coerces draftId to a RegExp) + a raw (unescaped) replace RegExp applied
|
|
42973
|
+
// to basePath and body together.
|
|
42974
|
+
if (basePath.search(draftId) >= 0 ||
|
|
42975
|
+
stringifiedBody.search(draftId) >= 0) {
|
|
42976
|
+
basePath = basePath.replace(new RegExp(draftId, 'g'), canonicalId);
|
|
42977
|
+
stringifiedBody = stringifiedBody.replace(new RegExp(draftId, 'g'), canonicalId);
|
|
42978
|
+
queueOperationMutated = true;
|
|
42979
|
+
}
|
|
42863
42980
|
}
|
|
42864
42981
|
// if the action is performed on a previous draft id, we need to replace the action
|
|
42865
42982
|
// with a new one at the updated canonical key
|
|
@@ -52438,6 +52555,20 @@ function obtainFailureMessage(error) {
|
|
|
52438
52555
|
}
|
|
52439
52556
|
}
|
|
52440
52557
|
|
|
52558
|
+
// Mobile impl of the shared DraftQueueInstrumentation seam (@W-23515682). Forwards to
|
|
52559
|
+
// ldsMobileInstrumentation via metrics.ts; injected at construction (DraftQueueFactory).
|
|
52560
|
+
const draftQueueInstrumentation = {
|
|
52561
|
+
onGetQueueActions(durationMs, actionCount) {
|
|
52562
|
+
reportDraftQueueGetQueueActions(durationMs, actionCount);
|
|
52563
|
+
},
|
|
52564
|
+
onNotifyChangedListeners(eventType, listenerCount) {
|
|
52565
|
+
reportDraftQueueNotifyChangedListeners(eventType, listenerCount);
|
|
52566
|
+
},
|
|
52567
|
+
onDurableOperation(op, segment, durationMs) {
|
|
52568
|
+
reportDraftQueueDurableOperation(op, segment, durationMs);
|
|
52569
|
+
},
|
|
52570
|
+
};
|
|
52571
|
+
|
|
52441
52572
|
// so eslint doesn't complain about nimbus
|
|
52442
52573
|
/* global __nimbus */
|
|
52443
52574
|
function buildLdsDraftQueue(durableStore) {
|
|
@@ -52446,7 +52577,8 @@ function buildLdsDraftQueue(durableStore) {
|
|
|
52446
52577
|
__nimbus.plugins.LdsDraftQueue !== undefined) {
|
|
52447
52578
|
return new NimbusDraftQueue();
|
|
52448
52579
|
}
|
|
52449
|
-
const
|
|
52580
|
+
const useSnapshot = draftQueueStoreSnapshotGate.isOpen({ fallback: true });
|
|
52581
|
+
const draftQueue = new DurableDraftQueue(new DurableDraftStore(durableStore, draftQueueInstrumentation, useSnapshot), draftQueueInstrumentation);
|
|
52450
52582
|
return instrumentDraftQueue(draftQueue);
|
|
52451
52583
|
}
|
|
52452
52584
|
|
|
@@ -55925,18 +56057,34 @@ class SideEffectStore {
|
|
|
55925
56057
|
this.initialize();
|
|
55926
56058
|
}
|
|
55927
56059
|
async addEffects(effects) {
|
|
56060
|
+
const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
|
|
55928
56061
|
const durableEntries = {};
|
|
55929
56062
|
effects.forEach((effect) => {
|
|
55930
56063
|
durableEntries[effect.uniqueId] = { data: effect };
|
|
55931
56064
|
this.allEffects.set(effect.uniqueId, effect);
|
|
56065
|
+
if (incremental) {
|
|
56066
|
+
this.addEffectToKeyMap(effect);
|
|
56067
|
+
}
|
|
55932
56068
|
});
|
|
55933
|
-
|
|
56069
|
+
// Killswitch (W-23515682): closed → original O(N) full rebuild on every add.
|
|
56070
|
+
if (!incremental) {
|
|
56071
|
+
this.regenerateKeyToEffectMap();
|
|
56072
|
+
}
|
|
55934
56073
|
await this.durableStore.setEntries(durableEntries, SIDE_EFFECT_SEGMENT);
|
|
55935
56074
|
}
|
|
55936
56075
|
async removeEffects(effects) {
|
|
56076
|
+
const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
|
|
55937
56077
|
const effectKeys = effects.map((e) => e.uniqueId);
|
|
55938
|
-
effects.forEach((
|
|
55939
|
-
|
|
56078
|
+
effects.forEach((effect) => {
|
|
56079
|
+
this.allEffects.delete(effect.uniqueId);
|
|
56080
|
+
if (incremental) {
|
|
56081
|
+
this.removeEffectFromKeyMap(effect);
|
|
56082
|
+
}
|
|
56083
|
+
});
|
|
56084
|
+
// Killswitch (W-23515682): closed → original O(N) full rebuild on every remove.
|
|
56085
|
+
if (!incremental) {
|
|
56086
|
+
this.regenerateKeyToEffectMap();
|
|
56087
|
+
}
|
|
55940
56088
|
await this.durableStore.evictEntries(effectKeys, SIDE_EFFECT_SEGMENT);
|
|
55941
56089
|
}
|
|
55942
56090
|
getEffects(key) {
|
|
@@ -55971,6 +56119,39 @@ class SideEffectStore {
|
|
|
55971
56119
|
});
|
|
55972
56120
|
this.regenerateKeyToEffectMap();
|
|
55973
56121
|
}
|
|
56122
|
+
addEffectToKeyMap(effect) {
|
|
56123
|
+
const recordEffects = this.keyToEffectMap.get(effect.key);
|
|
56124
|
+
if (recordEffects) {
|
|
56125
|
+
// Effects are keyed by uniqueId (mirroring allEffects.set / regenerateKeyToEffectMap):
|
|
56126
|
+
// a re-add of the same uniqueId must REPLACE the resident entry in place, not append a
|
|
56127
|
+
// duplicate. The action-completion replay path (setSideEffectsForActions on still-queued
|
|
56128
|
+
// actions) re-adds resident effects with no preceding remove; without this dedup a
|
|
56129
|
+
// record-field-increment would sit in the array twice and be applied twice (e.g. a
|
|
56130
|
+
// ProductConsumed QuantityOnHand -=5 becomes -=10). W-23515682.
|
|
56131
|
+
const index = recordEffects.findIndex((e) => e.uniqueId === effect.uniqueId);
|
|
56132
|
+
if (index !== -1) {
|
|
56133
|
+
recordEffects[index] = effect;
|
|
56134
|
+
}
|
|
56135
|
+
else {
|
|
56136
|
+
recordEffects.push(effect);
|
|
56137
|
+
}
|
|
56138
|
+
}
|
|
56139
|
+
else {
|
|
56140
|
+
this.keyToEffectMap.set(effect.key, [effect]);
|
|
56141
|
+
}
|
|
56142
|
+
}
|
|
56143
|
+
removeEffectFromKeyMap(effect) {
|
|
56144
|
+
const recordEffects = this.keyToEffectMap.get(effect.key);
|
|
56145
|
+
if (recordEffects) {
|
|
56146
|
+
const index = recordEffects.findIndex((e) => e.uniqueId === effect.uniqueId);
|
|
56147
|
+
if (index !== -1) {
|
|
56148
|
+
recordEffects.splice(index, 1);
|
|
56149
|
+
if (recordEffects.length === 0) {
|
|
56150
|
+
this.keyToEffectMap.delete(effect.key);
|
|
56151
|
+
}
|
|
56152
|
+
}
|
|
56153
|
+
}
|
|
56154
|
+
}
|
|
55974
56155
|
regenerateKeyToEffectMap() {
|
|
55975
56156
|
this.keyToEffectMap.clear();
|
|
55976
56157
|
this.allEffects.forEach((effect) => {
|
|
@@ -59137,7 +59318,7 @@ function buildServiceDescriptor$b(luvio) {
|
|
|
59137
59318
|
},
|
|
59138
59319
|
};
|
|
59139
59320
|
}
|
|
59140
|
-
// version: 1.
|
|
59321
|
+
// version: 1.452.0-bcd113b7fd
|
|
59141
59322
|
|
|
59142
59323
|
/**
|
|
59143
59324
|
* Copyright (c) 2022, Salesforce, Inc.,
|
|
@@ -59163,7 +59344,7 @@ function buildServiceDescriptor$a(notifyRecordUpdateAvailable, getNormalizedLuvi
|
|
|
59163
59344
|
},
|
|
59164
59345
|
};
|
|
59165
59346
|
}
|
|
59166
|
-
// version: 1.
|
|
59347
|
+
// version: 1.452.0-bcd113b7fd
|
|
59167
59348
|
|
|
59168
59349
|
function findExecutableOperation(input) {
|
|
59169
59350
|
const operations = input.query.definitions.filter(
|
|
@@ -61875,4 +62056,4 @@ register({
|
|
|
61875
62056
|
});
|
|
61876
62057
|
|
|
61877
62058
|
export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, ingest$1o as ingestDenormalizedRecordRepresentation, initializeOneStore, registerReportObserver, reportGraphqlQueryParseError };
|
|
61878
|
-
// version: 1.
|
|
62059
|
+
// version: 1.452.0-bcd113b7fd
|
|
@@ -10,6 +10,20 @@ export declare function reportGraphqlAdapterError(errorCode: string): void;
|
|
|
10
10
|
export declare function reportGraphqlQueryInstrumentation(data: QueryInstrumentation): void;
|
|
11
11
|
export declare function incrementGraphQLRefreshUndfined(): void;
|
|
12
12
|
export declare function reportDraftActionEvent(state: 'added' | 'uploading' | 'completed' | 'deleted' | 'updated' | 'failed', draftCount: number, message?: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* @W-23515682: count (amplification — queue re-reads per drain) + duration histogram (the
|
|
15
|
+
* pre/post signal for the getAllDrafts clone-on-write fix). actionCount is queue depth, never content.
|
|
16
|
+
*/
|
|
17
|
+
export declare function reportDraftQueueGetQueueActions(durationMs: number, actionCount: number): void;
|
|
18
|
+
/**
|
|
19
|
+
* @W-23515682: count per event type — listener fan-out / amplification per event.
|
|
20
|
+
*/
|
|
21
|
+
export declare function reportDraftQueueNotifyChangedListeners(eventType: string, listenerCount: number): void;
|
|
22
|
+
/**
|
|
23
|
+
* @W-23515682: count + duration histogram by operation kind + segment — attributes the
|
|
24
|
+
* client-CPU-vs-durable split of per-action processing time.
|
|
25
|
+
*/
|
|
26
|
+
export declare function reportDraftQueueDurableOperation(op: string, segment: string, durationMs: number): void;
|
|
13
27
|
/**
|
|
14
28
|
* Reports an exception thrown while uploading a draft action. The thrown error is
|
|
15
29
|
* otherwise swallowed by the upload handler's retry path, leaving the failure
|