@salesforce/lds-runtime-bridge 1.232.0 → 1.235.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.
@@ -12,7 +12,7 @@
12
12
  * *******************************************************************************************
13
13
  */
14
14
  import { setDefaultLuvio } from 'force/ldsEngine';
15
- import { StoreKeySet, serializeStructuredKey, Reader, deepFreeze, emitAdapterEvent, InMemoryStore, Environment, Luvio } from 'force/luvioEngine';
15
+ import { StoreKeySet, serializeStructuredKey, StringKeyInMemoryStore, Reader, deepFreeze, emitAdapterEvent, InMemoryStore, Environment, Luvio } from 'force/luvioEngine';
16
16
  import { instrumentLuvio } from 'force/ldsInstrumentation';
17
17
  import { keyBuilderRecord } from 'force/ldsAdaptersUiapi';
18
18
  import '@salesforce/gate/lds.graphqlEvalExcludeStaleRecords';
@@ -131,7 +131,7 @@ function publishDurableStoreEntries(durableRecords, put, publishMetadata) {
131
131
  * will refresh the snapshot from network, and then run the results from network
132
132
  * through L2 ingestion, returning the subsequent revived snapshot.
133
133
  */
134
- function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, durableStoreErrorHandler, buildL1Snapshot, reviveMetrics = { l2Trips: [] }) {
134
+ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics = { l2Trips: [] }) {
135
135
  const { recordId, select, missingLinks, seenRecords, state } = unavailableSnapshot;
136
136
  // L2 can only revive Unfulfilled snapshots that have a selector since they have the
137
137
  // info needed to revive (like missingLinks) and rebuild. Otherwise return L1 snapshot.
@@ -141,10 +141,21 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
141
141
  metrics: reviveMetrics,
142
142
  });
143
143
  }
144
- // in case L1 store changes/deallocs a record while we are doing the async read
145
- // we attempt to read all keys from L2 - so combine recordId with any seenRecords
146
- const keysToReviveSet = new StoreKeySet().add(recordId);
147
- keysToReviveSet.merge(seenRecords);
144
+ const keysToReviveSet = new StoreKeySet();
145
+ if (revivingStore) {
146
+ // Any stale keys since the last l2 read should be cleared and fetched again
147
+ for (const staleKey of revivingStore.staleEntries) {
148
+ keysToReviveSet.add(staleKey);
149
+ }
150
+ revivingStore.clearStale();
151
+ }
152
+ else {
153
+ // when not using a reviving store:
154
+ // in case L1 store changes/deallocs a record while we are doing the async read
155
+ // we attempt to read all keys from L2 - so combine recordId with any seenRecords
156
+ keysToReviveSet.add(recordId);
157
+ keysToReviveSet.merge(seenRecords);
158
+ }
148
159
  keysToReviveSet.merge(missingLinks);
149
160
  const keysToRevive = keysToReviveSet.keysAsArray();
150
161
  const canonicalKeys = keysToRevive.map((x) => serializeStructuredKey(baseEnvironment.storeGetCanonicalKey(x)));
@@ -194,7 +205,7 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
194
205
  for (let i = 0, len = newKeys.length; i < len; i++) {
195
206
  const newSnapshotSeenKey = newKeys[i];
196
207
  if (!alreadyRequestedOrRevivedSet.has(newSnapshotSeenKey)) {
197
- return reviveSnapshot(baseEnvironment, durableStore, snapshot, durableStoreErrorHandler, buildL1Snapshot, reviveMetrics);
208
+ return reviveSnapshot(baseEnvironment, durableStore, snapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics);
198
209
  }
199
210
  }
200
211
  }
@@ -283,8 +294,9 @@ class DurableTTLStore {
283
294
  }
284
295
  }
285
296
 
286
- function flushInMemoryStoreValuesToDurableStore(store, durableStore, durableStoreErrorHandler, redirects, additionalDurableStoreOperations = []) {
297
+ function flushInMemoryStoreValuesToDurableStore(store, durableStore, durableStoreErrorHandler, redirects, additionalDurableStoreOperations = [], enableDurableMetadataRefresh = false) {
287
298
  const durableRecords = create$2(null);
299
+ const refreshedDurableRecords = create$2(null);
288
300
  const evictedRecords = create$2(null);
289
301
  const { records, metadata: storeMetadata, visitedIds, refreshedIds, } = store.fallbackStringKeyInMemoryStore;
290
302
  // TODO: W-8909393 Once metadata is stored in its own segment we need to
@@ -294,32 +306,36 @@ function flushInMemoryStoreValuesToDurableStore(store, durableStore, durableStor
294
306
  for (let i = 0, len = keys$1.length; i < len; i += 1) {
295
307
  const key = keys$1[i];
296
308
  const record = records[key];
309
+ const wasVisited = visitedIds[key] !== undefined;
297
310
  // this record has been evicted, evict from DS
298
- if (record === undefined) {
311
+ if (wasVisited && record === undefined) {
299
312
  evictedRecords[key] = true;
300
313
  continue;
301
314
  }
302
315
  const metadata = storeMetadata[key];
303
- durableRecords[key] = {
304
- data: record,
305
- };
306
- if (metadata !== undefined) {
307
- durableRecords[key].metadata = {
308
- ...metadata,
309
- metadataVersion: DURABLE_METADATA_VERSION,
310
- };
311
- }
316
+ const entries = wasVisited === true || enableDurableMetadataRefresh === false
317
+ ? durableRecords
318
+ : refreshedDurableRecords;
319
+ setRecordTo(entries, key, record, metadata);
312
320
  }
313
321
  const durableStoreOperations = additionalDurableStoreOperations;
314
- // publishes
315
322
  const recordKeys = keys$2(durableRecords);
316
323
  if (recordKeys.length > 0) {
324
+ // publishes with data
317
325
  durableStoreOperations.push({
318
326
  type: 'setEntries',
319
327
  entries: durableRecords,
320
328
  segment: DefaultDurableSegment,
321
329
  });
322
330
  }
331
+ if (keys$2(refreshedDurableRecords).length > 0) {
332
+ // publishes with only metadata updates
333
+ durableStoreOperations.push({
334
+ type: 'setMetadata',
335
+ entries: refreshedDurableRecords,
336
+ segment: DefaultDurableSegment,
337
+ });
338
+ }
323
339
  // redirects
324
340
  redirects.forEach((value, key) => {
325
341
  durableStoreOperations.push({
@@ -346,6 +362,17 @@ function flushInMemoryStoreValuesToDurableStore(store, durableStore, durableStor
346
362
  }
347
363
  return Promise.resolve();
348
364
  }
365
+ function setRecordTo(entries, key, record, metadata) {
366
+ entries[key] = {
367
+ data: record,
368
+ };
369
+ if (metadata !== undefined) {
370
+ entries[key].metadata = {
371
+ ...metadata,
372
+ metadataVersion: DURABLE_METADATA_VERSION,
373
+ };
374
+ }
375
+ }
349
376
 
350
377
  const DurableEnvironmentEventDiscriminator = 'durable';
351
378
  function emitDurableEnvironmentAdapterEvent(eventData, observers) {
@@ -390,6 +417,50 @@ async function reviveRedirects(durableStore, env) {
390
417
  }
391
418
  }
392
419
 
420
+ function buildRevivingStagingStore(upstreamStore) {
421
+ const localStore = new StringKeyInMemoryStore();
422
+ const staleEntries = new Set();
423
+ function readEntry(key) {
424
+ if (typeof key !== 'string') {
425
+ return upstreamStore.readEntry(key);
426
+ }
427
+ let storeEntry = localStore.readEntry(key);
428
+ if (!storeEntry) {
429
+ // read from upstream store...
430
+ storeEntry = upstreamStore.readEntry(key);
431
+ // put it in our store to avoid it getting evicted prior to the next durable store read
432
+ localStore.put(key, storeEntry);
433
+ }
434
+ return storeEntry;
435
+ }
436
+ // Entries are marked stale by the durable store change listener. They are not
437
+ // immediately evicted so as to not result in a cache miss during a rebuild.
438
+ // The revive process will clear stale entries and read them from the durable store
439
+ // on the next revive loop.
440
+ function markStale(key) {
441
+ staleEntries.add(key);
442
+ }
443
+ // The revive loop clears stale entries right before reading from the durable store.
444
+ // Any stale entries will be revived to ensure they are present in L1 and match the
445
+ // latest data.
446
+ function clearStale() {
447
+ for (const key of staleEntries) {
448
+ localStore.dealloc(key);
449
+ }
450
+ staleEntries.clear();
451
+ }
452
+ // All functions other than `readEntry` pass through to the upstream store.
453
+ // A reviving store is only "active" during a call to `environment.storeLookup`, and will
454
+ // be used by the reader attempting to build an L1 snapshot. Immediately after the L1 rebuild
455
+ // the reviving store becomes inactive other than receiving change notifications.
456
+ return create$2(upstreamStore, {
457
+ readEntry: { value: readEntry },
458
+ markStale: { value: markStale },
459
+ clearStale: { value: clearStale },
460
+ staleEntries: { value: staleEntries },
461
+ });
462
+ }
463
+
393
464
  const AdapterContextSegment = 'ADAPTER-CONTEXT';
394
465
  const ADAPTER_CONTEXT_ID_SUFFIX = '__NAMED_CONTEXT';
395
466
  async function reviveOrCreateContext(adapterId, durableStore, durableStoreErrorHandler, contextStores, pendingContextStoreKeys, onContextLoaded) {
@@ -445,14 +516,16 @@ function isUnfulfilledSnapshot(cachedSnapshotResult) {
445
516
  * @param durableStore A DurableStore implementation
446
517
  * @param instrumentation An instrumentation function implementation
447
518
  */
448
- function makeDurable(environment, { durableStore, instrumentation }) {
449
- let ingestStagingStore = null;
519
+ function makeDurable(environment, { durableStore, instrumentation, useRevivingStore, enableDurableMetadataRefresh = false, }) {
520
+ let stagingStore = null;
450
521
  const durableTTLStore = new DurableTTLStore(durableStore);
451
522
  const mergeKeysPromiseMap = new Map();
452
523
  // When a context store is mutated we write it to L2, which causes DS on change
453
524
  // event. If this instance of makeDurable caused that L2 write we can ignore that
454
525
  // on change event. This Set helps us do that.
455
526
  const pendingContextStoreKeys = new Set();
527
+ // Reviving stores are tracked so that they can be notified of durable store change notifications.
528
+ const revivingStores = new Set();
456
529
  // redirects that need to be flushed to the durable store
457
530
  const pendingStoreRedirects = new Map();
458
531
  const contextStores = create$2(null);
@@ -478,6 +551,7 @@ function makeDurable(environment, { durableStore, instrumentation }) {
478
551
  const defaultSegmentKeys = [];
479
552
  const adapterContextSegmentKeys = [];
480
553
  const redirectSegmentKeys = [];
554
+ const metadataRefreshSegmentKeys = [];
481
555
  const messagingSegmentKeys = [];
482
556
  let shouldBroadcast = false;
483
557
  for (let i = 0, len = changes.length; i < len; i++) {
@@ -485,7 +559,12 @@ function makeDurable(environment, { durableStore, instrumentation }) {
485
559
  // we only care about changes to the data which is stored in the default
486
560
  // segment or the adapter context
487
561
  if (change.segment === DefaultDurableSegment) {
488
- defaultSegmentKeys.push(...change.ids);
562
+ if (change.type === 'setMetadata') {
563
+ metadataRefreshSegmentKeys.push(...change.ids);
564
+ }
565
+ else {
566
+ defaultSegmentKeys.push(...change.ids);
567
+ }
489
568
  }
490
569
  else if (change.segment === AdapterContextSegment) {
491
570
  adapterContextSegmentKeys.push(...change.ids);
@@ -549,9 +628,26 @@ function makeDurable(environment, { durableStore, instrumentation }) {
549
628
  // and go through an entire broadcast/revive cycle for unchanged data
550
629
  // call base environment storeEvict so this evict is not tracked for durable deletion
551
630
  environment.storeEvict(key);
631
+ for (const revivingStore of revivingStores) {
632
+ revivingStore.markStale(key);
633
+ }
552
634
  }
553
635
  shouldBroadcast = true;
554
636
  }
637
+ // process metadata only refreshes
638
+ if (metadataRefreshSegmentKeys.length > 0) {
639
+ const entries = await durableStore.getMetadata(metadataRefreshSegmentKeys, DefaultDurableSegment);
640
+ if (entries !== undefined) {
641
+ const entryKeys = keys$2(entries);
642
+ for (let i = 0, len = entryKeys.length; i < len; i++) {
643
+ const entryKey = entryKeys[i];
644
+ const { metadata } = entries[entryKey];
645
+ if (metadata !== undefined) {
646
+ environment.putStoreMetadata(entryKey, metadata, false);
647
+ }
648
+ }
649
+ }
650
+ }
555
651
  if (shouldBroadcast) {
556
652
  await environment.storeBroadcast(rebuildSnapshot, environment.snapshotAvailable);
557
653
  }
@@ -577,10 +673,10 @@ function makeDurable(environment, { durableStore, instrumentation }) {
577
673
  };
578
674
  const storePublish = function (key, data) {
579
675
  validateNotDisposed();
580
- if (ingestStagingStore === null) {
581
- ingestStagingStore = buildIngestStagingStore(environment);
676
+ if (stagingStore === null) {
677
+ stagingStore = buildIngestStagingStore(environment);
582
678
  }
583
- ingestStagingStore.publish(key, data);
679
+ stagingStore.publish(key, data);
584
680
  // remove record from main luvio L1 cache while we are on the synchronous path
585
681
  // because we do not want some other code attempting to use the
586
682
  // in-memory values before the durable store onChanged handler
@@ -589,26 +685,26 @@ function makeDurable(environment, { durableStore, instrumentation }) {
589
685
  };
590
686
  const publishStoreMetadata = function (recordId, storeMetadata) {
591
687
  validateNotDisposed();
592
- if (ingestStagingStore === null) {
593
- ingestStagingStore = buildIngestStagingStore(environment);
688
+ if (stagingStore === null) {
689
+ stagingStore = buildIngestStagingStore(environment);
594
690
  }
595
- ingestStagingStore.publishMetadata(recordId, storeMetadata);
691
+ stagingStore.publishMetadata(recordId, storeMetadata);
596
692
  };
597
693
  const storeIngest = function (key, ingest, response, luvio) {
598
694
  validateNotDisposed();
599
695
  // we don't ingest to the luvio L1 store from network directly, we ingest to
600
696
  // L2 and let DurableStore on change event revive keys into luvio L1 store
601
- if (ingestStagingStore === null) {
602
- ingestStagingStore = buildIngestStagingStore(environment);
697
+ if (stagingStore === null) {
698
+ stagingStore = buildIngestStagingStore(environment);
603
699
  }
604
- environment.storeIngest(key, ingest, response, luvio, ingestStagingStore);
700
+ environment.storeIngest(key, ingest, response, luvio, stagingStore);
605
701
  };
606
702
  const storeIngestError = function (key, errorSnapshot, storeMetadataParams, _storeOverride) {
607
703
  validateNotDisposed();
608
- if (ingestStagingStore === null) {
609
- ingestStagingStore = buildIngestStagingStore(environment);
704
+ if (stagingStore === null) {
705
+ stagingStore = buildIngestStagingStore(environment);
610
706
  }
611
- environment.storeIngestError(key, errorSnapshot, storeMetadataParams, ingestStagingStore);
707
+ environment.storeIngestError(key, errorSnapshot, storeMetadataParams, stagingStore);
612
708
  };
613
709
  const storeBroadcast = function (_rebuildSnapshot, _snapshotDataAvailable) {
614
710
  validateNotDisposed();
@@ -619,19 +715,19 @@ function makeDurable(environment, { durableStore, instrumentation }) {
619
715
  };
620
716
  const publishChangesToDurableStore = function (additionalDurableStoreOperations) {
621
717
  validateNotDisposed();
622
- if (ingestStagingStore === null) {
718
+ if (stagingStore === null) {
623
719
  return Promise.resolve();
624
720
  }
625
- const promise = flushInMemoryStoreValuesToDurableStore(ingestStagingStore, durableStore, durableStoreErrorHandler, new Map(pendingStoreRedirects), additionalDurableStoreOperations);
721
+ const promise = flushInMemoryStoreValuesToDurableStore(stagingStore, durableStore, durableStoreErrorHandler, new Map(pendingStoreRedirects), additionalDurableStoreOperations, enableDurableMetadataRefresh);
626
722
  pendingStoreRedirects.clear();
627
- ingestStagingStore = null;
723
+ stagingStore = null;
628
724
  return promise;
629
725
  };
630
726
  const storeLookup = function (sel, createSnapshot, refresh, ttlStrategy) {
631
727
  validateNotDisposed();
632
- // if this lookup is right after an ingest there will be a staging store
633
- if (ingestStagingStore !== null) {
634
- const reader = new Reader(ingestStagingStore, sel.variables, refresh, undefined, ttlStrategy);
728
+ // if this lookup is right after an ingest or during a revive there will be a staging store
729
+ if (stagingStore !== null) {
730
+ const reader = new Reader(stagingStore, sel.variables, refresh, undefined, ttlStrategy);
635
731
  return reader.read(sel);
636
732
  }
637
733
  // otherwise this is from buildCachedSnapshot and we should use the luvio
@@ -640,24 +736,24 @@ function makeDurable(environment, { durableStore, instrumentation }) {
640
736
  };
641
737
  const storeEvict = function (key) {
642
738
  validateNotDisposed();
643
- if (ingestStagingStore === null) {
644
- ingestStagingStore = buildIngestStagingStore(environment);
739
+ if (stagingStore === null) {
740
+ stagingStore = buildIngestStagingStore(environment);
645
741
  }
646
- ingestStagingStore.evict(key);
742
+ stagingStore.evict(key);
647
743
  };
648
744
  const getNode = function (key) {
649
745
  validateNotDisposed();
650
- if (ingestStagingStore === null) {
651
- ingestStagingStore = buildIngestStagingStore(environment);
746
+ if (stagingStore === null) {
747
+ stagingStore = buildIngestStagingStore(environment);
652
748
  }
653
- return environment.getNode(key, ingestStagingStore);
749
+ return environment.getNode(key, stagingStore);
654
750
  };
655
751
  const wrapNormalizedGraphNode = function (normalized) {
656
752
  validateNotDisposed();
657
- if (ingestStagingStore === null) {
658
- ingestStagingStore = buildIngestStagingStore(environment);
753
+ if (stagingStore === null) {
754
+ stagingStore = buildIngestStagingStore(environment);
659
755
  }
660
- return environment.wrapNormalizedGraphNode(normalized, ingestStagingStore);
756
+ return environment.wrapNormalizedGraphNode(normalized, stagingStore);
661
757
  };
662
758
  const rebuildSnapshot = function (snapshot, onRebuild) {
663
759
  validateNotDisposed();
@@ -669,7 +765,7 @@ function makeDurable(environment, { durableStore, instrumentation }) {
669
765
  return;
670
766
  }
671
767
  // Do an L2 revive and emit to subscriber using the callback.
672
- reviveSnapshot(environment, durableStore, rebuilt, durableStoreErrorHandler, () => {
768
+ reviveSnapshotWrapper(rebuilt, () => {
673
769
  // reviveSnapshot will revive into L1, and since "records" is a reference
674
770
  // (and not a copy) to the L1 records we can use it for rebuild
675
771
  let rebuiltSnap;
@@ -710,10 +806,10 @@ function makeDurable(environment, { durableStore, instrumentation }) {
710
806
  // the next publishChangesToDurableStore. NOTE: we don't need to call
711
807
  // redirect on the base environment store because staging store and base
712
808
  // L1 store share the same redirect and reverseRedirectKeys
713
- if (ingestStagingStore === null) {
714
- ingestStagingStore = buildIngestStagingStore(environment);
809
+ if (stagingStore === null) {
810
+ stagingStore = buildIngestStagingStore(environment);
715
811
  }
716
- ingestStagingStore.redirect(existingKey, canonicalKey);
812
+ stagingStore.redirect(existingKey, canonicalKey);
717
813
  };
718
814
  const storeSetTTLOverride = function (namespace, representationName, ttl) {
719
815
  validateNotDisposed();
@@ -754,7 +850,7 @@ function makeDurable(environment, { durableStore, instrumentation }) {
754
850
  if (isUnfulfilledSnapshot(snapshot)) {
755
851
  const start = Date.now();
756
852
  emitDurableEnvironmentAdapterEvent({ type: 'l2-revive-start' }, adapterRequestContext.eventObservers);
757
- const revivedSnapshot = reviveSnapshot(environment, durableStore, snapshot, durableStoreErrorHandler, () => injectedStoreLookup(snapshot.select, snapshot.refresh)).then((result) => {
853
+ const revivedSnapshot = reviveSnapshotWrapper(snapshot, () => injectedStoreLookup(snapshot.select, snapshot.refresh)).then((result) => {
758
854
  emitDurableEnvironmentAdapterEvent({
759
855
  type: 'l2-revive-end',
760
856
  snapshot: result.snapshot,
@@ -779,15 +875,15 @@ function makeDurable(environment, { durableStore, instrumentation }) {
779
875
  };
780
876
  const getIngestStagingStoreRecords = function () {
781
877
  validateNotDisposed();
782
- if (ingestStagingStore !== null) {
783
- return ingestStagingStore.fallbackStringKeyInMemoryStore.records;
878
+ if (stagingStore !== null) {
879
+ return stagingStore.fallbackStringKeyInMemoryStore.records;
784
880
  }
785
881
  return {};
786
882
  };
787
883
  const getIngestStagingStoreMetadata = function () {
788
884
  validateNotDisposed();
789
- if (ingestStagingStore !== null) {
790
- return ingestStagingStore.fallbackStringKeyInMemoryStore.metadata;
885
+ if (stagingStore !== null) {
886
+ return stagingStore.fallbackStringKeyInMemoryStore.metadata;
791
887
  }
792
888
  return {};
793
889
  };
@@ -826,22 +922,20 @@ function makeDurable(environment, { durableStore, instrumentation }) {
826
922
  }
827
923
  await Promise.all(pendingPromises);
828
924
  const entries = await durableStore.getEntries(keysToReviveAsArray, DefaultDurableSegment);
829
- ingestStagingStore = buildIngestStagingStore(environment);
925
+ stagingStore = buildIngestStagingStore(environment);
830
926
  publishDurableStoreEntries(entries, (key, record) => {
831
927
  if (typeof key === 'string') {
832
- ingestStagingStore.fallbackStringKeyInMemoryStore.records[key] =
833
- record;
928
+ stagingStore.fallbackStringKeyInMemoryStore.records[key] = record;
834
929
  }
835
930
  else {
836
- ingestStagingStore.recordsMap.set(key, record);
931
+ stagingStore.recordsMap.set(key, record);
837
932
  }
838
933
  }, (key, metadata) => {
839
934
  if (typeof key === 'string') {
840
- ingestStagingStore.fallbackStringKeyInMemoryStore.metadata[key] =
841
- metadata;
935
+ stagingStore.fallbackStringKeyInMemoryStore.metadata[key] = metadata;
842
936
  }
843
937
  else {
844
- ingestStagingStore.metadataMap.set(key, metadata);
938
+ stagingStore.metadataMap.set(key, metadata);
845
939
  }
846
940
  });
847
941
  snapshotFromMemoryIngest = await ingestAndBroadcastFunc();
@@ -870,7 +964,7 @@ function makeDurable(environment, { durableStore, instrumentation }) {
870
964
  // we aren't doing any merging so we don't have to synchronize, the
871
965
  // underlying DurableStore implementation takes care of R/W sync
872
966
  // so all we have to do is ingest then write to L2
873
- ingestStagingStore = buildIngestStagingStore(environment);
967
+ stagingStore = buildIngestStagingStore(environment);
874
968
  snapshotFromMemoryIngest = await ingestAndBroadcastFunc();
875
969
  }
876
970
  if (snapshotFromMemoryIngest === undefined) {
@@ -881,12 +975,12 @@ function makeDurable(environment, { durableStore, instrumentation }) {
881
975
  }
882
976
  // if snapshot from staging store lookup is unfulfilled then do an L2 lookup
883
977
  const { select, refresh } = snapshotFromMemoryIngest;
884
- const result = await reviveSnapshot(environment, durableStore, snapshotFromMemoryIngest, durableStoreErrorHandler, () => environment.storeLookup(select, environment.createSnapshot, refresh));
978
+ const result = await reviveSnapshotWrapper(snapshotFromMemoryIngest, () => environment.storeLookup(select, environment.createSnapshot, refresh));
885
979
  return result.snapshot;
886
980
  };
887
981
  const handleErrorResponse = async function (ingestAndBroadcastFunc) {
888
982
  validateNotDisposed();
889
- ingestStagingStore = buildIngestStagingStore(environment);
983
+ stagingStore = buildIngestStagingStore(environment);
890
984
  return ingestAndBroadcastFunc();
891
985
  };
892
986
  const getNotifyChangeStoreEntries = function (keys) {
@@ -937,6 +1031,27 @@ function makeDurable(environment, { durableStore, instrumentation }) {
937
1031
  await durableStore.setEntries({ notifyStoreUpdateAvailable: { data: entryKeys } }, MessagingDurableSegment);
938
1032
  return Promise.resolve(undefined);
939
1033
  };
1034
+ const reviveSnapshotWrapper = function (unavailableSnapshot, buildL1Snapshot) {
1035
+ let revivingStore = undefined;
1036
+ if (useRevivingStore) {
1037
+ // NOTE: `store` is private, there doesn't seem to be a better,
1038
+ // cleaner way of accessing it from a derived environment.
1039
+ let baseStore = environment.store;
1040
+ // If we're rebuilding during an ingest, the existing staging store should be the base store.
1041
+ if (stagingStore) {
1042
+ baseStore = stagingStore;
1043
+ }
1044
+ let revivingStore = buildRevivingStagingStore(baseStore);
1045
+ revivingStores.add(revivingStore);
1046
+ }
1047
+ return reviveSnapshot(environment, durableStore, unavailableSnapshot, durableStoreErrorHandler, () => {
1048
+ const tempStore = stagingStore;
1049
+ const result = buildL1Snapshot();
1050
+ stagingStore = tempStore;
1051
+ return result;
1052
+ }, revivingStore).finally(() => {
1053
+ });
1054
+ };
940
1055
  // set the default cache policy of the base environment
941
1056
  environment.setDefaultCachePolicy({
942
1057
  type: 'stale-while-revalidate',
@@ -971,7 +1086,7 @@ function makeDurable(environment, { durableStore, instrumentation }) {
971
1086
  });
972
1087
  }
973
1088
 
974
- const { keys: keys$1, create: create$1, assign: assign$1, entries } = Object;
1089
+ const { keys: keys$1, create: create$1, assign: assign$1, entries, values: values$1 } = Object;
975
1090
  const { stringify, parse } = JSON;
976
1091
 
977
1092
  function selectColumnsFromTableWhereKeyIn(columnNames, table, keyColumnName, whereIn) {
@@ -1005,6 +1120,22 @@ class LdsDataTable {
1005
1120
  }, reject);
1006
1121
  });
1007
1122
  }
1123
+ getMetadataByKeys(keys) {
1124
+ const query = selectColumnsFromTableWhereKeyIn([COLUMN_NAME_KEY$2, COLUMN_NAME_METADATA$1], this.tableName, COLUMN_NAME_KEY$2, keys);
1125
+ return new Promise((resolve, reject) => {
1126
+ this.plugin.query(query, keys, (results) => {
1127
+ resolve(results.rows.reduce((entries, row) => {
1128
+ const [key, stringifiedMetadata] = row;
1129
+ if (stringifiedMetadata !== undefined) {
1130
+ entries[key] = {
1131
+ metadata: parse(stringifiedMetadata),
1132
+ };
1133
+ }
1134
+ return entries;
1135
+ }, {}));
1136
+ }, reject);
1137
+ });
1138
+ }
1008
1139
  getAll() {
1009
1140
  return new Promise((resolve, reject) => {
1010
1141
  this.plugin.query(this.getAllQuery, [], (x) => {
@@ -1031,6 +1162,24 @@ class LdsDataTable {
1031
1162
  }, []),
1032
1163
  };
1033
1164
  }
1165
+ metadataToUpdateOperations(entries, segment) {
1166
+ return {
1167
+ type: 'update',
1168
+ table: this.tableName,
1169
+ keyColumn: COLUMN_NAME_KEY$2,
1170
+ context: {
1171
+ segment,
1172
+ type: 'setMetadata',
1173
+ },
1174
+ columns: [COLUMN_NAME_METADATA$1],
1175
+ values: keys$1(entries).reduce((values, key) => {
1176
+ const { metadata } = entries[key];
1177
+ const row = [metadata ? stringify(metadata) : null];
1178
+ values[key] = row;
1179
+ return values;
1180
+ }, {}),
1181
+ };
1182
+ }
1034
1183
  mapToDurableEntries(sqliteResult) {
1035
1184
  return sqliteResult.rows.reduce((entries, row) => {
1036
1185
  const [key, stringifiedData, stringifiedMetadata] = row;
@@ -1077,6 +1226,25 @@ class LdsInternalDataTable {
1077
1226
  }, reject);
1078
1227
  });
1079
1228
  }
1229
+ getMetadataByKeys(keys, namespace) {
1230
+ if (namespace === undefined) {
1231
+ throw Error('LdsInternalDataTable requires namespace');
1232
+ }
1233
+ const query = selectColumnsFromTableWhereKeyInNamespaced([COLUMN_NAME_KEY$1, COLUMN_NAME_METADATA], this.tableName, COLUMN_NAME_KEY$1, keys, COLUMN_NAME_NAMESPACE);
1234
+ return new Promise((resolve, reject) => {
1235
+ this.plugin.query(query, [namespace].concat(keys), (results) => {
1236
+ resolve(results.rows.reduce((entries, row) => {
1237
+ const [key, stringifiedMetadata] = row;
1238
+ if (stringifiedMetadata !== undefined) {
1239
+ entries[key] = {
1240
+ metadata: parse(stringifiedMetadata),
1241
+ };
1242
+ }
1243
+ return entries;
1244
+ }, {}));
1245
+ }, reject);
1246
+ });
1247
+ }
1080
1248
  getAll(namespace) {
1081
1249
  return new Promise((resolve, reject) => {
1082
1250
  this.plugin.query(this.getAllQuery, [namespace], (x) => {
@@ -1110,6 +1278,42 @@ class LdsInternalDataTable {
1110
1278
  }, []),
1111
1279
  };
1112
1280
  }
1281
+ metadataToUpdateOperations(entries, segment) {
1282
+ return {
1283
+ type: 'update',
1284
+ table: this.tableName,
1285
+ keyColumn: COLUMN_NAME_KEY$1,
1286
+ context: {
1287
+ segment,
1288
+ type: 'setMetadata',
1289
+ },
1290
+ columns: [COLUMN_NAME_METADATA],
1291
+ values: keys$1(entries).reduce((values, key) => {
1292
+ const { metadata } = entries[key];
1293
+ const row = [metadata ? stringify(metadata) : null];
1294
+ values[key] = row;
1295
+ return values;
1296
+ }, {}),
1297
+ };
1298
+ }
1299
+ metadataToUpdateSQLQueries(entries, segment) {
1300
+ return keys$1(entries).reduce((accu, key) => {
1301
+ const { metadata } = entries[key];
1302
+ if (metadata !== undefined) {
1303
+ accu.push({
1304
+ sql: `UPDATE ${this.tableName} SET ${COLUMN_NAME_METADATA} = ? WHERE (${COLUMN_NAME_KEY$1} IS ? AND ${COLUMN_NAME_NAMESPACE} IS ?)`,
1305
+ params: [stringify(metadata), key, segment],
1306
+ change: {
1307
+ ids: [key],
1308
+ segment,
1309
+ type: 'setMetadata',
1310
+ isExternalChange: false,
1311
+ },
1312
+ });
1313
+ }
1314
+ return accu;
1315
+ }, []);
1316
+ }
1113
1317
  mapToDurableEntries(sqliteResult) {
1114
1318
  return sqliteResult.rows.reduce((entries, row) => {
1115
1319
  const [key, stringifiedData, stringifiedMetadata] = row;
@@ -1146,9 +1350,16 @@ class NimbusSqliteStore {
1146
1350
  });
1147
1351
  });
1148
1352
  }
1353
+ batchQuery(queries) {
1354
+ const promises = queries.map((q) => this.query(q.sql, q.params));
1355
+ return Promise.all(promises);
1356
+ }
1149
1357
  async getEntries(entryIds, segment) {
1150
1358
  return this.getTable(segment).getByKeys(entryIds, segment);
1151
1359
  }
1360
+ async getMetadata(entryIds, segment) {
1361
+ return this.getTable(segment).getMetadataByKeys(entryIds, segment);
1362
+ }
1152
1363
  getAllEntries(segment) {
1153
1364
  return this.getTable(segment).getAll(segment);
1154
1365
  }
@@ -1157,12 +1368,30 @@ class NimbusSqliteStore {
1157
1368
  const upsertOperation = table.entriesToUpsertOperations(entries, segment);
1158
1369
  return this.batchOperationAsPromise([upsertOperation]);
1159
1370
  }
1371
+ setMetadata(entries, segment) {
1372
+ const table = this.getTable(segment);
1373
+ const operation = this.plugin.supportsBatchUpdates === undefined ||
1374
+ this.plugin.supportsBatchUpdates() === false
1375
+ ? table.entriesToUpsertOperations(entries, segment)
1376
+ : table.metadataToUpdateOperations(entries, segment);
1377
+ return this.batchOperationAsPromise([operation]);
1378
+ }
1160
1379
  batchOperations(operations) {
1161
1380
  const sqliteOperations = operations.reduce((acc, cur) => {
1162
1381
  if (cur.type === 'setEntries') {
1163
1382
  const table = this.getTable(cur.segment);
1164
1383
  acc.push(table.entriesToUpsertOperations(cur.entries, cur.segment));
1165
1384
  }
1385
+ else if (cur.type === 'setMetadata') {
1386
+ const table = this.getTable(cur.segment);
1387
+ if (this.plugin.supportsBatchUpdates === undefined ||
1388
+ this.plugin.supportsBatchUpdates() === false) {
1389
+ acc.push(table.entriesToUpsertOperations(cur.entries, cur.segment));
1390
+ }
1391
+ else {
1392
+ acc.push(table.metadataToUpdateOperations(cur.entries, cur.segment));
1393
+ }
1394
+ }
1166
1395
  else {
1167
1396
  acc.push(this.idsToDeleteOperation(cur.ids, cur.segment));
1168
1397
  }
@@ -1179,8 +1408,15 @@ class NimbusSqliteStore {
1179
1408
  this.plugin
1180
1409
  .registerOnChangedListener(async (changes) => {
1181
1410
  const durableChanges = changes.map((c) => {
1411
+ let type = c.type === 'upsert' ? 'setEntries' : 'evictEntries';
1412
+ // if our context contains a type then set that as our main level type
1413
+ // allows us in the future of updates to specify the segment change happening
1414
+ // example being update call on metadata only or updating data
1415
+ if (c.type === 'update' && c.context.type !== undefined) {
1416
+ type = c.context.type;
1417
+ }
1182
1418
  return {
1183
- type: c.type === 'upsert' ? 'setEntries' : 'evictEntries',
1419
+ type,
1184
1420
  ids: c.keys,
1185
1421
  isExternalChange: false,
1186
1422
  segment: c.context.segment,
@@ -6470,14 +6706,30 @@ function makeRecordDenormalizingDurableStore(luvio, durableStore, getStoreRecord
6470
6706
  const operationsWithDenormedRecords = [];
6471
6707
  for (let i = 0, len = operations.length; i < len; i++) {
6472
6708
  const operation = operations[i];
6473
- if (operation.segment !== DefaultDurableSegment || operation.type !== 'setEntries') {
6474
- operationsWithDenormedRecords.push(operation);
6475
- continue;
6709
+ if (durableStore.plugin !== undefined &&
6710
+ durableStore.plugin.supportsBatchUpdates !== undefined &&
6711
+ durableStore.plugin.supportsBatchUpdates() === true) {
6712
+ if (operation.segment !== DefaultDurableSegment ||
6713
+ operation.type !== 'setEntries') {
6714
+ operationsWithDenormedRecords.push(operation);
6715
+ continue;
6716
+ }
6717
+ operationsWithDenormedRecords.push({
6718
+ ...operation,
6719
+ entries: denormalizeEntries(operation.entries),
6720
+ });
6721
+ }
6722
+ else {
6723
+ if (operation.segment !== DefaultDurableSegment ||
6724
+ operation.type === 'evictEntries') {
6725
+ operationsWithDenormedRecords.push(operation);
6726
+ continue;
6727
+ }
6728
+ operationsWithDenormedRecords.push({
6729
+ ...operation,
6730
+ entries: denormalizeEntries(operation.entries),
6731
+ });
6476
6732
  }
6477
- operationsWithDenormedRecords.push({
6478
- ...operation,
6479
- entries: denormalizeEntries(operation.entries),
6480
- });
6481
6733
  }
6482
6734
  return durableStore.batchOperations(operationsWithDenormedRecords);
6483
6735
  };
@@ -6567,4 +6819,4 @@ function ldsRuntimeBridge() {
6567
6819
  }
6568
6820
 
6569
6821
  export { ldsRuntimeBridge as default };
6570
- // version: 1.232.0-968cf099f
6822
+ // version: 1.235.0-3790decf0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-runtime-bridge",
3
- "version": "1.232.0",
3
+ "version": "1.235.0",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "LDS runtime for bridge.app.",
6
6
  "main": "dist/ldsRuntimeBridge.js",