@salesforce/lds-runtime-mobile 1.454.0 → 1.455.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 +420 -49
- package/dist/types/drafts/AbstractResourceRequestActionHandler.d.ts +2 -0
- package/dist/types/drafts/UiApiDraftRecordService.d.ts +4 -2
- package/dist/types/drafts/sideEffects/SideEffectStore.d.ts +1 -0
- package/dist/types/instrumentation/metrics.d.ts +53 -0
- package/package.json +16 -16
- package/sfdc/main.js +420 -49
- package/sfdc/types/drafts/AbstractResourceRequestActionHandler.d.ts +2 -0
- package/sfdc/types/drafts/UiApiDraftRecordService.d.ts +4 -2
- package/sfdc/types/drafts/sideEffects/SideEffectStore.d.ts +1 -0
- package/sfdc/types/instrumentation/metrics.d.ts +53 -0
package/sfdc/main.js
CHANGED
|
@@ -1973,16 +1973,18 @@ class LdsAbortController {
|
|
|
1973
1973
|
}
|
|
1974
1974
|
|
|
1975
1975
|
class AsyncWorkerPool {
|
|
1976
|
-
constructor(concurrency) {
|
|
1976
|
+
constructor(concurrency, onWorkTiming) {
|
|
1977
1977
|
this.queue = [];
|
|
1978
1978
|
this.activeWork = [];
|
|
1979
1979
|
this.concurrency = concurrency;
|
|
1980
|
+
this.onWorkTiming = onWorkTiming;
|
|
1980
1981
|
}
|
|
1981
1982
|
push(work) {
|
|
1982
1983
|
return new Promise((resolve, reject) => {
|
|
1983
1984
|
this.queue.push({
|
|
1984
1985
|
...work,
|
|
1985
1986
|
workFn: (abortController) => work.workFn(abortController).then(resolve).catch(reject),
|
|
1987
|
+
enqueuedAt: Date.now(),
|
|
1986
1988
|
});
|
|
1987
1989
|
this.doWork();
|
|
1988
1990
|
});
|
|
@@ -2025,10 +2027,30 @@ class AsyncWorkerPool {
|
|
|
2025
2027
|
const abortController = new LdsAbortController();
|
|
2026
2028
|
const newWork = { ...work, abortController };
|
|
2027
2029
|
this.activeWork.push(newWork);
|
|
2028
|
-
const { workFn } = work;
|
|
2030
|
+
const { workFn, enqueuedAt } = work;
|
|
2031
|
+
const onWorkTiming = this.onWorkTiming;
|
|
2032
|
+
let startedAt = 0;
|
|
2033
|
+
let waitMs = 0;
|
|
2034
|
+
let queueDepthAtStart = 0;
|
|
2035
|
+
if (onWorkTiming !== undefined) {
|
|
2036
|
+
startedAt = Date.now();
|
|
2037
|
+
waitMs = startedAt - enqueuedAt;
|
|
2038
|
+
// depth AFTER this unit was shifted off — units still waiting behind it.
|
|
2039
|
+
queueDepthAtStart = this.queue.length;
|
|
2040
|
+
}
|
|
2029
2041
|
workFn(abortController).finally(() => {
|
|
2030
|
-
|
|
2031
|
-
|
|
2042
|
+
try {
|
|
2043
|
+
if (onWorkTiming !== undefined) {
|
|
2044
|
+
onWorkTiming(waitMs, Date.now() - startedAt, queueDepthAtStart);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
catch {
|
|
2048
|
+
// Instrumentation must not prevent the pool from releasing the worker slot.
|
|
2049
|
+
}
|
|
2050
|
+
finally {
|
|
2051
|
+
this.activeWork = this.activeWork.filter((w) => w !== newWork);
|
|
2052
|
+
this.doWork();
|
|
2053
|
+
}
|
|
2032
2054
|
});
|
|
2033
2055
|
}
|
|
2034
2056
|
}
|
|
@@ -2358,6 +2380,25 @@ const DRAFT_SEGMENT = 'DRAFT';
|
|
|
2358
2380
|
*/
|
|
2359
2381
|
const DRAFT_ACTION_RETRY_COUNT_METADATA_KEY = 'retryCount';
|
|
2360
2382
|
class DurableDraftQueue {
|
|
2383
|
+
// Instrumentation is strictly measure-only: a broken sink must never change queue behavior.
|
|
2384
|
+
// Pass the sink into the callback so methods are invoked through their owning object and retain
|
|
2385
|
+
// their receiver (some sinks use `this` to reach their metric registry).
|
|
2386
|
+
emitInstrumentation(emit) {
|
|
2387
|
+
const { instrumentation } = this;
|
|
2388
|
+
if (instrumentation === undefined) {
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
try {
|
|
2392
|
+
emit(instrumentation);
|
|
2393
|
+
}
|
|
2394
|
+
catch {
|
|
2395
|
+
// Telemetry failures must not interrupt draft processing.
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
resetDrainCycle() {
|
|
2399
|
+
this.drainCycleStart = undefined;
|
|
2400
|
+
this.drainCycleBatchCount = 0;
|
|
2401
|
+
}
|
|
2361
2402
|
getHandler(id) {
|
|
2362
2403
|
const handler = this.handlers[id];
|
|
2363
2404
|
if (handler === undefined) {
|
|
@@ -2380,9 +2421,19 @@ class DurableDraftQueue {
|
|
|
2380
2421
|
? __nimbus.plugins.JSLoggerPlugin
|
|
2381
2422
|
: undefined;
|
|
2382
2423
|
this.handlers = {};
|
|
2424
|
+
// Drain-wait attribution state. uploadStartTimes stamps when each action first began
|
|
2425
|
+
// uploading (keyed by action id) so actionCompleted/failed can emit its end-to-end drain time,
|
|
2426
|
+
// including automatic retries/backoff. actionQueueWaitEmittedIds ensures those retries do not
|
|
2427
|
+
// report the original enqueue wait more than once. drainCycleStart/BatchCount track one full
|
|
2428
|
+
// idle->empty sweep. All measure-only; when no instrumentation is injected these stay untouched.
|
|
2429
|
+
this.uploadStartTimes = new Map();
|
|
2430
|
+
this.actionQueueWaitEmittedIds = new Set();
|
|
2431
|
+
this.drainCycleBatchCount = 0;
|
|
2383
2432
|
this.draftStore = draftStore;
|
|
2384
|
-
this.workerPool = new AsyncWorkerPool(1);
|
|
2385
2433
|
this.instrumentation = instrumentation;
|
|
2434
|
+
this.workerPool = new AsyncWorkerPool(1, instrumentation?.onWorkerTiming !== undefined
|
|
2435
|
+
? (waitMs, holdMs, queueDepth) => this.emitInstrumentation((sink) => sink.onWorkerTiming?.(waitMs, holdMs, queueDepth))
|
|
2436
|
+
: undefined);
|
|
2386
2437
|
}
|
|
2387
2438
|
addHandler(handler) {
|
|
2388
2439
|
const id = handler.handlerId;
|
|
@@ -2470,6 +2521,9 @@ class DurableDraftQueue {
|
|
|
2470
2521
|
clearTimeout(this.timeoutHandler);
|
|
2471
2522
|
this.timeoutHandler = undefined;
|
|
2472
2523
|
}
|
|
2524
|
+
// A stop/user edit breaks the current sweep. A later restart starts a fresh cycle rather
|
|
2525
|
+
// than attributing stopped time to an offline drain.
|
|
2526
|
+
this.resetDrainCycle();
|
|
2473
2527
|
this.state = DraftQueueState.Stopped;
|
|
2474
2528
|
}
|
|
2475
2529
|
async getQueueActions() {
|
|
@@ -2500,7 +2554,7 @@ class DurableDraftQueue {
|
|
|
2500
2554
|
}
|
|
2501
2555
|
return aTime - bTime;
|
|
2502
2556
|
});
|
|
2503
|
-
|
|
2557
|
+
this.emitInstrumentation((sink) => sink.onGetQueueActions?.(Date.now() - start, sorted.length));
|
|
2504
2558
|
return sorted;
|
|
2505
2559
|
}
|
|
2506
2560
|
async enqueue(handlerId, data, observabilityContext) {
|
|
@@ -2544,6 +2598,13 @@ class DurableDraftQueue {
|
|
|
2544
2598
|
await handler.handleActionCompleted(action, queueOperations, values$2(this.handlers));
|
|
2545
2599
|
this.retryIntervalMilliseconds = 0;
|
|
2546
2600
|
this.uploadingActionId = undefined;
|
|
2601
|
+
// The action finished draining — emit its end-to-end drain time and
|
|
2602
|
+
// count it toward the current drain-cycle batch. Measure-only.
|
|
2603
|
+
this.emitActionDrainDuration(action.id, action.handler);
|
|
2604
|
+
this.actionQueueWaitEmittedIds.delete(action.id);
|
|
2605
|
+
if (this.drainCycleStart !== undefined) {
|
|
2606
|
+
this.drainCycleBatchCount += 1;
|
|
2607
|
+
}
|
|
2547
2608
|
await this.notifyChangedListeners({
|
|
2548
2609
|
type: DraftQueueEventType.ActionCompleted,
|
|
2549
2610
|
action,
|
|
@@ -2555,6 +2616,16 @@ class DurableDraftQueue {
|
|
|
2555
2616
|
},
|
|
2556
2617
|
});
|
|
2557
2618
|
}
|
|
2619
|
+
// Read+clear the action's upload-start stamp and emit its drain duration. Safe to
|
|
2620
|
+
// call when instrumentation is absent or the id was never stamped (no-op). Never emits the id.
|
|
2621
|
+
emitActionDrainDuration(id, handlerId) {
|
|
2622
|
+
const start = this.uploadStartTimes.get(id);
|
|
2623
|
+
if (start === undefined) {
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
this.uploadStartTimes.delete(id);
|
|
2627
|
+
this.emitInstrumentation((sink) => sink.onActionDrainDuration?.(handlerId, Date.now() - start));
|
|
2628
|
+
}
|
|
2558
2629
|
async actionFailed(action, retry, retryDelayInMs, actionDataChanged) {
|
|
2559
2630
|
if (actionDataChanged === true) {
|
|
2560
2631
|
await this.draftStore.writeAction({
|
|
@@ -2564,14 +2635,24 @@ class DurableDraftQueue {
|
|
|
2564
2635
|
}
|
|
2565
2636
|
this.uploadingActionId = undefined;
|
|
2566
2637
|
if (retry && this.state !== DraftQueueState.Stopped) {
|
|
2638
|
+
// A retry means this action is NOT done draining. Keep its first-upload stamp and the
|
|
2639
|
+
// open cycle so the eventual outcome includes network time and retry/backoff.
|
|
2567
2640
|
this.state = DraftQueueState.Waiting;
|
|
2568
2641
|
return retryDelayInMs !== undefined
|
|
2569
2642
|
? this.scheduleRetryWithSpecifiedDelay(retryDelayInMs)
|
|
2570
2643
|
: this.scheduleRetry();
|
|
2571
2644
|
}
|
|
2572
2645
|
else if (isDraftError(action)) {
|
|
2646
|
+
// Terminal failure — this action is done draining. Emit its drain time.
|
|
2647
|
+
this.emitActionDrainDuration(action.id, action.handler);
|
|
2648
|
+
// This was not a clean drain-to-empty. Discard the sweep before user intervention so a
|
|
2649
|
+
// later manual retry/removal cannot inherit its elapsed time or completed batch count.
|
|
2650
|
+
this.resetDrainCycle();
|
|
2573
2651
|
return this.handleServerError(action, action.error);
|
|
2574
2652
|
}
|
|
2653
|
+
// Any other terminal path — ensure no stale start stamp lingers.
|
|
2654
|
+
this.emitActionDrainDuration(action.id, action.handler);
|
|
2655
|
+
this.resetDrainCycle();
|
|
2575
2656
|
}
|
|
2576
2657
|
handle(action) {
|
|
2577
2658
|
const handler = this.getHandler(action.handler);
|
|
@@ -2591,12 +2672,22 @@ class DurableDraftQueue {
|
|
|
2591
2672
|
this.state = DraftQueueState.Started;
|
|
2592
2673
|
}
|
|
2593
2674
|
this.processingAction = undefined;
|
|
2675
|
+
// The queue drained empty — close the drain-cycle window if one was open.
|
|
2676
|
+
if (this.drainCycleStart !== undefined) {
|
|
2677
|
+
const durationMs = Date.now() - this.drainCycleStart;
|
|
2678
|
+
const batchSize = this.drainCycleBatchCount;
|
|
2679
|
+
this.resetDrainCycle();
|
|
2680
|
+
this.emitInstrumentation((sink) => sink.onDrainCycle?.(durationMs, batchSize));
|
|
2681
|
+
}
|
|
2594
2682
|
return ProcessActionResult.NO_ACTION_TO_PROCESS;
|
|
2595
2683
|
}
|
|
2596
2684
|
const { status, id } = action;
|
|
2597
2685
|
if (status === DraftActionStatus.Error) {
|
|
2598
2686
|
this.state = DraftQueueState.Error;
|
|
2599
2687
|
this.processingAction = undefined;
|
|
2688
|
+
// The sweep is blocked on an errored action, not a clean drain-to-empty —
|
|
2689
|
+
// discard the open drain-cycle window so it doesn't inflate a later cycle's duration.
|
|
2690
|
+
this.resetDrainCycle();
|
|
2600
2691
|
await this.notifyChangedListeners({
|
|
2601
2692
|
type: DraftQueueEventType.ActionFailed,
|
|
2602
2693
|
action: action,
|
|
@@ -2614,6 +2705,31 @@ class DurableDraftQueue {
|
|
|
2614
2705
|
if (this.state === DraftQueueState.Waiting) {
|
|
2615
2706
|
this.state = DraftQueueState.Started;
|
|
2616
2707
|
}
|
|
2708
|
+
// The action is now leaving the queue to upload. Emit head-of-line FIFO wait
|
|
2709
|
+
// (enqueue -> upload start), stamp its drain-start for end-to-end timing on completion, and
|
|
2710
|
+
// open a drain-cycle window if the queue was previously idle. All measure-only.
|
|
2711
|
+
const { instrumentation } = this;
|
|
2712
|
+
if (instrumentation !== undefined) {
|
|
2713
|
+
const now = Date.now();
|
|
2714
|
+
const { timestamp } = action;
|
|
2715
|
+
if (instrumentation.onActionQueueWait !== undefined &&
|
|
2716
|
+
!this.actionQueueWaitEmittedIds.has(id) &&
|
|
2717
|
+
typeof timestamp === 'number' &&
|
|
2718
|
+
timestamp > 0) {
|
|
2719
|
+
// Mark before invoking the sink: even a throwing callback must not make a retry
|
|
2720
|
+
// report the same action's enqueue wait again.
|
|
2721
|
+
this.actionQueueWaitEmittedIds.add(id);
|
|
2722
|
+
this.emitInstrumentation((sink) => sink.onActionQueueWait?.(action.handler, now - timestamp));
|
|
2723
|
+
}
|
|
2724
|
+
if (instrumentation.onActionDrainDuration !== undefined &&
|
|
2725
|
+
!this.uploadStartTimes.has(id)) {
|
|
2726
|
+
this.uploadStartTimes.set(id, now);
|
|
2727
|
+
}
|
|
2728
|
+
if (instrumentation.onDrainCycle !== undefined && this.drainCycleStart === undefined) {
|
|
2729
|
+
this.drainCycleStart = now;
|
|
2730
|
+
this.drainCycleBatchCount = 0;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2617
2733
|
await this.notifyChangedListeners({
|
|
2618
2734
|
type: DraftQueueEventType.ActionUploading,
|
|
2619
2735
|
action: { ...action, status: DraftActionStatus.Uploading },
|
|
@@ -2651,7 +2767,7 @@ class DurableDraftQueue {
|
|
|
2651
2767
|
results.push(listener(event));
|
|
2652
2768
|
}
|
|
2653
2769
|
// W-23515682: listener fan-out per event type. Measure-only.
|
|
2654
|
-
this.
|
|
2770
|
+
this.emitInstrumentation((sink) => sink.onNotifyChangedListeners?.(event.type, draftQueueLen));
|
|
2655
2771
|
await Promise.all(results);
|
|
2656
2772
|
}
|
|
2657
2773
|
/**
|
|
@@ -2680,9 +2796,17 @@ class DurableDraftQueue {
|
|
|
2680
2796
|
const shouldDeleteRelated = handler.shouldDeleteActionByTagOnRemoval(action);
|
|
2681
2797
|
if (shouldDeleteRelated) {
|
|
2682
2798
|
await this.draftStore.deleteByTag(action.tag);
|
|
2799
|
+
queue.forEach((queuedAction) => {
|
|
2800
|
+
if (queuedAction.tag === action.tag) {
|
|
2801
|
+
this.uploadStartTimes.delete(queuedAction.id);
|
|
2802
|
+
this.actionQueueWaitEmittedIds.delete(queuedAction.id);
|
|
2803
|
+
}
|
|
2804
|
+
});
|
|
2683
2805
|
}
|
|
2684
2806
|
else {
|
|
2685
2807
|
await this.draftStore.deleteDraft(action.id);
|
|
2808
|
+
this.uploadStartTimes.delete(action.id);
|
|
2809
|
+
this.actionQueueWaitEmittedIds.delete(action.id);
|
|
2686
2810
|
}
|
|
2687
2811
|
await handler.handleActionRemoved(action, queue.filter((x) => x.id !== actionId));
|
|
2688
2812
|
await this.notifyChangedListeners({
|
|
@@ -42336,6 +42460,20 @@ const DRAFT_QUEUE_GET_QUEUE_ACTIONS_DURATION = 'draft-queue-get-queue-actions-du
|
|
|
42336
42460
|
const DRAFT_QUEUE_NOTIFY_CHANGED_LISTENERS_COUNT = 'draft-queue-notify-changed-listeners-count';
|
|
42337
42461
|
const DRAFT_QUEUE_DURABLE_OP_COUNT = 'draft-queue-durable-op-count';
|
|
42338
42462
|
const DRAFT_QUEUE_DURABLE_OP_DURATION = 'draft-queue-durable-op-duration';
|
|
42463
|
+
// Drain-wait attribution telemetry: the wall-clock spine of where an offline drain actually spends
|
|
42464
|
+
// WAITING. Operational counts/durations/depths only — no draft content, field values, or server
|
|
42465
|
+
// record ids (matching the existing draft-queue privacy stance).
|
|
42466
|
+
const DRAFT_QUEUE_WORKER_WAIT_DURATION = 'draft-queue-worker-wait-duration';
|
|
42467
|
+
const DRAFT_QUEUE_WORKER_HOLD_DURATION = 'draft-queue-worker-hold-duration';
|
|
42468
|
+
const DRAFT_QUEUE_ACTION_QUEUE_WAIT_DURATION = 'draft-queue-action-queue-wait-duration';
|
|
42469
|
+
const DRAFT_QUEUE_ACTION_DRAIN_DURATION = 'draft-queue-action-drain-duration';
|
|
42470
|
+
const DRAFT_QUEUE_DRAIN_CYCLE_DURATION = 'draft-queue-drain-cycle-duration';
|
|
42471
|
+
const DRAFT_QUEUE_DRAIN_CYCLE_BATCH_SIZE = 'draft-queue-drain-cycle-batch-size';
|
|
42472
|
+
const DRAFT_QUEUE_ACTION_NETWORK_DURATION = 'draft-queue-action-network-duration';
|
|
42473
|
+
const DRAFT_QUEUE_ACTION_RETRY_ATTEMPT = 'draft-queue-action-retry-attempt';
|
|
42474
|
+
const DRAFT_QUEUE_SIDEEFFECT_OP_DURATION = 'draft-queue-sideeffect-op-duration';
|
|
42475
|
+
const DRAFT_QUEUE_SIDEEFFECT_MAP_SIZE = 'draft-queue-sideeffect-map-size';
|
|
42476
|
+
const DRAFT_QUEUE_SIDEEFFECT_REGION_DURATION = 'draft-queue-sideeffect-region-duration';
|
|
42339
42477
|
/** Content Document */
|
|
42340
42478
|
const CREATE_CONTENT_DOCUMENT_AND_VERSION_TOTAL_SYNTHESIZE_CALLS = 'content-document-version-total-synthesize-calls';
|
|
42341
42479
|
const CREATE_CONTENT_DOCUMENT_AND_VERSION_DRAFT_SYNTHESIZE_ERROR = 'create-content-document-version-draft-synthesize-error';
|
|
@@ -42455,6 +42593,134 @@ function reportDraftQueueDurableOperation(op, segment, durationMs) {
|
|
|
42455
42593
|
});
|
|
42456
42594
|
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_DURABLE_OP_DURATION, durationMs);
|
|
42457
42595
|
}
|
|
42596
|
+
const DRAIN_DURATION_BUCKETS = [50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000];
|
|
42597
|
+
const WORKER_DEPTH_BUCKETS = [1, 2, 5, 10, 20, 50, 100, 250, 500];
|
|
42598
|
+
// Handler ids are extensible through DraftManager, so only retain the fixed mobile handlers in
|
|
42599
|
+
// metric tags. Everything else shares one bounded bucket rather than creating an unbounded series.
|
|
42600
|
+
function draftQueueHandlerTagOf(handlerId) {
|
|
42601
|
+
switch (handlerId) {
|
|
42602
|
+
case 'LDS_ACTION_HANDLER':
|
|
42603
|
+
case 'ContentDocumentCompositeRepresentationActionHandler':
|
|
42604
|
+
case 'QUICK_ACTION_HANDLER':
|
|
42605
|
+
case 'UPDATE_RECORD_QUICK_ACTION_HANDLER':
|
|
42606
|
+
return handlerId;
|
|
42607
|
+
default:
|
|
42608
|
+
return 'custom';
|
|
42609
|
+
}
|
|
42610
|
+
}
|
|
42611
|
+
// Coarse, bounded-cardinality label for the pool depth a unit saw when it began, so the
|
|
42612
|
+
// wait/hold durations can be sliced BY backlog size without emitting depth as a free integer tag.
|
|
42613
|
+
function workerDepthBucketOf(queueDepth) {
|
|
42614
|
+
if (queueDepth <= 0) {
|
|
42615
|
+
return '0';
|
|
42616
|
+
}
|
|
42617
|
+
if (queueDepth <= 2) {
|
|
42618
|
+
return '1-2';
|
|
42619
|
+
}
|
|
42620
|
+
if (queueDepth <= 10) {
|
|
42621
|
+
return '3-10';
|
|
42622
|
+
}
|
|
42623
|
+
if (queueDepth <= 50) {
|
|
42624
|
+
return '11-50';
|
|
42625
|
+
}
|
|
42626
|
+
if (queueDepth <= 250) {
|
|
42627
|
+
return '51-250';
|
|
42628
|
+
}
|
|
42629
|
+
return '250+';
|
|
42630
|
+
}
|
|
42631
|
+
/**
|
|
42632
|
+
* Per-unit lifecycle split for the AsyncWorkerPool(1) serial queue, emitted once when a unit settles
|
|
42633
|
+
* so the two halves are a JOINED event (rather than shipping wait and depth as unrelated aggregates).
|
|
42634
|
+
* Isolates WHERE the time goes:
|
|
42635
|
+
* waitMs — enqueue -> work began (waiting on the serial queue before any logic runs).
|
|
42636
|
+
* holdMs — work began -> work settled (the critical section's own O(N) service time).
|
|
42637
|
+
* Both are additionally tagged by a depthBucket so "does per-unit cost scale with backlog depth"
|
|
42638
|
+
* (the O(N^2) signature) survives aggregation. queueDepth itself is also tracked. Operational only.
|
|
42639
|
+
*/
|
|
42640
|
+
function reportDraftQueueWorkerTiming(waitMs, holdMs, queueDepth) {
|
|
42641
|
+
const depthBucket = workerDepthBucketOf(queueDepth);
|
|
42642
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_WORKER_WAIT_DURATION, waitMs, undefined, {
|
|
42643
|
+
depthBucket,
|
|
42644
|
+
});
|
|
42645
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_WORKER_HOLD_DURATION, holdMs, undefined, {
|
|
42646
|
+
depthBucket,
|
|
42647
|
+
});
|
|
42648
|
+
ldsMobileInstrumentation.bucketValue(DRAFT_QUEUE_WORKER_WAIT_DURATION + '-depth', queueDepth, WORKER_DEPTH_BUCKETS);
|
|
42649
|
+
}
|
|
42650
|
+
/**
|
|
42651
|
+
* Head-of-line FIFO wait for one draft (enqueue -> upload start), bucketed so the
|
|
42652
|
+
* "does wait grow with backlog depth" (O(N^2)) signal is visible per handler type. Custom handler
|
|
42653
|
+
* ids are normalized to one bounded tag so caller-defined values never become metric dimensions.
|
|
42654
|
+
*/
|
|
42655
|
+
function reportDraftQueueActionQueueWait(handlerId, waitMs) {
|
|
42656
|
+
ldsMobileInstrumentation.bucketValue(DRAFT_QUEUE_ACTION_QUEUE_WAIT_DURATION, waitMs, DRAIN_DURATION_BUCKETS, undefined, { handlerId: draftQueueHandlerTagOf(handlerId) });
|
|
42657
|
+
}
|
|
42658
|
+
/**
|
|
42659
|
+
* End-to-end drain time for one action (first upload attempt -> completed/failed), including
|
|
42660
|
+
* retry/backoff. Compare it with network-duration and retry-attempt signals to understand the
|
|
42661
|
+
* non-request portion of the drain interval.
|
|
42662
|
+
*/
|
|
42663
|
+
function reportDraftQueueActionDrainDuration(handlerId, durationMs) {
|
|
42664
|
+
ldsMobileInstrumentation.bucketValue(DRAFT_QUEUE_ACTION_DRAIN_DURATION, durationMs, DRAIN_DURATION_BUCKETS, undefined, { handlerId: draftQueueHandlerTagOf(handlerId) });
|
|
42665
|
+
}
|
|
42666
|
+
/**
|
|
42667
|
+
* One full drain sweep — first action starts uploading -> queue empties. Both raw values share a
|
|
42668
|
+
* bounded batch-size cohort so aggregate duration remains sliceable by the number of actions drained.
|
|
42669
|
+
*/
|
|
42670
|
+
function reportDraftQueueDrainCycle(durationMs, batchSize) {
|
|
42671
|
+
const batchSizeBucket = workerDepthBucketOf(batchSize);
|
|
42672
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_DRAIN_CYCLE_DURATION, durationMs, undefined, {
|
|
42673
|
+
batchSizeBucket,
|
|
42674
|
+
});
|
|
42675
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_DRAIN_CYCLE_BATCH_SIZE, batchSize, undefined, {
|
|
42676
|
+
batchSizeBucket,
|
|
42677
|
+
});
|
|
42678
|
+
}
|
|
42679
|
+
/**
|
|
42680
|
+
* Server round-trip duration for one upload. Isolates slow-network from CPU-bound
|
|
42681
|
+
* quadratic work — today only a THROWN error is captured, so a slow-but-successful (or slow 4xx/5xx)
|
|
42682
|
+
* response is invisible. Tags: handler type, HTTP method, and a bucketed status class ('2xx'/'4xx'/
|
|
42683
|
+
* '5xx'/'0'). No URL, body, or record id.
|
|
42684
|
+
*/
|
|
42685
|
+
function reportDraftQueueActionNetworkDuration(handlerId, method, statusBucket, durationMs) {
|
|
42686
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_ACTION_NETWORK_DURATION, durationMs, undefined, {
|
|
42687
|
+
handlerId: draftQueueHandlerTagOf(handlerId),
|
|
42688
|
+
method,
|
|
42689
|
+
statusBucket,
|
|
42690
|
+
});
|
|
42691
|
+
}
|
|
42692
|
+
/**
|
|
42693
|
+
* Retry event count. Repeated events identify actions cycling through the drain even when the retry
|
|
42694
|
+
* cap is disabled and an exact attempt number is not persisted. cappedOut marks cap exhaustion.
|
|
42695
|
+
*/
|
|
42696
|
+
function reportDraftQueueActionRetryAttempt(handlerId, cappedOut) {
|
|
42697
|
+
ldsMobileInstrumentation.incrementCounter(DRAFT_QUEUE_ACTION_RETRY_ATTEMPT, 1, undefined, {
|
|
42698
|
+
handlerId: draftQueueHandlerTagOf(handlerId),
|
|
42699
|
+
cappedOut: String(cappedOut),
|
|
42700
|
+
});
|
|
42701
|
+
}
|
|
42702
|
+
/**
|
|
42703
|
+
* SideEffectStore operation duration + map size. The incremental map sped up the by-KEY lookup but
|
|
42704
|
+
* getEffectsByTag still linearly scans all effects — this measures whether that residual scan is
|
|
42705
|
+
* silently quadratic during churn. op is 'add'|'remove'|'getByTag'; incremental flags the fast path;
|
|
42706
|
+
* mapSize is an integer effect count. No record keys, field names, or field values.
|
|
42707
|
+
*/
|
|
42708
|
+
function reportDraftQueueSideEffectOp(op, incremental, durationMs, mapSize) {
|
|
42709
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_SIDEEFFECT_OP_DURATION, durationMs, undefined, {
|
|
42710
|
+
op,
|
|
42711
|
+
incremental: String(incremental),
|
|
42712
|
+
});
|
|
42713
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_SIDEEFFECT_MAP_SIZE, mapSize, undefined, {
|
|
42714
|
+
op,
|
|
42715
|
+
});
|
|
42716
|
+
}
|
|
42717
|
+
/**
|
|
42718
|
+
* Broad side-effect lifecycle duration. The fixed region and lifecycle tags identify which part of
|
|
42719
|
+
* serialized draft processing is slow without emitting record keys, action ids, or field data.
|
|
42720
|
+
*/
|
|
42721
|
+
function reportDraftQueueSideEffectRegionDuration(region, lifecycle, durationMs) {
|
|
42722
|
+
ldsMobileInstrumentation.trackValue(DRAFT_QUEUE_SIDEEFFECT_REGION_DURATION, durationMs, undefined, { region, lifecycle });
|
|
42723
|
+
}
|
|
42458
42724
|
/**
|
|
42459
42725
|
* Reports an exception thrown while uploading a draft action. The thrown error is
|
|
42460
42726
|
* otherwise swallowed by the upload handler's retry path, leaving the failure
|
|
@@ -42662,8 +42928,16 @@ class AbstractResourceRequestActionHandler {
|
|
|
42662
42928
|
const dispatchOptions = action.observabilityContext !== undefined
|
|
42663
42929
|
? { requestCorrelator: { observabilityContext: action.observabilityContext } }
|
|
42664
42930
|
: {};
|
|
42931
|
+
const method = request?.method?.toUpperCase() ?? 'UNKNOWN';
|
|
42932
|
+
const networkStart = Date.now();
|
|
42933
|
+
let responseReceived = false;
|
|
42665
42934
|
try {
|
|
42666
42935
|
const response = await this.networkAdapter(request, dispatchOptions);
|
|
42936
|
+
responseReceived = true;
|
|
42937
|
+
// Time the round-trip regardless of outcome (ok, retryable HTTP error, or non-ok
|
|
42938
|
+
// terminal error) — today only a THROWN error is visible; a slow-but-successful or
|
|
42939
|
+
// slow 4xx/5xx response is not.
|
|
42940
|
+
this.reportNetworkDuration(method, statusBucketOf(response.status), networkStart);
|
|
42667
42941
|
if (response.ok) {
|
|
42668
42942
|
await actionCompleted({
|
|
42669
42943
|
...action,
|
|
@@ -42819,6 +43093,11 @@ class AbstractResourceRequestActionHandler {
|
|
|
42819
43093
|
const error = normalizeError$1(e);
|
|
42820
43094
|
const attempt = this.getUploadRetryCount(action) + 1;
|
|
42821
43095
|
reportDraftActionUploadError(error, this.handlerId, attempt);
|
|
43096
|
+
// A thrown network call still has a round-trip duration worth surfacing — a slow
|
|
43097
|
+
// FAILING call is otherwise as invisible as a slow successful one.
|
|
43098
|
+
if (!responseReceived) {
|
|
43099
|
+
this.reportNetworkDuration(method, 'error', networkStart);
|
|
43100
|
+
}
|
|
42822
43101
|
await this.retryOrCap(action, error, actionErrored);
|
|
42823
43102
|
return ProcessActionResult.NETWORK_ERROR;
|
|
42824
43103
|
}
|
|
@@ -42854,12 +43133,17 @@ class AbstractResourceRequestActionHandler {
|
|
|
42854
43133
|
*/
|
|
42855
43134
|
async retryOrCap(action, lastFailure, actionErrored, retryDelayInMs, actionDataChanged) {
|
|
42856
43135
|
if (!draftQueueMaxRetryAttemptsGate.isOpen({ fallback: false })) {
|
|
42857
|
-
// Gate closed: preserve the pre-existing unbounded-retry behavior exactly.
|
|
43136
|
+
// Gate closed: preserve the pre-existing unbounded-retry behavior exactly. The
|
|
43137
|
+
// metric is still emitted (measure-only) so retry attempts are visible even with
|
|
43138
|
+
// the cap gate closed — cappedOut is always false here since nothing ever caps
|
|
43139
|
+
// out on this path.
|
|
43140
|
+
this.reportRetryAttempt(false);
|
|
42858
43141
|
await actionErrored(action, true, retryDelayInMs, actionDataChanged);
|
|
42859
43142
|
return;
|
|
42860
43143
|
}
|
|
42861
43144
|
const attempt = this.getUploadRetryCount(action) + 1;
|
|
42862
43145
|
if (attempt >= MAX_RETRY_ATTEMPTS) {
|
|
43146
|
+
this.reportRetryAttempt(true);
|
|
42863
43147
|
await actionErrored({
|
|
42864
43148
|
...action,
|
|
42865
43149
|
error: buildMaxRetryError(lastFailure),
|
|
@@ -42867,6 +43151,7 @@ class AbstractResourceRequestActionHandler {
|
|
|
42867
43151
|
}, false);
|
|
42868
43152
|
return;
|
|
42869
43153
|
}
|
|
43154
|
+
this.reportRetryAttempt(false);
|
|
42870
43155
|
// Persist the incremented attempt count so it survives restarts. Forcing
|
|
42871
43156
|
// actionDataChanged ensures the queue writes the updated metadata before the
|
|
42872
43157
|
// action is rescheduled for retry.
|
|
@@ -42880,12 +43165,12 @@ class AbstractResourceRequestActionHandler {
|
|
|
42880
43165
|
await actionErrored(retryingAction, true, retryDelayInMs, true);
|
|
42881
43166
|
}
|
|
42882
43167
|
async handleActionEnqueued(action) {
|
|
42883
|
-
const impactedKeys = await this.recordService.setSideEffectsForActions([action]);
|
|
42884
|
-
await this.recordService.reapplyRecordSideEffects(impactedKeys);
|
|
43168
|
+
const impactedKeys = await this.recordService.setSideEffectsForActions([action], 'enqueued');
|
|
43169
|
+
await this.recordService.reapplyRecordSideEffects(impactedKeys, 'enqueued');
|
|
42885
43170
|
}
|
|
42886
43171
|
async handleActionRemoved(action) {
|
|
42887
43172
|
const impactedKeys = await this.recordService.removeSideEffectsForAction(action);
|
|
42888
|
-
await this.recordService.reapplyRecordSideEffects(impactedKeys);
|
|
43173
|
+
await this.recordService.reapplyRecordSideEffects(impactedKeys, 'removed');
|
|
42889
43174
|
}
|
|
42890
43175
|
async handleActionReplaced(target, source) {
|
|
42891
43176
|
// remove both the siden effects from the actions being merged before we
|
|
@@ -42893,10 +43178,10 @@ class AbstractResourceRequestActionHandler {
|
|
|
42893
43178
|
const impactedKeysFromRemove = await this.recordService.removeSideEffectsForAction(source);
|
|
42894
43179
|
const removedSideEffectFromSet = await this.recordService.removeSideEffectsForAction(target);
|
|
42895
43180
|
// revert the values in the DEFAULT store back to what it was before the removed side effects
|
|
42896
|
-
await this.recordService.reapplyRecordSideEffects(new Set([...impactedKeysFromRemove, ...removedSideEffectFromSet]));
|
|
43181
|
+
await this.recordService.reapplyRecordSideEffects(new Set([...impactedKeysFromRemove, ...removedSideEffectFromSet]), 'replaced-remove');
|
|
42897
43182
|
// set the new side effect values for the merged action
|
|
42898
|
-
const impactedKeysFromSet = await this.recordService.setSideEffectsForActions([target]);
|
|
42899
|
-
await this.recordService.reapplyRecordSideEffects(new Set([...impactedKeysFromSet]));
|
|
43183
|
+
const impactedKeysFromSet = await this.recordService.setSideEffectsForActions([target], 'replaced-set');
|
|
43184
|
+
await this.recordService.reapplyRecordSideEffects(new Set([...impactedKeysFromSet]), 'replaced-set');
|
|
42900
43185
|
}
|
|
42901
43186
|
async handleActionCompleted(action, queueOperations) {
|
|
42902
43187
|
const { data: request, tag } = action;
|
|
@@ -42930,8 +43215,8 @@ class AbstractResourceRequestActionHandler {
|
|
|
42930
43215
|
actionsNeedingReplay.push(qo.action);
|
|
42931
43216
|
}
|
|
42932
43217
|
});
|
|
42933
|
-
const impactedKeys = await this.recordService.setSideEffectsForActions(actionsNeedingReplay);
|
|
42934
|
-
await this.recordService.reapplyRecordSideEffects(impactedKeys);
|
|
43218
|
+
const impactedKeys = await this.recordService.setSideEffectsForActions(actionsNeedingReplay, 'completed-replay');
|
|
43219
|
+
await this.recordService.reapplyRecordSideEffects(impactedKeys, 'completed-replay');
|
|
42935
43220
|
}
|
|
42936
43221
|
getQueueOperationsForCompletingDrafts(queue, action) {
|
|
42937
43222
|
const queueOperations = [];
|
|
@@ -43257,6 +43542,39 @@ class AbstractResourceRequestActionHandler {
|
|
|
43257
43542
|
return new StoreKeyMap();
|
|
43258
43543
|
});
|
|
43259
43544
|
}
|
|
43545
|
+
reportNetworkDuration(method, statusBucket, startedAt) {
|
|
43546
|
+
try {
|
|
43547
|
+
reportDraftQueueActionNetworkDuration(this.handlerId, method, statusBucket, Date.now() - startedAt);
|
|
43548
|
+
}
|
|
43549
|
+
catch {
|
|
43550
|
+
// Telemetry must not change upload processing behavior.
|
|
43551
|
+
}
|
|
43552
|
+
}
|
|
43553
|
+
reportRetryAttempt(cappedOut) {
|
|
43554
|
+
try {
|
|
43555
|
+
reportDraftQueueActionRetryAttempt(this.handlerId, cappedOut);
|
|
43556
|
+
}
|
|
43557
|
+
catch {
|
|
43558
|
+
// Telemetry must not change retry or cap behavior.
|
|
43559
|
+
}
|
|
43560
|
+
}
|
|
43561
|
+
}
|
|
43562
|
+
// Classifies an HTTP status into a small, greppable tag set for the network-duration metric —
|
|
43563
|
+
// never the raw status, to keep the metric's tag cardinality bounded.
|
|
43564
|
+
function statusBucketOf(status) {
|
|
43565
|
+
if (status === 0) {
|
|
43566
|
+
return '0';
|
|
43567
|
+
}
|
|
43568
|
+
if (status >= 200 && status <= 299) {
|
|
43569
|
+
return '2xx';
|
|
43570
|
+
}
|
|
43571
|
+
if (status >= 400 && status <= 499) {
|
|
43572
|
+
return '4xx';
|
|
43573
|
+
}
|
|
43574
|
+
if (status >= 500) {
|
|
43575
|
+
return '5xx';
|
|
43576
|
+
}
|
|
43577
|
+
return 'other';
|
|
43260
43578
|
}
|
|
43261
43579
|
function actionsForTag(tag, queue) {
|
|
43262
43580
|
return queue.filter((action) => action.tag === tag);
|
|
@@ -43620,10 +43938,16 @@ class UiApiDraftRecordService {
|
|
|
43620
43938
|
}
|
|
43621
43939
|
// calculates the side effects for a given set of actions
|
|
43622
43940
|
// and returns a set of impacted record keys
|
|
43623
|
-
async setSideEffectsForActions(changedActions) {
|
|
43624
|
-
const
|
|
43625
|
-
|
|
43626
|
-
|
|
43941
|
+
async setSideEffectsForActions(changedActions, lifecycle) {
|
|
43942
|
+
const startedAt = Date.now();
|
|
43943
|
+
try {
|
|
43944
|
+
const operations = await this.recalculateForChangedActions(changedActions);
|
|
43945
|
+
await this.sideEffectStore.addEffects(operations);
|
|
43946
|
+
return new Set(operations.map((o) => o.key));
|
|
43947
|
+
}
|
|
43948
|
+
finally {
|
|
43949
|
+
this.reportSideEffectRegionDuration('set', lifecycle, startedAt);
|
|
43950
|
+
}
|
|
43627
43951
|
}
|
|
43628
43952
|
// removes the associated side effects for a given action and returns
|
|
43629
43953
|
// a set of impacted record keys
|
|
@@ -43634,31 +43958,45 @@ class UiApiDraftRecordService {
|
|
|
43634
43958
|
}
|
|
43635
43959
|
// reads a set of records out of the durable store, re-applies current side
|
|
43636
43960
|
// effects and persists the changed record
|
|
43637
|
-
async reapplyRecordSideEffects(recordKeys) {
|
|
43638
|
-
const
|
|
43639
|
-
|
|
43640
|
-
|
|
43641
|
-
|
|
43642
|
-
|
|
43643
|
-
|
|
43644
|
-
|
|
43645
|
-
|
|
43646
|
-
|
|
43647
|
-
|
|
43648
|
-
|
|
43649
|
-
|
|
43650
|
-
|
|
43651
|
-
|
|
43652
|
-
|
|
43653
|
-
if (recordEntry
|
|
43654
|
-
|
|
43655
|
-
metadata
|
|
43961
|
+
async reapplyRecordSideEffects(recordKeys, lifecycle) {
|
|
43962
|
+
const startedAt = Date.now();
|
|
43963
|
+
try {
|
|
43964
|
+
const luvio = this.getLuvio();
|
|
43965
|
+
const keys = Array.from(recordKeys);
|
|
43966
|
+
const recordEntries = await this.durableRecordStore.getRecordsWithMetadata(keys);
|
|
43967
|
+
keys.forEach((key) => {
|
|
43968
|
+
let record;
|
|
43969
|
+
const metadata = {
|
|
43970
|
+
namespace: 'UiApi',
|
|
43971
|
+
representationName: 'RecordRepresentation',
|
|
43972
|
+
version: VERSION$1v,
|
|
43973
|
+
ingestionTimestamp: Date.now(),
|
|
43974
|
+
ttl: Number.MAX_SAFE_INTEGER,
|
|
43975
|
+
};
|
|
43976
|
+
const recordEntry = recordEntries.get(key);
|
|
43977
|
+
if (recordEntry) {
|
|
43978
|
+
record = recordEntry.record;
|
|
43979
|
+
if (recordEntry.metadata) {
|
|
43980
|
+
metadata.ingestionTimestamp = recordEntry.metadata.ingestionTimestamp;
|
|
43981
|
+
metadata.ttl = RecordRepresentationTTL;
|
|
43982
|
+
}
|
|
43656
43983
|
}
|
|
43657
|
-
|
|
43658
|
-
|
|
43659
|
-
|
|
43660
|
-
|
|
43661
|
-
|
|
43984
|
+
luvio.storePublish(key, record);
|
|
43985
|
+
luvio.publishStoreMetadata(key, metadata);
|
|
43986
|
+
});
|
|
43987
|
+
await luvio.storeBroadcast();
|
|
43988
|
+
}
|
|
43989
|
+
finally {
|
|
43990
|
+
this.reportSideEffectRegionDuration('reapply', lifecycle, startedAt);
|
|
43991
|
+
}
|
|
43992
|
+
}
|
|
43993
|
+
reportSideEffectRegionDuration(region, lifecycle, startedAt) {
|
|
43994
|
+
try {
|
|
43995
|
+
reportDraftQueueSideEffectRegionDuration(region, lifecycle, Date.now() - startedAt);
|
|
43996
|
+
}
|
|
43997
|
+
catch {
|
|
43998
|
+
// Telemetry must not change draft processing behavior.
|
|
43999
|
+
}
|
|
43662
44000
|
}
|
|
43663
44001
|
async synthesizeId(apiName) {
|
|
43664
44002
|
const objectInfo = await this.objectInfoService.getObjectInfo(apiName);
|
|
@@ -43894,7 +44232,7 @@ class UpdateRecordQuickActionExecutionRepresentationHandler extends AbstractQuic
|
|
|
43894
44232
|
// since we don't ingest a record from the response, we need to manually trigger a re-apply here to ensure we
|
|
43895
44233
|
// re-ingest the record without effects applied
|
|
43896
44234
|
await this.recordService.removeSideEffectsForAction(action);
|
|
43897
|
-
await this.recordService.reapplyRecordSideEffects(new Set([action.tag]));
|
|
44235
|
+
await this.recordService.reapplyRecordSideEffects(new Set([action.tag]), 'quick-action-completed');
|
|
43898
44236
|
// notify luvio that record is now changed and needs to be refreshed
|
|
43899
44237
|
await luvio.notifyStoreUpdateAvailable([action.tag]);
|
|
43900
44238
|
}
|
|
@@ -52580,6 +52918,18 @@ const draftQueueInstrumentation = {
|
|
|
52580
52918
|
onDurableOperation(op, segment, durationMs) {
|
|
52581
52919
|
reportDraftQueueDurableOperation(op, segment, durationMs);
|
|
52582
52920
|
},
|
|
52921
|
+
onWorkerTiming(waitMs, holdMs, queueDepth) {
|
|
52922
|
+
reportDraftQueueWorkerTiming(waitMs, holdMs, queueDepth);
|
|
52923
|
+
},
|
|
52924
|
+
onActionQueueWait(handlerId, waitMs) {
|
|
52925
|
+
reportDraftQueueActionQueueWait(handlerId, waitMs);
|
|
52926
|
+
},
|
|
52927
|
+
onActionDrainDuration(handlerId, durationMs) {
|
|
52928
|
+
reportDraftQueueActionDrainDuration(handlerId, durationMs);
|
|
52929
|
+
},
|
|
52930
|
+
onDrainCycle(durationMs, batchSize) {
|
|
52931
|
+
reportDraftQueueDrainCycle(durationMs, batchSize);
|
|
52932
|
+
},
|
|
52583
52933
|
};
|
|
52584
52934
|
|
|
52585
52935
|
// so eslint doesn't complain about nimbus
|
|
@@ -56070,6 +56420,9 @@ class SideEffectStore {
|
|
|
56070
56420
|
this.initialize();
|
|
56071
56421
|
}
|
|
56072
56422
|
async addEffects(effects) {
|
|
56423
|
+
// Wall-clock across the whole method (including the durable write), matching
|
|
56424
|
+
// how the equivalent removeEffects/getEffectsByTag windows are measured.
|
|
56425
|
+
const startTime = Date.now();
|
|
56073
56426
|
const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
|
|
56074
56427
|
const durableEntries = {};
|
|
56075
56428
|
effects.forEach((effect) => {
|
|
@@ -56084,8 +56437,11 @@ class SideEffectStore {
|
|
|
56084
56437
|
this.regenerateKeyToEffectMap();
|
|
56085
56438
|
}
|
|
56086
56439
|
await this.durableStore.setEntries(durableEntries, SIDE_EFFECT_SEGMENT);
|
|
56440
|
+
this.reportOperation('add', incremental, startTime);
|
|
56087
56441
|
}
|
|
56088
56442
|
async removeEffects(effects) {
|
|
56443
|
+
// See addEffects — same wall-clock-across-the-method measurement.
|
|
56444
|
+
const startTime = Date.now();
|
|
56089
56445
|
const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
|
|
56090
56446
|
const effectKeys = effects.map((e) => e.uniqueId);
|
|
56091
56447
|
effects.forEach((effect) => {
|
|
@@ -56099,6 +56455,7 @@ class SideEffectStore {
|
|
|
56099
56455
|
this.regenerateKeyToEffectMap();
|
|
56100
56456
|
}
|
|
56101
56457
|
await this.durableStore.evictEntries(effectKeys, SIDE_EFFECT_SEGMENT);
|
|
56458
|
+
this.reportOperation('remove', incremental, startTime);
|
|
56102
56459
|
}
|
|
56103
56460
|
getEffects(key) {
|
|
56104
56461
|
const effects = this.keyToEffectMap.get(key);
|
|
@@ -56108,6 +56465,10 @@ class SideEffectStore {
|
|
|
56108
56465
|
return effects;
|
|
56109
56466
|
}
|
|
56110
56467
|
getEffectsByTag(tag) {
|
|
56468
|
+
// This scan is still O(N) over every effect regardless of the incremental-map
|
|
56469
|
+
// gate — timing it tells us whether that residual scan is the quadratic hotspot during churn.
|
|
56470
|
+
const startTime = Date.now();
|
|
56471
|
+
const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
|
|
56111
56472
|
const filteredEffects = [];
|
|
56112
56473
|
const { keyToEffectMap } = this;
|
|
56113
56474
|
// Loop through every entry in the map
|
|
@@ -56117,7 +56478,17 @@ class SideEffectStore {
|
|
|
56117
56478
|
// Add the matching effects to the filtered list
|
|
56118
56479
|
filteredEffects.push(...matchingEffects);
|
|
56119
56480
|
}
|
|
56120
|
-
|
|
56481
|
+
const result = filteredEffects.sort((a, b) => a.timestamp - b.timestamp);
|
|
56482
|
+
this.reportOperation('getByTag', incremental, startTime);
|
|
56483
|
+
return result;
|
|
56484
|
+
}
|
|
56485
|
+
reportOperation(op, incremental, startedAt) {
|
|
56486
|
+
try {
|
|
56487
|
+
reportDraftQueueSideEffectOp(op, incremental, Date.now() - startedAt, this.allEffects.size);
|
|
56488
|
+
}
|
|
56489
|
+
catch {
|
|
56490
|
+
// Telemetry must not change side-effect store behavior.
|
|
56491
|
+
}
|
|
56121
56492
|
}
|
|
56122
56493
|
async initialize() {
|
|
56123
56494
|
const effects = await this.durableStore.getAllEntries(SIDE_EFFECT_SEGMENT);
|
|
@@ -59331,7 +59702,7 @@ function buildServiceDescriptor$b(luvio) {
|
|
|
59331
59702
|
},
|
|
59332
59703
|
};
|
|
59333
59704
|
}
|
|
59334
|
-
// version: 1.
|
|
59705
|
+
// version: 1.455.0-c53c97de2a
|
|
59335
59706
|
|
|
59336
59707
|
/**
|
|
59337
59708
|
* Copyright (c) 2022, Salesforce, Inc.,
|
|
@@ -59357,7 +59728,7 @@ function buildServiceDescriptor$a(notifyRecordUpdateAvailable, getNormalizedLuvi
|
|
|
59357
59728
|
},
|
|
59358
59729
|
};
|
|
59359
59730
|
}
|
|
59360
|
-
// version: 1.
|
|
59731
|
+
// version: 1.455.0-c53c97de2a
|
|
59361
59732
|
|
|
59362
59733
|
function findExecutableOperation(input) {
|
|
59363
59734
|
const operations = input.query.definitions.filter(
|
|
@@ -62069,4 +62440,4 @@ register({
|
|
|
62069
62440
|
});
|
|
62070
62441
|
|
|
62071
62442
|
export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, ingest$1o as ingestDenormalizedRecordRepresentation, initializeOneStore, registerReportObserver, reportGraphqlQueryParseError };
|
|
62072
|
-
// version: 1.
|
|
62443
|
+
// version: 1.455.0-c53c97de2a
|