@salesforce/lds-worker-api 1.453.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.
|
@@ -4148,7 +4148,7 @@ function createResourceParamsImpl(config, configMetadata) {
|
|
|
4148
4148
|
}
|
|
4149
4149
|
return resourceParams;
|
|
4150
4150
|
}
|
|
4151
|
-
// engine version: 0.161.
|
|
4151
|
+
// engine version: 0.161.2-bd1bd38a
|
|
4152
4152
|
|
|
4153
4153
|
/**
|
|
4154
4154
|
* Copyright (c) 2022, Salesforce, Inc.,
|
|
@@ -4276,7 +4276,7 @@ function withDefaultLuvio(callback) {
|
|
|
4276
4276
|
}
|
|
4277
4277
|
callbacks.push(callback);
|
|
4278
4278
|
}
|
|
4279
|
-
// version: 1.
|
|
4279
|
+
// version: 1.455.0-c53c97de2a
|
|
4280
4280
|
|
|
4281
4281
|
// TODO [TD-0081508]: once that TD is fulfilled we can probably change this file
|
|
4282
4282
|
function instrumentAdapter$1(createFunction, _metadata) {
|
|
@@ -5320,7 +5320,7 @@ function createGraphQLWireAdapterConstructor(luvio, adapter, metadata, astResolv
|
|
|
5320
5320
|
const { apiFamily, name } = metadata;
|
|
5321
5321
|
return createGraphQLWireAdapterConstructor$1(adapter, `${apiFamily}.${name}`, luvio, astResolver);
|
|
5322
5322
|
}
|
|
5323
|
-
// version: 1.
|
|
5323
|
+
// version: 1.455.0-c53c97de2a
|
|
5324
5324
|
|
|
5325
5325
|
function isSupportedEntity(_objectApiName) {
|
|
5326
5326
|
return true;
|
|
@@ -5426,11 +5426,11 @@ var TypeCheckShapes;
|
|
|
5426
5426
|
TypeCheckShapes[TypeCheckShapes["Integer"] = 3] = "Integer";
|
|
5427
5427
|
TypeCheckShapes[TypeCheckShapes["Unsupported"] = 4] = "Unsupported";
|
|
5428
5428
|
})(TypeCheckShapes || (TypeCheckShapes = {}));
|
|
5429
|
-
// engine version: 0.161.
|
|
5429
|
+
// engine version: 0.161.2-bd1bd38a
|
|
5430
5430
|
|
|
5431
5431
|
const { keys: ObjectKeys$4, create: ObjectCreate$4 } = Object;
|
|
5432
5432
|
|
|
5433
|
-
const { assign: assign$7, create: create$9, freeze: freeze$6, isFrozen: isFrozen$4, keys: keys$a } = Object;
|
|
5433
|
+
const { assign: assign$7, create: create$9, freeze: freeze$6, getPrototypeOf: getPrototypeOf$3, isFrozen: isFrozen$4, keys: keys$a } = Object;
|
|
5434
5434
|
|
|
5435
5435
|
/**
|
|
5436
5436
|
* Defines configuration for the module with a default value which can be overridden by the runtime environment.
|
|
@@ -6065,7 +6065,7 @@ function buildAdapterValidationConfig$3(displayName, paramsMeta) {
|
|
|
6065
6065
|
}
|
|
6066
6066
|
const keyPrefix$3 = 'UiApi';
|
|
6067
6067
|
|
|
6068
|
-
const { assign: assign$6, create: create$8, freeze: freeze$5, isFrozen: isFrozen$3, keys: keys$9 } = Object;
|
|
6068
|
+
const { assign: assign$6, create: create$8, freeze: freeze$5, getPrototypeOf: getPrototypeOf$2, isFrozen: isFrozen$3, keys: keys$9 } = Object;
|
|
6069
6069
|
const { hasOwnProperty: hasOwnProperty$5 } = Object.prototype;
|
|
6070
6070
|
const { split: split$1, endsWith: endsWith$1 } = String.prototype;
|
|
6071
6071
|
const { isArray: isArray$8 } = Array;
|
|
@@ -32547,7 +32547,7 @@ withDefaultLuvio((luvio) => {
|
|
|
32547
32547
|
throttle(60, 60000, setupNotifyAllListRecordUpdateAvailable(luvio));
|
|
32548
32548
|
throttle(60, 60000, setupNotifyAllListInfoSummaryUpdateAvailable(luvio));
|
|
32549
32549
|
});
|
|
32550
|
-
// version: 1.
|
|
32550
|
+
// version: 1.455.0-96e9b41a18
|
|
32551
32551
|
|
|
32552
32552
|
var draftQueueMaxRetryAttemptsGate = {
|
|
32553
32553
|
isOpen: function (e) {
|
|
@@ -47849,16 +47849,18 @@ class LdsAbortController {
|
|
|
47849
47849
|
}
|
|
47850
47850
|
|
|
47851
47851
|
class AsyncWorkerPool {
|
|
47852
|
-
constructor(concurrency) {
|
|
47852
|
+
constructor(concurrency, onWorkTiming) {
|
|
47853
47853
|
this.queue = [];
|
|
47854
47854
|
this.activeWork = [];
|
|
47855
47855
|
this.concurrency = concurrency;
|
|
47856
|
+
this.onWorkTiming = onWorkTiming;
|
|
47856
47857
|
}
|
|
47857
47858
|
push(work) {
|
|
47858
47859
|
return new Promise((resolve, reject) => {
|
|
47859
47860
|
this.queue.push({
|
|
47860
47861
|
...work,
|
|
47861
47862
|
workFn: (abortController) => work.workFn(abortController).then(resolve).catch(reject),
|
|
47863
|
+
enqueuedAt: Date.now(),
|
|
47862
47864
|
});
|
|
47863
47865
|
this.doWork();
|
|
47864
47866
|
});
|
|
@@ -47901,10 +47903,30 @@ class AsyncWorkerPool {
|
|
|
47901
47903
|
const abortController = new LdsAbortController();
|
|
47902
47904
|
const newWork = { ...work, abortController };
|
|
47903
47905
|
this.activeWork.push(newWork);
|
|
47904
|
-
const { workFn } = work;
|
|
47906
|
+
const { workFn, enqueuedAt } = work;
|
|
47907
|
+
const onWorkTiming = this.onWorkTiming;
|
|
47908
|
+
let startedAt = 0;
|
|
47909
|
+
let waitMs = 0;
|
|
47910
|
+
let queueDepthAtStart = 0;
|
|
47911
|
+
if (onWorkTiming !== undefined) {
|
|
47912
|
+
startedAt = Date.now();
|
|
47913
|
+
waitMs = startedAt - enqueuedAt;
|
|
47914
|
+
// depth AFTER this unit was shifted off — units still waiting behind it.
|
|
47915
|
+
queueDepthAtStart = this.queue.length;
|
|
47916
|
+
}
|
|
47905
47917
|
workFn(abortController).finally(() => {
|
|
47906
|
-
|
|
47907
|
-
|
|
47918
|
+
try {
|
|
47919
|
+
if (onWorkTiming !== undefined) {
|
|
47920
|
+
onWorkTiming(waitMs, Date.now() - startedAt, queueDepthAtStart);
|
|
47921
|
+
}
|
|
47922
|
+
}
|
|
47923
|
+
catch {
|
|
47924
|
+
// Instrumentation must not prevent the pool from releasing the worker slot.
|
|
47925
|
+
}
|
|
47926
|
+
finally {
|
|
47927
|
+
this.activeWork = this.activeWork.filter((w) => w !== newWork);
|
|
47928
|
+
this.doWork();
|
|
47929
|
+
}
|
|
47908
47930
|
});
|
|
47909
47931
|
}
|
|
47910
47932
|
}
|
|
@@ -48234,6 +48256,25 @@ const DRAFT_SEGMENT = 'DRAFT';
|
|
|
48234
48256
|
*/
|
|
48235
48257
|
const DRAFT_ACTION_RETRY_COUNT_METADATA_KEY = 'retryCount';
|
|
48236
48258
|
class DurableDraftQueue {
|
|
48259
|
+
// Instrumentation is strictly measure-only: a broken sink must never change queue behavior.
|
|
48260
|
+
// Pass the sink into the callback so methods are invoked through their owning object and retain
|
|
48261
|
+
// their receiver (some sinks use `this` to reach their metric registry).
|
|
48262
|
+
emitInstrumentation(emit) {
|
|
48263
|
+
const { instrumentation } = this;
|
|
48264
|
+
if (instrumentation === undefined) {
|
|
48265
|
+
return;
|
|
48266
|
+
}
|
|
48267
|
+
try {
|
|
48268
|
+
emit(instrumentation);
|
|
48269
|
+
}
|
|
48270
|
+
catch {
|
|
48271
|
+
// Telemetry failures must not interrupt draft processing.
|
|
48272
|
+
}
|
|
48273
|
+
}
|
|
48274
|
+
resetDrainCycle() {
|
|
48275
|
+
this.drainCycleStart = undefined;
|
|
48276
|
+
this.drainCycleBatchCount = 0;
|
|
48277
|
+
}
|
|
48237
48278
|
getHandler(id) {
|
|
48238
48279
|
const handler = this.handlers[id];
|
|
48239
48280
|
if (handler === undefined) {
|
|
@@ -48256,9 +48297,19 @@ class DurableDraftQueue {
|
|
|
48256
48297
|
? __nimbus.plugins.JSLoggerPlugin
|
|
48257
48298
|
: undefined;
|
|
48258
48299
|
this.handlers = {};
|
|
48300
|
+
// Drain-wait attribution state. uploadStartTimes stamps when each action first began
|
|
48301
|
+
// uploading (keyed by action id) so actionCompleted/failed can emit its end-to-end drain time,
|
|
48302
|
+
// including automatic retries/backoff. actionQueueWaitEmittedIds ensures those retries do not
|
|
48303
|
+
// report the original enqueue wait more than once. drainCycleStart/BatchCount track one full
|
|
48304
|
+
// idle->empty sweep. All measure-only; when no instrumentation is injected these stay untouched.
|
|
48305
|
+
this.uploadStartTimes = new Map();
|
|
48306
|
+
this.actionQueueWaitEmittedIds = new Set();
|
|
48307
|
+
this.drainCycleBatchCount = 0;
|
|
48259
48308
|
this.draftStore = draftStore;
|
|
48260
|
-
this.workerPool = new AsyncWorkerPool(1);
|
|
48261
48309
|
this.instrumentation = instrumentation;
|
|
48310
|
+
this.workerPool = new AsyncWorkerPool(1, instrumentation?.onWorkerTiming !== undefined
|
|
48311
|
+
? (waitMs, holdMs, queueDepth) => this.emitInstrumentation((sink) => sink.onWorkerTiming?.(waitMs, holdMs, queueDepth))
|
|
48312
|
+
: undefined);
|
|
48262
48313
|
}
|
|
48263
48314
|
addHandler(handler) {
|
|
48264
48315
|
const id = handler.handlerId;
|
|
@@ -48346,6 +48397,9 @@ class DurableDraftQueue {
|
|
|
48346
48397
|
clearTimeout(this.timeoutHandler);
|
|
48347
48398
|
this.timeoutHandler = undefined;
|
|
48348
48399
|
}
|
|
48400
|
+
// A stop/user edit breaks the current sweep. A later restart starts a fresh cycle rather
|
|
48401
|
+
// than attributing stopped time to an offline drain.
|
|
48402
|
+
this.resetDrainCycle();
|
|
48349
48403
|
this.state = DraftQueueState.Stopped;
|
|
48350
48404
|
}
|
|
48351
48405
|
async getQueueActions() {
|
|
@@ -48376,7 +48430,7 @@ class DurableDraftQueue {
|
|
|
48376
48430
|
}
|
|
48377
48431
|
return aTime - bTime;
|
|
48378
48432
|
});
|
|
48379
|
-
|
|
48433
|
+
this.emitInstrumentation((sink) => sink.onGetQueueActions?.(Date.now() - start, sorted.length));
|
|
48380
48434
|
return sorted;
|
|
48381
48435
|
}
|
|
48382
48436
|
async enqueue(handlerId, data, observabilityContext) {
|
|
@@ -48420,6 +48474,13 @@ class DurableDraftQueue {
|
|
|
48420
48474
|
await handler.handleActionCompleted(action, queueOperations, values$2(this.handlers));
|
|
48421
48475
|
this.retryIntervalMilliseconds = 0;
|
|
48422
48476
|
this.uploadingActionId = undefined;
|
|
48477
|
+
// The action finished draining — emit its end-to-end drain time and
|
|
48478
|
+
// count it toward the current drain-cycle batch. Measure-only.
|
|
48479
|
+
this.emitActionDrainDuration(action.id, action.handler);
|
|
48480
|
+
this.actionQueueWaitEmittedIds.delete(action.id);
|
|
48481
|
+
if (this.drainCycleStart !== undefined) {
|
|
48482
|
+
this.drainCycleBatchCount += 1;
|
|
48483
|
+
}
|
|
48423
48484
|
await this.notifyChangedListeners({
|
|
48424
48485
|
type: DraftQueueEventType.ActionCompleted,
|
|
48425
48486
|
action,
|
|
@@ -48431,6 +48492,16 @@ class DurableDraftQueue {
|
|
|
48431
48492
|
},
|
|
48432
48493
|
});
|
|
48433
48494
|
}
|
|
48495
|
+
// Read+clear the action's upload-start stamp and emit its drain duration. Safe to
|
|
48496
|
+
// call when instrumentation is absent or the id was never stamped (no-op). Never emits the id.
|
|
48497
|
+
emitActionDrainDuration(id, handlerId) {
|
|
48498
|
+
const start = this.uploadStartTimes.get(id);
|
|
48499
|
+
if (start === undefined) {
|
|
48500
|
+
return;
|
|
48501
|
+
}
|
|
48502
|
+
this.uploadStartTimes.delete(id);
|
|
48503
|
+
this.emitInstrumentation((sink) => sink.onActionDrainDuration?.(handlerId, Date.now() - start));
|
|
48504
|
+
}
|
|
48434
48505
|
async actionFailed(action, retry, retryDelayInMs, actionDataChanged) {
|
|
48435
48506
|
if (actionDataChanged === true) {
|
|
48436
48507
|
await this.draftStore.writeAction({
|
|
@@ -48440,14 +48511,24 @@ class DurableDraftQueue {
|
|
|
48440
48511
|
}
|
|
48441
48512
|
this.uploadingActionId = undefined;
|
|
48442
48513
|
if (retry && this.state !== DraftQueueState.Stopped) {
|
|
48514
|
+
// A retry means this action is NOT done draining. Keep its first-upload stamp and the
|
|
48515
|
+
// open cycle so the eventual outcome includes network time and retry/backoff.
|
|
48443
48516
|
this.state = DraftQueueState.Waiting;
|
|
48444
48517
|
return retryDelayInMs !== undefined
|
|
48445
48518
|
? this.scheduleRetryWithSpecifiedDelay(retryDelayInMs)
|
|
48446
48519
|
: this.scheduleRetry();
|
|
48447
48520
|
}
|
|
48448
48521
|
else if (isDraftError(action)) {
|
|
48522
|
+
// Terminal failure — this action is done draining. Emit its drain time.
|
|
48523
|
+
this.emitActionDrainDuration(action.id, action.handler);
|
|
48524
|
+
// This was not a clean drain-to-empty. Discard the sweep before user intervention so a
|
|
48525
|
+
// later manual retry/removal cannot inherit its elapsed time or completed batch count.
|
|
48526
|
+
this.resetDrainCycle();
|
|
48449
48527
|
return this.handleServerError(action, action.error);
|
|
48450
48528
|
}
|
|
48529
|
+
// Any other terminal path — ensure no stale start stamp lingers.
|
|
48530
|
+
this.emitActionDrainDuration(action.id, action.handler);
|
|
48531
|
+
this.resetDrainCycle();
|
|
48451
48532
|
}
|
|
48452
48533
|
handle(action) {
|
|
48453
48534
|
const handler = this.getHandler(action.handler);
|
|
@@ -48467,12 +48548,22 @@ class DurableDraftQueue {
|
|
|
48467
48548
|
this.state = DraftQueueState.Started;
|
|
48468
48549
|
}
|
|
48469
48550
|
this.processingAction = undefined;
|
|
48551
|
+
// The queue drained empty — close the drain-cycle window if one was open.
|
|
48552
|
+
if (this.drainCycleStart !== undefined) {
|
|
48553
|
+
const durationMs = Date.now() - this.drainCycleStart;
|
|
48554
|
+
const batchSize = this.drainCycleBatchCount;
|
|
48555
|
+
this.resetDrainCycle();
|
|
48556
|
+
this.emitInstrumentation((sink) => sink.onDrainCycle?.(durationMs, batchSize));
|
|
48557
|
+
}
|
|
48470
48558
|
return ProcessActionResult.NO_ACTION_TO_PROCESS;
|
|
48471
48559
|
}
|
|
48472
48560
|
const { status, id } = action;
|
|
48473
48561
|
if (status === DraftActionStatus.Error) {
|
|
48474
48562
|
this.state = DraftQueueState.Error;
|
|
48475
48563
|
this.processingAction = undefined;
|
|
48564
|
+
// The sweep is blocked on an errored action, not a clean drain-to-empty —
|
|
48565
|
+
// discard the open drain-cycle window so it doesn't inflate a later cycle's duration.
|
|
48566
|
+
this.resetDrainCycle();
|
|
48476
48567
|
await this.notifyChangedListeners({
|
|
48477
48568
|
type: DraftQueueEventType.ActionFailed,
|
|
48478
48569
|
action: action,
|
|
@@ -48490,6 +48581,31 @@ class DurableDraftQueue {
|
|
|
48490
48581
|
if (this.state === DraftQueueState.Waiting) {
|
|
48491
48582
|
this.state = DraftQueueState.Started;
|
|
48492
48583
|
}
|
|
48584
|
+
// The action is now leaving the queue to upload. Emit head-of-line FIFO wait
|
|
48585
|
+
// (enqueue -> upload start), stamp its drain-start for end-to-end timing on completion, and
|
|
48586
|
+
// open a drain-cycle window if the queue was previously idle. All measure-only.
|
|
48587
|
+
const { instrumentation } = this;
|
|
48588
|
+
if (instrumentation !== undefined) {
|
|
48589
|
+
const now = Date.now();
|
|
48590
|
+
const { timestamp } = action;
|
|
48591
|
+
if (instrumentation.onActionQueueWait !== undefined &&
|
|
48592
|
+
!this.actionQueueWaitEmittedIds.has(id) &&
|
|
48593
|
+
typeof timestamp === 'number' &&
|
|
48594
|
+
timestamp > 0) {
|
|
48595
|
+
// Mark before invoking the sink: even a throwing callback must not make a retry
|
|
48596
|
+
// report the same action's enqueue wait again.
|
|
48597
|
+
this.actionQueueWaitEmittedIds.add(id);
|
|
48598
|
+
this.emitInstrumentation((sink) => sink.onActionQueueWait?.(action.handler, now - timestamp));
|
|
48599
|
+
}
|
|
48600
|
+
if (instrumentation.onActionDrainDuration !== undefined &&
|
|
48601
|
+
!this.uploadStartTimes.has(id)) {
|
|
48602
|
+
this.uploadStartTimes.set(id, now);
|
|
48603
|
+
}
|
|
48604
|
+
if (instrumentation.onDrainCycle !== undefined && this.drainCycleStart === undefined) {
|
|
48605
|
+
this.drainCycleStart = now;
|
|
48606
|
+
this.drainCycleBatchCount = 0;
|
|
48607
|
+
}
|
|
48608
|
+
}
|
|
48493
48609
|
await this.notifyChangedListeners({
|
|
48494
48610
|
type: DraftQueueEventType.ActionUploading,
|
|
48495
48611
|
action: { ...action, status: DraftActionStatus.Uploading },
|
|
@@ -48527,7 +48643,7 @@ class DurableDraftQueue {
|
|
|
48527
48643
|
results.push(listener(event));
|
|
48528
48644
|
}
|
|
48529
48645
|
// W-23515682: listener fan-out per event type. Measure-only.
|
|
48530
|
-
this.
|
|
48646
|
+
this.emitInstrumentation((sink) => sink.onNotifyChangedListeners?.(event.type, draftQueueLen));
|
|
48531
48647
|
await Promise.all(results);
|
|
48532
48648
|
}
|
|
48533
48649
|
/**
|
|
@@ -48556,9 +48672,17 @@ class DurableDraftQueue {
|
|
|
48556
48672
|
const shouldDeleteRelated = handler.shouldDeleteActionByTagOnRemoval(action);
|
|
48557
48673
|
if (shouldDeleteRelated) {
|
|
48558
48674
|
await this.draftStore.deleteByTag(action.tag);
|
|
48675
|
+
queue.forEach((queuedAction) => {
|
|
48676
|
+
if (queuedAction.tag === action.tag) {
|
|
48677
|
+
this.uploadStartTimes.delete(queuedAction.id);
|
|
48678
|
+
this.actionQueueWaitEmittedIds.delete(queuedAction.id);
|
|
48679
|
+
}
|
|
48680
|
+
});
|
|
48559
48681
|
}
|
|
48560
48682
|
else {
|
|
48561
48683
|
await this.draftStore.deleteDraft(action.id);
|
|
48684
|
+
this.uploadStartTimes.delete(action.id);
|
|
48685
|
+
this.actionQueueWaitEmittedIds.delete(action.id);
|
|
48562
48686
|
}
|
|
48563
48687
|
await handler.handleActionRemoved(action, queue.filter((x) => x.id !== actionId));
|
|
48564
48688
|
await this.notifyChangedListeners({
|
|
@@ -80190,6 +80314,20 @@ const DRAFT_QUEUE_GET_QUEUE_ACTIONS_DURATION = 'draft-queue-get-queue-actions-du
|
|
|
80190
80314
|
const DRAFT_QUEUE_NOTIFY_CHANGED_LISTENERS_COUNT = 'draft-queue-notify-changed-listeners-count';
|
|
80191
80315
|
const DRAFT_QUEUE_DURABLE_OP_COUNT = 'draft-queue-durable-op-count';
|
|
80192
80316
|
const DRAFT_QUEUE_DURABLE_OP_DURATION = 'draft-queue-durable-op-duration';
|
|
80317
|
+
// Drain-wait attribution telemetry: the wall-clock spine of where an offline drain actually spends
|
|
80318
|
+
// WAITING. Operational counts/durations/depths only — no draft content, field values, or server
|
|
80319
|
+
// record ids (matching the existing draft-queue privacy stance).
|
|
80320
|
+
const DRAFT_QUEUE_WORKER_WAIT_DURATION = 'draft-queue-worker-wait-duration';
|
|
80321
|
+
const DRAFT_QUEUE_WORKER_HOLD_DURATION = 'draft-queue-worker-hold-duration';
|
|
80322
|
+
const DRAFT_QUEUE_ACTION_QUEUE_WAIT_DURATION = 'draft-queue-action-queue-wait-duration';
|
|
80323
|
+
const DRAFT_QUEUE_ACTION_DRAIN_DURATION = 'draft-queue-action-drain-duration';
|
|
80324
|
+
const DRAFT_QUEUE_DRAIN_CYCLE_DURATION = 'draft-queue-drain-cycle-duration';
|
|
80325
|
+
const DRAFT_QUEUE_DRAIN_CYCLE_BATCH_SIZE = 'draft-queue-drain-cycle-batch-size';
|
|
80326
|
+
const DRAFT_QUEUE_ACTION_NETWORK_DURATION = 'draft-queue-action-network-duration';
|
|
80327
|
+
const DRAFT_QUEUE_ACTION_RETRY_ATTEMPT = 'draft-queue-action-retry-attempt';
|
|
80328
|
+
const DRAFT_QUEUE_SIDEEFFECT_OP_DURATION = 'draft-queue-sideeffect-op-duration';
|
|
80329
|
+
const DRAFT_QUEUE_SIDEEFFECT_MAP_SIZE = 'draft-queue-sideeffect-map-size';
|
|
80330
|
+
const DRAFT_QUEUE_SIDEEFFECT_REGION_DURATION = 'draft-queue-sideeffect-region-duration';
|
|
80193
80331
|
/** Content Document */
|
|
80194
80332
|
const CREATE_CONTENT_DOCUMENT_AND_VERSION_TOTAL_SYNTHESIZE_CALLS = 'content-document-version-total-synthesize-calls';
|
|
80195
80333
|
const CREATE_CONTENT_DOCUMENT_AND_VERSION_DRAFT_SYNTHESIZE_ERROR = 'create-content-document-version-draft-synthesize-error';
|
|
@@ -80309,6 +80447,134 @@ function reportDraftQueueDurableOperation(op, segment, durationMs) {
|
|
|
80309
80447
|
});
|
|
80310
80448
|
ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_DURABLE_OP_DURATION, durationMs);
|
|
80311
80449
|
}
|
|
80450
|
+
const DRAIN_DURATION_BUCKETS = [50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000];
|
|
80451
|
+
const WORKER_DEPTH_BUCKETS = [1, 2, 5, 10, 20, 50, 100, 250, 500];
|
|
80452
|
+
// Handler ids are extensible through DraftManager, so only retain the fixed mobile handlers in
|
|
80453
|
+
// metric tags. Everything else shares one bounded bucket rather than creating an unbounded series.
|
|
80454
|
+
function draftQueueHandlerTagOf(handlerId) {
|
|
80455
|
+
switch (handlerId) {
|
|
80456
|
+
case 'LDS_ACTION_HANDLER':
|
|
80457
|
+
case 'ContentDocumentCompositeRepresentationActionHandler':
|
|
80458
|
+
case 'QUICK_ACTION_HANDLER':
|
|
80459
|
+
case 'UPDATE_RECORD_QUICK_ACTION_HANDLER':
|
|
80460
|
+
return handlerId;
|
|
80461
|
+
default:
|
|
80462
|
+
return 'custom';
|
|
80463
|
+
}
|
|
80464
|
+
}
|
|
80465
|
+
// Coarse, bounded-cardinality label for the pool depth a unit saw when it began, so the
|
|
80466
|
+
// wait/hold durations can be sliced BY backlog size without emitting depth as a free integer tag.
|
|
80467
|
+
function workerDepthBucketOf(queueDepth) {
|
|
80468
|
+
if (queueDepth <= 0) {
|
|
80469
|
+
return '0';
|
|
80470
|
+
}
|
|
80471
|
+
if (queueDepth <= 2) {
|
|
80472
|
+
return '1-2';
|
|
80473
|
+
}
|
|
80474
|
+
if (queueDepth <= 10) {
|
|
80475
|
+
return '3-10';
|
|
80476
|
+
}
|
|
80477
|
+
if (queueDepth <= 50) {
|
|
80478
|
+
return '11-50';
|
|
80479
|
+
}
|
|
80480
|
+
if (queueDepth <= 250) {
|
|
80481
|
+
return '51-250';
|
|
80482
|
+
}
|
|
80483
|
+
return '250+';
|
|
80484
|
+
}
|
|
80485
|
+
/**
|
|
80486
|
+
* Per-unit lifecycle split for the AsyncWorkerPool(1) serial queue, emitted once when a unit settles
|
|
80487
|
+
* so the two halves are a JOINED event (rather than shipping wait and depth as unrelated aggregates).
|
|
80488
|
+
* Isolates WHERE the time goes:
|
|
80489
|
+
* waitMs — enqueue -> work began (waiting on the serial queue before any logic runs).
|
|
80490
|
+
* holdMs — work began -> work settled (the critical section's own O(N) service time).
|
|
80491
|
+
* Both are additionally tagged by a depthBucket so "does per-unit cost scale with backlog depth"
|
|
80492
|
+
* (the O(N^2) signature) survives aggregation. queueDepth itself is also tracked. Operational only.
|
|
80493
|
+
*/
|
|
80494
|
+
function reportDraftQueueWorkerTiming(waitMs, holdMs, queueDepth) {
|
|
80495
|
+
const depthBucket = workerDepthBucketOf(queueDepth);
|
|
80496
|
+
ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_WORKER_WAIT_DURATION, waitMs, undefined, {
|
|
80497
|
+
depthBucket,
|
|
80498
|
+
});
|
|
80499
|
+
ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_WORKER_HOLD_DURATION, holdMs, undefined, {
|
|
80500
|
+
depthBucket,
|
|
80501
|
+
});
|
|
80502
|
+
ldsMobileInstrumentation$2.bucketValue(DRAFT_QUEUE_WORKER_WAIT_DURATION + '-depth', queueDepth, WORKER_DEPTH_BUCKETS);
|
|
80503
|
+
}
|
|
80504
|
+
/**
|
|
80505
|
+
* Head-of-line FIFO wait for one draft (enqueue -> upload start), bucketed so the
|
|
80506
|
+
* "does wait grow with backlog depth" (O(N^2)) signal is visible per handler type. Custom handler
|
|
80507
|
+
* ids are normalized to one bounded tag so caller-defined values never become metric dimensions.
|
|
80508
|
+
*/
|
|
80509
|
+
function reportDraftQueueActionQueueWait(handlerId, waitMs) {
|
|
80510
|
+
ldsMobileInstrumentation$2.bucketValue(DRAFT_QUEUE_ACTION_QUEUE_WAIT_DURATION, waitMs, DRAIN_DURATION_BUCKETS, undefined, { handlerId: draftQueueHandlerTagOf(handlerId) });
|
|
80511
|
+
}
|
|
80512
|
+
/**
|
|
80513
|
+
* End-to-end drain time for one action (first upload attempt -> completed/failed), including
|
|
80514
|
+
* retry/backoff. Compare it with network-duration and retry-attempt signals to understand the
|
|
80515
|
+
* non-request portion of the drain interval.
|
|
80516
|
+
*/
|
|
80517
|
+
function reportDraftQueueActionDrainDuration(handlerId, durationMs) {
|
|
80518
|
+
ldsMobileInstrumentation$2.bucketValue(DRAFT_QUEUE_ACTION_DRAIN_DURATION, durationMs, DRAIN_DURATION_BUCKETS, undefined, { handlerId: draftQueueHandlerTagOf(handlerId) });
|
|
80519
|
+
}
|
|
80520
|
+
/**
|
|
80521
|
+
* One full drain sweep — first action starts uploading -> queue empties. Both raw values share a
|
|
80522
|
+
* bounded batch-size cohort so aggregate duration remains sliceable by the number of actions drained.
|
|
80523
|
+
*/
|
|
80524
|
+
function reportDraftQueueDrainCycle(durationMs, batchSize) {
|
|
80525
|
+
const batchSizeBucket = workerDepthBucketOf(batchSize);
|
|
80526
|
+
ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_DRAIN_CYCLE_DURATION, durationMs, undefined, {
|
|
80527
|
+
batchSizeBucket,
|
|
80528
|
+
});
|
|
80529
|
+
ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_DRAIN_CYCLE_BATCH_SIZE, batchSize, undefined, {
|
|
80530
|
+
batchSizeBucket,
|
|
80531
|
+
});
|
|
80532
|
+
}
|
|
80533
|
+
/**
|
|
80534
|
+
* Server round-trip duration for one upload. Isolates slow-network from CPU-bound
|
|
80535
|
+
* quadratic work — today only a THROWN error is captured, so a slow-but-successful (or slow 4xx/5xx)
|
|
80536
|
+
* response is invisible. Tags: handler type, HTTP method, and a bucketed status class ('2xx'/'4xx'/
|
|
80537
|
+
* '5xx'/'0'). No URL, body, or record id.
|
|
80538
|
+
*/
|
|
80539
|
+
function reportDraftQueueActionNetworkDuration(handlerId, method, statusBucket, durationMs) {
|
|
80540
|
+
ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_ACTION_NETWORK_DURATION, durationMs, undefined, {
|
|
80541
|
+
handlerId: draftQueueHandlerTagOf(handlerId),
|
|
80542
|
+
method,
|
|
80543
|
+
statusBucket,
|
|
80544
|
+
});
|
|
80545
|
+
}
|
|
80546
|
+
/**
|
|
80547
|
+
* Retry event count. Repeated events identify actions cycling through the drain even when the retry
|
|
80548
|
+
* cap is disabled and an exact attempt number is not persisted. cappedOut marks cap exhaustion.
|
|
80549
|
+
*/
|
|
80550
|
+
function reportDraftQueueActionRetryAttempt(handlerId, cappedOut) {
|
|
80551
|
+
ldsMobileInstrumentation$2.incrementCounter(DRAFT_QUEUE_ACTION_RETRY_ATTEMPT, 1, undefined, {
|
|
80552
|
+
handlerId: draftQueueHandlerTagOf(handlerId),
|
|
80553
|
+
cappedOut: String(cappedOut),
|
|
80554
|
+
});
|
|
80555
|
+
}
|
|
80556
|
+
/**
|
|
80557
|
+
* SideEffectStore operation duration + map size. The incremental map sped up the by-KEY lookup but
|
|
80558
|
+
* getEffectsByTag still linearly scans all effects — this measures whether that residual scan is
|
|
80559
|
+
* silently quadratic during churn. op is 'add'|'remove'|'getByTag'; incremental flags the fast path;
|
|
80560
|
+
* mapSize is an integer effect count. No record keys, field names, or field values.
|
|
80561
|
+
*/
|
|
80562
|
+
function reportDraftQueueSideEffectOp(op, incremental, durationMs, mapSize) {
|
|
80563
|
+
ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_SIDEEFFECT_OP_DURATION, durationMs, undefined, {
|
|
80564
|
+
op,
|
|
80565
|
+
incremental: String(incremental),
|
|
80566
|
+
});
|
|
80567
|
+
ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_SIDEEFFECT_MAP_SIZE, mapSize, undefined, {
|
|
80568
|
+
op,
|
|
80569
|
+
});
|
|
80570
|
+
}
|
|
80571
|
+
/**
|
|
80572
|
+
* Broad side-effect lifecycle duration. The fixed region and lifecycle tags identify which part of
|
|
80573
|
+
* serialized draft processing is slow without emitting record keys, action ids, or field data.
|
|
80574
|
+
*/
|
|
80575
|
+
function reportDraftQueueSideEffectRegionDuration(region, lifecycle, durationMs) {
|
|
80576
|
+
ldsMobileInstrumentation$2.trackValue(DRAFT_QUEUE_SIDEEFFECT_REGION_DURATION, durationMs, undefined, { region, lifecycle });
|
|
80577
|
+
}
|
|
80312
80578
|
/**
|
|
80313
80579
|
* Reports an exception thrown while uploading a draft action. The thrown error is
|
|
80314
80580
|
* otherwise swallowed by the upload handler's retry path, leaving the failure
|
|
@@ -80516,8 +80782,16 @@ class AbstractResourceRequestActionHandler {
|
|
|
80516
80782
|
const dispatchOptions = action.observabilityContext !== undefined
|
|
80517
80783
|
? { requestCorrelator: { observabilityContext: action.observabilityContext } }
|
|
80518
80784
|
: {};
|
|
80785
|
+
const method = request?.method?.toUpperCase() ?? 'UNKNOWN';
|
|
80786
|
+
const networkStart = Date.now();
|
|
80787
|
+
let responseReceived = false;
|
|
80519
80788
|
try {
|
|
80520
80789
|
const response = await this.networkAdapter(request, dispatchOptions);
|
|
80790
|
+
responseReceived = true;
|
|
80791
|
+
// Time the round-trip regardless of outcome (ok, retryable HTTP error, or non-ok
|
|
80792
|
+
// terminal error) — today only a THROWN error is visible; a slow-but-successful or
|
|
80793
|
+
// slow 4xx/5xx response is not.
|
|
80794
|
+
this.reportNetworkDuration(method, statusBucketOf(response.status), networkStart);
|
|
80521
80795
|
if (response.ok) {
|
|
80522
80796
|
await actionCompleted({
|
|
80523
80797
|
...action,
|
|
@@ -80603,19 +80877,32 @@ class AbstractResourceRequestActionHandler {
|
|
|
80603
80877
|
actionDataChanged = true;
|
|
80604
80878
|
}
|
|
80605
80879
|
else if (!this.isServerGeneratedError(response.body)) {
|
|
80606
|
-
// Killswitch CLOSED (default) —
|
|
80880
|
+
// Killswitch CLOSED (default) — an edge/gateway-injected 400 (no
|
|
80607
80881
|
// recognizable server error body) is retried with the SAME key, so the
|
|
80608
80882
|
// server can dedupe a write that may already have committed at the origin.
|
|
80609
|
-
//
|
|
80610
|
-
//
|
|
80611
|
-
// retrying it unchanged would only be rejected again, so the draft is
|
|
80612
|
-
// errored for the user to fix or discard — the same terminal outcome as
|
|
80613
|
-
// before this fix, minus the duplicating key rotation. The key is only ever
|
|
80614
|
-
// rotated by the recognized idempotency error codes above, by a backdating
|
|
80615
|
-
// collision (new body), or when the user edits an errored draft
|
|
80616
|
-
// (mergeActions/handleReplaceAction).
|
|
80883
|
+
// Rotating here is what duplicated the WOLI records (@W-23445154), so the
|
|
80884
|
+
// key is preserved.
|
|
80617
80885
|
shouldRetry = true;
|
|
80618
80886
|
}
|
|
80887
|
+
else {
|
|
80888
|
+
// Killswitch CLOSED — a REAL, server-generated 400 (UiApiError[] or an
|
|
80889
|
+
// enhancedErrorType body, e.g. a field-validation failure). The origin
|
|
80890
|
+
// understood the request and rejected it, so it definitively did NOT
|
|
80891
|
+
// commit — there is no already-written record to duplicate, which is what
|
|
80892
|
+
// makes rotation safe here (unlike the edge-injected case above).
|
|
80893
|
+
//
|
|
80894
|
+
// We still do NOT retry (shouldRetry stays false): the request as-is would
|
|
80895
|
+
// just be rejected again and wedge the queue. The draft errors for the user
|
|
80896
|
+
// to fix or discard. But we DO rotate the key: the server's idempotency
|
|
80897
|
+
// layer caches the 400 against the key, so if the same request is later
|
|
80898
|
+
// resubmitted unchanged after the server-side validation rule changes to
|
|
80899
|
+
// allow it, reusing the key would replay that cached 400 and the now-valid
|
|
80900
|
+
// write would never be applied. Rotating gives the retried request a fresh
|
|
80901
|
+
// key so the origin re-evaluates it. @W-23445154
|
|
80902
|
+
updatedAction.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
80903
|
+
reportIdempotencyKeyRotated(updatedAction.targetId, 'server-validation-400');
|
|
80904
|
+
actionDataChanged = true;
|
|
80905
|
+
}
|
|
80619
80906
|
}
|
|
80620
80907
|
if (response.status === HttpStatusCode$2.BadRequest &&
|
|
80621
80908
|
this.isBackdatingError(response.body, action)) {
|
|
@@ -80660,6 +80947,11 @@ class AbstractResourceRequestActionHandler {
|
|
|
80660
80947
|
const error = normalizeError$1(e);
|
|
80661
80948
|
const attempt = this.getUploadRetryCount(action) + 1;
|
|
80662
80949
|
reportDraftActionUploadError(error, this.handlerId, attempt);
|
|
80950
|
+
// A thrown network call still has a round-trip duration worth surfacing — a slow
|
|
80951
|
+
// FAILING call is otherwise as invisible as a slow successful one.
|
|
80952
|
+
if (!responseReceived) {
|
|
80953
|
+
this.reportNetworkDuration(method, 'error', networkStart);
|
|
80954
|
+
}
|
|
80663
80955
|
await this.retryOrCap(action, error, actionErrored);
|
|
80664
80956
|
return ProcessActionResult.NETWORK_ERROR;
|
|
80665
80957
|
}
|
|
@@ -80695,12 +80987,17 @@ class AbstractResourceRequestActionHandler {
|
|
|
80695
80987
|
*/
|
|
80696
80988
|
async retryOrCap(action, lastFailure, actionErrored, retryDelayInMs, actionDataChanged) {
|
|
80697
80989
|
if (!draftQueueMaxRetryAttemptsGate.isOpen({ fallback: false })) {
|
|
80698
|
-
// Gate closed: preserve the pre-existing unbounded-retry behavior exactly.
|
|
80990
|
+
// Gate closed: preserve the pre-existing unbounded-retry behavior exactly. The
|
|
80991
|
+
// metric is still emitted (measure-only) so retry attempts are visible even with
|
|
80992
|
+
// the cap gate closed — cappedOut is always false here since nothing ever caps
|
|
80993
|
+
// out on this path.
|
|
80994
|
+
this.reportRetryAttempt(false);
|
|
80699
80995
|
await actionErrored(action, true, retryDelayInMs, actionDataChanged);
|
|
80700
80996
|
return;
|
|
80701
80997
|
}
|
|
80702
80998
|
const attempt = this.getUploadRetryCount(action) + 1;
|
|
80703
80999
|
if (attempt >= MAX_RETRY_ATTEMPTS) {
|
|
81000
|
+
this.reportRetryAttempt(true);
|
|
80704
81001
|
await actionErrored({
|
|
80705
81002
|
...action,
|
|
80706
81003
|
error: buildMaxRetryError(lastFailure),
|
|
@@ -80708,6 +81005,7 @@ class AbstractResourceRequestActionHandler {
|
|
|
80708
81005
|
}, false);
|
|
80709
81006
|
return;
|
|
80710
81007
|
}
|
|
81008
|
+
this.reportRetryAttempt(false);
|
|
80711
81009
|
// Persist the incremented attempt count so it survives restarts. Forcing
|
|
80712
81010
|
// actionDataChanged ensures the queue writes the updated metadata before the
|
|
80713
81011
|
// action is rescheduled for retry.
|
|
@@ -80721,12 +81019,12 @@ class AbstractResourceRequestActionHandler {
|
|
|
80721
81019
|
await actionErrored(retryingAction, true, retryDelayInMs, true);
|
|
80722
81020
|
}
|
|
80723
81021
|
async handleActionEnqueued(action) {
|
|
80724
|
-
const impactedKeys = await this.recordService.setSideEffectsForActions([action]);
|
|
80725
|
-
await this.recordService.reapplyRecordSideEffects(impactedKeys);
|
|
81022
|
+
const impactedKeys = await this.recordService.setSideEffectsForActions([action], 'enqueued');
|
|
81023
|
+
await this.recordService.reapplyRecordSideEffects(impactedKeys, 'enqueued');
|
|
80726
81024
|
}
|
|
80727
81025
|
async handleActionRemoved(action) {
|
|
80728
81026
|
const impactedKeys = await this.recordService.removeSideEffectsForAction(action);
|
|
80729
|
-
await this.recordService.reapplyRecordSideEffects(impactedKeys);
|
|
81027
|
+
await this.recordService.reapplyRecordSideEffects(impactedKeys, 'removed');
|
|
80730
81028
|
}
|
|
80731
81029
|
async handleActionReplaced(target, source) {
|
|
80732
81030
|
// remove both the siden effects from the actions being merged before we
|
|
@@ -80734,10 +81032,10 @@ class AbstractResourceRequestActionHandler {
|
|
|
80734
81032
|
const impactedKeysFromRemove = await this.recordService.removeSideEffectsForAction(source);
|
|
80735
81033
|
const removedSideEffectFromSet = await this.recordService.removeSideEffectsForAction(target);
|
|
80736
81034
|
// revert the values in the DEFAULT store back to what it was before the removed side effects
|
|
80737
|
-
await this.recordService.reapplyRecordSideEffects(new Set([...impactedKeysFromRemove, ...removedSideEffectFromSet]));
|
|
81035
|
+
await this.recordService.reapplyRecordSideEffects(new Set([...impactedKeysFromRemove, ...removedSideEffectFromSet]), 'replaced-remove');
|
|
80738
81036
|
// set the new side effect values for the merged action
|
|
80739
|
-
const impactedKeysFromSet = await this.recordService.setSideEffectsForActions([target]);
|
|
80740
|
-
await this.recordService.reapplyRecordSideEffects(new Set([...impactedKeysFromSet]));
|
|
81037
|
+
const impactedKeysFromSet = await this.recordService.setSideEffectsForActions([target], 'replaced-set');
|
|
81038
|
+
await this.recordService.reapplyRecordSideEffects(new Set([...impactedKeysFromSet]), 'replaced-set');
|
|
80741
81039
|
}
|
|
80742
81040
|
async handleActionCompleted(action, queueOperations) {
|
|
80743
81041
|
const { data: request, tag } = action;
|
|
@@ -80771,8 +81069,8 @@ class AbstractResourceRequestActionHandler {
|
|
|
80771
81069
|
actionsNeedingReplay.push(qo.action);
|
|
80772
81070
|
}
|
|
80773
81071
|
});
|
|
80774
|
-
const impactedKeys = await this.recordService.setSideEffectsForActions(actionsNeedingReplay);
|
|
80775
|
-
await this.recordService.reapplyRecordSideEffects(impactedKeys);
|
|
81072
|
+
const impactedKeys = await this.recordService.setSideEffectsForActions(actionsNeedingReplay, 'completed-replay');
|
|
81073
|
+
await this.recordService.reapplyRecordSideEffects(impactedKeys, 'completed-replay');
|
|
80776
81074
|
}
|
|
80777
81075
|
getQueueOperationsForCompletingDrafts(queue, action) {
|
|
80778
81076
|
const queueOperations = [];
|
|
@@ -81098,6 +81396,39 @@ class AbstractResourceRequestActionHandler {
|
|
|
81098
81396
|
return new StoreKeyMap();
|
|
81099
81397
|
});
|
|
81100
81398
|
}
|
|
81399
|
+
reportNetworkDuration(method, statusBucket, startedAt) {
|
|
81400
|
+
try {
|
|
81401
|
+
reportDraftQueueActionNetworkDuration(this.handlerId, method, statusBucket, Date.now() - startedAt);
|
|
81402
|
+
}
|
|
81403
|
+
catch {
|
|
81404
|
+
// Telemetry must not change upload processing behavior.
|
|
81405
|
+
}
|
|
81406
|
+
}
|
|
81407
|
+
reportRetryAttempt(cappedOut) {
|
|
81408
|
+
try {
|
|
81409
|
+
reportDraftQueueActionRetryAttempt(this.handlerId, cappedOut);
|
|
81410
|
+
}
|
|
81411
|
+
catch {
|
|
81412
|
+
// Telemetry must not change retry or cap behavior.
|
|
81413
|
+
}
|
|
81414
|
+
}
|
|
81415
|
+
}
|
|
81416
|
+
// Classifies an HTTP status into a small, greppable tag set for the network-duration metric —
|
|
81417
|
+
// never the raw status, to keep the metric's tag cardinality bounded.
|
|
81418
|
+
function statusBucketOf(status) {
|
|
81419
|
+
if (status === 0) {
|
|
81420
|
+
return '0';
|
|
81421
|
+
}
|
|
81422
|
+
if (status >= 200 && status <= 299) {
|
|
81423
|
+
return '2xx';
|
|
81424
|
+
}
|
|
81425
|
+
if (status >= 400 && status <= 499) {
|
|
81426
|
+
return '4xx';
|
|
81427
|
+
}
|
|
81428
|
+
if (status >= 500) {
|
|
81429
|
+
return '5xx';
|
|
81430
|
+
}
|
|
81431
|
+
return 'other';
|
|
81101
81432
|
}
|
|
81102
81433
|
function actionsForTag(tag, queue) {
|
|
81103
81434
|
return queue.filter((action) => action.tag === tag);
|
|
@@ -81461,10 +81792,16 @@ class UiApiDraftRecordService {
|
|
|
81461
81792
|
}
|
|
81462
81793
|
// calculates the side effects for a given set of actions
|
|
81463
81794
|
// and returns a set of impacted record keys
|
|
81464
|
-
async setSideEffectsForActions(changedActions) {
|
|
81465
|
-
const
|
|
81466
|
-
|
|
81467
|
-
|
|
81795
|
+
async setSideEffectsForActions(changedActions, lifecycle) {
|
|
81796
|
+
const startedAt = Date.now();
|
|
81797
|
+
try {
|
|
81798
|
+
const operations = await this.recalculateForChangedActions(changedActions);
|
|
81799
|
+
await this.sideEffectStore.addEffects(operations);
|
|
81800
|
+
return new Set(operations.map((o) => o.key));
|
|
81801
|
+
}
|
|
81802
|
+
finally {
|
|
81803
|
+
this.reportSideEffectRegionDuration('set', lifecycle, startedAt);
|
|
81804
|
+
}
|
|
81468
81805
|
}
|
|
81469
81806
|
// removes the associated side effects for a given action and returns
|
|
81470
81807
|
// a set of impacted record keys
|
|
@@ -81475,31 +81812,45 @@ class UiApiDraftRecordService {
|
|
|
81475
81812
|
}
|
|
81476
81813
|
// reads a set of records out of the durable store, re-applies current side
|
|
81477
81814
|
// effects and persists the changed record
|
|
81478
|
-
async reapplyRecordSideEffects(recordKeys) {
|
|
81479
|
-
const
|
|
81480
|
-
|
|
81481
|
-
|
|
81482
|
-
|
|
81483
|
-
|
|
81484
|
-
|
|
81485
|
-
|
|
81486
|
-
|
|
81487
|
-
|
|
81488
|
-
|
|
81489
|
-
|
|
81490
|
-
|
|
81491
|
-
|
|
81492
|
-
|
|
81493
|
-
|
|
81494
|
-
if (recordEntry
|
|
81495
|
-
|
|
81496
|
-
metadata
|
|
81815
|
+
async reapplyRecordSideEffects(recordKeys, lifecycle) {
|
|
81816
|
+
const startedAt = Date.now();
|
|
81817
|
+
try {
|
|
81818
|
+
const luvio = this.getLuvio();
|
|
81819
|
+
const keys = Array.from(recordKeys);
|
|
81820
|
+
const recordEntries = await this.durableRecordStore.getRecordsWithMetadata(keys);
|
|
81821
|
+
keys.forEach((key) => {
|
|
81822
|
+
let record;
|
|
81823
|
+
const metadata = {
|
|
81824
|
+
namespace: 'UiApi',
|
|
81825
|
+
representationName: 'RecordRepresentation',
|
|
81826
|
+
version: VERSION$1v$1,
|
|
81827
|
+
ingestionTimestamp: Date.now(),
|
|
81828
|
+
ttl: Number.MAX_SAFE_INTEGER,
|
|
81829
|
+
};
|
|
81830
|
+
const recordEntry = recordEntries.get(key);
|
|
81831
|
+
if (recordEntry) {
|
|
81832
|
+
record = recordEntry.record;
|
|
81833
|
+
if (recordEntry.metadata) {
|
|
81834
|
+
metadata.ingestionTimestamp = recordEntry.metadata.ingestionTimestamp;
|
|
81835
|
+
metadata.ttl = TTL$z;
|
|
81836
|
+
}
|
|
81497
81837
|
}
|
|
81498
|
-
|
|
81499
|
-
|
|
81500
|
-
|
|
81501
|
-
|
|
81502
|
-
|
|
81838
|
+
luvio.storePublish(key, record);
|
|
81839
|
+
luvio.publishStoreMetadata(key, metadata);
|
|
81840
|
+
});
|
|
81841
|
+
await luvio.storeBroadcast();
|
|
81842
|
+
}
|
|
81843
|
+
finally {
|
|
81844
|
+
this.reportSideEffectRegionDuration('reapply', lifecycle, startedAt);
|
|
81845
|
+
}
|
|
81846
|
+
}
|
|
81847
|
+
reportSideEffectRegionDuration(region, lifecycle, startedAt) {
|
|
81848
|
+
try {
|
|
81849
|
+
reportDraftQueueSideEffectRegionDuration(region, lifecycle, Date.now() - startedAt);
|
|
81850
|
+
}
|
|
81851
|
+
catch {
|
|
81852
|
+
// Telemetry must not change draft processing behavior.
|
|
81853
|
+
}
|
|
81503
81854
|
}
|
|
81504
81855
|
async synthesizeId(apiName) {
|
|
81505
81856
|
const objectInfo = await this.objectInfoService.getObjectInfo(apiName);
|
|
@@ -81735,7 +82086,7 @@ class UpdateRecordQuickActionExecutionRepresentationHandler extends AbstractQuic
|
|
|
81735
82086
|
// since we don't ingest a record from the response, we need to manually trigger a re-apply here to ensure we
|
|
81736
82087
|
// re-ingest the record without effects applied
|
|
81737
82088
|
await this.recordService.removeSideEffectsForAction(action);
|
|
81738
|
-
await this.recordService.reapplyRecordSideEffects(new Set([action.tag]));
|
|
82089
|
+
await this.recordService.reapplyRecordSideEffects(new Set([action.tag]), 'quick-action-completed');
|
|
81739
82090
|
// notify luvio that record is now changed and needs to be refreshed
|
|
81740
82091
|
await luvio.notifyStoreUpdateAvailable([action.tag]);
|
|
81741
82092
|
}
|
|
@@ -90415,6 +90766,18 @@ const draftQueueInstrumentation = {
|
|
|
90415
90766
|
onDurableOperation(op, segment, durationMs) {
|
|
90416
90767
|
reportDraftQueueDurableOperation(op, segment, durationMs);
|
|
90417
90768
|
},
|
|
90769
|
+
onWorkerTiming(waitMs, holdMs, queueDepth) {
|
|
90770
|
+
reportDraftQueueWorkerTiming(waitMs, holdMs, queueDepth);
|
|
90771
|
+
},
|
|
90772
|
+
onActionQueueWait(handlerId, waitMs) {
|
|
90773
|
+
reportDraftQueueActionQueueWait(handlerId, waitMs);
|
|
90774
|
+
},
|
|
90775
|
+
onActionDrainDuration(handlerId, durationMs) {
|
|
90776
|
+
reportDraftQueueActionDrainDuration(handlerId, durationMs);
|
|
90777
|
+
},
|
|
90778
|
+
onDrainCycle(durationMs, batchSize) {
|
|
90779
|
+
reportDraftQueueDrainCycle(durationMs, batchSize);
|
|
90780
|
+
},
|
|
90418
90781
|
};
|
|
90419
90782
|
|
|
90420
90783
|
// so eslint doesn't complain about nimbus
|
|
@@ -93757,6 +94120,9 @@ class SideEffectStore {
|
|
|
93757
94120
|
this.initialize();
|
|
93758
94121
|
}
|
|
93759
94122
|
async addEffects(effects) {
|
|
94123
|
+
// Wall-clock across the whole method (including the durable write), matching
|
|
94124
|
+
// how the equivalent removeEffects/getEffectsByTag windows are measured.
|
|
94125
|
+
const startTime = Date.now();
|
|
93760
94126
|
const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
|
|
93761
94127
|
const durableEntries = {};
|
|
93762
94128
|
effects.forEach((effect) => {
|
|
@@ -93771,8 +94137,11 @@ class SideEffectStore {
|
|
|
93771
94137
|
this.regenerateKeyToEffectMap();
|
|
93772
94138
|
}
|
|
93773
94139
|
await this.durableStore.setEntries(durableEntries, SIDE_EFFECT_SEGMENT);
|
|
94140
|
+
this.reportOperation('add', incremental, startTime);
|
|
93774
94141
|
}
|
|
93775
94142
|
async removeEffects(effects) {
|
|
94143
|
+
// See addEffects — same wall-clock-across-the-method measurement.
|
|
94144
|
+
const startTime = Date.now();
|
|
93776
94145
|
const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
|
|
93777
94146
|
const effectKeys = effects.map((e) => e.uniqueId);
|
|
93778
94147
|
effects.forEach((effect) => {
|
|
@@ -93786,6 +94155,7 @@ class SideEffectStore {
|
|
|
93786
94155
|
this.regenerateKeyToEffectMap();
|
|
93787
94156
|
}
|
|
93788
94157
|
await this.durableStore.evictEntries(effectKeys, SIDE_EFFECT_SEGMENT);
|
|
94158
|
+
this.reportOperation('remove', incremental, startTime);
|
|
93789
94159
|
}
|
|
93790
94160
|
getEffects(key) {
|
|
93791
94161
|
const effects = this.keyToEffectMap.get(key);
|
|
@@ -93795,6 +94165,10 @@ class SideEffectStore {
|
|
|
93795
94165
|
return effects;
|
|
93796
94166
|
}
|
|
93797
94167
|
getEffectsByTag(tag) {
|
|
94168
|
+
// This scan is still O(N) over every effect regardless of the incremental-map
|
|
94169
|
+
// gate — timing it tells us whether that residual scan is the quadratic hotspot during churn.
|
|
94170
|
+
const startTime = Date.now();
|
|
94171
|
+
const incremental = draftQueueSideEffectMapGate.isOpen({ fallback: true });
|
|
93798
94172
|
const filteredEffects = [];
|
|
93799
94173
|
const { keyToEffectMap } = this;
|
|
93800
94174
|
// Loop through every entry in the map
|
|
@@ -93804,7 +94178,17 @@ class SideEffectStore {
|
|
|
93804
94178
|
// Add the matching effects to the filtered list
|
|
93805
94179
|
filteredEffects.push(...matchingEffects);
|
|
93806
94180
|
}
|
|
93807
|
-
|
|
94181
|
+
const result = filteredEffects.sort((a, b) => a.timestamp - b.timestamp);
|
|
94182
|
+
this.reportOperation('getByTag', incremental, startTime);
|
|
94183
|
+
return result;
|
|
94184
|
+
}
|
|
94185
|
+
reportOperation(op, incremental, startedAt) {
|
|
94186
|
+
try {
|
|
94187
|
+
reportDraftQueueSideEffectOp(op, incremental, Date.now() - startedAt, this.allEffects.size);
|
|
94188
|
+
}
|
|
94189
|
+
catch {
|
|
94190
|
+
// Telemetry must not change side-effect store behavior.
|
|
94191
|
+
}
|
|
93808
94192
|
}
|
|
93809
94193
|
async initialize() {
|
|
93810
94194
|
const effects = await this.durableStore.getAllEntries(SIDE_EFFECT_SEGMENT);
|
|
@@ -97001,7 +97385,7 @@ function buildServiceDescriptor$b(luvio) {
|
|
|
97001
97385
|
},
|
|
97002
97386
|
};
|
|
97003
97387
|
}
|
|
97004
|
-
// version: 1.
|
|
97388
|
+
// version: 1.455.0-c53c97de2a
|
|
97005
97389
|
|
|
97006
97390
|
/**
|
|
97007
97391
|
* Copyright (c) 2022, Salesforce, Inc.,
|
|
@@ -97027,7 +97411,7 @@ function buildServiceDescriptor$a(notifyRecordUpdateAvailable, getNormalizedLuvi
|
|
|
97027
97411
|
},
|
|
97028
97412
|
};
|
|
97029
97413
|
}
|
|
97030
|
-
// version: 1.
|
|
97414
|
+
// version: 1.455.0-c53c97de2a
|
|
97031
97415
|
|
|
97032
97416
|
function findExecutableOperation$1(input) {
|
|
97033
97417
|
const operations = input.query.definitions.filter(
|
|
@@ -99736,7 +100120,7 @@ register$1({
|
|
|
99736
100120
|
id: '@salesforce/lds-network-adapter',
|
|
99737
100121
|
instrument: instrument$2,
|
|
99738
100122
|
});
|
|
99739
|
-
// version: 1.
|
|
100123
|
+
// version: 1.455.0-c53c97de2a
|
|
99740
100124
|
|
|
99741
100125
|
const { create: create$2, keys: keys$2 } = Object;
|
|
99742
100126
|
const { stringify, parse } = JSON;
|
|
@@ -107271,7 +107655,7 @@ function registerCallback(cb) {
|
|
|
107271
107655
|
cb(graphql_v1_import, graphql_imperative$1, graphql_imperative_legacy_v1_import, graphql_state_manager, useOneStoreGraphQL);
|
|
107272
107656
|
}
|
|
107273
107657
|
}
|
|
107274
|
-
// version: 1.
|
|
107658
|
+
// version: 1.455.0-96e9b41a18
|
|
107275
107659
|
|
|
107276
107660
|
function createFragmentMap(documentNode) {
|
|
107277
107661
|
const fragments = {};
|
|
@@ -124000,7 +124384,7 @@ function keyBuilderFromType(luvio, object) {
|
|
|
124000
124384
|
return keyBuilder$z(luvio, keyParams);
|
|
124001
124385
|
}
|
|
124002
124386
|
|
|
124003
|
-
const { assign: assign$1, create: create$1, freeze: freeze$1, isFrozen: isFrozen$1, keys: keys$1 } = Object;
|
|
124387
|
+
const { assign: assign$1, create: create$1, freeze: freeze$1, getPrototypeOf: getPrototypeOf$1, isFrozen: isFrozen$1, keys: keys$1 } = Object;
|
|
124004
124388
|
const { isArray: isArray$2 } = Array;
|
|
124005
124389
|
const { concat: concat$1, filter: filter$1, includes: includes$1, push: push$1, reduce: reduce$1 } = Array.prototype;
|
|
124006
124390
|
|
|
@@ -136514,7 +136898,7 @@ register$1({
|
|
|
136514
136898
|
configuration: { ...configurationForGraphQLAdapters$1 },
|
|
136515
136899
|
instrument: instrument$1,
|
|
136516
136900
|
});
|
|
136517
|
-
// version: 1.
|
|
136901
|
+
// version: 1.455.0-96e9b41a18
|
|
136518
136902
|
|
|
136519
136903
|
// On core the unstable adapters are re-exported with different names,
|
|
136520
136904
|
// we want to match them here.
|
|
@@ -136666,7 +137050,7 @@ withDefaultLuvio((luvio) => {
|
|
|
136666
137050
|
unstable_graphQL_imperative = createImperativeAdapter(luvio, createInstrumentedAdapter(ldsAdapter, adapterMetadata), adapterMetadata);
|
|
136667
137051
|
graphQLImperative = ldsAdapter;
|
|
136668
137052
|
});
|
|
136669
|
-
// version: 1.
|
|
137053
|
+
// version: 1.455.0-96e9b41a18
|
|
136670
137054
|
|
|
136671
137055
|
var gqlApi = /*#__PURE__*/Object.freeze({
|
|
136672
137056
|
__proto__: null,
|
|
@@ -137465,7 +137849,7 @@ const callbacks$1 = [];
|
|
|
137465
137849
|
function register(r) {
|
|
137466
137850
|
callbacks$1.forEach((callback) => callback(r));
|
|
137467
137851
|
}
|
|
137468
|
-
// version: 1.
|
|
137852
|
+
// version: 1.455.0-c53c97de2a
|
|
137469
137853
|
|
|
137470
137854
|
/**
|
|
137471
137855
|
* Copyright (c) 2022, Salesforce, Inc.,
|
|
@@ -137751,7 +138135,7 @@ function buildAdapterValidationConfig(displayName, paramsMeta) {
|
|
|
137751
138135
|
}
|
|
137752
138136
|
const keyPrefix = 'UiApi';
|
|
137753
138137
|
|
|
137754
|
-
const { assign, create, freeze, isFrozen, keys } = Object;
|
|
138138
|
+
const { assign, create, freeze, getPrototypeOf, isFrozen, keys } = Object;
|
|
137755
138139
|
const { isArray } = Array;
|
|
137756
138140
|
const { concat, filter, includes, push, reduce } = Array.prototype;
|
|
137757
138141
|
|
|
@@ -138754,4 +139138,4 @@ const { luvio } = getRuntime();
|
|
|
138754
139138
|
setDefaultLuvio({ luvio });
|
|
138755
139139
|
|
|
138756
139140
|
export { createPrimingSession, draftManager, draftQueue, evictCacheRecordsByIds, evictExpiredCacheEntries, executeAdapter, executeMutatingAdapter, getImperativeAdapterNames, importLuvioAdapterModule, importOneStoreAdapterModule, invokeAdapter, invokeAdapterWithDraftToMerge, invokeAdapterWithDraftToReplace, invokeAdapterWithMetadata, nimbusDraftQueue, registerReportObserver, setMetadataTTL, setUiApiRecordTTL, stopEviction, subscribeToAdapter };
|
|
138757
|
-
// version: 1.
|
|
139141
|
+
// version: 1.455.0-c53c97de2a
|