@salesforce/lds-runtime-aura 1.246.0 → 1.248.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.
Files changed (20) hide show
  1. package/dist/ldsEngineCreator.js +324 -49
  2. package/dist/types/aura-instrumentation/utils/observability.d.ts +2 -2
  3. package/dist/types/predictive-loading/pages/lex-default-page.d.ts +1 -0
  4. package/dist/types/predictive-loading/pages/predictive-prefetch-page.d.ts +1 -0
  5. package/dist/types/predictive-loading/pages/record-home-page.d.ts +11 -2
  6. package/dist/types/predictive-loading/prefetcher/lex-predictive-prefetcher.d.ts +3 -1
  7. package/dist/types/predictive-loading/prefetcher/predictive-prefetcher.d.ts +1 -1
  8. package/dist/types/predictive-loading/repository/prefetch-repository.d.ts +3 -0
  9. package/dist/types/predictive-loading/repository/utils.d.ts +2 -0
  10. package/dist/types/predictive-loading/request-strategy/get-record-actions-request-strategy.d.ts +22 -0
  11. package/dist/types/predictive-loading/request-strategy/get-record-avatars-request-strategy.d.ts +22 -0
  12. package/dist/types/predictive-loading/request-strategy/get-record-request-strategy.d.ts +3 -1
  13. package/dist/types/predictive-loading/request-strategy/get-records-request-strategy.d.ts +1 -0
  14. package/dist/types/predictive-loading/request-strategy/index.d.ts +2 -0
  15. package/dist/types/predictive-loading/request-strategy/luvio-adapter-request-strategy.d.ts +3 -1
  16. package/dist/types/predictive-loading/storage/aura-prefetch-storage.d.ts +18 -0
  17. package/dist/types/predictive-loading/storage/in-memory-prefetch-storage.d.ts +3 -3
  18. package/dist/types/predictive-loading/storage/index.d.ts +3 -2
  19. package/dist/types/predictive-loading/storage/local-prefetch-storage.d.ts +2 -2
  20. package/package.json +5 -5
@@ -15,7 +15,8 @@
15
15
  import { HttpStatusCode, InMemoryStore, 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 { getRecordAdapterFactory, getRecordsAdapterFactory, instrument, configuration, InMemoryRecordRepresentationQueryEvaluator, UiApiNamespace, RecordRepresentationRepresentationType, registerPrefetcher } from 'force/ldsAdaptersUiapi';
18
+ import { getRecordAvatarsAdapterFactory, getRecordAdapterFactory, getRecordsAdapterFactory, getRecordActionsAdapterFactory, instrument, configuration, InMemoryRecordRepresentationQueryEvaluator, UiApiNamespace, RecordRepresentationRepresentationType, registerPrefetcher } from 'force/ldsAdaptersUiapi';
19
+ import { createStorage, clearStorages } from 'force/ldsStorage';
19
20
  import { withRegistration, register, setDefaultLuvio } from 'force/ldsEngine';
20
21
  import { REFRESH_ADAPTER_EVENT, ADAPTER_UNFULFILLED_ERROR, instrument as instrument$2 } from 'force/ldsBindings';
21
22
  import { counter, registerCacheStats, perfStart, perfEnd, registerPeriodicLogger, interaction, timer } from 'instrumentation/service';
@@ -23,7 +24,6 @@ import { LRUCache, instrumentAdapter, instrumentLuvio, setupInstrumentation as s
23
24
  import auraNetworkAdapter, { instrument as instrument$1, forceRecordTransactionsDisabled, ldsNetworkAdapterInstrument, dispatchAuraAction, defaultActionConfig } from 'force/ldsNetwork';
24
25
  import { instrument as instrument$3 } from 'force/adsBridge';
25
26
  import { buildJwtNetworkAdapter } from 'force/ldsNetworkFetchWithJwt';
26
- import { clearStorages } from 'force/ldsStorage';
27
27
 
28
28
  class PredictivePrefetchPage {
29
29
  constructor(context) {
@@ -42,6 +42,9 @@ class LexDefaultPage extends PredictivePrefetchPage {
42
42
  resolveSimilarRequest(similarRequest) {
43
43
  return similarRequest;
44
44
  }
45
+ getAlwaysRunRequests() {
46
+ return [];
47
+ }
45
48
  }
46
49
 
47
50
  class RecordHomePage extends PredictivePrefetchPage {
@@ -55,14 +58,18 @@ class RecordHomePage extends PredictivePrefetchPage {
55
58
  };
56
59
  }
57
60
  buildSaveRequestData(request) {
58
- if (request.adapterName === 'getRecord') {
59
- return this.buildGetRecordSaveRequestData(request);
60
- }
61
- else if (request.adapterName === 'getRecords') {
62
- return this.buildGetRecordsSaveRequestData(request);
63
- }
64
- else {
65
- return { request, context: this.context };
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 };
66
73
  }
67
74
  }
68
75
  buildGetRecordSaveRequestData(request) {
@@ -121,8 +128,27 @@ class RecordHomePage extends PredictivePrefetchPage {
121
128
  if (similarRequest.adapterName === 'getRecords') {
122
129
  return this.requestStrategies.getRecords.buildConcreteRequest(similarRequest, this.context);
123
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
+ }
124
137
  return similarRequest;
125
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
+ }
126
152
  static handlesContext(context) {
127
153
  const maybeRecordHomePageContext = context;
128
154
  return (maybeRecordHomePageContext !== undefined &&
@@ -149,11 +175,13 @@ class ApplicationPredictivePrefetcher {
149
175
  get context() {
150
176
  return this._context;
151
177
  }
152
- stopRecording() {
178
+ async stopRecording() {
153
179
  this.isRecording = false;
180
+ await this.repository.flushRequestsToStorage();
154
181
  }
155
182
  startRecording() {
156
183
  this.isRecording = true;
184
+ this.repository.clearRequestBuffer();
157
185
  }
158
186
  saveRequest(request) {
159
187
  if (!this.isRecording) {
@@ -167,10 +195,10 @@ class ApplicationPredictivePrefetcher {
167
195
  async predict() {
168
196
  const exactPageRequests = (await this.repository.getPageRequests(this.context)) || [];
169
197
  const similarPageRequests = await this.getSimilarPageRequests();
170
- const predictedRequests = this.requestRunner.reduceRequests([
171
- ...exactPageRequests,
172
- ...similarPageRequests,
173
- ]);
198
+ const predictedRequests = [
199
+ ...this.requestRunner.reduceRequests([...exactPageRequests, ...similarPageRequests]),
200
+ ...this.page.getAlwaysRunRequests(),
201
+ ];
174
202
  this.queuedPredictionRequests.push(...predictedRequests);
175
203
  return Promise.all(predictedRequests.map((request) => this.requestRunner.runRequest(request))).then();
176
204
  }
@@ -262,31 +290,74 @@ function stableJSONStringify$1(node) {
262
290
  }
263
291
  return '{' + out + '}';
264
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
+ }
265
323
 
266
324
  class PrefetchRepository {
267
325
  constructor(storage) {
268
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();
269
347
  }
270
348
  async saveRequest(key, request) {
271
349
  const identifier = stableJSONStringify$1(key);
272
- const rawPage = await this.storage.get(identifier);
273
- const page = rawPage === undefined ? { identifier, requests: [] } : JSON.parse(rawPage);
274
- const stringifiedRequest = stableJSONStringify$1(request);
275
- let existingRequestEntry = page.requests.find((requestEntry) => stableJSONStringify$1(requestEntry.request) === stringifiedRequest);
276
- if (existingRequestEntry === undefined) {
277
- existingRequestEntry = { request, requestMetadata: { requestTimes: [] } };
278
- page.requests.push(existingRequestEntry);
279
- }
280
- existingRequestEntry.requestMetadata.requestTimes.push(Date.now());
281
- return this.storage.set(identifier, stableJSONStringify$1(page));
350
+ const batchForKey = this.requestBuffer.get(identifier) || [];
351
+ batchForKey.push({
352
+ request,
353
+ requestTime: Date.now(),
354
+ });
355
+ this.requestBuffer.set(identifier, batchForKey);
282
356
  }
283
357
  async getPage(key) {
284
358
  const identifier = stableJSONStringify$1(key);
285
359
  const rawPage = await this.storage.get(identifier);
286
- if (rawPage === undefined) {
287
- return;
288
- }
289
- return JSON.parse(rawPage);
360
+ return rawPage;
290
361
  }
291
362
  async getPageRequests(key) {
292
363
  const page = await this.getPage(key);
@@ -310,14 +381,95 @@ class LuvioAdapterRequestStrategy extends RequestStrategy {
310
381
  transformForSave(request) {
311
382
  return request;
312
383
  }
313
- reduce(requests) {
314
- return requests;
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;
315
466
  }
316
467
  }
317
468
 
318
469
  class GetRecordRequestStrategy extends LuvioAdapterRequestStrategy {
319
470
  constructor() {
320
471
  super(...arguments);
472
+ this.adapterName = 'getRecord';
321
473
  this.adapterFactory = getRecordAdapterFactory;
322
474
  }
323
475
  buildConcreteRequest(similarRequest, context) {
@@ -333,19 +485,21 @@ class GetRecordRequestStrategy extends LuvioAdapterRequestStrategy {
333
485
  if (request.config.fields === undefined && request.config.optionalFields === undefined) {
334
486
  return request;
335
487
  }
488
+ let optionalFields = request.config.optionalFields || [];
489
+ if (!ArrayIsArray(optionalFields)) {
490
+ optionalFields = [optionalFields];
491
+ }
336
492
  return {
337
493
  ...request,
338
494
  config: {
339
495
  ...request.config,
340
496
  fields: undefined,
341
- optionalFields: [
342
- ...(request.config.fields || []),
343
- ...(request.config.optionalFields || []),
344
- ],
497
+ optionalFields: [...(request.config.fields || []), ...optionalFields],
345
498
  },
346
499
  };
347
500
  }
348
- reduce(requests) {
501
+ reduce(unfilteredRequests) {
502
+ const requests = this.filterRequests(unfilteredRequests);
349
503
  const recordIdToRequestMap = {};
350
504
  const resultRequests = [];
351
505
  requests.forEach((request) => {
@@ -390,6 +544,7 @@ class GetRecordRequestStrategy extends LuvioAdapterRequestStrategy {
390
544
  class GetRecordsRequestStrategy extends LuvioAdapterRequestStrategy {
391
545
  constructor() {
392
546
  super(...arguments);
547
+ this.adapterName = 'getRecords';
393
548
  this.adapterFactory = getRecordsAdapterFactory;
394
549
  }
395
550
  buildConcreteRequest(similarRequest, context) {
@@ -403,18 +558,104 @@ class GetRecordsRequestStrategy extends LuvioAdapterRequestStrategy {
403
558
  }
404
559
  }
405
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
+
406
645
  class LexRequestRunner {
407
646
  constructor(luvio) {
408
647
  this.luvio = luvio;
409
648
  this.requestStrategies = {
410
649
  getRecord: new GetRecordRequestStrategy(),
411
650
  getRecords: new GetRecordsRequestStrategy(),
651
+ getRecordActions: new GetRecordActionsRequestStrategy(),
652
+ getRecordAvatars: new GetRecordAvatarsRequestStrategy(),
412
653
  };
413
654
  }
414
655
  reduceRequests(requests) {
415
- const getRecordRequests = requests.filter((request) => request.adapterName === 'getRecord');
416
- const otherRequests = requests.filter((request) => request.adapterName !== 'getRecord');
417
- return [...this.requestStrategies.getRecord.reduce(getRecordRequests), ...otherRequests];
656
+ return Object.values(this.requestStrategies)
657
+ .map((strategy) => strategy.reduce(requests))
658
+ .flat();
418
659
  }
419
660
  runRequest(request) {
420
661
  if (request.adapterName in this.requestStrategies) {
@@ -425,16 +666,49 @@ class LexRequestRunner {
425
666
  }
426
667
  }
427
668
 
428
- class LocalPrefetchStorage {
429
- constructor(localStorage) {
430
- this.localStorage = localStorage;
669
+ class InMemoryPrefetchStorage {
670
+ constructor() {
671
+ this.data = {};
431
672
  }
432
673
  set(key, value) {
433
- this.localStorage.setItem(key, value);
674
+ this.data[key] = value;
434
675
  return Promise.resolve();
435
676
  }
436
677
  get(key) {
437
- return Promise.resolve(this.localStorage.getItem(key) || undefined);
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;
438
712
  }
439
713
  }
440
714
 
@@ -446,8 +720,8 @@ class LocalPrefetchStorage {
446
720
  *
447
721
  * Below are the R.E.A.D.S. metrics for the Lightning Data Service, defined here[2].
448
722
  *
449
- * [1] https://salesforce.quip.com/NfW9AsbGEaTY
450
- * [2] https://salesforce.quip.com/1dFvAba1b0eq
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
451
725
  */
452
726
  const OBSERVABILITY_NAMESPACE = 'LIGHTNING.lds.service';
453
727
  const ADAPTER_INVOCATION_COUNT_METRIC_NAME = 'request';
@@ -1138,7 +1412,6 @@ function setupMetadataWatcher(luvio) {
1138
1412
  });
1139
1413
  }
1140
1414
 
1141
- // import { createStorage } from '@salesforce/lds-aura-storage';
1142
1415
  // This code *should* be in lds-network-adapter, but when combined with the Aura
1143
1416
  // component test workaround in lds-default-luvio it creates a circular dependecy
1144
1417
  // between lds-default-luvio and lds-network-adapter. We do the register on behalf
@@ -1167,12 +1440,14 @@ function setupQueryEvaluators(luvio, store) {
1167
1440
  }
1168
1441
  let __lexPrefetcher;
1169
1442
  function setupPredictivePrefetcher(luvio) {
1170
- const storage = new LocalPrefetchStorage(window.localStorage);
1443
+ const storage = buildAuraPrefetchStorage();
1171
1444
  const repository = new PrefetchRepository(storage);
1172
1445
  const requestRunner = new LexRequestRunner(luvio);
1173
1446
  const prefetcher = new LexPredictivePrefetcher({ context: 'unknown' }, repository, requestRunner, {
1174
1447
  getRecord: new GetRecordRequestStrategy(),
1175
1448
  getRecords: new GetRecordsRequestStrategy(),
1449
+ getRecordActions: new GetRecordActionsRequestStrategy(),
1450
+ getRecordAvatars: new GetRecordAvatarsRequestStrategy(),
1176
1451
  });
1177
1452
  registerPrefetcher(luvio, prefetcher);
1178
1453
  __lexPrefetcher = prefetcher;
@@ -1225,4 +1500,4 @@ function ldsEngineCreator() {
1225
1500
  }
1226
1501
 
1227
1502
  export { ldsEngineCreator as default, initializeLDS, predictiveLoadPage };
1228
- // version: 1.246.0-8357100fc
1503
+ // version: 1.248.0-1f7f01112
@@ -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] https://salesforce.quip.com/NfW9AsbGEaTY
10
- * [2] https://salesforce.quip.com/1dFvAba1b0eq
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";
@@ -8,4 +8,5 @@ export declare class LexDefaultPage extends PredictivePrefetchPage<LexRequest, D
8
8
  request: import("./record-home-page").RecordHomePageRequest;
9
9
  };
10
10
  resolveSimilarRequest(similarRequest: LexRequest): LexRequest;
11
+ getAlwaysRunRequests(): LexRequest[];
11
12
  }
@@ -7,4 +7,5 @@ export declare abstract class PredictivePrefetchPage<Request, Context> {
7
7
  request: Request;
8
8
  };
9
9
  abstract resolveSimilarRequest(similarRequest: Request): Request;
10
+ abstract getAlwaysRunRequests(): Request[];
10
11
  }
@@ -1,5 +1,5 @@
1
1
  import type { LexContext } from '../prefetcher';
2
- import type { GetRecordRequest, GetRecordsRequest, GetRecordRequestStrategy, GetRecordsRequestStrategy } from '../request-strategy';
2
+ import type { GetRecordRequest, GetRecordsRequest, GetRecordRequestStrategy, GetRecordsRequestStrategy, GetRecordActionsRequestStrategy, GetRecordActionsRequest, GetRecordAvatarsRequest, GetRecordAvatarsRequestStrategy } from '../request-strategy';
3
3
  import { PredictivePrefetchPage } from './predictive-prefetch-page';
4
4
  export type RecordHomePageContext = {
5
5
  objectApiName: string;
@@ -7,13 +7,15 @@ export type RecordHomePageContext = {
7
7
  actionName: string;
8
8
  type: 'recordPage';
9
9
  };
10
- export type RecordHomePageRequest = GetRecordRequest | GetRecordsRequest;
10
+ export type RecordHomePageRequest = GetRecordRequest | GetRecordsRequest | GetRecordActionsRequest | GetRecordAvatarsRequest;
11
11
  export declare class RecordHomePage extends PredictivePrefetchPage<RecordHomePageRequest, RecordHomePageContext> {
12
12
  private requestStrategies;
13
13
  similarContext: RecordHomePageContext;
14
14
  constructor(context: RecordHomePageContext, requestStrategies: {
15
15
  getRecord: GetRecordRequestStrategy;
16
16
  getRecords: GetRecordsRequestStrategy;
17
+ getRecordActions: GetRecordActionsRequestStrategy;
18
+ getRecordAvatars: GetRecordAvatarsRequestStrategy;
17
19
  });
18
20
  buildSaveRequestData(request: RecordHomePageRequest): {
19
21
  request: GetRecordRequest;
@@ -21,6 +23,12 @@ export declare class RecordHomePage extends PredictivePrefetchPage<RecordHomePag
21
23
  } | {
22
24
  request: GetRecordsRequest;
23
25
  context: RecordHomePageContext;
26
+ } | {
27
+ request: GetRecordActionsRequest;
28
+ context: RecordHomePageContext;
29
+ } | {
30
+ request: GetRecordAvatarsRequest;
31
+ context: RecordHomePageContext;
24
32
  };
25
33
  buildGetRecordSaveRequestData(request: GetRecordRequest): {
26
34
  request: GetRecordRequest;
@@ -33,5 +41,6 @@ export declare class RecordHomePage extends PredictivePrefetchPage<RecordHomePag
33
41
  isGetRecordRequestContextDependent(request: GetRecordRequest): boolean;
34
42
  isGetRecordsRequestContextDependent(request: GetRecordsRequest): boolean;
35
43
  resolveSimilarRequest(similarRequest: RecordHomePageRequest): RecordHomePageRequest;
44
+ getAlwaysRunRequests(): RecordHomePageRequest[];
36
45
  static handlesContext(context: LexContext): context is RecordHomePageContext;
37
46
  }
@@ -1,6 +1,6 @@
1
1
  import type { DefaultPageContext, PredictivePrefetchPage } from '../pages';
2
2
  import { ApplicationPredictivePrefetcher } from './predictive-prefetcher';
3
- import type { GetRecordRequestStrategy, GetRecordsRequestStrategy } from '../request-strategy';
3
+ import type { GetRecordActionsRequestStrategy, GetRecordAvatarsRequestStrategy, GetRecordRequestStrategy, GetRecordsRequestStrategy } from '../request-strategy';
4
4
  import type { RequestRunner } from '../request-runner';
5
5
  import type { PrefetchRepository } from '../repository/prefetch-repository';
6
6
  import type { RecordHomePageContext, RecordHomePageRequest } from '../pages/record-home-page';
@@ -11,6 +11,8 @@ export declare class LexPredictivePrefetcher extends ApplicationPredictivePrefet
11
11
  constructor(context: LexContext, repository: PrefetchRepository, requestRunner: RequestRunner<LexRequest>, requestStrategies: {
12
12
  getRecord: GetRecordRequestStrategy;
13
13
  getRecords: GetRecordsRequestStrategy;
14
+ getRecordActions: GetRecordActionsRequestStrategy;
15
+ getRecordAvatars: GetRecordAvatarsRequestStrategy;
14
16
  });
15
17
  getPage(): PredictivePrefetchPage<LexRequest, LexContext>;
16
18
  }
@@ -12,7 +12,7 @@ export declare abstract class ApplicationPredictivePrefetcher<Request, Context e
12
12
  abstract getPage(): PredictivePrefetchPage<Request, Context>;
13
13
  set context(value: Context);
14
14
  get context(): Context;
15
- stopRecording(): void;
15
+ stopRecording(): Promise<void>;
16
16
  startRecording(): void;
17
17
  saveRequest(request: Request): Promise<void>;
18
18
  predict(): Promise<void>;
@@ -16,7 +16,10 @@ export type RequestEntry<Request> = {
16
16
  };
17
17
  export declare class PrefetchRepository {
18
18
  private storage;
19
+ private requestBuffer;
19
20
  constructor(storage: PrefetchStorage);
21
+ clearRequestBuffer(): void;
22
+ flushRequestsToStorage(): Promise<void>;
20
23
  saveRequest<Request>(key: Key, request: Request): Promise<void>;
21
24
  getPage<Request>(key: Key): Promise<PageEntry<Request> | undefined>;
22
25
  getPageRequests<Request>(key: Key): Promise<Request[]>;
@@ -11,3 +11,5 @@ export declare const ArrayIsArray: (arg: any) => arg is any[];
11
11
  * @returns JSON.stringified value with consistent ordering of keys.
12
12
  */
13
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,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 {};
@@ -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 {};
@@ -4,13 +4,15 @@ export type GetRecordRequest = {
4
4
  adapterName: 'getRecord';
5
5
  config: GetRecordConfig;
6
6
  };
7
+ import type { LuvioAdapterRequest } from './luvio-adapter-request';
7
8
  type GetRecordContext = {
8
9
  recordId: string;
9
10
  };
10
11
  export declare class GetRecordRequestStrategy extends LuvioAdapterRequestStrategy<GetRecordConfig, GetRecordRequest, GetRecordContext> {
12
+ adapterName: string;
11
13
  adapterFactory: import("@luvio/engine").AdapterFactory<GetRecordConfig, import("@salesforce/lds-adapters-uiapi").RecordRepresentation>;
12
14
  buildConcreteRequest(similarRequest: GetRecordRequest, context: GetRecordContext): GetRecordRequest;
13
15
  transformForSave(request: GetRecordRequest): GetRecordRequest;
14
- reduce(requests: GetRecordRequest[]): GetRecordRequest[];
16
+ reduce(unfilteredRequests: LuvioAdapterRequest<unknown>[]): GetRecordRequest[];
15
17
  }
16
18
  export {};
@@ -8,6 +8,7 @@ type GetRecordsContext = {
8
8
  recordId: string;
9
9
  };
10
10
  export declare class GetRecordsRequestStrategy extends LuvioAdapterRequestStrategy<GetRecordsConfig, GetRecordsRequest, GetRecordsContext> {
11
+ adapterName: string;
11
12
  adapterFactory: import("@luvio/engine").AdapterFactory<GetRecordsConfig, import("@salesforce/lds-adapters-uiapi").BatchRepresentation>;
12
13
  buildConcreteRequest(similarRequest: GetRecordsRequest, context: GetRecordsContext): GetRecordsRequest;
13
14
  }
@@ -1,5 +1,7 @@
1
+ export * from './get-record-avatars-request-strategy';
1
2
  export * from './get-record-request-strategy';
2
3
  export * from './get-records-request-strategy';
4
+ export * from './get-record-actions-request-strategy';
3
5
  export * from './luvio-adapter-request-strategy';
4
6
  export * from './luvio-adapter-request';
5
7
  export * from './request-strategy';
@@ -2,7 +2,9 @@ import type { AdapterFactory } from '@luvio/engine';
2
2
  import { RequestStrategy } from './request-strategy';
3
3
  import type { LuvioAdapterRequest } from './luvio-adapter-request';
4
4
  export declare abstract class LuvioAdapterRequestStrategy<AdapterConfig, Request extends LuvioAdapterRequest<AdapterConfig>, Context> extends RequestStrategy<Request, Context> {
5
+ abstract adapterName: string;
5
6
  abstract adapterFactory: AdapterFactory<AdapterConfig, any>;
6
7
  transformForSave(request: Request): Request;
7
- reduce(requests: Request[]): Request[];
8
+ protected filterRequests(unfilteredRequests: LuvioAdapterRequest<unknown>[]): Request[];
9
+ reduce(unfilteredRequests: LuvioAdapterRequest<unknown>[]): Request[];
8
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
+ }
@@ -1,6 +1,6 @@
1
1
  import type { PrefetchStorage } from '.';
2
2
  export declare class InMemoryPrefetchStorage implements PrefetchStorage {
3
- data: Record<string, string>;
4
- set(key: string, value: string): Promise<void>;
5
- get(key: string): Promise<string | undefined>;
3
+ data: Record<string, any>;
4
+ set<T>(key: string, value: T): Promise<void>;
5
+ get<T>(key: string): Promise<T | undefined>;
6
6
  }
@@ -1,6 +1,7 @@
1
1
  export type PrefetchStorage = {
2
- get(key: string): Promise<string | undefined>;
3
- set(key: string, value: string): Promise<void>;
2
+ get<T>(key: string): Promise<T | undefined>;
3
+ set<T>(key: string, value: T): Promise<void>;
4
4
  };
5
5
  export * from './in-memory-prefetch-storage';
6
6
  export * from './local-prefetch-storage';
7
+ export { buildAuraPrefetchStorage } from './aura-prefetch-storage';
@@ -2,6 +2,6 @@ import type { PrefetchStorage } from '.';
2
2
  export declare class LocalPrefetchStorage implements PrefetchStorage {
3
3
  private localStorage;
4
4
  constructor(localStorage: typeof window.localStorage);
5
- set(key: string, value: string): Promise<void>;
6
- get(key: string): Promise<string | undefined>;
5
+ set<T>(key: string, value: T): Promise<void>;
6
+ get<T>(key: string): Promise<T | undefined>;
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-runtime-aura",
3
- "version": "1.246.0",
3
+ "version": "1.248.0",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "LDS engine for Aura runtime",
6
6
  "main": "dist/ldsEngineCreator.js",
@@ -43,15 +43,15 @@
43
43
  "@salesforce/lds-network-fetch-with-jwt": "*"
44
44
  },
45
45
  "dependencies": {
46
- "@luvio/network-adapter-composable": "0.150.6"
46
+ "@luvio/network-adapter-composable": "0.151.1"
47
47
  },
48
48
  "luvioBundlesize": [
49
49
  {
50
50
  "path": "./dist/ldsEngineCreator.js",
51
51
  "maxSize": {
52
- "none": "50 kB",
53
- "min": "22 kB",
54
- "compressed": "10 kB"
52
+ "none": "60 kB",
53
+ "min": "28 kB",
54
+ "compressed": "12 kB"
55
55
  }
56
56
  }
57
57
  ],