@salesforce/lds-runtime-aura 1.245.0 → 1.247.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 +734 -7
- package/dist/types/aura-instrumentation/main.d.ts +2 -1
- package/dist/types/aura-instrumentation/utils/observability.d.ts +2 -2
- package/dist/types/main.d.ts +16 -0
- package/dist/types/predictive-loading/index.d.ts +6 -0
- package/dist/types/predictive-loading/pages/index.d.ts +3 -0
- package/dist/types/predictive-loading/pages/lex-default-page.d.ts +12 -0
- package/dist/types/predictive-loading/pages/predictive-prefetch-page.d.ts +11 -0
- package/dist/types/predictive-loading/pages/record-home-page.d.ts +46 -0
- package/dist/types/predictive-loading/prefetcher/index.d.ts +2 -0
- package/dist/types/predictive-loading/prefetcher/lex-predictive-prefetcher.d.ts +18 -0
- package/dist/types/predictive-loading/prefetcher/predictive-prefetcher.d.ts +20 -0
- package/dist/types/predictive-loading/repository/index.d.ts +2 -0
- package/dist/types/predictive-loading/repository/prefetch-repository.d.ts +27 -0
- package/dist/types/predictive-loading/repository/utils.d.ts +15 -0
- package/dist/types/predictive-loading/request-runner/index.d.ts +2 -0
- package/dist/types/predictive-loading/request-runner/lex-request-runner.d.ts +10 -0
- package/dist/types/predictive-loading/request-runner/request-runner.d.ts +4 -0
- package/dist/types/predictive-loading/request-strategy/get-record-actions-request-strategy.d.ts +22 -0
- package/dist/types/predictive-loading/request-strategy/get-record-avatars-request-strategy.d.ts +22 -0
- package/dist/types/predictive-loading/request-strategy/get-record-request-strategy.d.ts +18 -0
- package/dist/types/predictive-loading/request-strategy/get-records-request-strategy.d.ts +15 -0
- package/dist/types/predictive-loading/request-strategy/index.d.ts +7 -0
- package/dist/types/predictive-loading/request-strategy/luvio-adapter-request-strategy.d.ts +10 -0
- package/dist/types/predictive-loading/request-strategy/luvio-adapter-request.d.ts +4 -0
- package/dist/types/predictive-loading/request-strategy/request-strategy.d.ts +5 -0
- package/dist/types/predictive-loading/storage/aura-prefetch-storage.d.ts +18 -0
- package/dist/types/predictive-loading/storage/in-memory-prefetch-storage.d.ts +6 -0
- package/dist/types/predictive-loading/storage/index.d.ts +7 -0
- package/dist/types/predictive-loading/storage/local-prefetch-storage.d.ts +7 -0
- package/package.json +9 -6
package/dist/ldsEngineCreator.js
CHANGED
|
@@ -14,15 +14,703 @@
|
|
|
14
14
|
/* proxy-compat-disable */
|
|
15
15
|
import { HttpStatusCode, InMemoryStore, Environment, Luvio, InMemoryStoreQueryEvaluator } from 'force/luvioEngine';
|
|
16
16
|
import ldsTrackedFieldsBehaviorGate from '@salesforce/gate/lds.useNewTrackedFieldBehavior';
|
|
17
|
-
import
|
|
17
|
+
import usePredictiveLoading from '@salesforce/gate/lds.usePredictiveLoading';
|
|
18
|
+
import { getRecordAvatarsAdapterFactory, getRecordAdapterFactory, getRecordsAdapterFactory, getRecordActionsAdapterFactory, instrument, configuration, InMemoryRecordRepresentationQueryEvaluator, UiApiNamespace, RecordRepresentationRepresentationType, registerPrefetcher } from 'force/ldsAdaptersUiapi';
|
|
19
|
+
import { createStorage, clearStorages } from 'force/ldsStorage';
|
|
18
20
|
import { withRegistration, register, setDefaultLuvio } from 'force/ldsEngine';
|
|
19
21
|
import { REFRESH_ADAPTER_EVENT, ADAPTER_UNFULFILLED_ERROR, instrument as instrument$2 } from 'force/ldsBindings';
|
|
20
22
|
import { counter, registerCacheStats, perfStart, perfEnd, registerPeriodicLogger, interaction, timer } from 'instrumentation/service';
|
|
21
|
-
import { LRUCache, instrumentAdapter, instrumentLuvio, setupInstrumentation as setupInstrumentation$1, logObjectInfoChanged as logObjectInfoChanged$1, updatePercentileHistogramMetric, incrementCounterMetric, incrementGetRecordNotifyChangeAllowCount, incrementGetRecordNotifyChangeDropCount, incrementNotifyRecordUpdateAvailableAllowCount, incrementNotifyRecordUpdateAvailableDropCount, setLdsAdaptersUiapiInstrumentation, setLdsNetworkAdapterInstrumentation } from 'force/ldsInstrumentation';
|
|
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';
|
|
22
24
|
import auraNetworkAdapter, { instrument as instrument$1, forceRecordTransactionsDisabled, ldsNetworkAdapterInstrument, dispatchAuraAction, defaultActionConfig } from 'force/ldsNetwork';
|
|
23
25
|
import { instrument as instrument$3 } from 'force/adsBridge';
|
|
24
26
|
import { buildJwtNetworkAdapter } from 'force/ldsNetworkFetchWithJwt';
|
|
25
|
-
|
|
27
|
+
|
|
28
|
+
class PredictivePrefetchPage {
|
|
29
|
+
constructor(context) {
|
|
30
|
+
this.context = context;
|
|
31
|
+
this.similarContext = undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class LexDefaultPage extends PredictivePrefetchPage {
|
|
36
|
+
constructor(context) {
|
|
37
|
+
super(context);
|
|
38
|
+
}
|
|
39
|
+
buildSaveRequestData(request) {
|
|
40
|
+
return { context: this.context, request };
|
|
41
|
+
}
|
|
42
|
+
resolveSimilarRequest(similarRequest) {
|
|
43
|
+
return similarRequest;
|
|
44
|
+
}
|
|
45
|
+
getAlwaysRunRequests() {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class RecordHomePage extends PredictivePrefetchPage {
|
|
51
|
+
constructor(context, requestStrategies) {
|
|
52
|
+
super(context);
|
|
53
|
+
this.requestStrategies = requestStrategies;
|
|
54
|
+
const { recordId: _, ...rest } = this.context;
|
|
55
|
+
this.similarContext = {
|
|
56
|
+
recordId: '*',
|
|
57
|
+
...rest,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
buildSaveRequestData(request) {
|
|
61
|
+
const { adapterName } = request;
|
|
62
|
+
switch (adapterName) {
|
|
63
|
+
case 'getRecord':
|
|
64
|
+
return this.buildGetRecordSaveRequestData(request);
|
|
65
|
+
case 'getRecords':
|
|
66
|
+
return this.buildGetRecordsSaveRequestData(request);
|
|
67
|
+
case 'getRecordActions':
|
|
68
|
+
return this.requestStrategies.getRecordActions.buildGetRecordActionsSaveRequestData(this.similarContext, this.context, request);
|
|
69
|
+
case 'getRecordAvatars':
|
|
70
|
+
return this.requestStrategies.getRecordAvatars.buildGetRecordAvatarsSaveRequestData(this.similarContext, this.context, request);
|
|
71
|
+
default:
|
|
72
|
+
return { request, context: this.context };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
buildGetRecordSaveRequestData(request) {
|
|
76
|
+
if (this.isGetRecordRequestContextDependent(request)) {
|
|
77
|
+
return {
|
|
78
|
+
request: this.requestStrategies.getRecord.transformForSave({
|
|
79
|
+
...request,
|
|
80
|
+
config: {
|
|
81
|
+
...request.config,
|
|
82
|
+
recordId: '*',
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
context: this.similarContext,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
request: this.requestStrategies.getRecord.transformForSave(request),
|
|
90
|
+
context: this.context,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
buildGetRecordsSaveRequestData(request) {
|
|
94
|
+
if (this.isGetRecordsRequestContextDependent(request)) {
|
|
95
|
+
return {
|
|
96
|
+
request: this.requestStrategies.getRecords.transformForSave({
|
|
97
|
+
...request,
|
|
98
|
+
config: {
|
|
99
|
+
...request.config,
|
|
100
|
+
records: [
|
|
101
|
+
{
|
|
102
|
+
...request.config.records[0],
|
|
103
|
+
recordIds: ['*'],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
context: this.context,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
request: this.requestStrategies.getRecords.transformForSave(request),
|
|
113
|
+
context: this.context,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
isGetRecordRequestContextDependent(request) {
|
|
117
|
+
return request.config.recordId === this.context.recordId;
|
|
118
|
+
}
|
|
119
|
+
isGetRecordsRequestContextDependent(request) {
|
|
120
|
+
const isSingleRecordRequest = request.config.records.length === 1 && request.config.records[0].recordIds.length === 1;
|
|
121
|
+
return (isSingleRecordRequest &&
|
|
122
|
+
request.config.records[0].recordIds[0] === this.context.recordId);
|
|
123
|
+
}
|
|
124
|
+
resolveSimilarRequest(similarRequest) {
|
|
125
|
+
if (similarRequest.adapterName === 'getRecord') {
|
|
126
|
+
return this.requestStrategies.getRecord.buildConcreteRequest(similarRequest, this.context);
|
|
127
|
+
}
|
|
128
|
+
if (similarRequest.adapterName === 'getRecords') {
|
|
129
|
+
return this.requestStrategies.getRecords.buildConcreteRequest(similarRequest, this.context);
|
|
130
|
+
}
|
|
131
|
+
if (similarRequest.adapterName === 'getRecordActions') {
|
|
132
|
+
return this.requestStrategies.getRecordActions.buildConcreteRequest(similarRequest, this.context);
|
|
133
|
+
}
|
|
134
|
+
if (similarRequest.adapterName === 'getRecordAvatars') {
|
|
135
|
+
return this.requestStrategies.getRecordAvatars.buildConcreteRequest(similarRequest, this.context);
|
|
136
|
+
}
|
|
137
|
+
return similarRequest;
|
|
138
|
+
}
|
|
139
|
+
// Record Home performs best when we always do a minimal getRecord alongside the other requests.
|
|
140
|
+
getAlwaysRunRequests() {
|
|
141
|
+
const { recordId, objectApiName } = this.context;
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
adapterName: 'getRecord',
|
|
145
|
+
config: {
|
|
146
|
+
recordId,
|
|
147
|
+
optionalFields: [`${objectApiName}.Id`, `${objectApiName}.RecordTypeId`],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
static handlesContext(context) {
|
|
153
|
+
const maybeRecordHomePageContext = context;
|
|
154
|
+
return (maybeRecordHomePageContext !== undefined &&
|
|
155
|
+
maybeRecordHomePageContext.actionName !== undefined &&
|
|
156
|
+
maybeRecordHomePageContext.objectApiName !== undefined &&
|
|
157
|
+
maybeRecordHomePageContext.recordId !== undefined &&
|
|
158
|
+
maybeRecordHomePageContext.type === 'recordPage');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
class ApplicationPredictivePrefetcher {
|
|
163
|
+
constructor(context, repository, requestRunner) {
|
|
164
|
+
this.repository = repository;
|
|
165
|
+
this.requestRunner = requestRunner;
|
|
166
|
+
this.isRecording = false;
|
|
167
|
+
this.queuedPredictionRequests = [];
|
|
168
|
+
this._context = context;
|
|
169
|
+
this.page = this.getPage();
|
|
170
|
+
}
|
|
171
|
+
set context(value) {
|
|
172
|
+
this._context = value;
|
|
173
|
+
this.page = this.getPage();
|
|
174
|
+
}
|
|
175
|
+
get context() {
|
|
176
|
+
return this._context;
|
|
177
|
+
}
|
|
178
|
+
async stopRecording() {
|
|
179
|
+
this.isRecording = false;
|
|
180
|
+
await this.repository.flushRequestsToStorage();
|
|
181
|
+
}
|
|
182
|
+
startRecording() {
|
|
183
|
+
this.isRecording = true;
|
|
184
|
+
this.repository.clearRequestBuffer();
|
|
185
|
+
}
|
|
186
|
+
saveRequest(request) {
|
|
187
|
+
if (!this.isRecording) {
|
|
188
|
+
return Promise.resolve();
|
|
189
|
+
}
|
|
190
|
+
const { request: requestToSave, context } = this.page.buildSaveRequestData(request);
|
|
191
|
+
// No need to diferentiate from predictions requests because these
|
|
192
|
+
// are made from the adapters factory, which are not prediction aware.
|
|
193
|
+
return this.repository.saveRequest(context, requestToSave);
|
|
194
|
+
}
|
|
195
|
+
async predict() {
|
|
196
|
+
const exactPageRequests = (await this.repository.getPageRequests(this.context)) || [];
|
|
197
|
+
const similarPageRequests = await this.getSimilarPageRequests();
|
|
198
|
+
const predictedRequests = [
|
|
199
|
+
...this.requestRunner.reduceRequests([...exactPageRequests, ...similarPageRequests]),
|
|
200
|
+
...this.page.getAlwaysRunRequests(),
|
|
201
|
+
];
|
|
202
|
+
this.queuedPredictionRequests.push(...predictedRequests);
|
|
203
|
+
return Promise.all(predictedRequests.map((request) => this.requestRunner.runRequest(request))).then();
|
|
204
|
+
}
|
|
205
|
+
async getSimilarPageRequests() {
|
|
206
|
+
let resolvedSimilarPageRequests = [];
|
|
207
|
+
if (this.page.similarContext !== undefined) {
|
|
208
|
+
const similarPageRequests = await this.repository.getPageRequests(this.page.similarContext);
|
|
209
|
+
if (similarPageRequests !== undefined) {
|
|
210
|
+
resolvedSimilarPageRequests = similarPageRequests.map((request) => this.page.resolveSimilarRequest(request));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return resolvedSimilarPageRequests;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
class LexPredictivePrefetcher extends ApplicationPredictivePrefetcher {
|
|
218
|
+
constructor(context, repository, requestRunner,
|
|
219
|
+
// These strategies need to be in sync with the "predictiveDataLoadCapable" list
|
|
220
|
+
// from scripts/lds-uiapi-plugin.js
|
|
221
|
+
requestStrategies) {
|
|
222
|
+
super(context, repository, requestRunner);
|
|
223
|
+
this.requestStrategies = requestStrategies;
|
|
224
|
+
this.page = this.getPage();
|
|
225
|
+
}
|
|
226
|
+
getPage() {
|
|
227
|
+
if (RecordHomePage.handlesContext(this.context)) {
|
|
228
|
+
return new RecordHomePage(this.context, this.requestStrategies);
|
|
229
|
+
}
|
|
230
|
+
return new LexDefaultPage(this.context);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Copy-pasted from adapter-utils. This util should be extracted from generated code and imported in prefetch repository.
|
|
235
|
+
const { keys: ObjectKeys } = Object;
|
|
236
|
+
const { stringify: JSONStringify } = JSON;
|
|
237
|
+
const { isArray: ArrayIsArray } = Array;
|
|
238
|
+
/**
|
|
239
|
+
* A deterministic JSON stringify implementation. Heavily adapted from https://github.com/epoberezkin/fast-json-stable-stringify.
|
|
240
|
+
* This is needed because insertion order for JSON.stringify(object) affects output:
|
|
241
|
+
* JSON.stringify({a: 1, b: 2})
|
|
242
|
+
* "{"a":1,"b":2}"
|
|
243
|
+
* JSON.stringify({b: 2, a: 1})
|
|
244
|
+
* "{"b":2,"a":1}"
|
|
245
|
+
* @param data Data to be JSON-stringified.
|
|
246
|
+
* @returns JSON.stringified value with consistent ordering of keys.
|
|
247
|
+
*/
|
|
248
|
+
function stableJSONStringify$1(node) {
|
|
249
|
+
// This is for Date values.
|
|
250
|
+
if (node && node.toJSON && typeof node.toJSON === 'function') {
|
|
251
|
+
// eslint-disable-next-line no-param-reassign
|
|
252
|
+
node = node.toJSON();
|
|
253
|
+
}
|
|
254
|
+
if (node === undefined) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (typeof node === 'number') {
|
|
258
|
+
return isFinite(node) ? '' + node : 'null';
|
|
259
|
+
}
|
|
260
|
+
if (typeof node !== 'object') {
|
|
261
|
+
return JSONStringify(node);
|
|
262
|
+
}
|
|
263
|
+
let i;
|
|
264
|
+
let out;
|
|
265
|
+
if (ArrayIsArray(node)) {
|
|
266
|
+
out = '[';
|
|
267
|
+
for (i = 0; i < node.length; i++) {
|
|
268
|
+
if (i) {
|
|
269
|
+
out += ',';
|
|
270
|
+
}
|
|
271
|
+
out += stableJSONStringify$1(node[i]) || 'null';
|
|
272
|
+
}
|
|
273
|
+
return out + ']';
|
|
274
|
+
}
|
|
275
|
+
if (node === null) {
|
|
276
|
+
return 'null';
|
|
277
|
+
}
|
|
278
|
+
const keys = ObjectKeys(node).sort();
|
|
279
|
+
out = '';
|
|
280
|
+
for (i = 0; i < keys.length; i++) {
|
|
281
|
+
const key = keys[i];
|
|
282
|
+
const value = stableJSONStringify$1(node[key]);
|
|
283
|
+
if (!value) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (out) {
|
|
287
|
+
out += ',';
|
|
288
|
+
}
|
|
289
|
+
out += JSONStringify(key) + ':' + value;
|
|
290
|
+
}
|
|
291
|
+
return '{' + out + '}';
|
|
292
|
+
}
|
|
293
|
+
function isObject(obj) {
|
|
294
|
+
return obj !== null && typeof obj === 'object';
|
|
295
|
+
}
|
|
296
|
+
function deepEquals(objA, objB) {
|
|
297
|
+
if (objA === objB)
|
|
298
|
+
return true;
|
|
299
|
+
if (objA instanceof Date && objB instanceof Date)
|
|
300
|
+
return objA.getTime() === objB.getTime();
|
|
301
|
+
// If one of them is not an object, they are not deeply equal
|
|
302
|
+
if (!isObject(objA) || !isObject(objB))
|
|
303
|
+
return false;
|
|
304
|
+
// Filter out keys set as undefined, we can compare undefined as equals.
|
|
305
|
+
const keysA = ObjectKeys(objA).filter((key) => objA[key] !== undefined);
|
|
306
|
+
const keysB = ObjectKeys(objB).filter((key) => objB[key] !== undefined);
|
|
307
|
+
// If the objects do not have the same set of keys, they are not deeply equal
|
|
308
|
+
if (keysA.length !== keysB.length)
|
|
309
|
+
return false;
|
|
310
|
+
for (const key of keysA) {
|
|
311
|
+
const valA = objA[key];
|
|
312
|
+
const valB = objB[key];
|
|
313
|
+
const areObjects = isObject(valA) && isObject(valB);
|
|
314
|
+
// If both values are objects, recursively compare them
|
|
315
|
+
if (areObjects && !deepEquals(valA, valB))
|
|
316
|
+
return false;
|
|
317
|
+
// If only one value is an object or if the values are not strictly equal, they are not deeply equal
|
|
318
|
+
if (!areObjects && valA !== valB)
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
class PrefetchRepository {
|
|
325
|
+
constructor(storage) {
|
|
326
|
+
this.storage = storage;
|
|
327
|
+
this.requestBuffer = new Map();
|
|
328
|
+
}
|
|
329
|
+
clearRequestBuffer() {
|
|
330
|
+
this.requestBuffer.clear();
|
|
331
|
+
}
|
|
332
|
+
async flushRequestsToStorage() {
|
|
333
|
+
for (const [id, batch] of this.requestBuffer) {
|
|
334
|
+
const rawPage = await this.storage.get(id);
|
|
335
|
+
const page = rawPage === undefined ? { id, requests: [] } : rawPage;
|
|
336
|
+
batch.forEach(({ request, requestTime }) => {
|
|
337
|
+
let existingRequestEntry = page.requests.find(({ request: storedRequest }) => deepEquals(storedRequest, request));
|
|
338
|
+
if (existingRequestEntry === undefined) {
|
|
339
|
+
existingRequestEntry = { request, requestMetadata: { requestTimes: [] } };
|
|
340
|
+
page.requests.push(existingRequestEntry);
|
|
341
|
+
}
|
|
342
|
+
existingRequestEntry.requestMetadata.requestTimes.push(requestTime);
|
|
343
|
+
});
|
|
344
|
+
await this.storage.set(id, page);
|
|
345
|
+
}
|
|
346
|
+
this.clearRequestBuffer();
|
|
347
|
+
}
|
|
348
|
+
async saveRequest(key, request) {
|
|
349
|
+
const identifier = stableJSONStringify$1(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
|
+
async getPage(key) {
|
|
358
|
+
const identifier = stableJSONStringify$1(key);
|
|
359
|
+
const rawPage = await this.storage.get(identifier);
|
|
360
|
+
return rawPage;
|
|
361
|
+
}
|
|
362
|
+
async getPageRequests(key) {
|
|
363
|
+
const page = await this.getPage(key);
|
|
364
|
+
if (page === undefined) {
|
|
365
|
+
return [];
|
|
366
|
+
}
|
|
367
|
+
return page.requests.map((requestEntry) => requestEntry.request);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
class RequestStrategy {
|
|
372
|
+
transformForSave(request) {
|
|
373
|
+
return request;
|
|
374
|
+
}
|
|
375
|
+
reduce(requests) {
|
|
376
|
+
return requests;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
class LuvioAdapterRequestStrategy extends RequestStrategy {
|
|
381
|
+
transformForSave(request) {
|
|
382
|
+
return request;
|
|
383
|
+
}
|
|
384
|
+
filterRequests(unfilteredRequests) {
|
|
385
|
+
return unfilteredRequests.filter((request) => request.adapterName === this.adapterName);
|
|
386
|
+
}
|
|
387
|
+
reduce(unfilteredRequests) {
|
|
388
|
+
return this.filterRequests(unfilteredRequests);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function normalizeRecordIds$1(recordIds) {
|
|
393
|
+
if (!Array.isArray(recordIds)) {
|
|
394
|
+
return [recordIds];
|
|
395
|
+
}
|
|
396
|
+
return recordIds;
|
|
397
|
+
}
|
|
398
|
+
function canCombine$1(reqA, reqB) {
|
|
399
|
+
return reqA.formFactor === reqB.formFactor;
|
|
400
|
+
}
|
|
401
|
+
function combineRequests$1(reqA, reqB) {
|
|
402
|
+
const combined = { ...reqA };
|
|
403
|
+
combined.recordIds = Array.from(new Set([...normalizeRecordIds$1(reqA.recordIds), ...normalizeRecordIds$1(reqB.recordIds)]));
|
|
404
|
+
return combined;
|
|
405
|
+
}
|
|
406
|
+
class GetRecordAvatarsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
407
|
+
constructor() {
|
|
408
|
+
super(...arguments);
|
|
409
|
+
this.adapterName = 'getRecordAvatars';
|
|
410
|
+
this.adapterFactory = getRecordAvatarsAdapterFactory;
|
|
411
|
+
}
|
|
412
|
+
buildConcreteRequest(similarRequest, context) {
|
|
413
|
+
return {
|
|
414
|
+
...similarRequest,
|
|
415
|
+
config: {
|
|
416
|
+
...similarRequest.config,
|
|
417
|
+
recordIds: [context.recordId],
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
buildGetRecordAvatarsSaveRequestData(similarContext, context, request) {
|
|
422
|
+
if (this.isGetRecordAvatarsRequestContextDependent(context, request)) {
|
|
423
|
+
return {
|
|
424
|
+
request: this.transformForSave({
|
|
425
|
+
...request,
|
|
426
|
+
config: {
|
|
427
|
+
...request.config,
|
|
428
|
+
recordIds: ['*'],
|
|
429
|
+
},
|
|
430
|
+
}),
|
|
431
|
+
context: similarContext,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return {
|
|
435
|
+
request: this.transformForSave(request),
|
|
436
|
+
context,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
isGetRecordAvatarsRequestContextDependent(context, request) {
|
|
440
|
+
return (request.config.recordIds &&
|
|
441
|
+
(context.recordId === request.config.recordIds || // some may set this as string instead of array
|
|
442
|
+
(request.config.recordIds.length === 1 &&
|
|
443
|
+
request.config.recordIds[0] === context.recordId)));
|
|
444
|
+
}
|
|
445
|
+
reduce(unfilteredRequests) {
|
|
446
|
+
const requests = this.filterRequests(unfilteredRequests);
|
|
447
|
+
const visitedRequests = new Set();
|
|
448
|
+
const reducedRequests = [];
|
|
449
|
+
for (let i = 0, n = requests.length; i < n; i++) {
|
|
450
|
+
const currentRequest = requests[i];
|
|
451
|
+
if (!visitedRequests.has(currentRequest)) {
|
|
452
|
+
const combinedRequest = { ...currentRequest };
|
|
453
|
+
for (let j = i + 1; j < n; j++) {
|
|
454
|
+
const hasNotBeenVisited = !visitedRequests.has(requests[j]);
|
|
455
|
+
const canCombineConfigs = canCombine$1(combinedRequest.config, requests[j].config);
|
|
456
|
+
if (hasNotBeenVisited && canCombineConfigs) {
|
|
457
|
+
combinedRequest.config = combineRequests$1(combinedRequest.config, requests[j].config);
|
|
458
|
+
visitedRequests.add(requests[j]);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
reducedRequests.push(combinedRequest);
|
|
462
|
+
visitedRequests.add(currentRequest);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return reducedRequests;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
class GetRecordRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
470
|
+
constructor() {
|
|
471
|
+
super(...arguments);
|
|
472
|
+
this.adapterName = 'getRecord';
|
|
473
|
+
this.adapterFactory = getRecordAdapterFactory;
|
|
474
|
+
}
|
|
475
|
+
buildConcreteRequest(similarRequest, context) {
|
|
476
|
+
return {
|
|
477
|
+
...similarRequest,
|
|
478
|
+
config: {
|
|
479
|
+
...similarRequest.config,
|
|
480
|
+
recordId: context.recordId,
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
transformForSave(request) {
|
|
485
|
+
if (request.config.fields === undefined && request.config.optionalFields === undefined) {
|
|
486
|
+
return request;
|
|
487
|
+
}
|
|
488
|
+
let optionalFields = request.config.optionalFields || [];
|
|
489
|
+
if (!ArrayIsArray(optionalFields)) {
|
|
490
|
+
optionalFields = [optionalFields];
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
...request,
|
|
494
|
+
config: {
|
|
495
|
+
...request.config,
|
|
496
|
+
fields: undefined,
|
|
497
|
+
optionalFields: [...(request.config.fields || []), ...optionalFields],
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
reduce(unfilteredRequests) {
|
|
502
|
+
const requests = this.filterRequests(unfilteredRequests);
|
|
503
|
+
const recordIdToRequestMap = {};
|
|
504
|
+
const resultRequests = [];
|
|
505
|
+
requests.forEach((request) => {
|
|
506
|
+
if (request.config.fields === undefined &&
|
|
507
|
+
request.config.optionalFields === undefined) {
|
|
508
|
+
resultRequests.push(request);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (recordIdToRequestMap[request.config.recordId] === undefined) {
|
|
512
|
+
recordIdToRequestMap[request.config.recordId] = [];
|
|
513
|
+
}
|
|
514
|
+
recordIdToRequestMap[request.config.recordId].push(request);
|
|
515
|
+
});
|
|
516
|
+
Object.entries(recordIdToRequestMap).forEach(([recordId, requests]) => {
|
|
517
|
+
const fields = new Set();
|
|
518
|
+
const optionalFields = new Set();
|
|
519
|
+
requests.forEach((request) => {
|
|
520
|
+
if (request.config.fields !== undefined) {
|
|
521
|
+
request.config.fields.forEach((field) => {
|
|
522
|
+
fields.add(field);
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
if (request.config.optionalFields !== undefined) {
|
|
526
|
+
request.config.optionalFields.forEach((field) => {
|
|
527
|
+
optionalFields.add(field);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
resultRequests.push({
|
|
532
|
+
adapterName: 'getRecord',
|
|
533
|
+
config: {
|
|
534
|
+
recordId,
|
|
535
|
+
fields: Array.from(fields),
|
|
536
|
+
optionalFields: Array.from(optionalFields),
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
return resultRequests;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
class GetRecordsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
545
|
+
constructor() {
|
|
546
|
+
super(...arguments);
|
|
547
|
+
this.adapterName = 'getRecords';
|
|
548
|
+
this.adapterFactory = getRecordsAdapterFactory;
|
|
549
|
+
}
|
|
550
|
+
buildConcreteRequest(similarRequest, context) {
|
|
551
|
+
return {
|
|
552
|
+
...similarRequest,
|
|
553
|
+
config: {
|
|
554
|
+
...similarRequest.config,
|
|
555
|
+
records: [{ ...similarRequest.config.records[0], recordIds: [context.recordId] }],
|
|
556
|
+
},
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function normalizeRecordIds(recordIds) {
|
|
562
|
+
if (!Array.isArray(recordIds)) {
|
|
563
|
+
return [recordIds];
|
|
564
|
+
}
|
|
565
|
+
return recordIds;
|
|
566
|
+
}
|
|
567
|
+
function canCombine(reqA, reqB) {
|
|
568
|
+
return (reqA.retrievalMode === reqB.retrievalMode &&
|
|
569
|
+
reqA.formFactor === reqB.formFactor &&
|
|
570
|
+
(reqA.actionTypes || []).toString() === (reqB.actionTypes || []).toString() &&
|
|
571
|
+
(reqA.sections || []).toString() === (reqB.sections || []).toString());
|
|
572
|
+
}
|
|
573
|
+
function combineRequests(reqA, reqB) {
|
|
574
|
+
const combined = { ...reqA };
|
|
575
|
+
// let's merge the recordIds
|
|
576
|
+
combined.recordIds = Array.from(new Set([...normalizeRecordIds(reqA.recordIds), ...normalizeRecordIds(reqB.recordIds)]));
|
|
577
|
+
if (combined.retrievalMode === 'ALL') {
|
|
578
|
+
const combinedSet = new Set([...(combined.apiNames || []), ...(reqB.apiNames || [])]);
|
|
579
|
+
combined.apiNames = Array.from(combinedSet);
|
|
580
|
+
}
|
|
581
|
+
return combined;
|
|
582
|
+
}
|
|
583
|
+
class GetRecordActionsRequestStrategy extends LuvioAdapterRequestStrategy {
|
|
584
|
+
constructor() {
|
|
585
|
+
super(...arguments);
|
|
586
|
+
this.adapterName = 'getRecordActions';
|
|
587
|
+
this.adapterFactory = getRecordActionsAdapterFactory;
|
|
588
|
+
}
|
|
589
|
+
buildConcreteRequest(similarRequest, context) {
|
|
590
|
+
return {
|
|
591
|
+
...similarRequest,
|
|
592
|
+
config: {
|
|
593
|
+
...similarRequest.config,
|
|
594
|
+
recordIds: [context.recordId],
|
|
595
|
+
},
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
buildGetRecordActionsSaveRequestData(similarContext, context, request) {
|
|
599
|
+
if (this.isGetRecordActionsRequestContextDependent(context, request)) {
|
|
600
|
+
return {
|
|
601
|
+
request: this.transformForSave({
|
|
602
|
+
...request,
|
|
603
|
+
config: {
|
|
604
|
+
...request.config,
|
|
605
|
+
recordIds: ['*'],
|
|
606
|
+
},
|
|
607
|
+
}),
|
|
608
|
+
context: similarContext,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
request: this.transformForSave(request),
|
|
613
|
+
context,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
isGetRecordActionsRequestContextDependent(context, request) {
|
|
617
|
+
return (request.config.recordIds &&
|
|
618
|
+
(context.recordId === request.config.recordIds || // some may set this as string instead of array
|
|
619
|
+
(request.config.recordIds.length === 1 &&
|
|
620
|
+
request.config.recordIds[0] === context.recordId)));
|
|
621
|
+
}
|
|
622
|
+
reduce(unfilteredRequests) {
|
|
623
|
+
const requests = this.filterRequests(unfilteredRequests);
|
|
624
|
+
const visitedRequests = new Set();
|
|
625
|
+
const reducedRequests = [];
|
|
626
|
+
for (let i = 0, n = requests.length; i < n; i++) {
|
|
627
|
+
const currentRequest = requests[i];
|
|
628
|
+
if (!visitedRequests.has(currentRequest)) {
|
|
629
|
+
const combinedRequest = { ...currentRequest };
|
|
630
|
+
for (let j = i + 1; j < n; j++) {
|
|
631
|
+
if (!visitedRequests.has(requests[j]) &&
|
|
632
|
+
canCombine(combinedRequest.config, requests[j].config)) {
|
|
633
|
+
combinedRequest.config = combineRequests(combinedRequest.config, requests[j].config);
|
|
634
|
+
visitedRequests.add(requests[j]);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
reducedRequests.push(combinedRequest);
|
|
638
|
+
visitedRequests.add(currentRequest);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return reducedRequests;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
class LexRequestRunner {
|
|
646
|
+
constructor(luvio) {
|
|
647
|
+
this.luvio = luvio;
|
|
648
|
+
this.requestStrategies = {
|
|
649
|
+
getRecord: new GetRecordRequestStrategy(),
|
|
650
|
+
getRecords: new GetRecordsRequestStrategy(),
|
|
651
|
+
getRecordActions: new GetRecordActionsRequestStrategy(),
|
|
652
|
+
getRecordAvatars: new GetRecordAvatarsRequestStrategy(),
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
reduceRequests(requests) {
|
|
656
|
+
return Object.values(this.requestStrategies)
|
|
657
|
+
.map((strategy) => strategy.reduce(requests))
|
|
658
|
+
.flat();
|
|
659
|
+
}
|
|
660
|
+
runRequest(request) {
|
|
661
|
+
if (request.adapterName in this.requestStrategies) {
|
|
662
|
+
const adapterFactory = this.requestStrategies[request.adapterName].adapterFactory;
|
|
663
|
+
return Promise.resolve(adapterFactory(this.luvio)(request.config)).then();
|
|
664
|
+
}
|
|
665
|
+
return Promise.resolve(undefined);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
class InMemoryPrefetchStorage {
|
|
670
|
+
constructor() {
|
|
671
|
+
this.data = {};
|
|
672
|
+
}
|
|
673
|
+
set(key, value) {
|
|
674
|
+
this.data[key] = value;
|
|
675
|
+
return Promise.resolve();
|
|
676
|
+
}
|
|
677
|
+
get(key) {
|
|
678
|
+
return Promise.resolve(this.data[key]);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const DEFAULT_STORAGE_OPTIONS = {
|
|
683
|
+
name: 'ldsPredictiveLoading',
|
|
684
|
+
persistent: true,
|
|
685
|
+
secure: true,
|
|
686
|
+
maxSize: 20 * 1024 * 1024,
|
|
687
|
+
// @todo: there's no way of setting a "no expire" value. We should determine the best ttl for this cache.
|
|
688
|
+
expiration: 24 * 60 * 60,
|
|
689
|
+
clearOnInit: false,
|
|
690
|
+
debugLogging: false,
|
|
691
|
+
};
|
|
692
|
+
function buildAuraPrefetchStorage(options = {}) {
|
|
693
|
+
const auraStorage = createStorage({
|
|
694
|
+
...DEFAULT_STORAGE_OPTIONS,
|
|
695
|
+
...options,
|
|
696
|
+
});
|
|
697
|
+
if (auraStorage === null) {
|
|
698
|
+
return new InMemoryPrefetchStorage();
|
|
699
|
+
}
|
|
700
|
+
return new AuraPrefetchStorage(auraStorage);
|
|
701
|
+
}
|
|
702
|
+
class AuraPrefetchStorage {
|
|
703
|
+
constructor(auraStorage) {
|
|
704
|
+
this.auraStorage = auraStorage;
|
|
705
|
+
}
|
|
706
|
+
set(key, value) {
|
|
707
|
+
return this.auraStorage.set(key, value);
|
|
708
|
+
}
|
|
709
|
+
async get(key) {
|
|
710
|
+
const result = await this.auraStorage.get(key);
|
|
711
|
+
return result ? result : undefined;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
26
714
|
|
|
27
715
|
/**
|
|
28
716
|
* Observability / Critical Availability Program (230+)
|
|
@@ -32,8 +720,8 @@ import { clearStorages } from 'force/ldsStorage';
|
|
|
32
720
|
*
|
|
33
721
|
* Below are the R.E.A.D.S. metrics for the Lightning Data Service, defined here[2].
|
|
34
722
|
*
|
|
35
|
-
* [1]
|
|
36
|
-
* [2]
|
|
723
|
+
* [1] Search "[M1] Lightning Data Service Design Spike" in Quip
|
|
724
|
+
* [2] Search "Lightning Data Service R.E.A.D.S. Metrics" in Quip
|
|
37
725
|
*/
|
|
38
726
|
const OBSERVABILITY_NAMESPACE = 'LIGHTNING.lds.service';
|
|
39
727
|
const ADAPTER_INVOCATION_COUNT_METRIC_NAME = 'request';
|
|
@@ -750,6 +1438,42 @@ function setupQueryEvaluators(luvio, store) {
|
|
|
750
1438
|
luvio.registerStoreQueryEvaluator(baseQueryEvaluator);
|
|
751
1439
|
luvio.registerTypeQueryEvaluator(UiApiNamespace, RecordRepresentationRepresentationType, recordRepresentationQueryEvaluator);
|
|
752
1440
|
}
|
|
1441
|
+
let __lexPrefetcher;
|
|
1442
|
+
function setupPredictivePrefetcher(luvio) {
|
|
1443
|
+
const storage = buildAuraPrefetchStorage();
|
|
1444
|
+
const repository = new PrefetchRepository(storage);
|
|
1445
|
+
const requestRunner = new LexRequestRunner(luvio);
|
|
1446
|
+
const prefetcher = new LexPredictivePrefetcher({ context: 'unknown' }, repository, requestRunner, {
|
|
1447
|
+
getRecord: new GetRecordRequestStrategy(),
|
|
1448
|
+
getRecords: new GetRecordsRequestStrategy(),
|
|
1449
|
+
getRecordActions: new GetRecordActionsRequestStrategy(),
|
|
1450
|
+
getRecordAvatars: new GetRecordAvatarsRequestStrategy(),
|
|
1451
|
+
});
|
|
1452
|
+
registerPrefetcher(luvio, prefetcher);
|
|
1453
|
+
__lexPrefetcher = prefetcher;
|
|
1454
|
+
}
|
|
1455
|
+
// Triggers a payload.
|
|
1456
|
+
async function predictiveLoadPage(preloadProps) {
|
|
1457
|
+
// the gate is disabled and the prefetcher was not setup.
|
|
1458
|
+
if (__lexPrefetcher === undefined) {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
// This chunk configures which page we're going to use to try and preload.
|
|
1462
|
+
const { objectApiName } = preloadProps.context;
|
|
1463
|
+
const { recordId, actionName } = preloadProps.pageReference.attributes;
|
|
1464
|
+
__lexPrefetcher.context = {
|
|
1465
|
+
objectApiName,
|
|
1466
|
+
recordId,
|
|
1467
|
+
actionName,
|
|
1468
|
+
type: 'recordPage',
|
|
1469
|
+
};
|
|
1470
|
+
// This chunk tells the prefetcher to receive events, send off any predictions we have from previous loads, then setup idle detection to stop predicting.
|
|
1471
|
+
__lexPrefetcher.startRecording();
|
|
1472
|
+
__lexPrefetcher.predict();
|
|
1473
|
+
onIdleDetected(() => {
|
|
1474
|
+
__lexPrefetcher.stopRecording();
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
753
1477
|
// LDS initialization logic, invoked directly by Aura component tests
|
|
754
1478
|
function initializeLDS() {
|
|
755
1479
|
const storeOptions = {
|
|
@@ -765,6 +1489,9 @@ function initializeLDS() {
|
|
|
765
1489
|
setupQueryEvaluators(luvio, store);
|
|
766
1490
|
setDefaultLuvio({ luvio });
|
|
767
1491
|
setTrackedFieldsConfig(ldsTrackedFieldsBehaviorGate.isOpen({ fallback: false }));
|
|
1492
|
+
if (usePredictiveLoading.isOpen({ fallback: false })) {
|
|
1493
|
+
setupPredictivePrefetcher(luvio);
|
|
1494
|
+
}
|
|
768
1495
|
}
|
|
769
1496
|
// service function to be invoked by Aura
|
|
770
1497
|
function ldsEngineCreator() {
|
|
@@ -772,5 +1499,5 @@ function ldsEngineCreator() {
|
|
|
772
1499
|
return { name: 'ldsEngineCreator' };
|
|
773
1500
|
}
|
|
774
1501
|
|
|
775
|
-
export { ldsEngineCreator as default, initializeLDS };
|
|
776
|
-
// version: 1.
|
|
1502
|
+
export { ldsEngineCreator as default, initializeLDS, predictiveLoadPage };
|
|
1503
|
+
// version: 1.247.0-4fe38c091
|
|
@@ -2,6 +2,8 @@ import type { FetchResponse, Luvio, InMemoryStore, Adapter, UnfulfilledSnapshot
|
|
|
2
2
|
import type { AdapterMetadata } from '@salesforce/lds-bindings';
|
|
3
3
|
import { ADAPTER_UNFULFILLED_ERROR } from '@salesforce/lds-bindings';
|
|
4
4
|
import type { CacheStatsLogger, Timer } from 'instrumentation/service';
|
|
5
|
+
import { onIdleDetected } from '@salesforce/lds-instrumentation';
|
|
6
|
+
export { onIdleDetected };
|
|
5
7
|
export interface AdapterUnfulfilledError {
|
|
6
8
|
[ADAPTER_UNFULFILLED_ERROR]: boolean;
|
|
7
9
|
adapterName: string;
|
|
@@ -176,4 +178,3 @@ export declare function setupInstrumentation(luvio: Luvio, store: InMemoryStore)
|
|
|
176
178
|
*/
|
|
177
179
|
export declare function logCRUDLightningInteraction(eventSource: string, attributes: object): void;
|
|
178
180
|
export declare const instrumentation: Instrumentation;
|
|
179
|
-
export {};
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Below are the R.E.A.D.S. metrics for the Lightning Data Service, defined here[2].
|
|
8
8
|
*
|
|
9
|
-
* [1]
|
|
10
|
-
* [2]
|
|
9
|
+
* [1] Search "[M1] Lightning Data Service Design Spike" in Quip
|
|
10
|
+
* [2] Search "Lightning Data Service R.E.A.D.S. Metrics" in Quip
|
|
11
11
|
*/
|
|
12
12
|
import type { MetricsKey } from 'instrumentation/service';
|
|
13
13
|
export declare const OBSERVABILITY_NAMESPACE = "LIGHTNING.lds.service";
|
package/dist/types/main.d.ts
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
type PreloadProps = {
|
|
2
|
+
context: {
|
|
3
|
+
objectApiName: string;
|
|
4
|
+
};
|
|
5
|
+
pageReference: LexPageReference;
|
|
6
|
+
};
|
|
7
|
+
type LexPageReference = {
|
|
8
|
+
attributes: {
|
|
9
|
+
actionName: string;
|
|
10
|
+
objectApiName: string;
|
|
11
|
+
recordId: string;
|
|
12
|
+
};
|
|
13
|
+
state: any;
|
|
14
|
+
type: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function predictiveLoadPage(preloadProps: PreloadProps): Promise<void>;
|
|
1
17
|
export declare function initializeLDS(): void;
|
|
2
18
|
declare function ldsEngineCreator(): {
|
|
3
19
|
name: string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { LexRequest } from '../prefetcher';
|
|
2
|
+
import { PredictivePrefetchPage } from './predictive-prefetch-page';
|
|
3
|
+
export type DefaultPageContext = Record<string, any>;
|
|
4
|
+
export declare class LexDefaultPage extends PredictivePrefetchPage<LexRequest, DefaultPageContext> {
|
|
5
|
+
constructor(context: DefaultPageContext);
|
|
6
|
+
buildSaveRequestData(request: LexRequest): {
|
|
7
|
+
context: DefaultPageContext;
|
|
8
|
+
request: import("./record-home-page").RecordHomePageRequest;
|
|
9
|
+
};
|
|
10
|
+
resolveSimilarRequest(similarRequest: LexRequest): LexRequest;
|
|
11
|
+
getAlwaysRunRequests(): LexRequest[];
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare abstract class PredictivePrefetchPage<Request, Context> {
|
|
2
|
+
context: Context;
|
|
3
|
+
similarContext: Partial<Context> | undefined;
|
|
4
|
+
constructor(context: Context);
|
|
5
|
+
abstract buildSaveRequestData(request: Request): {
|
|
6
|
+
context: Context;
|
|
7
|
+
request: Request;
|
|
8
|
+
};
|
|
9
|
+
abstract resolveSimilarRequest(similarRequest: Request): Request;
|
|
10
|
+
abstract getAlwaysRunRequests(): Request[];
|
|
11
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { LexContext } from '../prefetcher';
|
|
2
|
+
import type { GetRecordRequest, GetRecordsRequest, GetRecordRequestStrategy, GetRecordsRequestStrategy, GetRecordActionsRequestStrategy, GetRecordActionsRequest, GetRecordAvatarsRequest, GetRecordAvatarsRequestStrategy } from '../request-strategy';
|
|
3
|
+
import { PredictivePrefetchPage } from './predictive-prefetch-page';
|
|
4
|
+
export type RecordHomePageContext = {
|
|
5
|
+
objectApiName: string;
|
|
6
|
+
recordId: string;
|
|
7
|
+
actionName: string;
|
|
8
|
+
type: 'recordPage';
|
|
9
|
+
};
|
|
10
|
+
export type RecordHomePageRequest = GetRecordRequest | GetRecordsRequest | GetRecordActionsRequest | GetRecordAvatarsRequest;
|
|
11
|
+
export declare class RecordHomePage extends PredictivePrefetchPage<RecordHomePageRequest, RecordHomePageContext> {
|
|
12
|
+
private requestStrategies;
|
|
13
|
+
similarContext: RecordHomePageContext;
|
|
14
|
+
constructor(context: RecordHomePageContext, requestStrategies: {
|
|
15
|
+
getRecord: GetRecordRequestStrategy;
|
|
16
|
+
getRecords: GetRecordsRequestStrategy;
|
|
17
|
+
getRecordActions: GetRecordActionsRequestStrategy;
|
|
18
|
+
getRecordAvatars: GetRecordAvatarsRequestStrategy;
|
|
19
|
+
});
|
|
20
|
+
buildSaveRequestData(request: RecordHomePageRequest): {
|
|
21
|
+
request: GetRecordRequest;
|
|
22
|
+
context: RecordHomePageContext;
|
|
23
|
+
} | {
|
|
24
|
+
request: GetRecordsRequest;
|
|
25
|
+
context: RecordHomePageContext;
|
|
26
|
+
} | {
|
|
27
|
+
request: GetRecordActionsRequest;
|
|
28
|
+
context: RecordHomePageContext;
|
|
29
|
+
} | {
|
|
30
|
+
request: GetRecordAvatarsRequest;
|
|
31
|
+
context: RecordHomePageContext;
|
|
32
|
+
};
|
|
33
|
+
buildGetRecordSaveRequestData(request: GetRecordRequest): {
|
|
34
|
+
request: GetRecordRequest;
|
|
35
|
+
context: RecordHomePageContext;
|
|
36
|
+
};
|
|
37
|
+
buildGetRecordsSaveRequestData(request: GetRecordsRequest): {
|
|
38
|
+
request: GetRecordsRequest;
|
|
39
|
+
context: RecordHomePageContext;
|
|
40
|
+
};
|
|
41
|
+
isGetRecordRequestContextDependent(request: GetRecordRequest): boolean;
|
|
42
|
+
isGetRecordsRequestContextDependent(request: GetRecordsRequest): boolean;
|
|
43
|
+
resolveSimilarRequest(similarRequest: RecordHomePageRequest): RecordHomePageRequest;
|
|
44
|
+
getAlwaysRunRequests(): RecordHomePageRequest[];
|
|
45
|
+
static handlesContext(context: LexContext): context is RecordHomePageContext;
|
|
46
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DefaultPageContext, PredictivePrefetchPage } from '../pages';
|
|
2
|
+
import { ApplicationPredictivePrefetcher } from './predictive-prefetcher';
|
|
3
|
+
import type { GetRecordActionsRequestStrategy, GetRecordAvatarsRequestStrategy, GetRecordRequestStrategy, GetRecordsRequestStrategy } from '../request-strategy';
|
|
4
|
+
import type { RequestRunner } from '../request-runner';
|
|
5
|
+
import type { PrefetchRepository } from '../repository/prefetch-repository';
|
|
6
|
+
import type { RecordHomePageContext, RecordHomePageRequest } from '../pages/record-home-page';
|
|
7
|
+
export type LexRequest = RecordHomePageRequest;
|
|
8
|
+
export type LexContext = RecordHomePageContext | DefaultPageContext;
|
|
9
|
+
export declare class LexPredictivePrefetcher extends ApplicationPredictivePrefetcher<LexRequest, LexContext> {
|
|
10
|
+
private requestStrategies;
|
|
11
|
+
constructor(context: LexContext, repository: PrefetchRepository, requestRunner: RequestRunner<LexRequest>, requestStrategies: {
|
|
12
|
+
getRecord: GetRecordRequestStrategy;
|
|
13
|
+
getRecords: GetRecordsRequestStrategy;
|
|
14
|
+
getRecordActions: GetRecordActionsRequestStrategy;
|
|
15
|
+
getRecordAvatars: GetRecordAvatarsRequestStrategy;
|
|
16
|
+
});
|
|
17
|
+
getPage(): PredictivePrefetchPage<LexRequest, LexContext>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PrefetchRepository } from '../repository/prefetch-repository';
|
|
2
|
+
import type { PredictivePrefetchPage } from '../pages';
|
|
3
|
+
import type { RequestRunner } from '../request-runner';
|
|
4
|
+
export declare abstract class ApplicationPredictivePrefetcher<Request, Context extends Record<string, any>> {
|
|
5
|
+
private repository;
|
|
6
|
+
private requestRunner;
|
|
7
|
+
private _context;
|
|
8
|
+
isRecording: boolean;
|
|
9
|
+
page: PredictivePrefetchPage<Request, Context>;
|
|
10
|
+
queuedPredictionRequests: Request[];
|
|
11
|
+
constructor(context: Context, repository: PrefetchRepository, requestRunner: RequestRunner<Request>);
|
|
12
|
+
abstract getPage(): PredictivePrefetchPage<Request, Context>;
|
|
13
|
+
set context(value: Context);
|
|
14
|
+
get context(): Context;
|
|
15
|
+
stopRecording(): Promise<void>;
|
|
16
|
+
startRecording(): void;
|
|
17
|
+
saveRequest(request: Request): Promise<void>;
|
|
18
|
+
predict(): Promise<void>;
|
|
19
|
+
getSimilarPageRequests(): Promise<Request[]>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PrefetchStorage } from '../storage';
|
|
2
|
+
type Key = Record<string, any>;
|
|
3
|
+
export type History = {
|
|
4
|
+
version: '1.0';
|
|
5
|
+
pages: Key[];
|
|
6
|
+
};
|
|
7
|
+
export type PageEntry<Request> = {
|
|
8
|
+
id: string;
|
|
9
|
+
requests: RequestEntry<Request>[];
|
|
10
|
+
};
|
|
11
|
+
export type RequestEntry<Request> = {
|
|
12
|
+
request: Request;
|
|
13
|
+
requestMetadata: {
|
|
14
|
+
requestTimes: number[];
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export declare class PrefetchRepository {
|
|
18
|
+
private storage;
|
|
19
|
+
private requestBuffer;
|
|
20
|
+
constructor(storage: PrefetchStorage);
|
|
21
|
+
clearRequestBuffer(): void;
|
|
22
|
+
flushRequestsToStorage(): Promise<void>;
|
|
23
|
+
saveRequest<Request>(key: Key, request: Request): Promise<void>;
|
|
24
|
+
getPage<Request>(key: Key): Promise<PageEntry<Request> | undefined>;
|
|
25
|
+
getPageRequests<Request>(key: Key): Promise<Request[]>;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const ObjectPrototypeHasOwnProperty: (v: PropertyKey) => boolean;
|
|
2
|
+
export declare const ArrayIsArray: (arg: any) => arg is any[];
|
|
3
|
+
/**
|
|
4
|
+
* A deterministic JSON stringify implementation. Heavily adapted from https://github.com/epoberezkin/fast-json-stable-stringify.
|
|
5
|
+
* This is needed because insertion order for JSON.stringify(object) affects output:
|
|
6
|
+
* JSON.stringify({a: 1, b: 2})
|
|
7
|
+
* "{"a":1,"b":2}"
|
|
8
|
+
* JSON.stringify({b: 2, a: 1})
|
|
9
|
+
* "{"b":2,"a":1}"
|
|
10
|
+
* @param data Data to be JSON-stringified.
|
|
11
|
+
* @returns JSON.stringified value with consistent ordering of keys.
|
|
12
|
+
*/
|
|
13
|
+
export declare function stableJSONStringify(node: any): string | undefined;
|
|
14
|
+
export declare function isObject(obj: any): obj is Record<string, unknown>;
|
|
15
|
+
export declare function deepEquals(objA: any, objB: any): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Luvio } from '@luvio/engine';
|
|
2
|
+
import type { LexRequest } from '../prefetcher';
|
|
3
|
+
import type { RequestRunner } from './request-runner';
|
|
4
|
+
export declare class LexRequestRunner implements RequestRunner<LexRequest> {
|
|
5
|
+
private luvio;
|
|
6
|
+
private requestStrategies;
|
|
7
|
+
constructor(luvio: Luvio);
|
|
8
|
+
reduceRequests(requests: LexRequest[]): LexRequest[];
|
|
9
|
+
runRequest(request: LexRequest): Promise<void>;
|
|
10
|
+
}
|
package/dist/types/predictive-loading/request-strategy/get-record-actions-request-strategy.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GetRecordActionsConfig } from '@salesforce/lds-adapters-uiapi';
|
|
2
|
+
import { LuvioAdapterRequestStrategy } from './luvio-adapter-request-strategy';
|
|
3
|
+
import type { LuvioAdapterRequest } from './luvio-adapter-request';
|
|
4
|
+
export type GetRecordActionsRequest = {
|
|
5
|
+
adapterName: 'getRecordActions';
|
|
6
|
+
config: GetRecordActionsConfig;
|
|
7
|
+
};
|
|
8
|
+
type GetRecordActionsContext = {
|
|
9
|
+
recordId: string;
|
|
10
|
+
};
|
|
11
|
+
export declare class GetRecordActionsRequestStrategy extends LuvioAdapterRequestStrategy<GetRecordActionsConfig, GetRecordActionsRequest, GetRecordActionsContext> {
|
|
12
|
+
adapterName: string;
|
|
13
|
+
adapterFactory: import("@luvio/engine").AdapterFactory<GetRecordActionsConfig, import("packages/lds-adapters-uiapi/dist/es/es2018/types/src/generated/types/ActionRepresentation").ActionRepresentation>;
|
|
14
|
+
buildConcreteRequest(similarRequest: GetRecordActionsRequest, context: GetRecordActionsContext): GetRecordActionsRequest;
|
|
15
|
+
buildGetRecordActionsSaveRequestData<C extends GetRecordActionsContext>(similarContext: C, context: C, request: GetRecordActionsRequest): {
|
|
16
|
+
request: GetRecordActionsRequest;
|
|
17
|
+
context: C;
|
|
18
|
+
};
|
|
19
|
+
private isGetRecordActionsRequestContextDependent;
|
|
20
|
+
reduce(unfilteredRequests: LuvioAdapterRequest<unknown>[]): GetRecordActionsRequest[];
|
|
21
|
+
}
|
|
22
|
+
export {};
|
package/dist/types/predictive-loading/request-strategy/get-record-avatars-request-strategy.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GetRecordAvatarsConfig } from '@salesforce/lds-adapters-uiapi';
|
|
2
|
+
import { LuvioAdapterRequestStrategy } from './luvio-adapter-request-strategy';
|
|
3
|
+
import type { LuvioAdapterRequest } from './luvio-adapter-request';
|
|
4
|
+
export type GetRecordAvatarsRequest = {
|
|
5
|
+
adapterName: 'getRecordAvatars';
|
|
6
|
+
config: GetRecordAvatarsConfig;
|
|
7
|
+
};
|
|
8
|
+
type GetRecordAvatarsContext = {
|
|
9
|
+
recordId: string;
|
|
10
|
+
};
|
|
11
|
+
export declare class GetRecordAvatarsRequestStrategy extends LuvioAdapterRequestStrategy<GetRecordAvatarsConfig, GetRecordAvatarsRequest, GetRecordAvatarsContext> {
|
|
12
|
+
adapterName: string;
|
|
13
|
+
adapterFactory: import("@luvio/engine").AdapterFactory<GetRecordAvatarsConfig, import("packages/lds-adapters-uiapi/dist/es/es2018/types/src/generated/types/RecordAvatarBulkMapRepresentation").RecordAvatarBulkMapRepresentation>;
|
|
14
|
+
buildConcreteRequest(similarRequest: GetRecordAvatarsRequest, context: GetRecordAvatarsContext): GetRecordAvatarsRequest;
|
|
15
|
+
buildGetRecordAvatarsSaveRequestData<C extends GetRecordAvatarsContext>(similarContext: C, context: C, request: GetRecordAvatarsRequest): {
|
|
16
|
+
request: GetRecordAvatarsRequest;
|
|
17
|
+
context: C;
|
|
18
|
+
};
|
|
19
|
+
private isGetRecordAvatarsRequestContextDependent;
|
|
20
|
+
reduce(unfilteredRequests: LuvioAdapterRequest<unknown>[]): GetRecordAvatarsRequest[];
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { GetRecordConfig } from '@salesforce/lds-adapters-uiapi';
|
|
2
|
+
import { LuvioAdapterRequestStrategy } from './luvio-adapter-request-strategy';
|
|
3
|
+
export type GetRecordRequest = {
|
|
4
|
+
adapterName: 'getRecord';
|
|
5
|
+
config: GetRecordConfig;
|
|
6
|
+
};
|
|
7
|
+
import type { LuvioAdapterRequest } from './luvio-adapter-request';
|
|
8
|
+
type GetRecordContext = {
|
|
9
|
+
recordId: string;
|
|
10
|
+
};
|
|
11
|
+
export declare class GetRecordRequestStrategy extends LuvioAdapterRequestStrategy<GetRecordConfig, GetRecordRequest, GetRecordContext> {
|
|
12
|
+
adapterName: string;
|
|
13
|
+
adapterFactory: import("@luvio/engine").AdapterFactory<GetRecordConfig, import("@salesforce/lds-adapters-uiapi").RecordRepresentation>;
|
|
14
|
+
buildConcreteRequest(similarRequest: GetRecordRequest, context: GetRecordContext): GetRecordRequest;
|
|
15
|
+
transformForSave(request: GetRecordRequest): GetRecordRequest;
|
|
16
|
+
reduce(unfilteredRequests: LuvioAdapterRequest<unknown>[]): GetRecordRequest[];
|
|
17
|
+
}
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { GetRecordsConfig } from '@salesforce/lds-adapters-uiapi';
|
|
2
|
+
import { LuvioAdapterRequestStrategy } from './luvio-adapter-request-strategy';
|
|
3
|
+
export type GetRecordsRequest = {
|
|
4
|
+
adapterName: 'getRecords';
|
|
5
|
+
config: GetRecordsConfig;
|
|
6
|
+
};
|
|
7
|
+
type GetRecordsContext = {
|
|
8
|
+
recordId: string;
|
|
9
|
+
};
|
|
10
|
+
export declare class GetRecordsRequestStrategy extends LuvioAdapterRequestStrategy<GetRecordsConfig, GetRecordsRequest, GetRecordsContext> {
|
|
11
|
+
adapterName: string;
|
|
12
|
+
adapterFactory: import("@luvio/engine").AdapterFactory<GetRecordsConfig, import("@salesforce/lds-adapters-uiapi").BatchRepresentation>;
|
|
13
|
+
buildConcreteRequest(similarRequest: GetRecordsRequest, context: GetRecordsContext): GetRecordsRequest;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './get-record-avatars-request-strategy';
|
|
2
|
+
export * from './get-record-request-strategy';
|
|
3
|
+
export * from './get-records-request-strategy';
|
|
4
|
+
export * from './get-record-actions-request-strategy';
|
|
5
|
+
export * from './luvio-adapter-request-strategy';
|
|
6
|
+
export * from './luvio-adapter-request';
|
|
7
|
+
export * from './request-strategy';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AdapterFactory } from '@luvio/engine';
|
|
2
|
+
import { RequestStrategy } from './request-strategy';
|
|
3
|
+
import type { LuvioAdapterRequest } from './luvio-adapter-request';
|
|
4
|
+
export declare abstract class LuvioAdapterRequestStrategy<AdapterConfig, Request extends LuvioAdapterRequest<AdapterConfig>, Context> extends RequestStrategy<Request, Context> {
|
|
5
|
+
abstract adapterName: string;
|
|
6
|
+
abstract adapterFactory: AdapterFactory<AdapterConfig, any>;
|
|
7
|
+
transformForSave(request: Request): Request;
|
|
8
|
+
protected filterRequests(unfilteredRequests: LuvioAdapterRequest<unknown>[]): Request[];
|
|
9
|
+
reduce(unfilteredRequests: LuvioAdapterRequest<unknown>[]): Request[];
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type PrefetchStorage } from '.';
|
|
2
|
+
import { type AuraStorage, type AuraStorageConfig } from '@salesforce/lds-aura-storage';
|
|
3
|
+
export declare const DEFAULT_STORAGE_OPTIONS: {
|
|
4
|
+
name: string;
|
|
5
|
+
persistent: boolean;
|
|
6
|
+
secure: boolean;
|
|
7
|
+
maxSize: number;
|
|
8
|
+
expiration: number;
|
|
9
|
+
clearOnInit: boolean;
|
|
10
|
+
debugLogging: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare function buildAuraPrefetchStorage(options?: Partial<AuraStorageConfig>): PrefetchStorage;
|
|
13
|
+
export declare class AuraPrefetchStorage implements PrefetchStorage {
|
|
14
|
+
private auraStorage;
|
|
15
|
+
constructor(auraStorage: AuraStorage);
|
|
16
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
17
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type PrefetchStorage = {
|
|
2
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
3
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
4
|
+
};
|
|
5
|
+
export * from './in-memory-prefetch-storage';
|
|
6
|
+
export * from './local-prefetch-storage';
|
|
7
|
+
export { buildAuraPrefetchStorage } from './aura-prefetch-storage';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PrefetchStorage } from '.';
|
|
2
|
+
export declare class LocalPrefetchStorage implements PrefetchStorage {
|
|
3
|
+
private localStorage;
|
|
4
|
+
constructor(localStorage: typeof window.localStorage);
|
|
5
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
6
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/lds-runtime-aura",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.247.0",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"description": "LDS engine for Aura runtime",
|
|
6
6
|
"main": "dist/ldsEngineCreator.js",
|
|
@@ -43,16 +43,19 @@
|
|
|
43
43
|
"@salesforce/lds-network-fetch-with-jwt": "*"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@luvio/network-adapter-composable": "0.
|
|
46
|
+
"@luvio/network-adapter-composable": "0.151.0"
|
|
47
47
|
},
|
|
48
48
|
"luvioBundlesize": [
|
|
49
49
|
{
|
|
50
50
|
"path": "./dist/ldsEngineCreator.js",
|
|
51
51
|
"maxSize": {
|
|
52
|
-
"none": "
|
|
53
|
-
"min": "
|
|
54
|
-
"compressed": "
|
|
52
|
+
"none": "60 kB",
|
|
53
|
+
"min": "28 kB",
|
|
54
|
+
"compressed": "12 kB"
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
]
|
|
57
|
+
],
|
|
58
|
+
"volta": {
|
|
59
|
+
"extends": "../../package.json"
|
|
60
|
+
}
|
|
58
61
|
}
|