@salesforce/lds-runtime-aura 1.279.0 → 1.280.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/ldsEngineCreator.js +1723 -1342
- package/dist/types/__mocks__/@luvio/runtime.d.ts +25 -0
- package/dist/types/__mocks__/@salesforce/lds-instrumentation.d.ts +5 -0
- package/dist/types/__mocks__/aura.d.ts +3 -0
- package/dist/types/aura-instrumentation/main.d.ts +2 -2
- package/dist/types/main.d.ts +2 -1
- package/package.json +13 -11
package/dist/ldsEngineCreator.js
CHANGED
|
@@ -12,19 +12,363 @@
|
|
|
12
12
|
* *******************************************************************************************
|
|
13
13
|
*/
|
|
14
14
|
/* proxy-compat-disable */
|
|
15
|
-
import { HttpStatusCode, InMemoryStore, Environment, Luvio, InMemoryStoreQueryEvaluator } from 'force/luvioEngine';
|
|
15
|
+
import { HttpStatusCode, InMemoryStore as InMemoryStore$1, Environment, Luvio, InMemoryStoreQueryEvaluator } from 'force/luvioEngine';
|
|
16
16
|
import ldsTrackedFieldsBehaviorGate from '@salesforce/gate/lds.useNewTrackedFieldBehavior';
|
|
17
17
|
import usePredictiveLoading from '@salesforce/gate/lds.usePredictiveLoading';
|
|
18
|
-
import { getRecordAvatarsAdapterFactory, getRecordAdapterFactory, coerceFieldIdArray, getRecordsAdapterFactory, getRecordActionsAdapterFactory, getObjectInfoAdapterFactory, getObjectInfosAdapterFactory, getRelatedListsActionsAdapterFactory, getRelatedListInfoBatchAdapterFactory, getRelatedListRecordsBatchAdapterFactory, getRelatedListRecordsAdapterFactory,
|
|
19
|
-
import
|
|
20
|
-
import {
|
|
18
|
+
import { instrument, getRecordAvatarsAdapterFactory, getRecordAdapterFactory, coerceFieldIdArray, getRecordsAdapterFactory, getRecordActionsAdapterFactory, getObjectInfoAdapterFactory, getObjectInfosAdapterFactory, getRelatedListsActionsAdapterFactory, getRelatedListInfoBatchAdapterFactory, getRelatedListRecordsBatchAdapterFactory, getRelatedListRecordsAdapterFactory, configuration, InMemoryRecordRepresentationQueryEvaluator, UiApiNamespace, RecordRepresentationRepresentationType, registerPrefetcher } from 'force/ldsAdaptersUiapi';
|
|
19
|
+
import oneStoreEnabled from '@salesforce/gate/lds.oneStoreEnabled.ltng';
|
|
20
|
+
import { executeGlobalControllerRawResponse } from 'aura';
|
|
21
21
|
import { REFRESH_ADAPTER_EVENT, ADAPTER_UNFULFILLED_ERROR, instrument as instrument$2 } from 'force/ldsBindings';
|
|
22
22
|
import { counter, registerCacheStats, perfStart, perfEnd, registerPeriodicLogger, interaction, timer } from 'instrumentation/service';
|
|
23
|
-
import { LRUCache, instrumentAdapter, instrumentLuvio, setupInstrumentation as setupInstrumentation$1, logObjectInfoChanged as logObjectInfoChanged$1, updatePercentileHistogramMetric, incrementCounterMetric, incrementGetRecordNotifyChangeAllowCount, incrementGetRecordNotifyChangeDropCount, incrementNotifyRecordUpdateAvailableAllowCount, incrementNotifyRecordUpdateAvailableDropCount, setLdsAdaptersUiapiInstrumentation, setLdsNetworkAdapterInstrumentation, onIdleDetected } from 'force/ldsInstrumentation';
|
|
23
|
+
import { LRUCache, instrumentAdapter, instrumentLuvio, setupInstrumentation as setupInstrumentation$1, logObjectInfoChanged as logObjectInfoChanged$1, updatePercentileHistogramMetric, incrementCounterMetric, incrementGetRecordNotifyChangeAllowCount, incrementGetRecordNotifyChangeDropCount, incrementNotifyRecordUpdateAvailableAllowCount, incrementNotifyRecordUpdateAvailableDropCount, setLdsAdaptersUiapiInstrumentation, setLdsNetworkAdapterInstrumentation, executeAsyncActivity, METRIC_KEYS, onIdleDetected } from 'force/ldsInstrumentation';
|
|
24
24
|
import auraNetworkAdapter, { instrument as instrument$1, forceRecordTransactionsDisabled, ldsNetworkAdapterInstrument, dispatchAuraAction, defaultActionConfig } from 'force/ldsNetwork';
|
|
25
25
|
import { instrument as instrument$3 } from 'force/adsBridge';
|
|
26
|
+
import { withRegistration as withRegistration$1, register, setDefaultLuvio } from 'force/ldsEngine';
|
|
27
|
+
import { createStorage, clearStorages } from 'force/ldsStorage';
|
|
26
28
|
import { buildJwtNetworkAdapter } from 'force/ldsNetworkFetchWithJwt';
|
|
27
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
32
|
+
* All rights reserved.
|
|
33
|
+
* For full license text, see the LICENSE.txt file
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const { hasOwnProperty } = Object.prototype;
|
|
37
|
+
|
|
38
|
+
function resolvedPromiseLike(result) {
|
|
39
|
+
// Don't nest anything promise like
|
|
40
|
+
if (isPromiseLike(result)) {
|
|
41
|
+
return result.then((nextResult) => nextResult);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
then: (onFulfilled, _onRejected) => {
|
|
45
|
+
if (onFulfilled) {
|
|
46
|
+
try {
|
|
47
|
+
return resolvedPromiseLike(onFulfilled(result));
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
return rejectedPromiseLike(e);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// assume TResult1 == Result and just pass result down the chain
|
|
54
|
+
return resolvedPromiseLike(result);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns a PromiseLike object that rejects with the specified reason.
|
|
60
|
+
*
|
|
61
|
+
* @param reason rejection value
|
|
62
|
+
* @returns PromiseLike that rejects with reason
|
|
63
|
+
*/
|
|
64
|
+
function rejectedPromiseLike(reason) {
|
|
65
|
+
if (isPromiseLike(reason)) {
|
|
66
|
+
return reason.then((nextResult) => nextResult);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
then: (_onFulfilled, onRejected) => {
|
|
70
|
+
if (onRejected) {
|
|
71
|
+
try {
|
|
72
|
+
return resolvedPromiseLike(onRejected(reason));
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
return rejectedPromiseLike(e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// assume TResult2 == Result and just pass rejection down the chain
|
|
79
|
+
return rejectedPromiseLike(reason);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function isPromiseLike(value) {
|
|
84
|
+
return (value instanceof Promise ||
|
|
85
|
+
(typeof value === 'object' &&
|
|
86
|
+
value !== null &&
|
|
87
|
+
hasOwnProperty.call(value, 'then') &&
|
|
88
|
+
typeof value.then === 'function'));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A collection of keys, in no particular order.
|
|
93
|
+
*/
|
|
94
|
+
class KeySetImpl {
|
|
95
|
+
constructor(initialKeys) {
|
|
96
|
+
// TODO - probably better to use Set<Key>
|
|
97
|
+
this.data = {};
|
|
98
|
+
this.lengthInternal = 0;
|
|
99
|
+
if (initialKeys) {
|
|
100
|
+
initialKeys.forEach((key) => {
|
|
101
|
+
this.add(key);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
add(key) {
|
|
106
|
+
this.data[key] = true;
|
|
107
|
+
// TODO - need to account for adding a key that was already in the set
|
|
108
|
+
this.lengthInternal++;
|
|
109
|
+
}
|
|
110
|
+
contains(key) {
|
|
111
|
+
return this.data[key] === true;
|
|
112
|
+
}
|
|
113
|
+
elements() {
|
|
114
|
+
return Object.keys(this.data);
|
|
115
|
+
}
|
|
116
|
+
get length() {
|
|
117
|
+
return this.lengthInternal;
|
|
118
|
+
}
|
|
119
|
+
overlaps(other) {
|
|
120
|
+
const otherKeys = other.elements();
|
|
121
|
+
for (let j = 0; j < otherKeys.length; ++j) {
|
|
122
|
+
if (this.contains(otherKeys[j])) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
difference(other) {
|
|
129
|
+
const value = new KeySetImpl();
|
|
130
|
+
this.elements().forEach((key) => {
|
|
131
|
+
if (!other.contains(key)) {
|
|
132
|
+
value.add(key);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
isSubsetOf(other) {
|
|
138
|
+
return this.difference(other).length === 0;
|
|
139
|
+
}
|
|
140
|
+
equals(other) {
|
|
141
|
+
return this.length === other.length && this.elements().every((key) => other.contains(key));
|
|
142
|
+
}
|
|
143
|
+
toString() {
|
|
144
|
+
return `<<${JSON.stringify(this.elements())}>>`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* A simple in-memory implementation of the Store interface.
|
|
150
|
+
*
|
|
151
|
+
* Exported for testing purposes only.
|
|
152
|
+
*/
|
|
153
|
+
class InMemoryStore {
|
|
154
|
+
constructor() {
|
|
155
|
+
this.data = {};
|
|
156
|
+
}
|
|
157
|
+
// TODO - only intended for use in tests. We should refactor this into a subclass &
|
|
158
|
+
// add to a test util library.
|
|
159
|
+
clear() {
|
|
160
|
+
this.data = {};
|
|
161
|
+
}
|
|
162
|
+
delete(key, _options) {
|
|
163
|
+
delete this.data[key];
|
|
164
|
+
}
|
|
165
|
+
get(key, _options) {
|
|
166
|
+
return this.data[key];
|
|
167
|
+
}
|
|
168
|
+
set(key, value, _options) {
|
|
169
|
+
this.data[key] = value;
|
|
170
|
+
}
|
|
171
|
+
length() {
|
|
172
|
+
return this.keys().length;
|
|
173
|
+
}
|
|
174
|
+
keys() {
|
|
175
|
+
return new KeySetImpl(Object.keys(this.data));
|
|
176
|
+
}
|
|
177
|
+
toKeySet(keys) {
|
|
178
|
+
return new KeySetImpl(keys);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Constructs an in-memory implementation of StoreService.
|
|
183
|
+
*
|
|
184
|
+
* @returns in-memory implementation of StoreService
|
|
185
|
+
*/
|
|
186
|
+
function buildInMemoryStoreService() {
|
|
187
|
+
return {
|
|
188
|
+
store: new InMemoryStore(),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* A simple in-memory implementation of the MetadataRepository interface.
|
|
193
|
+
*
|
|
194
|
+
* Exported for testing purposes only.
|
|
195
|
+
*/
|
|
196
|
+
class InMemoryMetadataRepository {
|
|
197
|
+
constructor() {
|
|
198
|
+
this.data = {};
|
|
199
|
+
}
|
|
200
|
+
// TODO - only intended for use in tests. We should refactor this into a subclass &
|
|
201
|
+
// add to a test util library.
|
|
202
|
+
clear() {
|
|
203
|
+
this.data = {};
|
|
204
|
+
}
|
|
205
|
+
delete(key, _options) {
|
|
206
|
+
delete this.data[key];
|
|
207
|
+
}
|
|
208
|
+
get(key, _options) {
|
|
209
|
+
return this.data[key];
|
|
210
|
+
}
|
|
211
|
+
set(key, value, _options) {
|
|
212
|
+
this.data[key] = value;
|
|
213
|
+
}
|
|
214
|
+
setPartial(key, value, _options) {
|
|
215
|
+
const metadata = this.get(key);
|
|
216
|
+
if (metadata === undefined) {
|
|
217
|
+
throw new Error(`Metadata for key "${key}" not found`);
|
|
218
|
+
}
|
|
219
|
+
this.data[key] = { ...metadata, ...value };
|
|
220
|
+
}
|
|
221
|
+
expire(key, options) {
|
|
222
|
+
const { expirationTime = Date.now() } = options || {};
|
|
223
|
+
this.setPartial(key, { expirationTime });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Constructs an in-memory implementation of MetadataRepositoryService.
|
|
228
|
+
*
|
|
229
|
+
* @returns in-memory implementation of MetadataRepositoryService
|
|
230
|
+
*/
|
|
231
|
+
function buildInMemoryMetadataRepositoryService() {
|
|
232
|
+
return {
|
|
233
|
+
metadataRepository: new InMemoryMetadataRepository(),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* A simple implementation of KeyKeySubscriptionService.
|
|
239
|
+
*/
|
|
240
|
+
class DefaultKeySubscriptionService {
|
|
241
|
+
constructor() {
|
|
242
|
+
this.nextId = 1;
|
|
243
|
+
this.subscriptions = [];
|
|
244
|
+
}
|
|
245
|
+
subscribe(options) {
|
|
246
|
+
const subscriptionId = this.nextId++;
|
|
247
|
+
const { subscription, callback } = options;
|
|
248
|
+
this.subscriptions.push({ subscriptionId, keys: subscription, callback });
|
|
249
|
+
return () => {
|
|
250
|
+
this.subscriptions = this.subscriptions.filter((subscription) => subscription.subscriptionId !== subscriptionId);
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
publish(keys, _options) {
|
|
254
|
+
const subscriptions = this.subscriptions.slice();
|
|
255
|
+
for (let i = 0; i < subscriptions.length; ++i) {
|
|
256
|
+
const { keys: subscriptionKeys, callback } = subscriptions[i];
|
|
257
|
+
if (keys.overlaps(subscriptionKeys)) {
|
|
258
|
+
callback();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return resolvedPromiseLike(undefined);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Constructs a default KeySubscriptionService
|
|
266
|
+
*
|
|
267
|
+
* @returns default KeySubscriptionService
|
|
268
|
+
*/
|
|
269
|
+
function buildDefaultKeySubscriptionService() {
|
|
270
|
+
return {
|
|
271
|
+
keySubscription: new DefaultKeySubscriptionService(),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
class DefaultTypeNotFoundError extends Error {
|
|
276
|
+
}
|
|
277
|
+
class DefaultTypeRegistry {
|
|
278
|
+
constructor() {
|
|
279
|
+
this.registry = {};
|
|
280
|
+
this.TypeNotFoundError = DefaultTypeNotFoundError;
|
|
281
|
+
}
|
|
282
|
+
register(type, _options) {
|
|
283
|
+
if (!this.registry[type.namespace]) {
|
|
284
|
+
this.registry[type.namespace] = {};
|
|
285
|
+
}
|
|
286
|
+
this.registry[type.namespace][type.typeName] = type;
|
|
287
|
+
}
|
|
288
|
+
get(namespace, typeName, _options) {
|
|
289
|
+
const registryNamespace = this.registry[namespace];
|
|
290
|
+
if (!registryNamespace) {
|
|
291
|
+
throw new DefaultTypeNotFoundError(`namespace ${namespace} not found`);
|
|
292
|
+
}
|
|
293
|
+
const type = registryNamespace[typeName];
|
|
294
|
+
if (!type) {
|
|
295
|
+
throw new DefaultTypeNotFoundError(`type ${typeName} not found in namespace ${namespace}`);
|
|
296
|
+
}
|
|
297
|
+
return type;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Constructs an in-memory implementation of StoreService.
|
|
302
|
+
*
|
|
303
|
+
* @returns in-memory implementation of StoreService
|
|
304
|
+
*/
|
|
305
|
+
function buildDefaultTypeRegistryService() {
|
|
306
|
+
return {
|
|
307
|
+
typeRegistry: new DefaultTypeRegistry(),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
313
|
+
* All rights reserved.
|
|
314
|
+
* For full license text, see the LICENSE.txt file
|
|
315
|
+
*/
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Registrations that have already occurred.
|
|
319
|
+
*
|
|
320
|
+
* Note that Registrations are maintained as a list rather than a map to allow
|
|
321
|
+
* the same id to be registered multiple times with potentially different
|
|
322
|
+
* data.
|
|
323
|
+
*/
|
|
324
|
+
const registrations = [];
|
|
325
|
+
/**
|
|
326
|
+
* Invokes callback for each Registration, both past & future. That is, callback
|
|
327
|
+
* will be invoked exactly as many times as register() is called.
|
|
328
|
+
*
|
|
329
|
+
* Note that Registration ids are not guaranteed to be unique. The meaning of
|
|
330
|
+
* multiple Registrations with the same id is determined by the caller(s) of
|
|
331
|
+
* register().
|
|
332
|
+
*/
|
|
333
|
+
function forEachRegistration(callback) {
|
|
334
|
+
registrations.forEach((r) => callback(r));
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Invokes callback when the specified id is registered.
|
|
338
|
+
*
|
|
339
|
+
* Note that callback may be invoked:
|
|
340
|
+
*
|
|
341
|
+
* - multiple times if multiple calls to register() specify the id
|
|
342
|
+
* - never if the specified id is never registered
|
|
343
|
+
*/
|
|
344
|
+
function withRegistration(id, callback) {
|
|
345
|
+
forEachRegistration((r) => {
|
|
346
|
+
if (r.id === id) {
|
|
347
|
+
callback(r);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
354
|
+
* All rights reserved.
|
|
355
|
+
* For full license text, see the LICENSE.txt file
|
|
356
|
+
*/
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Indicates if a given instance of a service satisfies a request. Note that this function
|
|
360
|
+
* assumes the version numbers are valid strings.
|
|
361
|
+
*
|
|
362
|
+
* @param provided ServiceVersion of the service instance to be provided
|
|
363
|
+
* @param requested ServiceVersion requested
|
|
364
|
+
* @returns true if the service instance to be provided satisfies the request
|
|
365
|
+
*/
|
|
366
|
+
function satisfies(provided, requested) {
|
|
367
|
+
const providedN = provided.split('.').map((s) => parseInt(s));
|
|
368
|
+
const requestedN = requested.split('.').map((s) => parseInt(s));
|
|
369
|
+
return providedN[0] === requestedN[0] && providedN[1] >= requestedN[1];
|
|
370
|
+
}
|
|
371
|
+
|
|
28
372
|
class PredictivePrefetchPage {
|
|
29
373
|
constructor(context) {
|
|
30
374
|
this.context = context;
|
|
@@ -133,126 +477,88 @@ class RecordHomePage extends PredictivePrefetchPage {
|
|
|
133
477
|
}
|
|
134
478
|
}
|
|
135
479
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
480
|
+
/**
|
|
481
|
+
* Observability / Critical Availability Program (230+)
|
|
482
|
+
*
|
|
483
|
+
* This file is intended to be used as a consolidated place for all definitions, functions,
|
|
484
|
+
* and helpers related to "M1"[1].
|
|
485
|
+
*
|
|
486
|
+
* Below are the R.E.A.D.S. metrics for the Lightning Data Service, defined here[2].
|
|
487
|
+
*
|
|
488
|
+
* [1] Search "[M1] Lightning Data Service Design Spike" in Quip
|
|
489
|
+
* [2] Search "Lightning Data Service R.E.A.D.S. Metrics" in Quip
|
|
490
|
+
*/
|
|
491
|
+
const OBSERVABILITY_NAMESPACE = 'LIGHTNING.lds.service';
|
|
492
|
+
const ADAPTER_INVOCATION_COUNT_METRIC_NAME = 'request';
|
|
493
|
+
const ADAPTER_ERROR_COUNT_METRIC_NAME = 'error';
|
|
494
|
+
const NETWORK_ADAPTER_RESPONSE_METRIC_NAME = 'network-response';
|
|
495
|
+
/**
|
|
496
|
+
* W-8379680
|
|
497
|
+
* Counter for number of getApex requests.
|
|
498
|
+
*/
|
|
499
|
+
const GET_APEX_REQUEST_COUNT = {
|
|
500
|
+
get() {
|
|
501
|
+
return {
|
|
502
|
+
owner: OBSERVABILITY_NAMESPACE,
|
|
503
|
+
name: ADAPTER_INVOCATION_COUNT_METRIC_NAME + '.' + NORMALIZED_APEX_ADAPTER_NAME,
|
|
504
|
+
};
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
/**
|
|
508
|
+
* W-8828410
|
|
509
|
+
* Counter for the number of UnfulfilledSnapshotErrors the luvio engine has.
|
|
510
|
+
*/
|
|
511
|
+
const TOTAL_ADAPTER_ERROR_COUNT = {
|
|
512
|
+
get() {
|
|
513
|
+
return { owner: OBSERVABILITY_NAMESPACE, name: ADAPTER_ERROR_COUNT_METRIC_NAME };
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
/**
|
|
517
|
+
* W-8828410
|
|
518
|
+
* Counter for the number of invocations made into LDS by a wire adapter.
|
|
519
|
+
*/
|
|
520
|
+
const TOTAL_ADAPTER_REQUEST_SUCCESS_COUNT = {
|
|
521
|
+
get() {
|
|
522
|
+
return { owner: OBSERVABILITY_NAMESPACE, name: ADAPTER_INVOCATION_COUNT_METRIC_NAME };
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const { create, keys } = Object;
|
|
527
|
+
const { isArray } = Array;
|
|
528
|
+
const { stringify } = JSON;
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* A deterministic JSON stringify implementation. Heavily adapted from https://github.com/epoberezkin/fast-json-stable-stringify.
|
|
532
|
+
* This is needed because insertion order for JSON.stringify(object) affects output:
|
|
533
|
+
* JSON.stringify({a: 1, b: 2})
|
|
534
|
+
* "{"a":1,"b":2}"
|
|
535
|
+
* JSON.stringify({b: 2, a: 1})
|
|
536
|
+
* "{"b":2,"a":1}"
|
|
537
|
+
* Modified from the apex implementation to sort arrays non-destructively.
|
|
538
|
+
* @param data Data to be JSON-stringified.
|
|
539
|
+
* @returns JSON.stringified value with consistent ordering of keys.
|
|
540
|
+
*/
|
|
541
|
+
function stableJSONStringify$1(node) {
|
|
542
|
+
// This is for Date values.
|
|
543
|
+
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
544
|
+
// eslint-disable-next-line no-param-reassign
|
|
545
|
+
node = node.toJSON();
|
|
148
546
|
}
|
|
149
|
-
|
|
150
|
-
return
|
|
547
|
+
if (node === undefined) {
|
|
548
|
+
return;
|
|
151
549
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
await this.repository.flushRequestsToStorage();
|
|
550
|
+
if (typeof node === 'number') {
|
|
551
|
+
return isFinite(node) ? '' + node : 'null';
|
|
155
552
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
this.repository.clearRequestBuffer();
|
|
159
|
-
}
|
|
160
|
-
saveRequest(request) {
|
|
161
|
-
if (!this.isRecording) {
|
|
162
|
-
return Promise.resolve();
|
|
163
|
-
}
|
|
164
|
-
const { request: requestToSave, context } = this.page.buildSaveRequestData(request);
|
|
165
|
-
// No need to diferentiate from predictions requests because these
|
|
166
|
-
// are made from the adapters factory, which are not prediction aware.
|
|
167
|
-
return this.repository.saveRequest(context, requestToSave);
|
|
168
|
-
}
|
|
169
|
-
async predict() {
|
|
170
|
-
const exactPageRequests = (await this.repository.getPageRequests(this.context)) || [];
|
|
171
|
-
const similarPageRequests = await this.getSimilarPageRequests();
|
|
172
|
-
const alwaysRequests = this.page.getAlwaysRunRequests();
|
|
173
|
-
const predictedRequests = [
|
|
174
|
-
...alwaysRequests,
|
|
175
|
-
...this.requestRunner.reduceRequests([
|
|
176
|
-
...exactPageRequests,
|
|
177
|
-
...similarPageRequests,
|
|
178
|
-
...this.page.getAlwaysRunRequests(),
|
|
179
|
-
]),
|
|
180
|
-
];
|
|
181
|
-
this.queuedPredictionRequests.push(...predictedRequests);
|
|
182
|
-
return Promise.all(predictedRequests.map((request) => this.requestRunner.runRequest(request))).then();
|
|
183
|
-
}
|
|
184
|
-
getPredictionSummary() {
|
|
185
|
-
const exactPageRequests = this.repository.getPageRequests(this.context) || [];
|
|
186
|
-
const similarPageRequests = this.page.similarContext !== undefined
|
|
187
|
-
? this.repository.getPageRequests(this.page.similarContext)
|
|
188
|
-
: [];
|
|
189
|
-
return { exact: exactPageRequests.length, similar: similarPageRequests.length };
|
|
190
|
-
}
|
|
191
|
-
hasPredictions() {
|
|
192
|
-
const summary = this.getPredictionSummary();
|
|
193
|
-
return summary.exact > 0 || summary.similar > 0;
|
|
194
|
-
}
|
|
195
|
-
getSimilarPageRequests() {
|
|
196
|
-
let resolvedSimilarPageRequests = [];
|
|
197
|
-
if (this.page.similarContext !== undefined) {
|
|
198
|
-
const similarPageRequests = this.repository.getPageRequests(this.page.similarContext);
|
|
199
|
-
if (similarPageRequests !== undefined) {
|
|
200
|
-
resolvedSimilarPageRequests = similarPageRequests.map((request) => this.page.resolveSimilarRequest(request));
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return resolvedSimilarPageRequests;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
class LexPredictivePrefetcher extends ApplicationPredictivePrefetcher {
|
|
208
|
-
constructor(context, repository, requestRunner,
|
|
209
|
-
// These strategies need to be in sync with the "predictiveDataLoadCapable" list
|
|
210
|
-
// from scripts/lds-uiapi-plugin.js
|
|
211
|
-
requestStrategies) {
|
|
212
|
-
super(context, repository, requestRunner);
|
|
213
|
-
this.requestStrategies = requestStrategies;
|
|
214
|
-
this.page = this.getPage();
|
|
215
|
-
}
|
|
216
|
-
getPage() {
|
|
217
|
-
if (RecordHomePage.handlesContext(this.context)) {
|
|
218
|
-
return new RecordHomePage(this.context, this.requestStrategies);
|
|
219
|
-
}
|
|
220
|
-
return new LexDefaultPage(this.context);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Copy-pasted from adapter-utils. This util should be extracted from generated code and imported in prefetch repository.
|
|
225
|
-
const { keys: ObjectKeys$2 } = Object;
|
|
226
|
-
const { stringify: JSONStringify } = JSON;
|
|
227
|
-
const { isArray: ArrayIsArray$1 } = Array;
|
|
228
|
-
/**
|
|
229
|
-
* A deterministic JSON stringify implementation. Heavily adapted from https://github.com/epoberezkin/fast-json-stable-stringify.
|
|
230
|
-
* This is needed because insertion order for JSON.stringify(object) affects output:
|
|
231
|
-
* JSON.stringify({a: 1, b: 2})
|
|
232
|
-
* "{"a":1,"b":2}"
|
|
233
|
-
* JSON.stringify({b: 2, a: 1})
|
|
234
|
-
* "{"b":2,"a":1}"
|
|
235
|
-
* @param data Data to be JSON-stringified.
|
|
236
|
-
* @returns JSON.stringified value with consistent ordering of keys.
|
|
237
|
-
*/
|
|
238
|
-
function stableJSONStringify$1(node) {
|
|
239
|
-
// This is for Date values.
|
|
240
|
-
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
241
|
-
// eslint-disable-next-line no-param-reassign
|
|
242
|
-
node = node.toJSON();
|
|
243
|
-
}
|
|
244
|
-
if (node === undefined) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
if (typeof node === 'number') {
|
|
248
|
-
return isFinite(node) ? '' + node : 'null';
|
|
249
|
-
}
|
|
250
|
-
if (typeof node !== 'object') {
|
|
251
|
-
return JSONStringify(node);
|
|
553
|
+
if (typeof node !== 'object') {
|
|
554
|
+
return stringify(node);
|
|
252
555
|
}
|
|
253
556
|
let i;
|
|
254
557
|
let out;
|
|
255
|
-
if (
|
|
558
|
+
if (isArray(node)) {
|
|
559
|
+
// copy any array before sorting so we don't mutate the object.
|
|
560
|
+
// eslint-disable-next-line no-param-reassign
|
|
561
|
+
node = node.slice(0).sort();
|
|
256
562
|
out = '[';
|
|
257
563
|
for (i = 0; i < node.length; i++) {
|
|
258
564
|
if (i) {
|
|
@@ -265,10 +571,10 @@ function stableJSONStringify$1(node) {
|
|
|
265
571
|
if (node === null) {
|
|
266
572
|
return 'null';
|
|
267
573
|
}
|
|
268
|
-
const keys =
|
|
574
|
+
const keys$1 = keys(node).sort();
|
|
269
575
|
out = '';
|
|
270
|
-
for (i = 0; i < keys.length; i++) {
|
|
271
|
-
const key = keys[i];
|
|
576
|
+
for (i = 0; i < keys$1.length; i++) {
|
|
577
|
+
const key = keys$1[i];
|
|
272
578
|
const value = stableJSONStringify$1(node[key]);
|
|
273
579
|
if (!value) {
|
|
274
580
|
continue;
|
|
@@ -276,716 +582,843 @@ function stableJSONStringify$1(node) {
|
|
|
276
582
|
if (out) {
|
|
277
583
|
out += ',';
|
|
278
584
|
}
|
|
279
|
-
out +=
|
|
585
|
+
out += stringify(key) + ':' + value;
|
|
280
586
|
}
|
|
281
587
|
return '{' + out + '}';
|
|
282
588
|
}
|
|
283
|
-
function
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
function deepEquals(objA, objB) {
|
|
287
|
-
if (objA === objB)
|
|
288
|
-
return true;
|
|
289
|
-
if (objA instanceof Date && objB instanceof Date)
|
|
290
|
-
return objA.getTime() === objB.getTime();
|
|
291
|
-
// If one of them is not an object, they are not deeply equal
|
|
292
|
-
if (!isObject(objA) || !isObject(objB))
|
|
293
|
-
return false;
|
|
294
|
-
// Filter out keys set as undefined, we can compare undefined as equals.
|
|
295
|
-
const keysA = ObjectKeys$2(objA).filter((key) => objA[key] !== undefined);
|
|
296
|
-
const keysB = ObjectKeys$2(objB).filter((key) => objB[key] !== undefined);
|
|
297
|
-
// If the objects do not have the same set of keys, they are not deeply equal
|
|
298
|
-
if (keysA.length !== keysB.length)
|
|
299
|
-
return false;
|
|
300
|
-
for (const key of keysA) {
|
|
301
|
-
const valA = objA[key];
|
|
302
|
-
const valB = objB[key];
|
|
303
|
-
const areObjects = isObject(valA) && isObject(valB);
|
|
304
|
-
// If both values are objects, recursively compare them
|
|
305
|
-
if (areObjects && !deepEquals(valA, valB))
|
|
306
|
-
return false;
|
|
307
|
-
// If only one value is an object or if the values are not strictly equal, they are not deeply equal
|
|
308
|
-
if (!areObjects && valA !== valB)
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
return true;
|
|
589
|
+
function isPromise(value) {
|
|
590
|
+
// check for Thenable due to test frameworks using custom Promise impls
|
|
591
|
+
return value !== null && value.then !== undefined;
|
|
312
592
|
}
|
|
313
593
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
594
|
+
const APEX_ADAPTER_NAME = 'getApex';
|
|
595
|
+
const NORMALIZED_APEX_ADAPTER_NAME = `Apex.${APEX_ADAPTER_NAME}`;
|
|
596
|
+
const REFRESH_APEX_KEY = 'refreshApex';
|
|
597
|
+
const REFRESH_UIAPI_KEY = 'refreshUiApi';
|
|
598
|
+
const SUPPORTED_KEY = 'refreshSupported';
|
|
599
|
+
const UNSUPPORTED_KEY = 'refreshUnsupported';
|
|
600
|
+
const REFRESH_EVENTSOURCE = 'lds-refresh-summary';
|
|
601
|
+
const REFRESH_EVENTTYPE = 'system';
|
|
602
|
+
const REFRESH_PAYLOAD_TARGET = 'adapters';
|
|
603
|
+
const REFRESH_PAYLOAD_SCOPE = 'lds';
|
|
604
|
+
const INCOMING_WEAKETAG_0_KEY = 'incoming-weaketag-0';
|
|
605
|
+
const EXISTING_WEAKETAG_0_KEY = 'existing-weaketag-0';
|
|
606
|
+
const RECORD_API_NAME_CHANGE_COUNT_METRIC_NAME = 'record-api-name-change-count';
|
|
607
|
+
const NAMESPACE = 'lds';
|
|
608
|
+
const NETWORK_TRANSACTION_NAME = 'lds-network';
|
|
609
|
+
const CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX = 'out-of-ttl-miss';
|
|
610
|
+
// Aggregate Cache Stats and Metrics for all getApex invocations
|
|
611
|
+
const getApexCacheStats = registerLdsCacheStats(NORMALIZED_APEX_ADAPTER_NAME);
|
|
612
|
+
const getApexTtlCacheStats = registerLdsCacheStats(NORMALIZED_APEX_ADAPTER_NAME + ':' + CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX);
|
|
613
|
+
// Observability (READS)
|
|
614
|
+
const getApexRequestCountMetric = counter(GET_APEX_REQUEST_COUNT);
|
|
615
|
+
const totalAdapterRequestSuccessMetric = counter(TOTAL_ADAPTER_REQUEST_SUCCESS_COUNT);
|
|
616
|
+
const totalAdapterErrorMetric = counter(TOTAL_ADAPTER_ERROR_COUNT);
|
|
617
|
+
class Instrumentation {
|
|
618
|
+
constructor() {
|
|
619
|
+
this.adapterUnfulfilledErrorCounters = {};
|
|
620
|
+
this.recordApiNameChangeCounters = {};
|
|
621
|
+
this.refreshAdapterEvents = {};
|
|
622
|
+
this.refreshApiCallEventStats = {
|
|
623
|
+
[REFRESH_APEX_KEY]: 0,
|
|
624
|
+
[REFRESH_UIAPI_KEY]: 0,
|
|
625
|
+
[SUPPORTED_KEY]: 0,
|
|
626
|
+
[UNSUPPORTED_KEY]: 0,
|
|
627
|
+
};
|
|
628
|
+
this.lastRefreshApiCall = null;
|
|
629
|
+
this.weakEtagZeroEvents = {};
|
|
630
|
+
this.adapterCacheMisses = new LRUCache(250);
|
|
631
|
+
if (typeof window !== 'undefined' && window.addEventListener) {
|
|
632
|
+
window.addEventListener('beforeunload', () => {
|
|
633
|
+
if (keys(this.weakEtagZeroEvents).length > 0) {
|
|
634
|
+
perfStart(NETWORK_TRANSACTION_NAME);
|
|
635
|
+
perfEnd(NETWORK_TRANSACTION_NAME, this.weakEtagZeroEvents);
|
|
338
636
|
}
|
|
339
637
|
});
|
|
340
|
-
setPromises.push(this.storage.set(id, page));
|
|
341
|
-
}
|
|
342
|
-
this.clearRequestBuffer();
|
|
343
|
-
await Promise.all(setPromises);
|
|
344
|
-
}
|
|
345
|
-
getKeyId(key) {
|
|
346
|
-
return stableJSONStringify$1(key);
|
|
347
|
-
}
|
|
348
|
-
async saveRequest(key, request) {
|
|
349
|
-
const identifier = this.getKeyId(key);
|
|
350
|
-
const batchForKey = this.requestBuffer.get(identifier) || [];
|
|
351
|
-
batchForKey.push({
|
|
352
|
-
request,
|
|
353
|
-
requestTime: Date.now(),
|
|
354
|
-
});
|
|
355
|
-
this.requestBuffer.set(identifier, batchForKey);
|
|
356
|
-
}
|
|
357
|
-
getPage(key) {
|
|
358
|
-
const identifier = stableJSONStringify$1(key);
|
|
359
|
-
return this.storage.get(identifier);
|
|
360
|
-
}
|
|
361
|
-
getPageRequests(key) {
|
|
362
|
-
const page = this.getPage(key);
|
|
363
|
-
if (page === undefined) {
|
|
364
|
-
return [];
|
|
365
638
|
}
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
class RequestStrategy {
|
|
371
|
-
transformForSave(request) {
|
|
372
|
-
return request;
|
|
373
|
-
}
|
|
374
|
-
reduce(requests) {
|
|
375
|
-
return requests;
|
|
639
|
+
registerPeriodicLogger(NAMESPACE, this.logRefreshStats.bind(this));
|
|
376
640
|
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
class LuvioAdapterRequestStrategy extends RequestStrategy {
|
|
380
641
|
/**
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
* @param request - The request to transform
|
|
387
|
-
* @returns
|
|
642
|
+
* Instruments an existing adapter to log argus metrics and cache stats.
|
|
643
|
+
* @param adapter The adapter function.
|
|
644
|
+
* @param metadata The adapter metadata.
|
|
645
|
+
* @param wireConfigKeyFn Optional function to transform wire configs to a unique key.
|
|
646
|
+
* @returns The wrapped adapter.
|
|
388
647
|
*/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
648
|
+
instrumentAdapter(adapter, metadata) {
|
|
649
|
+
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
650
|
+
const { apiFamily, name, ttl } = metadata;
|
|
651
|
+
const adapterName = normalizeAdapterName(name, apiFamily);
|
|
652
|
+
const isGetApexAdapter = isApexAdapter(name);
|
|
653
|
+
const stats = isGetApexAdapter ? getApexCacheStats : registerLdsCacheStats(adapterName);
|
|
654
|
+
const ttlMissStats = isGetApexAdapter
|
|
655
|
+
? getApexTtlCacheStats
|
|
656
|
+
: registerLdsCacheStats(adapterName + ':' + CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX);
|
|
657
|
+
/**
|
|
658
|
+
* W-8076905
|
|
659
|
+
* Dynamically generated metric. Simple counter for all requests made by this adapter.
|
|
660
|
+
*/
|
|
661
|
+
const wireAdapterRequestMetric = isGetApexAdapter
|
|
662
|
+
? getApexRequestCountMetric
|
|
663
|
+
: counter(createMetricsKey(OBSERVABILITY_NAMESPACE, ADAPTER_INVOCATION_COUNT_METRIC_NAME, adapterName));
|
|
664
|
+
const instrumentedAdapter = (config, requestContext) => {
|
|
665
|
+
// increment overall and adapter request metrics
|
|
666
|
+
wireAdapterRequestMetric.increment(1);
|
|
667
|
+
totalAdapterRequestSuccessMetric.increment(1);
|
|
668
|
+
// execute adapter logic
|
|
669
|
+
const result = adapter(config, requestContext);
|
|
670
|
+
// In the case where the adapter returns a non-Pending Snapshot it is constructed out of the store
|
|
671
|
+
// (cache hit) whereas a Promise<Snapshot> or Pending Snapshot indicates a network request (cache miss).
|
|
672
|
+
//
|
|
673
|
+
// Note: we can't do a plain instanceof check for a promise here since the Promise may
|
|
674
|
+
// originate from another javascript realm (for example: in jest test). Instead we use a
|
|
675
|
+
// duck-typing approach by checking if the result has a then property.
|
|
676
|
+
//
|
|
677
|
+
// For adapters without persistent store:
|
|
678
|
+
// - total cache hit ratio:
|
|
679
|
+
// [in-memory cache hit count] / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
680
|
+
// For adapters with persistent store:
|
|
681
|
+
// - in-memory cache hit ratio:
|
|
682
|
+
// [in-memory cache hit count] / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
683
|
+
// - total cache hit ratio:
|
|
684
|
+
// ([in-memory cache hit count] + [store cache hit count]) / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
685
|
+
// if result === null then config is insufficient/invalid so do not log
|
|
686
|
+
if (isPromise(result)) {
|
|
687
|
+
stats.logMisses();
|
|
688
|
+
if (ttl !== undefined) {
|
|
689
|
+
this.logAdapterCacheMissOutOfTtlDuration(adapterName, config, ttlMissStats, Date.now(), ttl);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
else if (result !== null) {
|
|
693
|
+
stats.logHits();
|
|
694
|
+
}
|
|
695
|
+
return result;
|
|
696
|
+
};
|
|
697
|
+
// Set the name property on the function for debugging purposes.
|
|
698
|
+
Object.defineProperty(instrumentedAdapter, 'name', {
|
|
699
|
+
value: name + '__instrumented',
|
|
700
|
+
});
|
|
701
|
+
return instrumentAdapter(instrumentedAdapter, metadata);
|
|
400
702
|
}
|
|
401
703
|
/**
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
* @param
|
|
405
|
-
* @
|
|
704
|
+
* Logs when adapter requests come in. If we have subsequent cache misses on a given config, beyond its TTL then log the duration to metrics.
|
|
705
|
+
* Backed by an LRU Cache implementation to prevent too many record entries from being stored in-memory.
|
|
706
|
+
* @param name The wire adapter name.
|
|
707
|
+
* @param config The config passed into wire adapter.
|
|
708
|
+
* @param ttlMissStats CacheStatsLogger to log misses out of TTL.
|
|
709
|
+
* @param currentCacheMissTimestamp Timestamp for when the request was made.
|
|
710
|
+
* @param ttl TTL for the wire adapter.
|
|
406
711
|
*/
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
if (
|
|
414
|
-
|
|
415
|
-
for (let j = i + 1; j < n; j++) {
|
|
416
|
-
const hasNotBeenVisited = !visitedRequests.has(requests[j]);
|
|
417
|
-
const canCombineConfigs = this.canCombine(combinedRequest.config, requests[j].config);
|
|
418
|
-
if (hasNotBeenVisited && canCombineConfigs) {
|
|
419
|
-
combinedRequest.config = this.combineRequests(combinedRequest.config, requests[j].config);
|
|
420
|
-
visitedRequests.add(requests[j]);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
reducedRequests.push(combinedRequest);
|
|
424
|
-
visitedRequests.add(currentRequest);
|
|
712
|
+
logAdapterCacheMissOutOfTtlDuration(name, config, ttlMissStats, currentCacheMissTimestamp, ttl) {
|
|
713
|
+
const configKey = `${name}:${stableJSONStringify$1(config)}`;
|
|
714
|
+
const existingCacheMissTimestamp = this.adapterCacheMisses.get(configKey);
|
|
715
|
+
this.adapterCacheMisses.set(configKey, currentCacheMissTimestamp);
|
|
716
|
+
if (existingCacheMissTimestamp !== undefined) {
|
|
717
|
+
const duration = currentCacheMissTimestamp - existingCacheMissTimestamp;
|
|
718
|
+
if (duration > ttl) {
|
|
719
|
+
ttlMissStats.logMisses();
|
|
425
720
|
}
|
|
426
721
|
}
|
|
427
|
-
return reducedRequests;
|
|
428
722
|
}
|
|
429
723
|
/**
|
|
430
|
-
*
|
|
724
|
+
* Injected to LDS for Luvio specific instrumentation.
|
|
431
725
|
*
|
|
432
|
-
*
|
|
433
|
-
* @param reqA config of request A
|
|
434
|
-
* @param reqB config of request B
|
|
435
|
-
* @returns
|
|
726
|
+
* @param context The transaction context.
|
|
436
727
|
*/
|
|
437
|
-
|
|
438
|
-
|
|
728
|
+
instrumentLuvio(context) {
|
|
729
|
+
instrumentLuvio(context);
|
|
730
|
+
if (this.isRefreshAdapterEvent(context)) {
|
|
731
|
+
this.aggregateRefreshAdapterEvents(context);
|
|
732
|
+
}
|
|
733
|
+
else if (this.isAdapterUnfulfilledError(context)) {
|
|
734
|
+
this.incrementAdapterRequestErrorCount(context);
|
|
735
|
+
}
|
|
736
|
+
else ;
|
|
439
737
|
}
|
|
440
738
|
/**
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
* @
|
|
444
|
-
* @param reqB config of request B
|
|
445
|
-
* @returns
|
|
739
|
+
* Returns whether or not this is a RefreshAdapterEvent.
|
|
740
|
+
* @param context The transaction context.
|
|
741
|
+
* @returns Whether or not this is a RefreshAdapterEvent.
|
|
446
742
|
*/
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
450
|
-
throw new Error('Not implemented');
|
|
451
|
-
}
|
|
452
|
-
return reqA;
|
|
743
|
+
isRefreshAdapterEvent(context) {
|
|
744
|
+
return context[REFRESH_ADAPTER_EVENT] === true;
|
|
453
745
|
}
|
|
454
746
|
/**
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
* @param request
|
|
459
|
-
* @returns
|
|
747
|
+
* Returns whether or not this is an AdapterUnfulfilledError.
|
|
748
|
+
* @param context The transaction context.
|
|
749
|
+
* @returns Whether or not this is an AdapterUnfulfilledError.
|
|
460
750
|
*/
|
|
461
|
-
|
|
462
|
-
return
|
|
751
|
+
isAdapterUnfulfilledError(context) {
|
|
752
|
+
return context[ADAPTER_UNFULFILLED_ERROR] === true;
|
|
463
753
|
}
|
|
464
754
|
/**
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
* @param
|
|
469
|
-
* @param
|
|
470
|
-
* @param request
|
|
471
|
-
* @returns
|
|
755
|
+
* Specific instrumentation for getRecordNotifyChange.
|
|
756
|
+
* temporary implementation to match existing aura call for now
|
|
757
|
+
*
|
|
758
|
+
* @param uniqueWeakEtags whether weakEtags match or not
|
|
759
|
+
* @param error if dispatchResourceRequest fails for any reason
|
|
472
760
|
*/
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function normalizeRecordIds$1(recordIds) {
|
|
482
|
-
if (!Array.isArray(recordIds)) {
|
|
483
|
-
return [recordIds];
|
|
484
|
-
}
|
|
485
|
-
return recordIds;
|
|
486
|
-
}
|
|
487
|
-
class GetRecordAvatarsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
488
|
-
constructor() {
|
|
489
|
-
super(...arguments);
|
|
490
|
-
this.adapterName = 'getRecordAvatars';
|
|
491
|
-
this.adapterFactory = getRecordAvatarsAdapterFactory;
|
|
492
|
-
}
|
|
493
|
-
buildConcreteRequest(similarRequest, context) {
|
|
494
|
-
return {
|
|
495
|
-
...similarRequest,
|
|
496
|
-
config: {
|
|
497
|
-
...similarRequest.config,
|
|
498
|
-
recordIds: [context.recordId],
|
|
499
|
-
},
|
|
500
|
-
};
|
|
761
|
+
notifyChangeNetwork(uniqueWeakEtags, error) {
|
|
762
|
+
perfStart(NETWORK_TRANSACTION_NAME);
|
|
763
|
+
if (error === true) {
|
|
764
|
+
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': 'error' });
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': uniqueWeakEtags });
|
|
768
|
+
}
|
|
501
769
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
context: similarContext,
|
|
770
|
+
/**
|
|
771
|
+
* Parses and aggregates weakETagZero events to be sent in summarized log line.
|
|
772
|
+
* @param context The transaction context.
|
|
773
|
+
*/
|
|
774
|
+
aggregateWeakETagEvents(incomingWeakEtagZero, existingWeakEtagZero, apiName) {
|
|
775
|
+
const key = 'weaketag-0-' + apiName;
|
|
776
|
+
if (this.weakEtagZeroEvents[key] === undefined) {
|
|
777
|
+
this.weakEtagZeroEvents[key] = {
|
|
778
|
+
[EXISTING_WEAKETAG_0_KEY]: 0,
|
|
779
|
+
[INCOMING_WEAKETAG_0_KEY]: 0,
|
|
513
780
|
};
|
|
514
781
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
782
|
+
if (existingWeakEtagZero) {
|
|
783
|
+
this.weakEtagZeroEvents[key][EXISTING_WEAKETAG_0_KEY] += 1;
|
|
784
|
+
}
|
|
785
|
+
if (incomingWeakEtagZero) {
|
|
786
|
+
this.weakEtagZeroEvents[key][INCOMING_WEAKETAG_0_KEY] += 1;
|
|
787
|
+
}
|
|
519
788
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
789
|
+
/**
|
|
790
|
+
* Aggregates refresh adapter events to be sent in summarized log line.
|
|
791
|
+
* - how many times refreshApex is called
|
|
792
|
+
* - how many times refresh from lightning/uiRecordApi is called
|
|
793
|
+
* - number of supported calls: refreshApex called on apex adapter
|
|
794
|
+
* - number of unsupported calls: refreshApex on non-apex adapter
|
|
795
|
+
* + any use of refresh from uiRecordApi module
|
|
796
|
+
* - count of refresh calls per adapter
|
|
797
|
+
* @param context The refresh adapter event.
|
|
798
|
+
*/
|
|
799
|
+
aggregateRefreshAdapterEvents(context) {
|
|
800
|
+
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
801
|
+
// Adding additional logging that getApex adapters can invoke? Read normalizeAdapterName ts-doc.
|
|
802
|
+
const adapterName = normalizeAdapterName(context.adapterName);
|
|
803
|
+
if (this.lastRefreshApiCall === REFRESH_APEX_KEY) {
|
|
804
|
+
if (isApexAdapter(adapterName)) {
|
|
805
|
+
this.refreshApiCallEventStats[SUPPORTED_KEY] += 1;
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
else if (this.lastRefreshApiCall === REFRESH_UIAPI_KEY) {
|
|
812
|
+
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
813
|
+
}
|
|
814
|
+
if (this.refreshAdapterEvents[adapterName] === undefined) {
|
|
815
|
+
this.refreshAdapterEvents[adapterName] = 0;
|
|
816
|
+
}
|
|
817
|
+
this.refreshAdapterEvents[adapterName] += 1;
|
|
818
|
+
this.lastRefreshApiCall = null;
|
|
525
819
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
class GetRecordRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
537
|
-
constructor() {
|
|
538
|
-
super(...arguments);
|
|
539
|
-
this.adapterName = 'getRecord';
|
|
540
|
-
this.adapterFactory = getRecordAdapterFactory;
|
|
541
|
-
}
|
|
542
|
-
buildConcreteRequest(similarRequest, context) {
|
|
543
|
-
return {
|
|
544
|
-
...similarRequest,
|
|
545
|
-
config: {
|
|
546
|
-
...similarRequest.config,
|
|
547
|
-
recordId: context.recordId,
|
|
548
|
-
},
|
|
549
|
-
};
|
|
820
|
+
/**
|
|
821
|
+
* Increments call stat for incoming refresh api call, and sets the name
|
|
822
|
+
* to be used in {@link aggregateRefreshCalls}
|
|
823
|
+
* @param from The name of the refresh function called.
|
|
824
|
+
*/
|
|
825
|
+
handleRefreshApiCall(apiName) {
|
|
826
|
+
this.refreshApiCallEventStats[apiName] += 1;
|
|
827
|
+
// set function call to be used with aggregateRefreshCalls
|
|
828
|
+
this.lastRefreshApiCall = apiName;
|
|
550
829
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
830
|
+
/**
|
|
831
|
+
* W-7302241
|
|
832
|
+
* Logs refresh call summary stats as a LightningInteraction.
|
|
833
|
+
*/
|
|
834
|
+
logRefreshStats() {
|
|
835
|
+
if (keys(this.refreshAdapterEvents).length > 0) {
|
|
836
|
+
interaction(REFRESH_PAYLOAD_TARGET, REFRESH_PAYLOAD_SCOPE, this.refreshAdapterEvents, REFRESH_EVENTSOURCE, REFRESH_EVENTTYPE, this.refreshApiCallEventStats);
|
|
837
|
+
this.resetRefreshStats();
|
|
554
838
|
}
|
|
555
|
-
let fields = coerceFieldIdArray(request.config.fields) || [];
|
|
556
|
-
let optionalFields = coerceFieldIdArray(request.config.optionalFields) || [];
|
|
557
|
-
return {
|
|
558
|
-
...request,
|
|
559
|
-
config: {
|
|
560
|
-
...request.config,
|
|
561
|
-
fields: undefined,
|
|
562
|
-
optionalFields: [...fields, ...optionalFields],
|
|
563
|
-
},
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
canCombine(reqA, reqB) {
|
|
567
|
-
// must be same record and
|
|
568
|
-
return (reqA.recordId === reqB.recordId &&
|
|
569
|
-
// both requests are fields requests
|
|
570
|
-
(reqA.optionalFields !== undefined || reqB.optionalFields !== undefined) &&
|
|
571
|
-
(reqB.fields !== undefined || reqB.optionalFields !== undefined));
|
|
572
839
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
reqA.optionalFields.forEach((field) => optionalFields.add(field));
|
|
584
|
-
}
|
|
585
|
-
if (reqB.optionalFields !== undefined) {
|
|
586
|
-
reqB.optionalFields.forEach((field) => optionalFields.add(field));
|
|
587
|
-
}
|
|
588
|
-
return {
|
|
589
|
-
recordId: reqA.recordId,
|
|
590
|
-
fields: Array.from(fields),
|
|
591
|
-
optionalFields: Array.from(optionalFields),
|
|
840
|
+
/**
|
|
841
|
+
* Resets the stat trackers for refresh call events.
|
|
842
|
+
*/
|
|
843
|
+
resetRefreshStats() {
|
|
844
|
+
this.refreshAdapterEvents = {};
|
|
845
|
+
this.refreshApiCallEventStats = {
|
|
846
|
+
[REFRESH_APEX_KEY]: 0,
|
|
847
|
+
[REFRESH_UIAPI_KEY]: 0,
|
|
848
|
+
[SUPPORTED_KEY]: 0,
|
|
849
|
+
[UNSUPPORTED_KEY]: 0,
|
|
592
850
|
};
|
|
851
|
+
this.lastRefreshApiCall = null;
|
|
593
852
|
}
|
|
594
|
-
|
|
595
|
-
|
|
853
|
+
/**
|
|
854
|
+
* W-7801618
|
|
855
|
+
* Counter for occurrences where the incoming record to be merged has a different apiName.
|
|
856
|
+
* Dynamically generated metric, stored in an {@link RecordApiNameChangeCounters} object.
|
|
857
|
+
*
|
|
858
|
+
* @param context The transaction context.
|
|
859
|
+
*
|
|
860
|
+
* Note: Short-lived metric candidate, remove at the end of 230
|
|
861
|
+
*/
|
|
862
|
+
incrementRecordApiNameChangeCount(_incomingApiName, existingApiName) {
|
|
863
|
+
let apiNameChangeCounter = this.recordApiNameChangeCounters[existingApiName];
|
|
864
|
+
if (apiNameChangeCounter === undefined) {
|
|
865
|
+
apiNameChangeCounter = counter(createMetricsKey(NAMESPACE, RECORD_API_NAME_CHANGE_COUNT_METRIC_NAME, existingApiName));
|
|
866
|
+
this.recordApiNameChangeCounters[existingApiName] = apiNameChangeCounter;
|
|
867
|
+
}
|
|
868
|
+
apiNameChangeCounter.increment(1);
|
|
596
869
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
870
|
+
/**
|
|
871
|
+
* W-8620679
|
|
872
|
+
* Increment the counter for an UnfulfilledSnapshotError coming from luvio
|
|
873
|
+
*
|
|
874
|
+
* @param context The transaction context.
|
|
875
|
+
*/
|
|
876
|
+
incrementAdapterRequestErrorCount(context) {
|
|
877
|
+
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
878
|
+
const adapterName = normalizeAdapterName(context.adapterName);
|
|
879
|
+
let adapterRequestErrorCounter = this.adapterUnfulfilledErrorCounters[adapterName];
|
|
880
|
+
if (adapterRequestErrorCounter === undefined) {
|
|
881
|
+
adapterRequestErrorCounter = counter(createMetricsKey(OBSERVABILITY_NAMESPACE, ADAPTER_ERROR_COUNT_METRIC_NAME, adapterName));
|
|
882
|
+
this.adapterUnfulfilledErrorCounters[adapterName] = adapterRequestErrorCounter;
|
|
609
883
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
context,
|
|
613
|
-
};
|
|
884
|
+
adapterRequestErrorCounter.increment(1);
|
|
885
|
+
totalAdapterErrorMetric.increment(1);
|
|
614
886
|
}
|
|
615
887
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
this.adapterName = 'getRecords';
|
|
621
|
-
this.adapterFactory = getRecordsAdapterFactory;
|
|
622
|
-
}
|
|
623
|
-
buildConcreteRequest(similarRequest, context) {
|
|
624
|
-
return {
|
|
625
|
-
...similarRequest,
|
|
626
|
-
config: {
|
|
627
|
-
...similarRequest.config,
|
|
628
|
-
records: [{ ...similarRequest.config.records[0], recordIds: [context.recordId] }],
|
|
629
|
-
},
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
isContextDependent(context, request) {
|
|
633
|
-
const isSingleRecordRequest = request.config.records.length === 1 && request.config.records[0].recordIds.length === 1;
|
|
634
|
-
return isSingleRecordRequest && request.config.records[0].recordIds[0] === context.recordId;
|
|
888
|
+
function createMetricsKey(owner, name, unit) {
|
|
889
|
+
let metricName = name;
|
|
890
|
+
if (unit) {
|
|
891
|
+
metricName = metricName + '.' + unit;
|
|
635
892
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
return {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
893
|
+
return {
|
|
894
|
+
get() {
|
|
895
|
+
return { owner: owner, name: metricName };
|
|
896
|
+
},
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Returns whether adapter is an Apex one or not.
|
|
901
|
+
* @param adapterName The name of the adapter.
|
|
902
|
+
*/
|
|
903
|
+
function isApexAdapter(adapterName) {
|
|
904
|
+
return adapterName.indexOf(APEX_ADAPTER_NAME) > -1;
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Normalizes getApex adapter names to `Apex.getApex`. Non-Apex adapters will be prefixed with
|
|
908
|
+
* API family, if supplied. Example: `UiApi.getRecord`.
|
|
909
|
+
*
|
|
910
|
+
* Note: If you are adding additional logging that can come from getApex adapter contexts that provide
|
|
911
|
+
* the full getApex adapter name (i.e. getApex_[namespace]_[class]_[function]_[continuation]),
|
|
912
|
+
* ensure to call this method to normalize all logging to 'getApex'. This
|
|
913
|
+
* is because Argus has a 50k key cardinality limit. More context: W-8379680.
|
|
914
|
+
*
|
|
915
|
+
* @param adapterName The name of the adapter.
|
|
916
|
+
* @param apiFamily The API family of the adapter.
|
|
917
|
+
*/
|
|
918
|
+
function normalizeAdapterName(adapterName, apiFamily) {
|
|
919
|
+
if (isApexAdapter(adapterName)) {
|
|
920
|
+
return NORMALIZED_APEX_ADAPTER_NAME;
|
|
658
921
|
}
|
|
922
|
+
return apiFamily ? `${apiFamily}.${adapterName}` : adapterName;
|
|
659
923
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
924
|
+
const timerMetricTracker = create(null);
|
|
925
|
+
/**
|
|
926
|
+
* Calls instrumentation/service telemetry timer
|
|
927
|
+
* @param name Name of the metric
|
|
928
|
+
* @param duration number to update backing percentile histogram, negative numbers ignored
|
|
929
|
+
*/
|
|
930
|
+
function updateTimerMetric(name, duration) {
|
|
931
|
+
let metric = timerMetricTracker[name];
|
|
932
|
+
if (metric === undefined) {
|
|
933
|
+
metric = timer(createMetricsKey(NAMESPACE, name));
|
|
934
|
+
timerMetricTracker[name] = metric;
|
|
664
935
|
}
|
|
665
|
-
|
|
936
|
+
timerMetricAddDuration(metric, duration);
|
|
666
937
|
}
|
|
667
|
-
function
|
|
668
|
-
|
|
669
|
-
|
|
938
|
+
function timerMetricAddDuration(timer, duration) {
|
|
939
|
+
// Guard against negative values since it causes error to be thrown by MetricsService
|
|
940
|
+
if (duration >= 0) {
|
|
941
|
+
timer.addDuration(duration);
|
|
670
942
|
}
|
|
671
|
-
return ArrayIsArray$1(apiNames) ? apiNames : [apiNames];
|
|
672
943
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
944
|
+
/**
|
|
945
|
+
* W-10315098
|
|
946
|
+
* Increments the counter associated with the request response. Counts are bucketed by status.
|
|
947
|
+
*/
|
|
948
|
+
const requestResponseMetricTracker = create(null);
|
|
949
|
+
function incrementRequestResponseCount(cb) {
|
|
950
|
+
const status = cb().status;
|
|
951
|
+
let metric = requestResponseMetricTracker[status];
|
|
952
|
+
if (metric === undefined) {
|
|
953
|
+
metric = counter(createMetricsKey(OBSERVABILITY_NAMESPACE, NETWORK_ADAPTER_RESPONSE_METRIC_NAME, `${status.valueOf()}`));
|
|
954
|
+
requestResponseMetricTracker[status] = metric;
|
|
678
955
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
956
|
+
metric.increment();
|
|
957
|
+
}
|
|
958
|
+
function logObjectInfoChanged() {
|
|
959
|
+
logObjectInfoChanged$1();
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Create a new instrumentation cache stats and return it.
|
|
963
|
+
*
|
|
964
|
+
* @param name The cache logger name.
|
|
965
|
+
*/
|
|
966
|
+
function registerLdsCacheStats(name) {
|
|
967
|
+
return registerCacheStats(`${NAMESPACE}:${name}`);
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Add or overwrite hooks that require aura implementations
|
|
971
|
+
*/
|
|
972
|
+
function setAuraInstrumentationHooks() {
|
|
973
|
+
instrument({
|
|
974
|
+
recordConflictsResolved: (serverRequestCount) => {
|
|
975
|
+
// Ignore 0 values which can originate from ADS bridge
|
|
976
|
+
if (serverRequestCount > 0) {
|
|
977
|
+
updatePercentileHistogramMetric('record-conflicts-resolved', serverRequestCount);
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
nullDisplayValueConflict: ({ fieldType, areValuesEqual }) => {
|
|
981
|
+
const metricName = `merge-null-dv-count.${fieldType}`;
|
|
982
|
+
if (fieldType === 'scalar') {
|
|
983
|
+
incrementCounterMetric(`${metricName}.${areValuesEqual}`);
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
incrementCounterMetric(metricName);
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
getRecordNotifyChangeAllowed: incrementGetRecordNotifyChangeAllowCount,
|
|
990
|
+
getRecordNotifyChangeDropped: incrementGetRecordNotifyChangeDropCount,
|
|
991
|
+
notifyRecordUpdateAvailableAllowed: incrementNotifyRecordUpdateAvailableAllowCount,
|
|
992
|
+
notifyRecordUpdateAvailableDropped: incrementNotifyRecordUpdateAvailableDropCount,
|
|
993
|
+
recordApiNameChanged: instrumentation.incrementRecordApiNameChangeCount.bind(instrumentation),
|
|
994
|
+
weakEtagZero: instrumentation.aggregateWeakETagEvents.bind(instrumentation),
|
|
995
|
+
getRecordNotifyChangeNetworkResult: instrumentation.notifyChangeNetwork.bind(instrumentation),
|
|
996
|
+
});
|
|
997
|
+
withRegistration$1('@salesforce/lds-adapters-uiapi', (reg) => setLdsAdaptersUiapiInstrumentation(reg));
|
|
998
|
+
instrument$1({
|
|
999
|
+
logCrud: logCRUDLightningInteraction,
|
|
1000
|
+
networkResponse: incrementRequestResponseCount,
|
|
1001
|
+
});
|
|
1002
|
+
instrument$2({
|
|
1003
|
+
refreshCalled: instrumentation.handleRefreshApiCall.bind(instrumentation),
|
|
1004
|
+
instrumentAdapter: instrumentation.instrumentAdapter.bind(instrumentation),
|
|
1005
|
+
});
|
|
1006
|
+
instrument$3({
|
|
1007
|
+
timerMetricAddDuration: updateTimerMetric,
|
|
1008
|
+
});
|
|
1009
|
+
// Our getRecord through aggregate-ui CRUD logging has moved
|
|
1010
|
+
// to lds-network-adapter. We still need to respect the
|
|
1011
|
+
// orgs environment setting
|
|
1012
|
+
if (forceRecordTransactionsDisabled === false) {
|
|
1013
|
+
ldsNetworkAdapterInstrument({
|
|
1014
|
+
getRecordAggregateResolve: (cb) => {
|
|
1015
|
+
const { recordId, apiName } = cb();
|
|
1016
|
+
logCRUDLightningInteraction('read', {
|
|
1017
|
+
recordId,
|
|
1018
|
+
recordType: apiName,
|
|
1019
|
+
state: 'SUCCESS',
|
|
1020
|
+
});
|
|
685
1021
|
},
|
|
686
|
-
|
|
1022
|
+
getRecordAggregateReject: (cb) => {
|
|
1023
|
+
const recordId = cb();
|
|
1024
|
+
logCRUDLightningInteraction('read', {
|
|
1025
|
+
recordId,
|
|
1026
|
+
state: 'ERROR',
|
|
1027
|
+
});
|
|
1028
|
+
},
|
|
1029
|
+
});
|
|
687
1030
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1031
|
+
withRegistration$1('@salesforce/lds-network-adapter', (reg) => setLdsNetworkAdapterInstrumentation(reg));
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Initialize the instrumentation and instrument the LDS instance and the InMemoryStore.
|
|
1035
|
+
*
|
|
1036
|
+
* @param luvio The Luvio instance to instrument.
|
|
1037
|
+
* @param store The InMemoryStore to instrument.
|
|
1038
|
+
*/
|
|
1039
|
+
function setupInstrumentation(luvio, store) {
|
|
1040
|
+
setupInstrumentation$1(luvio, store);
|
|
1041
|
+
setAuraInstrumentationHooks();
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Note: locator.scope is set to 'force_record' in order for the instrumentation gate to work, which will
|
|
1045
|
+
* disable all crud operations if it is on.
|
|
1046
|
+
* @param eventSource - Source of the logging event.
|
|
1047
|
+
* @param attributes - Free form object of attributes to log.
|
|
1048
|
+
*/
|
|
1049
|
+
function logCRUDLightningInteraction(eventSource, attributes) {
|
|
1050
|
+
interaction(eventSource, 'force_record', null, eventSource, 'crud', attributes);
|
|
1051
|
+
}
|
|
1052
|
+
const instrumentation = new Instrumentation();
|
|
1053
|
+
|
|
1054
|
+
class ApplicationPredictivePrefetcher {
|
|
1055
|
+
constructor(context, repository, requestRunner) {
|
|
1056
|
+
this.repository = repository;
|
|
1057
|
+
this.requestRunner = requestRunner;
|
|
1058
|
+
this.isRecording = false;
|
|
1059
|
+
this.queuedPredictionRequests = [];
|
|
1060
|
+
this._context = context;
|
|
1061
|
+
this.page = this.getPage();
|
|
705
1062
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
(reqA.actionTypes || []).toString() === (reqB.actionTypes || []).toString() &&
|
|
710
|
-
(reqA.sections || []).toString() === (reqB.sections || []).toString());
|
|
1063
|
+
set context(value) {
|
|
1064
|
+
this._context = value;
|
|
1065
|
+
this.page = this.getPage();
|
|
711
1066
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1067
|
+
get context() {
|
|
1068
|
+
return this._context;
|
|
1069
|
+
}
|
|
1070
|
+
async stopRecording() {
|
|
1071
|
+
this.isRecording = false;
|
|
1072
|
+
await this.repository.flushRequestsToStorage();
|
|
1073
|
+
}
|
|
1074
|
+
startRecording() {
|
|
1075
|
+
this.isRecording = true;
|
|
1076
|
+
this.repository.clearRequestBuffer();
|
|
1077
|
+
}
|
|
1078
|
+
saveRequest(request) {
|
|
1079
|
+
if (!this.isRecording) {
|
|
1080
|
+
return Promise.resolve();
|
|
722
1081
|
}
|
|
723
|
-
return
|
|
1082
|
+
return executeAsyncActivity(METRIC_KEYS.PREDICTIVE_DATA_LOADING_SAVE_REQUEST, (_act) => {
|
|
1083
|
+
const { request: requestToSave, context } = this.page.buildSaveRequestData(request);
|
|
1084
|
+
// No need to differentiate from predictions requests because these
|
|
1085
|
+
// are made from the adapters factory, which are not prediction aware.
|
|
1086
|
+
return this.repository.saveRequest(context, requestToSave);
|
|
1087
|
+
});
|
|
724
1088
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1089
|
+
async predict() {
|
|
1090
|
+
const exactPageRequests = (await this.repository.getPageRequests(this.context)) || [];
|
|
1091
|
+
const similarPageRequests = await this.getSimilarPageRequests();
|
|
1092
|
+
const alwaysRequests = this.page.getAlwaysRunRequests();
|
|
1093
|
+
const predictedRequests = [
|
|
1094
|
+
...alwaysRequests,
|
|
1095
|
+
...this.requestRunner.reduceRequests([
|
|
1096
|
+
...exactPageRequests,
|
|
1097
|
+
...similarPageRequests,
|
|
1098
|
+
...this.page.getAlwaysRunRequests(),
|
|
1099
|
+
]),
|
|
1100
|
+
];
|
|
1101
|
+
this.queuedPredictionRequests.push(...predictedRequests);
|
|
1102
|
+
return Promise.all(predictedRequests.map((request) => this.requestRunner.runRequest(request))).then();
|
|
730
1103
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
this.adapterFactory = getObjectInfoAdapterFactory;
|
|
1104
|
+
getPredictionSummary() {
|
|
1105
|
+
const exactPageRequests = this.repository.getPageRequests(this.context) || [];
|
|
1106
|
+
const similarPageRequests = this.page.similarContext !== undefined
|
|
1107
|
+
? this.repository.getPageRequests(this.page.similarContext)
|
|
1108
|
+
: [];
|
|
1109
|
+
return { exact: exactPageRequests.length, similar: similarPageRequests.length };
|
|
738
1110
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
config: {
|
|
743
|
-
...similarRequest.config,
|
|
744
|
-
objectApiName: context.objectApiName,
|
|
745
|
-
},
|
|
746
|
-
};
|
|
1111
|
+
hasPredictions() {
|
|
1112
|
+
const summary = this.getPredictionSummary();
|
|
1113
|
+
return summary.exact > 0 || summary.similar > 0;
|
|
747
1114
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
1115
|
+
getSimilarPageRequests() {
|
|
1116
|
+
let resolvedSimilarPageRequests = [];
|
|
1117
|
+
if (this.page.similarContext !== undefined) {
|
|
1118
|
+
const similarPageRequests = this.repository.getPageRequests(this.page.similarContext);
|
|
1119
|
+
if (similarPageRequests !== undefined) {
|
|
1120
|
+
resolvedSimilarPageRequests = similarPageRequests.map((request) => this.page.resolveSimilarRequest(request));
|
|
1121
|
+
}
|
|
754
1122
|
}
|
|
755
|
-
return
|
|
756
|
-
request: this.transformForSave(request),
|
|
757
|
-
context,
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
isContextDependent(context, request) {
|
|
761
|
-
return (request.config.objectApiName !== undefined &&
|
|
762
|
-
context.objectApiName === request.config.objectApiName);
|
|
1123
|
+
return resolvedSimilarPageRequests;
|
|
763
1124
|
}
|
|
764
1125
|
}
|
|
765
1126
|
|
|
766
|
-
class
|
|
767
|
-
constructor(
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1127
|
+
class LexPredictivePrefetcher extends ApplicationPredictivePrefetcher {
|
|
1128
|
+
constructor(context, repository, requestRunner,
|
|
1129
|
+
// These strategies need to be in sync with the "predictiveDataLoadCapable" list
|
|
1130
|
+
// from scripts/lds-uiapi-plugin.js
|
|
1131
|
+
requestStrategies) {
|
|
1132
|
+
super(context, repository, requestRunner);
|
|
1133
|
+
this.requestStrategies = requestStrategies;
|
|
1134
|
+
this.page = this.getPage();
|
|
771
1135
|
}
|
|
772
|
-
|
|
773
|
-
|
|
1136
|
+
getPage() {
|
|
1137
|
+
if (RecordHomePage.handlesContext(this.context)) {
|
|
1138
|
+
return new RecordHomePage(this.context, this.requestStrategies);
|
|
1139
|
+
}
|
|
1140
|
+
return new LexDefaultPage(this.context);
|
|
774
1141
|
}
|
|
775
1142
|
}
|
|
776
1143
|
|
|
777
|
-
|
|
778
|
-
const {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
},
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
buildSaveRequestData(similarContext, context, request) {
|
|
800
|
-
if (this.isContextDependent(context, request)) {
|
|
801
|
-
return {
|
|
802
|
-
request: this.transformForSave({
|
|
803
|
-
...request,
|
|
804
|
-
config: {
|
|
805
|
-
...request.config,
|
|
806
|
-
recordIds: ['*'],
|
|
807
|
-
},
|
|
808
|
-
}),
|
|
809
|
-
context: similarContext,
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
return {
|
|
813
|
-
request: this.transformForSave(request),
|
|
814
|
-
context,
|
|
815
|
-
};
|
|
1144
|
+
// Copy-pasted from adapter-utils. This util should be extracted from generated code and imported in prefetch repository.
|
|
1145
|
+
const { keys: ObjectKeys$2 } = Object;
|
|
1146
|
+
const { stringify: JSONStringify } = JSON;
|
|
1147
|
+
const { isArray: ArrayIsArray$1 } = Array;
|
|
1148
|
+
/**
|
|
1149
|
+
* A deterministic JSON stringify implementation. Heavily adapted from https://github.com/epoberezkin/fast-json-stable-stringify.
|
|
1150
|
+
* This is needed because insertion order for JSON.stringify(object) affects output:
|
|
1151
|
+
* JSON.stringify({a: 1, b: 2})
|
|
1152
|
+
* "{"a":1,"b":2}"
|
|
1153
|
+
* JSON.stringify({b: 2, a: 1})
|
|
1154
|
+
* "{"b":2,"a":1}"
|
|
1155
|
+
* @param data Data to be JSON-stringified.
|
|
1156
|
+
* @returns JSON.stringified value with consistent ordering of keys.
|
|
1157
|
+
*/
|
|
1158
|
+
function stableJSONStringify(node) {
|
|
1159
|
+
// This is for Date values.
|
|
1160
|
+
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
1161
|
+
// eslint-disable-next-line no-param-reassign
|
|
1162
|
+
node = node.toJSON();
|
|
816
1163
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
(context.recordId === request.config.recordIds || // some may set this as string instead of array
|
|
820
|
-
(request.config.recordIds.length === 1 &&
|
|
821
|
-
request.config.recordIds[0] === context.recordId));
|
|
822
|
-
return isForContext && isReduceAbleRelatedListConfig(request.config);
|
|
1164
|
+
if (node === undefined) {
|
|
1165
|
+
return;
|
|
823
1166
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
* the individual relatedListAction config only have relatedListId.
|
|
827
|
-
*
|
|
828
|
-
* @param reqA
|
|
829
|
-
* @param reqB
|
|
830
|
-
* @returns boolean
|
|
831
|
-
*/
|
|
832
|
-
canCombine(reqA, reqB) {
|
|
833
|
-
const [recordIdA, recordIdB] = [reqA.recordIds, reqB.recordIds].map((recordIds) => {
|
|
834
|
-
return ArrayIsArray(recordIds)
|
|
835
|
-
? recordIds.length === 1
|
|
836
|
-
? recordIds[0]
|
|
837
|
-
: null
|
|
838
|
-
: recordIds;
|
|
839
|
-
});
|
|
840
|
-
return (recordIdA === recordIdB &&
|
|
841
|
-
recordIdA !== null &&
|
|
842
|
-
isReduceAbleRelatedListConfig(reqA) &&
|
|
843
|
-
isReduceAbleRelatedListConfig(reqB));
|
|
1167
|
+
if (typeof node === 'number') {
|
|
1168
|
+
return isFinite(node) ? '' + node : 'null';
|
|
844
1169
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
[reqA, reqB].forEach(({ relatedListsActionParameters }) => {
|
|
848
|
-
relatedListsActionParameters.forEach(({ relatedListId }) => relatedListsIncluded.add(relatedListId));
|
|
849
|
-
});
|
|
850
|
-
return {
|
|
851
|
-
recordIds: reqA.recordIds,
|
|
852
|
-
relatedListsActionParameters: ArrayFrom(relatedListsIncluded).map((relatedListId) => ({
|
|
853
|
-
relatedListId,
|
|
854
|
-
})),
|
|
855
|
-
};
|
|
1170
|
+
if (typeof node !== 'object') {
|
|
1171
|
+
return JSONStringify(node);
|
|
856
1172
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1173
|
+
let i;
|
|
1174
|
+
let out;
|
|
1175
|
+
if (ArrayIsArray$1(node)) {
|
|
1176
|
+
out = '[';
|
|
1177
|
+
for (i = 0; i < node.length; i++) {
|
|
1178
|
+
if (i) {
|
|
1179
|
+
out += ',';
|
|
1180
|
+
}
|
|
1181
|
+
out += stableJSONStringify(node[i]) || 'null';
|
|
1182
|
+
}
|
|
1183
|
+
return out + ']';
|
|
864
1184
|
}
|
|
865
|
-
|
|
866
|
-
return
|
|
1185
|
+
if (node === null) {
|
|
1186
|
+
return 'null';
|
|
867
1187
|
}
|
|
868
|
-
|
|
869
|
-
|
|
1188
|
+
const keys = ObjectKeys$2(node).sort();
|
|
1189
|
+
out = '';
|
|
1190
|
+
for (i = 0; i < keys.length; i++) {
|
|
1191
|
+
const key = keys[i];
|
|
1192
|
+
const value = stableJSONStringify(node[key]);
|
|
1193
|
+
if (!value) {
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
if (out) {
|
|
1197
|
+
out += ',';
|
|
1198
|
+
}
|
|
1199
|
+
out += JSONStringify(key) + ':' + value;
|
|
870
1200
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1201
|
+
return '{' + out + '}';
|
|
1202
|
+
}
|
|
1203
|
+
function isObject(obj) {
|
|
1204
|
+
return obj !== null && typeof obj === 'object';
|
|
1205
|
+
}
|
|
1206
|
+
function deepEquals(objA, objB) {
|
|
1207
|
+
if (objA === objB)
|
|
1208
|
+
return true;
|
|
1209
|
+
if (objA instanceof Date && objB instanceof Date)
|
|
1210
|
+
return objA.getTime() === objB.getTime();
|
|
1211
|
+
// If one of them is not an object, they are not deeply equal
|
|
1212
|
+
if (!isObject(objA) || !isObject(objB))
|
|
1213
|
+
return false;
|
|
1214
|
+
// Filter out keys set as undefined, we can compare undefined as equals.
|
|
1215
|
+
const keysA = ObjectKeys$2(objA).filter((key) => objA[key] !== undefined);
|
|
1216
|
+
const keysB = ObjectKeys$2(objB).filter((key) => objB[key] !== undefined);
|
|
1217
|
+
// If the objects do not have the same set of keys, they are not deeply equal
|
|
1218
|
+
if (keysA.length !== keysB.length)
|
|
1219
|
+
return false;
|
|
1220
|
+
for (const key of keysA) {
|
|
1221
|
+
const valA = objA[key];
|
|
1222
|
+
const valB = objB[key];
|
|
1223
|
+
const areObjects = isObject(valA) && isObject(valB);
|
|
1224
|
+
// If both values are objects, recursively compare them
|
|
1225
|
+
if (areObjects && !deepEquals(valA, valB))
|
|
1226
|
+
return false;
|
|
1227
|
+
// If only one value is an object or if the values are not strictly equal, they are not deeply equal
|
|
1228
|
+
if (!areObjects && valA !== valB)
|
|
1229
|
+
return false;
|
|
875
1230
|
}
|
|
1231
|
+
return true;
|
|
876
1232
|
}
|
|
877
1233
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
this.adapterName = GET_RELATED_LIST_RECORDS_BATCH_ADAPTER_NAME;
|
|
883
|
-
this.adapterFactory = getRelatedListRecordsBatchAdapterFactory;
|
|
1234
|
+
class PrefetchRepository {
|
|
1235
|
+
constructor(storage) {
|
|
1236
|
+
this.storage = storage;
|
|
1237
|
+
this.requestBuffer = new Map();
|
|
884
1238
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
...similarRequest,
|
|
888
|
-
config: {
|
|
889
|
-
...similarRequest.config,
|
|
890
|
-
parentRecordId: context.recordId,
|
|
891
|
-
},
|
|
892
|
-
};
|
|
1239
|
+
clearRequestBuffer() {
|
|
1240
|
+
this.requestBuffer.clear();
|
|
893
1241
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1242
|
+
async flushRequestsToStorage() {
|
|
1243
|
+
const setPromises = [];
|
|
1244
|
+
for (const [id, batch] of this.requestBuffer) {
|
|
1245
|
+
const page = { id, requests: [] };
|
|
1246
|
+
batch.forEach(({ request, requestTime }) => {
|
|
1247
|
+
const existingRequestEntry = page.requests.find(({ request: storedRequest }) => deepEquals(storedRequest, request));
|
|
1248
|
+
if (existingRequestEntry === undefined) {
|
|
1249
|
+
page.requests.push({
|
|
1250
|
+
request,
|
|
1251
|
+
requestMetadata: {
|
|
1252
|
+
requestTime,
|
|
1253
|
+
},
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
else if (requestTime < existingRequestEntry.requestMetadata.requestTime) {
|
|
1257
|
+
existingRequestEntry.requestMetadata.requestTime = requestTime;
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
setPromises.push(this.storage.set(id, page));
|
|
906
1261
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
context,
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
isContextDependent(context, request) {
|
|
913
|
-
return context.recordId === request.config.parentRecordId;
|
|
1262
|
+
this.clearRequestBuffer();
|
|
1263
|
+
await Promise.all(setPromises);
|
|
914
1264
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
* @param reqA The first GetRelatedListRecordsBatchConfig.
|
|
918
|
-
* @param reqB The first GetRelatedListRecordsBatchConfig.
|
|
919
|
-
* @returns true if the requests can be combined, otherwise false.
|
|
920
|
-
*/
|
|
921
|
-
canCombine(reqA, reqB) {
|
|
922
|
-
return reqA.parentRecordId === reqB.parentRecordId;
|
|
1265
|
+
getKeyId(key) {
|
|
1266
|
+
return stableJSONStringify(key);
|
|
923
1267
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
combineRequests(reqA, reqB) {
|
|
931
|
-
const relatedListParametersMap = new Set(reqA.relatedListParameters.map((relatedListParameter) => {
|
|
932
|
-
return stableJSONStringify$1(relatedListParameter);
|
|
933
|
-
}));
|
|
934
|
-
const reqBRelatedListParametersToAdd = reqB.relatedListParameters.filter((relatedListParameter) => {
|
|
935
|
-
return !relatedListParametersMap.has(stableJSONStringify$1(relatedListParameter));
|
|
1268
|
+
async saveRequest(key, request) {
|
|
1269
|
+
const identifier = this.getKeyId(key);
|
|
1270
|
+
const batchForKey = this.requestBuffer.get(identifier) || [];
|
|
1271
|
+
batchForKey.push({
|
|
1272
|
+
request,
|
|
1273
|
+
requestTime: Date.now(),
|
|
936
1274
|
});
|
|
937
|
-
|
|
938
|
-
|
|
1275
|
+
this.requestBuffer.set(identifier, batchForKey);
|
|
1276
|
+
}
|
|
1277
|
+
getPage(key) {
|
|
1278
|
+
const identifier = stableJSONStringify(key);
|
|
1279
|
+
return this.storage.get(identifier);
|
|
1280
|
+
}
|
|
1281
|
+
getPageRequests(key) {
|
|
1282
|
+
const page = this.getPage(key);
|
|
1283
|
+
if (page === undefined) {
|
|
1284
|
+
return [];
|
|
1285
|
+
}
|
|
1286
|
+
return page.requests.map((requestEntry) => requestEntry.request);
|
|
939
1287
|
}
|
|
940
1288
|
}
|
|
941
1289
|
|
|
942
|
-
class
|
|
1290
|
+
class RequestStrategy {
|
|
1291
|
+
transformForSave(request) {
|
|
1292
|
+
return request;
|
|
1293
|
+
}
|
|
1294
|
+
reduce(requests) {
|
|
1295
|
+
return requests;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
class LuvioAdapterRequestStrategy extends RequestStrategy {
|
|
1300
|
+
/**
|
|
1301
|
+
* Perform any transformations required to prepare the request for saving.
|
|
1302
|
+
*
|
|
1303
|
+
* e.g. If the request is for a record, we move all fields in the fields array
|
|
1304
|
+
* into the optionalFields array
|
|
1305
|
+
*
|
|
1306
|
+
* @param request - The request to transform
|
|
1307
|
+
* @returns
|
|
1308
|
+
*/
|
|
1309
|
+
transformForSave(request) {
|
|
1310
|
+
return request;
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Filter requests to only those that are for this strategy.
|
|
1314
|
+
*
|
|
1315
|
+
* @param unfilteredRequests array of requests to filter
|
|
1316
|
+
* @returns
|
|
1317
|
+
*/
|
|
1318
|
+
filterRequests(unfilteredRequests) {
|
|
1319
|
+
return unfilteredRequests.filter((request) => request.adapterName === this.adapterName);
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Reduce requests by combining those based on a strategies implementations
|
|
1323
|
+
* of canCombine and combineRequests.
|
|
1324
|
+
* @param unfilteredRequests array of requests to filter
|
|
1325
|
+
* @returns
|
|
1326
|
+
*/
|
|
1327
|
+
reduce(unfilteredRequests) {
|
|
1328
|
+
const requests = this.filterRequests(unfilteredRequests);
|
|
1329
|
+
const visitedRequests = new Set();
|
|
1330
|
+
const reducedRequests = [];
|
|
1331
|
+
for (let i = 0, n = requests.length; i < n; i++) {
|
|
1332
|
+
const currentRequest = requests[i];
|
|
1333
|
+
if (!visitedRequests.has(currentRequest)) {
|
|
1334
|
+
const combinedRequest = { ...currentRequest };
|
|
1335
|
+
for (let j = i + 1; j < n; j++) {
|
|
1336
|
+
const hasNotBeenVisited = !visitedRequests.has(requests[j]);
|
|
1337
|
+
const canCombineConfigs = this.canCombine(combinedRequest.config, requests[j].config);
|
|
1338
|
+
if (hasNotBeenVisited && canCombineConfigs) {
|
|
1339
|
+
combinedRequest.config = this.combineRequests(combinedRequest.config, requests[j].config);
|
|
1340
|
+
visitedRequests.add(requests[j]);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
reducedRequests.push(combinedRequest);
|
|
1344
|
+
visitedRequests.add(currentRequest);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return reducedRequests;
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Check if two requests can be combined.
|
|
1351
|
+
*
|
|
1352
|
+
* By default, requests are not combinable.
|
|
1353
|
+
* @param reqA config of request A
|
|
1354
|
+
* @param reqB config of request B
|
|
1355
|
+
* @returns
|
|
1356
|
+
*/
|
|
1357
|
+
canCombine(_reqA, _reqB) {
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Takes two request configs and combines them into a single request config.
|
|
1362
|
+
*
|
|
1363
|
+
* @param reqA config of request A
|
|
1364
|
+
* @param reqB config of request B
|
|
1365
|
+
* @returns
|
|
1366
|
+
*/
|
|
1367
|
+
combineRequests(reqA, _reqB) {
|
|
1368
|
+
// By default, this should never be called since requests aren't combinable
|
|
1369
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
1370
|
+
throw new Error('Not implemented');
|
|
1371
|
+
}
|
|
1372
|
+
return reqA;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Checks adapter config against request context to determine if the request is context dependent.
|
|
1376
|
+
*
|
|
1377
|
+
* By default, requests are not context dependent.
|
|
1378
|
+
* @param request
|
|
1379
|
+
* @returns
|
|
1380
|
+
*/
|
|
1381
|
+
isContextDependent(_context, _request) {
|
|
1382
|
+
return false;
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Builds request for saving,
|
|
1386
|
+
* - transforming the request
|
|
1387
|
+
* - handling the cases where the request is context dependent (this is homework for the subclass)
|
|
1388
|
+
* @param _similarContext Context with at least one parameter as a wildcard '*'
|
|
1389
|
+
* @param context Exact context for a given page
|
|
1390
|
+
* @param request
|
|
1391
|
+
* @returns
|
|
1392
|
+
*/
|
|
1393
|
+
buildSaveRequestData(_similarContext, context, request) {
|
|
1394
|
+
return {
|
|
1395
|
+
request: this.transformForSave(request),
|
|
1396
|
+
context,
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
function normalizeRecordIds$1(recordIds) {
|
|
1402
|
+
if (!Array.isArray(recordIds)) {
|
|
1403
|
+
return [recordIds];
|
|
1404
|
+
}
|
|
1405
|
+
return recordIds;
|
|
1406
|
+
}
|
|
1407
|
+
class GetRecordAvatarsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
943
1408
|
constructor() {
|
|
944
1409
|
super(...arguments);
|
|
945
|
-
this.adapterName = '
|
|
946
|
-
this.adapterFactory =
|
|
1410
|
+
this.adapterName = 'getRecordAvatars';
|
|
1411
|
+
this.adapterFactory = getRecordAvatarsAdapterFactory;
|
|
947
1412
|
}
|
|
948
1413
|
buildConcreteRequest(similarRequest, context) {
|
|
949
1414
|
return {
|
|
950
1415
|
...similarRequest,
|
|
951
1416
|
config: {
|
|
952
1417
|
...similarRequest.config,
|
|
953
|
-
|
|
1418
|
+
recordIds: [context.recordId],
|
|
954
1419
|
},
|
|
955
1420
|
};
|
|
956
1421
|
}
|
|
957
|
-
/**
|
|
958
|
-
*
|
|
959
|
-
* This method returns GetRelatedListRecordsRequest[] that won't be part of a batch request.
|
|
960
|
-
*
|
|
961
|
-
* ADG currently handles the batching of GetRelatedListRecords -> GetRelatedListRecordsBatch
|
|
962
|
-
* https://gitcore.soma.salesforce.com/core-2206/core-public/blob/p4/main/core/ui-laf-components/modules/laf/batchingPortable/reducers/RelatedListRecordsBatchReducer.js
|
|
963
|
-
*
|
|
964
|
-
* For performance reasons (fear to overfetch), we only check that the Single relatedListId is not present in any of the Batch requests,
|
|
965
|
-
* but we don't check for any other parameters.
|
|
966
|
-
*
|
|
967
|
-
* @param unfilteredRequests All of the request available for predictions.
|
|
968
|
-
* @returns GetRelatedListRecordsRequest[] That should be a prediction.
|
|
969
|
-
*/
|
|
970
|
-
reduce(unfilteredRequests) {
|
|
971
|
-
// Batch requests by [parentRecordId]->[RelatedListIds]
|
|
972
|
-
const batchRequests = unfilteredRequests.filter((request) => request.adapterName === GET_RELATED_LIST_RECORDS_BATCH_ADAPTER_NAME).reduce((acc, request) => {
|
|
973
|
-
// required properties, enforced by adapter typecheck
|
|
974
|
-
const { parentRecordId, relatedListParameters } = request.config;
|
|
975
|
-
const existingRlSet = acc.get(parentRecordId) || new Set();
|
|
976
|
-
// relatedListId enforced by adapter typecheck
|
|
977
|
-
relatedListParameters.forEach((rlParam) => existingRlSet.add(rlParam.relatedListId));
|
|
978
|
-
acc.set(parentRecordId, existingRlSet);
|
|
979
|
-
return acc;
|
|
980
|
-
}, new Map());
|
|
981
|
-
const singleRequests = unfilteredRequests.filter((request) => request.adapterName === this.adapterName);
|
|
982
|
-
return singleRequests.filter((request) => {
|
|
983
|
-
// required props enforced by adapter typecheck
|
|
984
|
-
const { parentRecordId, relatedListId } = request.config;
|
|
985
|
-
const batchForParentRecordId = batchRequests.get(parentRecordId);
|
|
986
|
-
return !(batchForParentRecordId && batchForParentRecordId.has(relatedListId));
|
|
987
|
-
});
|
|
988
|
-
}
|
|
989
1422
|
buildSaveRequestData(similarContext, context, request) {
|
|
990
1423
|
if (this.isContextDependent(context, request)) {
|
|
991
1424
|
return {
|
|
@@ -993,7 +1426,7 @@ class GetRelatedListRecordsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
|
993
1426
|
...request,
|
|
994
1427
|
config: {
|
|
995
1428
|
...request.config,
|
|
996
|
-
|
|
1429
|
+
recordIds: ['*'],
|
|
997
1430
|
},
|
|
998
1431
|
}),
|
|
999
1432
|
context: similarContext,
|
|
@@ -1005,684 +1438,597 @@ class GetRelatedListRecordsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
|
1005
1438
|
};
|
|
1006
1439
|
}
|
|
1007
1440
|
isContextDependent(context, request) {
|
|
1008
|
-
return
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
class LexRequestRunner {
|
|
1013
|
-
constructor(luvio) {
|
|
1014
|
-
this.luvio = luvio;
|
|
1015
|
-
this.requestStrategies = {
|
|
1016
|
-
getRecord: new GetRecordRequestStrategy(),
|
|
1017
|
-
getRecords: new GetRecordsRequestStrategy(),
|
|
1018
|
-
getRecordActions: new GetRecordActionsRequestStrategy(),
|
|
1019
|
-
getRecordAvatars: new GetRecordAvatarsRequestStrategy(),
|
|
1020
|
-
getObjectInfo: new GetObjectInfoRequestStrategy(),
|
|
1021
|
-
getObjectInfos: new GetObjectInfosRequestStrategy(),
|
|
1022
|
-
getRelatedListsActions: new GetRelatedListsActionsRequestStrategy(),
|
|
1023
|
-
getRelatedListInfoBatch: new GetRelatedListInfoBatchRequestStrategy(),
|
|
1024
|
-
getRelatedListRecords: new GetRelatedListRecordsRequestStrategy(),
|
|
1025
|
-
getRelatedListRecordsBatch: new GetRelatedListRecordsBatchRequestStrategy(),
|
|
1026
|
-
};
|
|
1441
|
+
return (request.config.recordIds &&
|
|
1442
|
+
(context.recordId === request.config.recordIds || // some may set this as string instead of array
|
|
1443
|
+
(request.config.recordIds.length === 1 &&
|
|
1444
|
+
request.config.recordIds[0] === context.recordId)));
|
|
1027
1445
|
}
|
|
1028
|
-
|
|
1029
|
-
return
|
|
1030
|
-
.map((strategy) => strategy.reduce(requests))
|
|
1031
|
-
.flat();
|
|
1446
|
+
canCombine(reqA, reqB) {
|
|
1447
|
+
return reqA.formFactor === reqB.formFactor;
|
|
1032
1448
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
}
|
|
1038
|
-
return Promise.resolve(undefined);
|
|
1449
|
+
combineRequests(reqA, reqB) {
|
|
1450
|
+
const combined = { ...reqA };
|
|
1451
|
+
combined.recordIds = Array.from(new Set([...normalizeRecordIds$1(reqA.recordIds), ...normalizeRecordIds$1(reqB.recordIds)]));
|
|
1452
|
+
return combined;
|
|
1039
1453
|
}
|
|
1040
1454
|
}
|
|
1041
1455
|
|
|
1042
|
-
class
|
|
1456
|
+
class GetRecordRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
1043
1457
|
constructor() {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
this.data[key] = value;
|
|
1048
|
-
return Promise.resolve();
|
|
1458
|
+
super(...arguments);
|
|
1459
|
+
this.adapterName = 'getRecord';
|
|
1460
|
+
this.adapterFactory = getRecordAdapterFactory;
|
|
1049
1461
|
}
|
|
1050
|
-
|
|
1051
|
-
return
|
|
1462
|
+
buildConcreteRequest(similarRequest, context) {
|
|
1463
|
+
return {
|
|
1464
|
+
...similarRequest,
|
|
1465
|
+
config: {
|
|
1466
|
+
...similarRequest.config,
|
|
1467
|
+
recordId: context.recordId,
|
|
1468
|
+
},
|
|
1469
|
+
};
|
|
1052
1470
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
const auraStorage = createStorage({
|
|
1068
|
-
...DEFAULT_STORAGE_OPTIONS,
|
|
1069
|
-
...options,
|
|
1070
|
-
});
|
|
1071
|
-
const inMemoryStorage = new InMemoryPrefetchStorage();
|
|
1072
|
-
if (auraStorage === null) {
|
|
1073
|
-
return inMemoryStorage;
|
|
1471
|
+
transformForSave(request) {
|
|
1472
|
+
if (request.config.fields === undefined && request.config.optionalFields === undefined) {
|
|
1473
|
+
return request;
|
|
1474
|
+
}
|
|
1475
|
+
let fields = coerceFieldIdArray(request.config.fields) || [];
|
|
1476
|
+
let optionalFields = coerceFieldIdArray(request.config.optionalFields) || [];
|
|
1477
|
+
return {
|
|
1478
|
+
...request,
|
|
1479
|
+
config: {
|
|
1480
|
+
...request.config,
|
|
1481
|
+
fields: undefined,
|
|
1482
|
+
optionalFields: [...fields, ...optionalFields],
|
|
1483
|
+
},
|
|
1484
|
+
};
|
|
1074
1485
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
/**
|
|
1082
|
-
* Because of aura is an event loop hog and we therefore need to minimize asynchronicity in LEX,
|
|
1083
|
-
* we need need to preload predictions and treat read operations sync. Not making it sync, will cause
|
|
1084
|
-
* some request to be sent the network when they could be dedupe against those from the predictions.
|
|
1085
|
-
*
|
|
1086
|
-
* Drawbacks of this approach:
|
|
1087
|
-
* 1. Loading all of aura storage into memory and then updating it based on changes to that in memory
|
|
1088
|
-
* representation means that updates to predictions in aura storage across different tabs will result
|
|
1089
|
-
* in overwrites, not graceful merges of predictions.
|
|
1090
|
-
* 2. If whoever is consuming this tries to get and run predictions before this is done loading,
|
|
1091
|
-
* then they will (potentially incorrectly) think that we don't have any predictions.
|
|
1092
|
-
*/
|
|
1093
|
-
auraStorage.getAll().then((results) => {
|
|
1094
|
-
ObjectKeys(results).forEach((key) => this.inMemoryStorage.set(key, results[key]));
|
|
1095
|
-
});
|
|
1486
|
+
canCombine(reqA, reqB) {
|
|
1487
|
+
// must be same record and
|
|
1488
|
+
return (reqA.recordId === reqB.recordId &&
|
|
1489
|
+
// both requests are fields requests
|
|
1490
|
+
(reqA.optionalFields !== undefined || reqB.optionalFields !== undefined) &&
|
|
1491
|
+
(reqB.fields !== undefined || reqB.optionalFields !== undefined));
|
|
1096
1492
|
}
|
|
1097
|
-
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1493
|
+
combineRequests(reqA, reqB) {
|
|
1494
|
+
const fields = new Set();
|
|
1495
|
+
const optionalFields = new Set();
|
|
1496
|
+
if (reqA.fields !== undefined) {
|
|
1497
|
+
reqA.fields.forEach((field) => fields.add(field));
|
|
1498
|
+
}
|
|
1499
|
+
if (reqB.fields !== undefined) {
|
|
1500
|
+
reqB.fields.forEach((field) => fields.add(field));
|
|
1501
|
+
}
|
|
1502
|
+
if (reqA.optionalFields !== undefined) {
|
|
1503
|
+
reqA.optionalFields.forEach((field) => optionalFields.add(field));
|
|
1504
|
+
}
|
|
1505
|
+
if (reqB.optionalFields !== undefined) {
|
|
1506
|
+
reqB.optionalFields.forEach((field) => optionalFields.add(field));
|
|
1507
|
+
}
|
|
1508
|
+
return {
|
|
1509
|
+
recordId: reqA.recordId,
|
|
1510
|
+
fields: Array.from(fields),
|
|
1511
|
+
optionalFields: Array.from(optionalFields),
|
|
1512
|
+
};
|
|
1106
1513
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1514
|
+
isContextDependent(context, request) {
|
|
1515
|
+
return request.config.recordId === context.recordId;
|
|
1516
|
+
}
|
|
1517
|
+
buildSaveRequestData(similarContext, context, request) {
|
|
1518
|
+
if (this.isContextDependent(context, request)) {
|
|
1519
|
+
return {
|
|
1520
|
+
request: this.transformForSave({
|
|
1521
|
+
...request,
|
|
1522
|
+
config: {
|
|
1523
|
+
...request.config,
|
|
1524
|
+
recordId: '*',
|
|
1525
|
+
},
|
|
1526
|
+
}),
|
|
1527
|
+
context: similarContext,
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
return {
|
|
1531
|
+
request: this.transformForSave(request),
|
|
1532
|
+
context,
|
|
1533
|
+
};
|
|
1110
1534
|
}
|
|
1111
1535
|
}
|
|
1112
1536
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
*
|
|
1121
|
-
* [1] Search "[M1] Lightning Data Service Design Spike" in Quip
|
|
1122
|
-
* [2] Search "Lightning Data Service R.E.A.D.S. Metrics" in Quip
|
|
1123
|
-
*/
|
|
1124
|
-
const OBSERVABILITY_NAMESPACE = 'LIGHTNING.lds.service';
|
|
1125
|
-
const ADAPTER_INVOCATION_COUNT_METRIC_NAME = 'request';
|
|
1126
|
-
const ADAPTER_ERROR_COUNT_METRIC_NAME = 'error';
|
|
1127
|
-
const NETWORK_ADAPTER_RESPONSE_METRIC_NAME = 'network-response';
|
|
1128
|
-
/**
|
|
1129
|
-
* W-8379680
|
|
1130
|
-
* Counter for number of getApex requests.
|
|
1131
|
-
*/
|
|
1132
|
-
const GET_APEX_REQUEST_COUNT = {
|
|
1133
|
-
get() {
|
|
1537
|
+
class GetRecordsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
1538
|
+
constructor() {
|
|
1539
|
+
super(...arguments);
|
|
1540
|
+
this.adapterName = 'getRecords';
|
|
1541
|
+
this.adapterFactory = getRecordsAdapterFactory;
|
|
1542
|
+
}
|
|
1543
|
+
buildConcreteRequest(similarRequest, context) {
|
|
1134
1544
|
return {
|
|
1135
|
-
|
|
1136
|
-
|
|
1545
|
+
...similarRequest,
|
|
1546
|
+
config: {
|
|
1547
|
+
...similarRequest.config,
|
|
1548
|
+
records: [{ ...similarRequest.config.records[0], recordIds: [context.recordId] }],
|
|
1549
|
+
},
|
|
1137
1550
|
};
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1551
|
+
}
|
|
1552
|
+
isContextDependent(context, request) {
|
|
1553
|
+
const isSingleRecordRequest = request.config.records.length === 1 && request.config.records[0].recordIds.length === 1;
|
|
1554
|
+
return isSingleRecordRequest && request.config.records[0].recordIds[0] === context.recordId;
|
|
1555
|
+
}
|
|
1556
|
+
buildSaveRequestData(similarContext, context, request) {
|
|
1557
|
+
if (this.isContextDependent(context, request)) {
|
|
1558
|
+
return {
|
|
1559
|
+
request: this.transformForSave({
|
|
1560
|
+
...request,
|
|
1561
|
+
config: {
|
|
1562
|
+
...request.config,
|
|
1563
|
+
records: [
|
|
1564
|
+
{
|
|
1565
|
+
...request.config.records[0],
|
|
1566
|
+
recordIds: ['*'],
|
|
1567
|
+
},
|
|
1568
|
+
],
|
|
1569
|
+
},
|
|
1570
|
+
}),
|
|
1571
|
+
context: similarContext,
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
return {
|
|
1575
|
+
request: this.transformForSave(request),
|
|
1576
|
+
context: context,
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1162
1580
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
* JSON.stringify({a: 1, b: 2})
|
|
1167
|
-
* "{"a":1,"b":2}"
|
|
1168
|
-
* JSON.stringify({b: 2, a: 1})
|
|
1169
|
-
* "{"b":2,"a":1}"
|
|
1170
|
-
* Modified from the apex implementation to sort arrays non-destructively.
|
|
1171
|
-
* @param data Data to be JSON-stringified.
|
|
1172
|
-
* @returns JSON.stringified value with consistent ordering of keys.
|
|
1173
|
-
*/
|
|
1174
|
-
function stableJSONStringify(node) {
|
|
1175
|
-
// This is for Date values.
|
|
1176
|
-
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
1177
|
-
// eslint-disable-next-line no-param-reassign
|
|
1178
|
-
node = node.toJSON();
|
|
1581
|
+
function normalizeRecordIds(recordIds) {
|
|
1582
|
+
if (!ArrayIsArray$1(recordIds)) {
|
|
1583
|
+
return [recordIds];
|
|
1179
1584
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1585
|
+
return recordIds;
|
|
1586
|
+
}
|
|
1587
|
+
function normalizeApiNames(apiNames) {
|
|
1588
|
+
if (apiNames === undefined || apiNames === null) {
|
|
1589
|
+
return [];
|
|
1182
1590
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1591
|
+
return ArrayIsArray$1(apiNames) ? apiNames : [apiNames];
|
|
1592
|
+
}
|
|
1593
|
+
class GetRecordActionsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
1594
|
+
constructor() {
|
|
1595
|
+
super(...arguments);
|
|
1596
|
+
this.adapterName = 'getRecordActions';
|
|
1597
|
+
this.adapterFactory = getRecordActionsAdapterFactory;
|
|
1185
1598
|
}
|
|
1186
|
-
|
|
1187
|
-
return
|
|
1599
|
+
buildConcreteRequest(similarRequest, context) {
|
|
1600
|
+
return {
|
|
1601
|
+
...similarRequest,
|
|
1602
|
+
config: {
|
|
1603
|
+
...similarRequest.config,
|
|
1604
|
+
recordIds: [context.recordId],
|
|
1605
|
+
},
|
|
1606
|
+
};
|
|
1188
1607
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1608
|
+
buildSaveRequestData(similarContext, context, request) {
|
|
1609
|
+
if (this.isContextDependent(context, request)) {
|
|
1610
|
+
return {
|
|
1611
|
+
request: this.transformForSave({
|
|
1612
|
+
...request,
|
|
1613
|
+
config: {
|
|
1614
|
+
...request.config,
|
|
1615
|
+
recordIds: ['*'],
|
|
1616
|
+
},
|
|
1617
|
+
}),
|
|
1618
|
+
context: similarContext,
|
|
1619
|
+
};
|
|
1201
1620
|
}
|
|
1202
|
-
return
|
|
1621
|
+
return {
|
|
1622
|
+
request: this.transformForSave(request),
|
|
1623
|
+
context,
|
|
1624
|
+
};
|
|
1203
1625
|
}
|
|
1204
|
-
|
|
1205
|
-
return
|
|
1626
|
+
canCombine(reqA, reqB) {
|
|
1627
|
+
return (reqA.retrievalMode === reqB.retrievalMode &&
|
|
1628
|
+
reqA.formFactor === reqB.formFactor &&
|
|
1629
|
+
(reqA.actionTypes || []).toString() === (reqB.actionTypes || []).toString() &&
|
|
1630
|
+
(reqA.sections || []).toString() === (reqB.sections || []).toString());
|
|
1206
1631
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1632
|
+
combineRequests(reqA, reqB) {
|
|
1633
|
+
const combined = { ...reqA };
|
|
1634
|
+
// let's merge the recordIds
|
|
1635
|
+
combined.recordIds = Array.from(new Set([...normalizeRecordIds(reqA.recordIds), ...normalizeRecordIds(reqB.recordIds)]));
|
|
1636
|
+
if (combined.retrievalMode === 'ALL') {
|
|
1637
|
+
const combinedSet = new Set([
|
|
1638
|
+
...normalizeApiNames(combined.apiNames),
|
|
1639
|
+
...normalizeApiNames(reqB.apiNames),
|
|
1640
|
+
]);
|
|
1641
|
+
combined.apiNames = Array.from(combinedSet);
|
|
1214
1642
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1643
|
+
return combined;
|
|
1644
|
+
}
|
|
1645
|
+
isContextDependent(context, request) {
|
|
1646
|
+
return (request.config.recordIds &&
|
|
1647
|
+
(context.recordId === request.config.recordIds || // some may set this as string instead of array
|
|
1648
|
+
(request.config.recordIds.length === 1 &&
|
|
1649
|
+
request.config.recordIds[0] === context.recordId)));
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
class GetObjectInfoRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
1654
|
+
constructor() {
|
|
1655
|
+
super(...arguments);
|
|
1656
|
+
this.adapterName = 'getObjectInfo';
|
|
1657
|
+
this.adapterFactory = getObjectInfoAdapterFactory;
|
|
1658
|
+
}
|
|
1659
|
+
buildConcreteRequest(similarRequest, context) {
|
|
1660
|
+
return {
|
|
1661
|
+
...similarRequest,
|
|
1662
|
+
config: {
|
|
1663
|
+
...similarRequest.config,
|
|
1664
|
+
objectApiName: context.objectApiName,
|
|
1665
|
+
},
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
buildSaveRequestData(similarContext, context, request) {
|
|
1669
|
+
if (this.isContextDependent(context, request)) {
|
|
1670
|
+
return {
|
|
1671
|
+
request: this.transformForSave(request),
|
|
1672
|
+
context: similarContext,
|
|
1673
|
+
};
|
|
1217
1674
|
}
|
|
1218
|
-
|
|
1675
|
+
return {
|
|
1676
|
+
request: this.transformForSave(request),
|
|
1677
|
+
context,
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
isContextDependent(context, request) {
|
|
1681
|
+
return (request.config.objectApiName !== undefined &&
|
|
1682
|
+
context.objectApiName === request.config.objectApiName);
|
|
1219
1683
|
}
|
|
1220
|
-
return '{' + out + '}';
|
|
1221
1684
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1685
|
+
|
|
1686
|
+
class GetObjectInfosRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
1687
|
+
constructor() {
|
|
1688
|
+
super(...arguments);
|
|
1689
|
+
this.adapterName = 'getObjectInfos';
|
|
1690
|
+
this.adapterFactory = getObjectInfosAdapterFactory;
|
|
1691
|
+
}
|
|
1692
|
+
buildConcreteRequest(similarRequest) {
|
|
1693
|
+
return similarRequest;
|
|
1694
|
+
}
|
|
1225
1695
|
}
|
|
1226
1696
|
|
|
1227
|
-
const
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
const REFRESH_PAYLOAD_TARGET = 'adapters';
|
|
1236
|
-
const REFRESH_PAYLOAD_SCOPE = 'lds';
|
|
1237
|
-
const INCOMING_WEAKETAG_0_KEY = 'incoming-weaketag-0';
|
|
1238
|
-
const EXISTING_WEAKETAG_0_KEY = 'existing-weaketag-0';
|
|
1239
|
-
const RECORD_API_NAME_CHANGE_COUNT_METRIC_NAME = 'record-api-name-change-count';
|
|
1240
|
-
const NAMESPACE = 'lds';
|
|
1241
|
-
const NETWORK_TRANSACTION_NAME = 'lds-network';
|
|
1242
|
-
const CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX = 'out-of-ttl-miss';
|
|
1243
|
-
// Aggregate Cache Stats and Metrics for all getApex invocations
|
|
1244
|
-
const getApexCacheStats = registerLdsCacheStats(NORMALIZED_APEX_ADAPTER_NAME);
|
|
1245
|
-
const getApexTtlCacheStats = registerLdsCacheStats(NORMALIZED_APEX_ADAPTER_NAME + ':' + CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX);
|
|
1246
|
-
// Observability (READS)
|
|
1247
|
-
const getApexRequestCountMetric = counter(GET_APEX_REQUEST_COUNT);
|
|
1248
|
-
const totalAdapterRequestSuccessMetric = counter(TOTAL_ADAPTER_REQUEST_SUCCESS_COUNT);
|
|
1249
|
-
const totalAdapterErrorMetric = counter(TOTAL_ADAPTER_ERROR_COUNT);
|
|
1250
|
-
class Instrumentation {
|
|
1697
|
+
const { keys: ObjectKeys$1 } = Object;
|
|
1698
|
+
const { isArray: ArrayIsArray, from: ArrayFrom } = Array;
|
|
1699
|
+
function isReduceAbleRelatedListConfig(config) {
|
|
1700
|
+
return config.relatedListsActionParameters.every((rlReq) => {
|
|
1701
|
+
return rlReq.relatedListId !== undefined && ObjectKeys$1(rlReq).length === 1;
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
class GetRelatedListsActionsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
1251
1705
|
constructor() {
|
|
1252
|
-
|
|
1253
|
-
this.
|
|
1254
|
-
this.
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1706
|
+
super(...arguments);
|
|
1707
|
+
this.adapterName = 'getRelatedListsActions';
|
|
1708
|
+
this.adapterFactory = getRelatedListsActionsAdapterFactory;
|
|
1709
|
+
}
|
|
1710
|
+
buildConcreteRequest(similarRequest, context) {
|
|
1711
|
+
return {
|
|
1712
|
+
...similarRequest,
|
|
1713
|
+
config: {
|
|
1714
|
+
...similarRequest.config,
|
|
1715
|
+
recordIds: [context.recordId],
|
|
1716
|
+
},
|
|
1260
1717
|
};
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
this.
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1718
|
+
}
|
|
1719
|
+
buildSaveRequestData(similarContext, context, request) {
|
|
1720
|
+
if (this.isContextDependent(context, request)) {
|
|
1721
|
+
return {
|
|
1722
|
+
request: this.transformForSave({
|
|
1723
|
+
...request,
|
|
1724
|
+
config: {
|
|
1725
|
+
...request.config,
|
|
1726
|
+
recordIds: ['*'],
|
|
1727
|
+
},
|
|
1728
|
+
}),
|
|
1729
|
+
context: similarContext,
|
|
1730
|
+
};
|
|
1271
1731
|
}
|
|
1272
|
-
|
|
1732
|
+
return {
|
|
1733
|
+
request: this.transformForSave(request),
|
|
1734
|
+
context,
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
isContextDependent(context, request) {
|
|
1738
|
+
const isForContext = request.config.recordIds &&
|
|
1739
|
+
(context.recordId === request.config.recordIds || // some may set this as string instead of array
|
|
1740
|
+
(request.config.recordIds.length === 1 &&
|
|
1741
|
+
request.config.recordIds[0] === context.recordId));
|
|
1742
|
+
return isForContext && isReduceAbleRelatedListConfig(request.config);
|
|
1273
1743
|
}
|
|
1274
1744
|
/**
|
|
1275
|
-
*
|
|
1276
|
-
*
|
|
1277
|
-
*
|
|
1278
|
-
* @param
|
|
1279
|
-
* @
|
|
1745
|
+
* Can only reduce two requests when they have the same recordId, and
|
|
1746
|
+
* the individual relatedListAction config only have relatedListId.
|
|
1747
|
+
*
|
|
1748
|
+
* @param reqA
|
|
1749
|
+
* @param reqB
|
|
1750
|
+
* @returns boolean
|
|
1280
1751
|
*/
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
? getApexTtlCacheStats
|
|
1289
|
-
: registerLdsCacheStats(adapterName + ':' + CACHE_STATS_OUT_OF_TTL_MISS_POSTFIX);
|
|
1290
|
-
/**
|
|
1291
|
-
* W-8076905
|
|
1292
|
-
* Dynamically generated metric. Simple counter for all requests made by this adapter.
|
|
1293
|
-
*/
|
|
1294
|
-
const wireAdapterRequestMetric = isGetApexAdapter
|
|
1295
|
-
? getApexRequestCountMetric
|
|
1296
|
-
: counter(createMetricsKey(OBSERVABILITY_NAMESPACE, ADAPTER_INVOCATION_COUNT_METRIC_NAME, adapterName));
|
|
1297
|
-
const instrumentedAdapter = (config, requestContext) => {
|
|
1298
|
-
// increment overall and adapter request metrics
|
|
1299
|
-
wireAdapterRequestMetric.increment(1);
|
|
1300
|
-
totalAdapterRequestSuccessMetric.increment(1);
|
|
1301
|
-
// execute adapter logic
|
|
1302
|
-
const result = adapter(config, requestContext);
|
|
1303
|
-
// In the case where the adapter returns a non-Pending Snapshot it is constructed out of the store
|
|
1304
|
-
// (cache hit) whereas a Promise<Snapshot> or Pending Snapshot indicates a network request (cache miss).
|
|
1305
|
-
//
|
|
1306
|
-
// Note: we can't do a plain instanceof check for a promise here since the Promise may
|
|
1307
|
-
// originate from another javascript realm (for example: in jest test). Instead we use a
|
|
1308
|
-
// duck-typing approach by checking if the result has a then property.
|
|
1309
|
-
//
|
|
1310
|
-
// For adapters without persistent store:
|
|
1311
|
-
// - total cache hit ratio:
|
|
1312
|
-
// [in-memory cache hit count] / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
1313
|
-
// For adapters with persistent store:
|
|
1314
|
-
// - in-memory cache hit ratio:
|
|
1315
|
-
// [in-memory cache hit count] / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
1316
|
-
// - total cache hit ratio:
|
|
1317
|
-
// ([in-memory cache hit count] + [store cache hit count]) / ([in-memory cache hit count] + [in-memory cache miss count])
|
|
1318
|
-
// if result === null then config is insufficient/invalid so do not log
|
|
1319
|
-
if (isPromise(result)) {
|
|
1320
|
-
stats.logMisses();
|
|
1321
|
-
if (ttl !== undefined) {
|
|
1322
|
-
this.logAdapterCacheMissOutOfTtlDuration(adapterName, config, ttlMissStats, Date.now(), ttl);
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
else if (result !== null) {
|
|
1326
|
-
stats.logHits();
|
|
1327
|
-
}
|
|
1328
|
-
return result;
|
|
1329
|
-
};
|
|
1330
|
-
// Set the name property on the function for debugging purposes.
|
|
1331
|
-
Object.defineProperty(instrumentedAdapter, 'name', {
|
|
1332
|
-
value: name + '__instrumented',
|
|
1752
|
+
canCombine(reqA, reqB) {
|
|
1753
|
+
const [recordIdA, recordIdB] = [reqA.recordIds, reqB.recordIds].map((recordIds) => {
|
|
1754
|
+
return ArrayIsArray(recordIds)
|
|
1755
|
+
? recordIds.length === 1
|
|
1756
|
+
? recordIds[0]
|
|
1757
|
+
: null
|
|
1758
|
+
: recordIds;
|
|
1333
1759
|
});
|
|
1334
|
-
return
|
|
1760
|
+
return (recordIdA === recordIdB &&
|
|
1761
|
+
recordIdA !== null &&
|
|
1762
|
+
isReduceAbleRelatedListConfig(reqA) &&
|
|
1763
|
+
isReduceAbleRelatedListConfig(reqB));
|
|
1335
1764
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1765
|
+
combineRequests(reqA, reqB) {
|
|
1766
|
+
const relatedListsIncluded = new Set();
|
|
1767
|
+
[reqA, reqB].forEach(({ relatedListsActionParameters }) => {
|
|
1768
|
+
relatedListsActionParameters.forEach(({ relatedListId }) => relatedListsIncluded.add(relatedListId));
|
|
1769
|
+
});
|
|
1770
|
+
return {
|
|
1771
|
+
recordIds: reqA.recordIds,
|
|
1772
|
+
relatedListsActionParameters: ArrayFrom(relatedListsIncluded).map((relatedListId) => ({
|
|
1773
|
+
relatedListId,
|
|
1774
|
+
})),
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
class GetRelatedListInfoBatchRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
1780
|
+
constructor() {
|
|
1781
|
+
super(...arguments);
|
|
1782
|
+
this.adapterName = 'getRelatedListInfoBatch';
|
|
1783
|
+
this.adapterFactory = getRelatedListInfoBatchAdapterFactory;
|
|
1355
1784
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
*
|
|
1359
|
-
* @param context The transaction context.
|
|
1360
|
-
*/
|
|
1361
|
-
instrumentLuvio(context) {
|
|
1362
|
-
instrumentLuvio(context);
|
|
1363
|
-
if (this.isRefreshAdapterEvent(context)) {
|
|
1364
|
-
this.aggregateRefreshAdapterEvents(context);
|
|
1365
|
-
}
|
|
1366
|
-
else if (this.isAdapterUnfulfilledError(context)) {
|
|
1367
|
-
this.incrementAdapterRequestErrorCount(context);
|
|
1368
|
-
}
|
|
1369
|
-
else ;
|
|
1785
|
+
buildConcreteRequest(similarRequest, _context) {
|
|
1786
|
+
return similarRequest;
|
|
1370
1787
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
* @param context The transaction context.
|
|
1374
|
-
* @returns Whether or not this is a RefreshAdapterEvent.
|
|
1375
|
-
*/
|
|
1376
|
-
isRefreshAdapterEvent(context) {
|
|
1377
|
-
return context[REFRESH_ADAPTER_EVENT] === true;
|
|
1788
|
+
canCombine(reqA, reqB) {
|
|
1789
|
+
return reqA.parentObjectApiName === reqB.parentObjectApiName;
|
|
1378
1790
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
*/
|
|
1384
|
-
isAdapterUnfulfilledError(context) {
|
|
1385
|
-
return context[ADAPTER_UNFULFILLED_ERROR] === true;
|
|
1791
|
+
combineRequests(reqA, reqB) {
|
|
1792
|
+
const combined = { ...reqA };
|
|
1793
|
+
combined.relatedListNames = Array.from(new Set([...reqA.relatedListNames, ...reqB.relatedListNames]));
|
|
1794
|
+
return combined;
|
|
1386
1795
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
perfStart(NETWORK_TRANSACTION_NAME);
|
|
1396
|
-
if (error === true) {
|
|
1397
|
-
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': 'error' });
|
|
1398
|
-
}
|
|
1399
|
-
else {
|
|
1400
|
-
perfEnd(NETWORK_TRANSACTION_NAME, { 'notify-change-network': uniqueWeakEtags });
|
|
1401
|
-
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
const GET_RELATED_LIST_RECORDS_BATCH_ADAPTER_NAME = 'getRelatedListRecordsBatch';
|
|
1799
|
+
class GetRelatedListRecordsBatchRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
1800
|
+
constructor() {
|
|
1801
|
+
super(...arguments);
|
|
1802
|
+
this.adapterName = GET_RELATED_LIST_RECORDS_BATCH_ADAPTER_NAME;
|
|
1803
|
+
this.adapterFactory = getRelatedListRecordsBatchAdapterFactory;
|
|
1402
1804
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1805
|
+
buildConcreteRequest(similarRequest, context) {
|
|
1806
|
+
return {
|
|
1807
|
+
...similarRequest,
|
|
1808
|
+
config: {
|
|
1809
|
+
...similarRequest.config,
|
|
1810
|
+
parentRecordId: context.recordId,
|
|
1811
|
+
},
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
buildSaveRequestData(similarContext, context, request) {
|
|
1815
|
+
if (this.isContextDependent(context, request)) {
|
|
1816
|
+
return {
|
|
1817
|
+
request: this.transformForSave({
|
|
1818
|
+
...request,
|
|
1819
|
+
config: {
|
|
1820
|
+
...request.config,
|
|
1821
|
+
parentRecordId: '*',
|
|
1822
|
+
},
|
|
1823
|
+
}),
|
|
1824
|
+
context: similarContext,
|
|
1413
1825
|
};
|
|
1414
1826
|
}
|
|
1415
|
-
|
|
1416
|
-
this.
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
this.weakEtagZeroEvents[key][INCOMING_WEAKETAG_0_KEY] += 1;
|
|
1420
|
-
}
|
|
1827
|
+
return {
|
|
1828
|
+
request: this.transformForSave(request),
|
|
1829
|
+
context,
|
|
1830
|
+
};
|
|
1421
1831
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
* - how many times refreshApex is called
|
|
1425
|
-
* - how many times refresh from lightning/uiRecordApi is called
|
|
1426
|
-
* - number of supported calls: refreshApex called on apex adapter
|
|
1427
|
-
* - number of unsupported calls: refreshApex on non-apex adapter
|
|
1428
|
-
* + any use of refresh from uiRecordApi module
|
|
1429
|
-
* - count of refresh calls per adapter
|
|
1430
|
-
* @param context The refresh adapter event.
|
|
1431
|
-
*/
|
|
1432
|
-
aggregateRefreshAdapterEvents(context) {
|
|
1433
|
-
// We are consolidating all apex adapter instrumentation calls under a single key
|
|
1434
|
-
// Adding additional logging that getApex adapters can invoke? Read normalizeAdapterName ts-doc.
|
|
1435
|
-
const adapterName = normalizeAdapterName(context.adapterName);
|
|
1436
|
-
if (this.lastRefreshApiCall === REFRESH_APEX_KEY) {
|
|
1437
|
-
if (isApexAdapter(adapterName)) {
|
|
1438
|
-
this.refreshApiCallEventStats[SUPPORTED_KEY] += 1;
|
|
1439
|
-
}
|
|
1440
|
-
else {
|
|
1441
|
-
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
else if (this.lastRefreshApiCall === REFRESH_UIAPI_KEY) {
|
|
1445
|
-
this.refreshApiCallEventStats[UNSUPPORTED_KEY] += 1;
|
|
1446
|
-
}
|
|
1447
|
-
if (this.refreshAdapterEvents[adapterName] === undefined) {
|
|
1448
|
-
this.refreshAdapterEvents[adapterName] = 0;
|
|
1449
|
-
}
|
|
1450
|
-
this.refreshAdapterEvents[adapterName] += 1;
|
|
1451
|
-
this.lastRefreshApiCall = null;
|
|
1832
|
+
isContextDependent(context, request) {
|
|
1833
|
+
return context.recordId === request.config.parentRecordId;
|
|
1452
1834
|
}
|
|
1453
1835
|
/**
|
|
1454
|
-
*
|
|
1455
|
-
*
|
|
1456
|
-
* @param
|
|
1836
|
+
* Can combine two seperate batch requests if the parentRecordId is the same.
|
|
1837
|
+
* @param reqA The first GetRelatedListRecordsBatchConfig.
|
|
1838
|
+
* @param reqB The first GetRelatedListRecordsBatchConfig.
|
|
1839
|
+
* @returns true if the requests can be combined, otherwise false.
|
|
1457
1840
|
*/
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
// set function call to be used with aggregateRefreshCalls
|
|
1461
|
-
this.lastRefreshApiCall = apiName;
|
|
1841
|
+
canCombine(reqA, reqB) {
|
|
1842
|
+
return reqA.parentRecordId === reqB.parentRecordId;
|
|
1462
1843
|
}
|
|
1463
1844
|
/**
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1845
|
+
* Merge the relatedListParameters together between two combinable batch requests.
|
|
1846
|
+
* @param reqA The first GetRelatedListRecordsBatchConfig.
|
|
1847
|
+
* @param reqB The first GetRelatedListRecordsBatchConfig.
|
|
1848
|
+
* @returns The combined request.
|
|
1466
1849
|
*/
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1850
|
+
combineRequests(reqA, reqB) {
|
|
1851
|
+
const relatedListParametersMap = new Set(reqA.relatedListParameters.map((relatedListParameter) => {
|
|
1852
|
+
return stableJSONStringify(relatedListParameter);
|
|
1853
|
+
}));
|
|
1854
|
+
const reqBRelatedListParametersToAdd = reqB.relatedListParameters.filter((relatedListParameter) => {
|
|
1855
|
+
return !relatedListParametersMap.has(stableJSONStringify(relatedListParameter));
|
|
1856
|
+
});
|
|
1857
|
+
reqA.relatedListParameters = reqA.relatedListParameters.concat(reqBRelatedListParametersToAdd);
|
|
1858
|
+
return reqA;
|
|
1472
1859
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
this.
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
class GetRelatedListRecordsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
1863
|
+
constructor() {
|
|
1864
|
+
super(...arguments);
|
|
1865
|
+
this.adapterName = 'getRelatedListRecords';
|
|
1866
|
+
this.adapterFactory = getRelatedListRecordsAdapterFactory;
|
|
1867
|
+
}
|
|
1868
|
+
buildConcreteRequest(similarRequest, context) {
|
|
1869
|
+
return {
|
|
1870
|
+
...similarRequest,
|
|
1871
|
+
config: {
|
|
1872
|
+
...similarRequest.config,
|
|
1873
|
+
parentRecordId: context.recordId,
|
|
1874
|
+
},
|
|
1483
1875
|
};
|
|
1484
|
-
this.lastRefreshApiCall = null;
|
|
1485
1876
|
}
|
|
1486
1877
|
/**
|
|
1487
|
-
* W-7801618
|
|
1488
|
-
* Counter for occurrences where the incoming record to be merged has a different apiName.
|
|
1489
|
-
* Dynamically generated metric, stored in an {@link RecordApiNameChangeCounters} object.
|
|
1490
1878
|
*
|
|
1491
|
-
*
|
|
1879
|
+
* This method returns GetRelatedListRecordsRequest[] that won't be part of a batch request.
|
|
1492
1880
|
*
|
|
1493
|
-
*
|
|
1881
|
+
* ADG currently handles the batching of GetRelatedListRecords -> GetRelatedListRecordsBatch
|
|
1882
|
+
* https://gitcore.soma.salesforce.com/core-2206/core-public/blob/p4/main/core/ui-laf-components/modules/laf/batchingPortable/reducers/RelatedListRecordsBatchReducer.js
|
|
1883
|
+
*
|
|
1884
|
+
* For performance reasons (fear to overfetch), we only check that the Single relatedListId is not present in any of the Batch requests,
|
|
1885
|
+
* but we don't check for any other parameters.
|
|
1886
|
+
*
|
|
1887
|
+
* @param unfilteredRequests All of the request available for predictions.
|
|
1888
|
+
* @returns GetRelatedListRecordsRequest[] That should be a prediction.
|
|
1494
1889
|
*/
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1890
|
+
reduce(unfilteredRequests) {
|
|
1891
|
+
// Batch requests by [parentRecordId]->[RelatedListIds]
|
|
1892
|
+
const batchRequests = unfilteredRequests.filter((request) => request.adapterName === GET_RELATED_LIST_RECORDS_BATCH_ADAPTER_NAME).reduce((acc, request) => {
|
|
1893
|
+
// required properties, enforced by adapter typecheck
|
|
1894
|
+
const { parentRecordId, relatedListParameters } = request.config;
|
|
1895
|
+
const existingRlSet = acc.get(parentRecordId) || new Set();
|
|
1896
|
+
// relatedListId enforced by adapter typecheck
|
|
1897
|
+
relatedListParameters.forEach((rlParam) => existingRlSet.add(rlParam.relatedListId));
|
|
1898
|
+
acc.set(parentRecordId, existingRlSet);
|
|
1899
|
+
return acc;
|
|
1900
|
+
}, new Map());
|
|
1901
|
+
const singleRequests = unfilteredRequests.filter((request) => request.adapterName === this.adapterName);
|
|
1902
|
+
return singleRequests.filter((request) => {
|
|
1903
|
+
// required props enforced by adapter typecheck
|
|
1904
|
+
const { parentRecordId, relatedListId } = request.config;
|
|
1905
|
+
const batchForParentRecordId = batchRequests.get(parentRecordId);
|
|
1906
|
+
return !(batchForParentRecordId && batchForParentRecordId.has(relatedListId));
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
buildSaveRequestData(similarContext, context, request) {
|
|
1910
|
+
if (this.isContextDependent(context, request)) {
|
|
1911
|
+
return {
|
|
1912
|
+
request: this.transformForSave({
|
|
1913
|
+
...request,
|
|
1914
|
+
config: {
|
|
1915
|
+
...request.config,
|
|
1916
|
+
parentRecordId: '*',
|
|
1917
|
+
},
|
|
1918
|
+
}),
|
|
1919
|
+
context: similarContext,
|
|
1920
|
+
};
|
|
1500
1921
|
}
|
|
1501
|
-
|
|
1922
|
+
return {
|
|
1923
|
+
request: this.transformForSave(request),
|
|
1924
|
+
context,
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
isContextDependent(context, request) {
|
|
1928
|
+
return context.recordId === request.config.parentRecordId;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
class LexRequestRunner {
|
|
1933
|
+
constructor(luvio) {
|
|
1934
|
+
this.luvio = luvio;
|
|
1935
|
+
this.requestStrategies = {
|
|
1936
|
+
getRecord: new GetRecordRequestStrategy(),
|
|
1937
|
+
getRecords: new GetRecordsRequestStrategy(),
|
|
1938
|
+
getRecordActions: new GetRecordActionsRequestStrategy(),
|
|
1939
|
+
getRecordAvatars: new GetRecordAvatarsRequestStrategy(),
|
|
1940
|
+
getObjectInfo: new GetObjectInfoRequestStrategy(),
|
|
1941
|
+
getObjectInfos: new GetObjectInfosRequestStrategy(),
|
|
1942
|
+
getRelatedListsActions: new GetRelatedListsActionsRequestStrategy(),
|
|
1943
|
+
getRelatedListInfoBatch: new GetRelatedListInfoBatchRequestStrategy(),
|
|
1944
|
+
getRelatedListRecords: new GetRelatedListRecordsRequestStrategy(),
|
|
1945
|
+
getRelatedListRecordsBatch: new GetRelatedListRecordsBatchRequestStrategy(),
|
|
1946
|
+
};
|
|
1502
1947
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
let adapterRequestErrorCounter = this.adapterUnfulfilledErrorCounters[adapterName];
|
|
1513
|
-
if (adapterRequestErrorCounter === undefined) {
|
|
1514
|
-
adapterRequestErrorCounter = counter(createMetricsKey(OBSERVABILITY_NAMESPACE, ADAPTER_ERROR_COUNT_METRIC_NAME, adapterName));
|
|
1515
|
-
this.adapterUnfulfilledErrorCounters[adapterName] = adapterRequestErrorCounter;
|
|
1948
|
+
reduceRequests(requests) {
|
|
1949
|
+
return Object.values(this.requestStrategies)
|
|
1950
|
+
.map((strategy) => strategy.reduce(requests))
|
|
1951
|
+
.flat();
|
|
1952
|
+
}
|
|
1953
|
+
runRequest(request) {
|
|
1954
|
+
if (request.adapterName in this.requestStrategies) {
|
|
1955
|
+
const adapterFactory = this.requestStrategies[request.adapterName].adapterFactory;
|
|
1956
|
+
return Promise.resolve(adapterFactory(this.luvio)(request.config)).then();
|
|
1516
1957
|
}
|
|
1517
|
-
|
|
1518
|
-
totalAdapterErrorMetric.increment(1);
|
|
1958
|
+
return Promise.resolve(undefined);
|
|
1519
1959
|
}
|
|
1520
1960
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1961
|
+
|
|
1962
|
+
class InMemoryPrefetchStorage {
|
|
1963
|
+
constructor() {
|
|
1964
|
+
this.data = {};
|
|
1525
1965
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
},
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1532
|
-
/**
|
|
1533
|
-
* Returns whether adapter is an Apex one or not.
|
|
1534
|
-
* @param adapterName The name of the adapter.
|
|
1535
|
-
*/
|
|
1536
|
-
function isApexAdapter(adapterName) {
|
|
1537
|
-
return adapterName.indexOf(APEX_ADAPTER_NAME) > -1;
|
|
1538
|
-
}
|
|
1539
|
-
/**
|
|
1540
|
-
* Normalizes getApex adapter names to `Apex.getApex`. Non-Apex adapters will be prefixed with
|
|
1541
|
-
* API family, if supplied. Example: `UiApi.getRecord`.
|
|
1542
|
-
*
|
|
1543
|
-
* Note: If you are adding additional logging that can come from getApex adapter contexts that provide
|
|
1544
|
-
* the full getApex adapter name (i.e. getApex_[namespace]_[class]_[function]_[continuation]),
|
|
1545
|
-
* ensure to call this method to normalize all logging to 'getApex'. This
|
|
1546
|
-
* is because Argus has a 50k key cardinality limit. More context: W-8379680.
|
|
1547
|
-
*
|
|
1548
|
-
* @param adapterName The name of the adapter.
|
|
1549
|
-
* @param apiFamily The API family of the adapter.
|
|
1550
|
-
*/
|
|
1551
|
-
function normalizeAdapterName(adapterName, apiFamily) {
|
|
1552
|
-
if (isApexAdapter(adapterName)) {
|
|
1553
|
-
return NORMALIZED_APEX_ADAPTER_NAME;
|
|
1966
|
+
set(key, value) {
|
|
1967
|
+
this.data[key] = value;
|
|
1968
|
+
return Promise.resolve();
|
|
1554
1969
|
}
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
const timerMetricTracker = create(null);
|
|
1558
|
-
/**
|
|
1559
|
-
* Calls instrumentation/service telemetry timer
|
|
1560
|
-
* @param name Name of the metric
|
|
1561
|
-
* @param duration number to update backing percentile histogram, negative numbers ignored
|
|
1562
|
-
*/
|
|
1563
|
-
function updateTimerMetric(name, duration) {
|
|
1564
|
-
let metric = timerMetricTracker[name];
|
|
1565
|
-
if (metric === undefined) {
|
|
1566
|
-
metric = timer(createMetricsKey(NAMESPACE, name));
|
|
1567
|
-
timerMetricTracker[name] = metric;
|
|
1970
|
+
get(key) {
|
|
1971
|
+
return this.data[key];
|
|
1568
1972
|
}
|
|
1569
|
-
timerMetricAddDuration(metric, duration);
|
|
1570
1973
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1974
|
+
|
|
1975
|
+
const { keys: ObjectKeys } = Object;
|
|
1976
|
+
const DEFAULT_STORAGE_OPTIONS = {
|
|
1977
|
+
name: 'ldsPredictiveLoading',
|
|
1978
|
+
persistent: true,
|
|
1979
|
+
secure: true,
|
|
1980
|
+
maxSize: 7 * 1024 * 1024,
|
|
1981
|
+
expiration: 12 * 60 * 60,
|
|
1982
|
+
clearOnInit: false,
|
|
1983
|
+
debugLogging: false,
|
|
1984
|
+
version: 3,
|
|
1985
|
+
};
|
|
1986
|
+
function buildAuraPrefetchStorage(options = {}) {
|
|
1987
|
+
const auraStorage = createStorage({
|
|
1988
|
+
...DEFAULT_STORAGE_OPTIONS,
|
|
1989
|
+
...options,
|
|
1990
|
+
});
|
|
1991
|
+
const inMemoryStorage = new InMemoryPrefetchStorage();
|
|
1992
|
+
if (auraStorage === null) {
|
|
1993
|
+
return inMemoryStorage;
|
|
1575
1994
|
}
|
|
1995
|
+
return new AuraPrefetchStorage(auraStorage, inMemoryStorage);
|
|
1576
1996
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1997
|
+
class AuraPrefetchStorage {
|
|
1998
|
+
constructor(auraStorage, inMemoryStorage) {
|
|
1999
|
+
this.auraStorage = auraStorage;
|
|
2000
|
+
this.inMemoryStorage = inMemoryStorage;
|
|
2001
|
+
/**
|
|
2002
|
+
* Because of aura is an event loop hog and we therefore need to minimize asynchronicity in LEX,
|
|
2003
|
+
* we need need to preload predictions and treat read operations sync. Not making it sync, will cause
|
|
2004
|
+
* some request to be sent the network when they could be dedupe against those from the predictions.
|
|
2005
|
+
*
|
|
2006
|
+
* Drawbacks of this approach:
|
|
2007
|
+
* 1. Loading all of aura storage into memory and then updating it based on changes to that in memory
|
|
2008
|
+
* representation means that updates to predictions in aura storage across different tabs will result
|
|
2009
|
+
* in overwrites, not graceful merges of predictions.
|
|
2010
|
+
* 2. If whoever is consuming this tries to get and run predictions before this is done loading,
|
|
2011
|
+
* then they will (potentially incorrectly) think that we don't have any predictions.
|
|
2012
|
+
*/
|
|
2013
|
+
auraStorage.getAll().then((results) => {
|
|
2014
|
+
ObjectKeys(results).forEach((key) => this.inMemoryStorage.set(key, results[key]));
|
|
2015
|
+
});
|
|
1588
2016
|
}
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
* Create a new instrumentation cache stats and return it.
|
|
1596
|
-
*
|
|
1597
|
-
* @param name The cache logger name.
|
|
1598
|
-
*/
|
|
1599
|
-
function registerLdsCacheStats(name) {
|
|
1600
|
-
return registerCacheStats(`${NAMESPACE}:${name}`);
|
|
1601
|
-
}
|
|
1602
|
-
/**
|
|
1603
|
-
* Add or overwrite hooks that require aura implementations
|
|
1604
|
-
*/
|
|
1605
|
-
function setAuraInstrumentationHooks() {
|
|
1606
|
-
instrument({
|
|
1607
|
-
recordConflictsResolved: (serverRequestCount) => {
|
|
1608
|
-
// Ignore 0 values which can originate from ADS bridge
|
|
1609
|
-
if (serverRequestCount > 0) {
|
|
1610
|
-
updatePercentileHistogramMetric('record-conflicts-resolved', serverRequestCount);
|
|
1611
|
-
}
|
|
1612
|
-
},
|
|
1613
|
-
nullDisplayValueConflict: ({ fieldType, areValuesEqual }) => {
|
|
1614
|
-
const metricName = `merge-null-dv-count.${fieldType}`;
|
|
1615
|
-
if (fieldType === 'scalar') {
|
|
1616
|
-
incrementCounterMetric(`${metricName}.${areValuesEqual}`);
|
|
1617
|
-
}
|
|
1618
|
-
else {
|
|
1619
|
-
incrementCounterMetric(metricName);
|
|
2017
|
+
set(key, value) {
|
|
2018
|
+
const inMemoryResult = this.inMemoryStorage.set(key, value);
|
|
2019
|
+
this.auraStorage.set(key, value).catch((error) => {
|
|
2020
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
2021
|
+
// eslint-disable-next-line no-console
|
|
2022
|
+
console.error('Error save LDS prediction: ', error);
|
|
1620
2023
|
}
|
|
1621
|
-
},
|
|
1622
|
-
getRecordNotifyChangeAllowed: incrementGetRecordNotifyChangeAllowCount,
|
|
1623
|
-
getRecordNotifyChangeDropped: incrementGetRecordNotifyChangeDropCount,
|
|
1624
|
-
notifyRecordUpdateAvailableAllowed: incrementNotifyRecordUpdateAvailableAllowCount,
|
|
1625
|
-
notifyRecordUpdateAvailableDropped: incrementNotifyRecordUpdateAvailableDropCount,
|
|
1626
|
-
recordApiNameChanged: instrumentation.incrementRecordApiNameChangeCount.bind(instrumentation),
|
|
1627
|
-
weakEtagZero: instrumentation.aggregateWeakETagEvents.bind(instrumentation),
|
|
1628
|
-
getRecordNotifyChangeNetworkResult: instrumentation.notifyChangeNetwork.bind(instrumentation),
|
|
1629
|
-
});
|
|
1630
|
-
withRegistration('@salesforce/lds-adapters-uiapi', (reg) => setLdsAdaptersUiapiInstrumentation(reg));
|
|
1631
|
-
instrument$1({
|
|
1632
|
-
logCrud: logCRUDLightningInteraction,
|
|
1633
|
-
networkResponse: incrementRequestResponseCount,
|
|
1634
|
-
});
|
|
1635
|
-
instrument$2({
|
|
1636
|
-
refreshCalled: instrumentation.handleRefreshApiCall.bind(instrumentation),
|
|
1637
|
-
instrumentAdapter: instrumentation.instrumentAdapter.bind(instrumentation),
|
|
1638
|
-
});
|
|
1639
|
-
instrument$3({
|
|
1640
|
-
timerMetricAddDuration: updateTimerMetric,
|
|
1641
|
-
});
|
|
1642
|
-
// Our getRecord through aggregate-ui CRUD logging has moved
|
|
1643
|
-
// to lds-network-adapter. We still need to respect the
|
|
1644
|
-
// orgs environment setting
|
|
1645
|
-
if (forceRecordTransactionsDisabled === false) {
|
|
1646
|
-
ldsNetworkAdapterInstrument({
|
|
1647
|
-
getRecordAggregateResolve: (cb) => {
|
|
1648
|
-
const { recordId, apiName } = cb();
|
|
1649
|
-
logCRUDLightningInteraction('read', {
|
|
1650
|
-
recordId,
|
|
1651
|
-
recordType: apiName,
|
|
1652
|
-
state: 'SUCCESS',
|
|
1653
|
-
});
|
|
1654
|
-
},
|
|
1655
|
-
getRecordAggregateReject: (cb) => {
|
|
1656
|
-
const recordId = cb();
|
|
1657
|
-
logCRUDLightningInteraction('read', {
|
|
1658
|
-
recordId,
|
|
1659
|
-
state: 'ERROR',
|
|
1660
|
-
});
|
|
1661
|
-
},
|
|
1662
2024
|
});
|
|
2025
|
+
return inMemoryResult;
|
|
2026
|
+
}
|
|
2027
|
+
get(key) {
|
|
2028
|
+
// we never read from the AuraStorage, except in construction.
|
|
2029
|
+
return this.inMemoryStorage.get(key);
|
|
1663
2030
|
}
|
|
1664
|
-
withRegistration('@salesforce/lds-network-adapter', (reg) => setLdsNetworkAdapterInstrumentation(reg));
|
|
1665
|
-
}
|
|
1666
|
-
/**
|
|
1667
|
-
* Initialize the instrumentation and instrument the LDS instance and the InMemoryStore.
|
|
1668
|
-
*
|
|
1669
|
-
* @param luvio The Luvio instance to instrument.
|
|
1670
|
-
* @param store The InMemoryStore to instrument.
|
|
1671
|
-
*/
|
|
1672
|
-
function setupInstrumentation(luvio, store) {
|
|
1673
|
-
setupInstrumentation$1(luvio, store);
|
|
1674
|
-
setAuraInstrumentationHooks();
|
|
1675
|
-
}
|
|
1676
|
-
/**
|
|
1677
|
-
* Note: locator.scope is set to 'force_record' in order for the instrumentation gate to work, which will
|
|
1678
|
-
* disable all crud operations if it is on.
|
|
1679
|
-
* @param eventSource - Source of the logging event.
|
|
1680
|
-
* @param attributes - Free form object of attributes to log.
|
|
1681
|
-
*/
|
|
1682
|
-
function logCRUDLightningInteraction(eventSource, attributes) {
|
|
1683
|
-
interaction(eventSource, 'force_record', null, eventSource, 'crud', attributes);
|
|
1684
2031
|
}
|
|
1685
|
-
const instrumentation = new Instrumentation();
|
|
1686
2032
|
|
|
1687
2033
|
class NoComposedAdapterTypeError extends TypeError {
|
|
1688
2034
|
constructor(message, resourceRequest) {
|
|
@@ -1900,7 +2246,7 @@ function buildPredictorForContext(context) {
|
|
|
1900
2246
|
});
|
|
1901
2247
|
},
|
|
1902
2248
|
runPredictions() {
|
|
1903
|
-
return __lexPrefetcher.predict();
|
|
2249
|
+
return executeAsyncActivity(METRIC_KEYS.PREDICTIVE_DATA_LOADING_PREDICT, (_act) => __lexPrefetcher.predict());
|
|
1904
2250
|
},
|
|
1905
2251
|
getPredictionSummary() {
|
|
1906
2252
|
return __lexPrefetcher.getPredictionSummary();
|
|
@@ -1912,7 +2258,7 @@ function initializeLDS() {
|
|
|
1912
2258
|
const storeOptions = {
|
|
1913
2259
|
scheduler: () => { },
|
|
1914
2260
|
};
|
|
1915
|
-
const store = new InMemoryStore(storeOptions);
|
|
2261
|
+
const store = new InMemoryStore$1(storeOptions);
|
|
1916
2262
|
const environment = new Environment(store, composedNetworkAdapter);
|
|
1917
2263
|
const luvio = new Luvio(environment, {
|
|
1918
2264
|
instrument: instrumentation.instrumentLuvio.bind(instrumentation),
|
|
@@ -1926,11 +2272,46 @@ function initializeLDS() {
|
|
|
1926
2272
|
setupPredictivePrefetcher(luvio);
|
|
1927
2273
|
}
|
|
1928
2274
|
}
|
|
2275
|
+
// Initializes OneStore in LEX
|
|
2276
|
+
function initializeOneStore() {
|
|
2277
|
+
// Build default set of services
|
|
2278
|
+
const services = {
|
|
2279
|
+
...buildDefaultKeySubscriptionService(),
|
|
2280
|
+
...buildInMemoryMetadataRepositoryService(),
|
|
2281
|
+
...buildInMemoryStoreService(),
|
|
2282
|
+
...buildDefaultTypeRegistryService(),
|
|
2283
|
+
...buildAuraNetworkService(),
|
|
2284
|
+
};
|
|
2285
|
+
const serviceVersions = {
|
|
2286
|
+
keySubscription: "1.0" /* KeySubscriptionServiceInfo.VERSION */,
|
|
2287
|
+
metadataRepository: "1.0" /* MetadataRepositoryServiceInfo.VERSION */,
|
|
2288
|
+
store: "1.0" /* StoreServiceInfo.VERSION */,
|
|
2289
|
+
typeRegistry: "1.0" /* TypeRegistryServiceInfo.VERSION */,
|
|
2290
|
+
auraNetwork: "1.0" /* AuraNetworkServiceInfo.VERSION */,
|
|
2291
|
+
};
|
|
2292
|
+
withRegistration('commandModule', (registration) => {
|
|
2293
|
+
const matchingServices = {};
|
|
2294
|
+
if (Object.entries(registration.services).every(([service, version]) => service in services &&
|
|
2295
|
+
satisfies(serviceVersions[service], version) &&
|
|
2296
|
+
(matchingServices[service] = services[service]))) {
|
|
2297
|
+
registration.setServices(matchingServices);
|
|
2298
|
+
}
|
|
2299
|
+
});
|
|
2300
|
+
}
|
|
2301
|
+
function buildAuraNetworkService() {
|
|
2302
|
+
const auraNetwork = {
|
|
2303
|
+
auraNetwork: executeGlobalControllerRawResponse,
|
|
2304
|
+
};
|
|
2305
|
+
return auraNetwork;
|
|
2306
|
+
}
|
|
1929
2307
|
// service function to be invoked by Aura
|
|
1930
2308
|
function ldsEngineCreator() {
|
|
1931
2309
|
initializeLDS();
|
|
2310
|
+
if (oneStoreEnabled.isOpen({ fallback: false })) {
|
|
2311
|
+
initializeOneStore();
|
|
2312
|
+
}
|
|
1932
2313
|
return { name: 'ldsEngineCreator' };
|
|
1933
2314
|
}
|
|
1934
2315
|
|
|
1935
|
-
export { buildPredictorForContext, ldsEngineCreator as default, initializeLDS };
|
|
1936
|
-
// version: 1.
|
|
2316
|
+
export { buildPredictorForContext, ldsEngineCreator as default, initializeLDS, initializeOneStore };
|
|
2317
|
+
// version: 1.280.0-92c104b03
|