@salesforce/lds-runtime-mobile 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.
Files changed (3) hide show
  1. package/dist/main.js +458 -156
  2. package/package.json +1 -1
  3. package/sfdc/main.js +458 -156
package/dist/main.js CHANGED
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { withRegistration, register } from '@salesforce/lds-default-luvio';
15
15
  import { setupInstrumentation, instrumentAdapter as instrumentAdapter$1, instrumentLuvio, setLdsAdaptersUiapiInstrumentation, setLdsNetworkAdapterInstrumentation } from '@salesforce/lds-instrumentation';
16
- import { HttpStatusCode, StoreKeySet, serializeStructuredKey, Reader, deepFreeze, emitAdapterEvent, createCustomAdapterEventEmitter, StoreKeyMap, isFileReference, Environment, Luvio, InMemoryStore } from '@luvio/engine';
16
+ import { HttpStatusCode, StoreKeySet, serializeStructuredKey, StringKeyInMemoryStore, Reader, deepFreeze, emitAdapterEvent, createCustomAdapterEventEmitter, StoreKeyMap, isFileReference, Environment, Luvio, InMemoryStore } from '@luvio/engine';
17
17
  import excludeStaleRecordsGate from '@salesforce/gate/lds.graphqlEvalExcludeStaleRecords';
18
18
  import { parseAndVisit, Kind, visit, execute, buildSchema, isObjectType, defaultFieldResolver } from '@luvio/graphql-parser';
19
19
  import { getRecordId18, keyBuilderQuickActionExecutionRepresentation, ingestQuickActionExecutionRepresentation, keyBuilderContentDocumentCompositeRepresentation, getResponseCacheKeysContentDocumentCompositeRepresentation, keyBuilderFromTypeContentDocumentCompositeRepresentation, ingestContentDocumentCompositeRepresentation, keyBuilderRecord, getTypeCacheKeysRecord, keyBuilderFromTypeRecordRepresentation, ingestRecord, RecordRepresentationRepresentationType, ObjectInfoRepresentationType, getRecordAdapterFactory, getObjectInfoAdapterFactory, getObjectInfosAdapterFactory, UiApiNamespace, RecordRepresentationType, RecordRepresentationTTL, RecordRepresentationVersion, getRecordsAdapterFactory } from '@salesforce/lds-adapters-uiapi';
@@ -33,6 +33,7 @@ import eagerEvalValidAt from '@salesforce/gate/lds.eagerEvalValidAt';
33
33
  import eagerEvalStaleWhileRevalidate from '@salesforce/gate/lds.eagerEvalStaleWhileRevalidate';
34
34
  import eagerEvalDefaultCachePolicy from '@salesforce/gate/lds.eagerEvalDefaultCachePolicy';
35
35
  import ldsPrimingGraphqlBatch from '@salesforce/gate/lds.primingGraphqlBatch';
36
+ import ldsMetadataRefreshEnabled from '@salesforce/gate/lds.metadataRefreshEnabled';
36
37
 
37
38
  /**
38
39
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -615,7 +616,7 @@ function publishDurableStoreEntries(durableRecords, put, publishMetadata) {
615
616
  * will refresh the snapshot from network, and then run the results from network
616
617
  * through L2 ingestion, returning the subsequent revived snapshot.
617
618
  */
618
- function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, durableStoreErrorHandler, buildL1Snapshot, reviveMetrics = { l2Trips: [] }) {
619
+ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics = { l2Trips: [] }) {
619
620
  const { recordId, select, missingLinks, seenRecords, state } = unavailableSnapshot;
620
621
  // L2 can only revive Unfulfilled snapshots that have a selector since they have the
621
622
  // info needed to revive (like missingLinks) and rebuild. Otherwise return L1 snapshot.
@@ -625,10 +626,21 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
625
626
  metrics: reviveMetrics,
626
627
  });
627
628
  }
628
- // in case L1 store changes/deallocs a record while we are doing the async read
629
- // we attempt to read all keys from L2 - so combine recordId with any seenRecords
630
- const keysToReviveSet = new StoreKeySet().add(recordId);
631
- keysToReviveSet.merge(seenRecords);
629
+ const keysToReviveSet = new StoreKeySet();
630
+ if (revivingStore) {
631
+ // Any stale keys since the last l2 read should be cleared and fetched again
632
+ for (const staleKey of revivingStore.staleEntries) {
633
+ keysToReviveSet.add(staleKey);
634
+ }
635
+ revivingStore.clearStale();
636
+ }
637
+ else {
638
+ // when not using a reviving store:
639
+ // in case L1 store changes/deallocs a record while we are doing the async read
640
+ // we attempt to read all keys from L2 - so combine recordId with any seenRecords
641
+ keysToReviveSet.add(recordId);
642
+ keysToReviveSet.merge(seenRecords);
643
+ }
632
644
  keysToReviveSet.merge(missingLinks);
633
645
  const keysToRevive = keysToReviveSet.keysAsArray();
634
646
  const canonicalKeys = keysToRevive.map((x) => serializeStructuredKey(baseEnvironment.storeGetCanonicalKey(x)));
@@ -678,7 +690,7 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
678
690
  for (let i = 0, len = newKeys.length; i < len; i++) {
679
691
  const newSnapshotSeenKey = newKeys[i];
680
692
  if (!alreadyRequestedOrRevivedSet.has(newSnapshotSeenKey)) {
681
- return reviveSnapshot(baseEnvironment, durableStore, snapshot, durableStoreErrorHandler, buildL1Snapshot, reviveMetrics);
693
+ return reviveSnapshot(baseEnvironment, durableStore, snapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics);
682
694
  }
683
695
  }
684
696
  }
@@ -767,8 +779,9 @@ class DurableTTLStore {
767
779
  }
768
780
  }
769
781
 
770
- function flushInMemoryStoreValuesToDurableStore(store, durableStore, durableStoreErrorHandler, redirects, additionalDurableStoreOperations = []) {
782
+ function flushInMemoryStoreValuesToDurableStore(store, durableStore, durableStoreErrorHandler, redirects, additionalDurableStoreOperations = [], enableDurableMetadataRefresh = false) {
771
783
  const durableRecords = create$6(null);
784
+ const refreshedDurableRecords = create$6(null);
772
785
  const evictedRecords = create$6(null);
773
786
  const { records, metadata: storeMetadata, visitedIds, refreshedIds, } = store.fallbackStringKeyInMemoryStore;
774
787
  // TODO: W-8909393 Once metadata is stored in its own segment we need to
@@ -778,32 +791,36 @@ function flushInMemoryStoreValuesToDurableStore(store, durableStore, durableStor
778
791
  for (let i = 0, len = keys$1.length; i < len; i += 1) {
779
792
  const key = keys$1[i];
780
793
  const record = records[key];
794
+ const wasVisited = visitedIds[key] !== undefined;
781
795
  // this record has been evicted, evict from DS
782
- if (record === undefined) {
796
+ if (wasVisited && record === undefined) {
783
797
  evictedRecords[key] = true;
784
798
  continue;
785
799
  }
786
800
  const metadata = storeMetadata[key];
787
- durableRecords[key] = {
788
- data: record,
789
- };
790
- if (metadata !== undefined) {
791
- durableRecords[key].metadata = {
792
- ...metadata,
793
- metadataVersion: DURABLE_METADATA_VERSION,
794
- };
795
- }
801
+ const entries = wasVisited === true || enableDurableMetadataRefresh === false
802
+ ? durableRecords
803
+ : refreshedDurableRecords;
804
+ setRecordTo(entries, key, record, metadata);
796
805
  }
797
806
  const durableStoreOperations = additionalDurableStoreOperations;
798
- // publishes
799
807
  const recordKeys = keys$7(durableRecords);
800
808
  if (recordKeys.length > 0) {
809
+ // publishes with data
801
810
  durableStoreOperations.push({
802
811
  type: 'setEntries',
803
812
  entries: durableRecords,
804
813
  segment: DefaultDurableSegment,
805
814
  });
806
815
  }
816
+ if (keys$7(refreshedDurableRecords).length > 0) {
817
+ // publishes with only metadata updates
818
+ durableStoreOperations.push({
819
+ type: 'setMetadata',
820
+ entries: refreshedDurableRecords,
821
+ segment: DefaultDurableSegment,
822
+ });
823
+ }
807
824
  // redirects
808
825
  redirects.forEach((value, key) => {
809
826
  durableStoreOperations.push({
@@ -830,6 +847,17 @@ function flushInMemoryStoreValuesToDurableStore(store, durableStore, durableStor
830
847
  }
831
848
  return Promise.resolve();
832
849
  }
850
+ function setRecordTo(entries, key, record, metadata) {
851
+ entries[key] = {
852
+ data: record,
853
+ };
854
+ if (metadata !== undefined) {
855
+ entries[key].metadata = {
856
+ ...metadata,
857
+ metadataVersion: DURABLE_METADATA_VERSION,
858
+ };
859
+ }
860
+ }
833
861
 
834
862
  const DurableEnvironmentEventDiscriminator = 'durable';
835
863
  function emitDurableEnvironmentAdapterEvent(eventData, observers) {
@@ -874,6 +902,50 @@ async function reviveRedirects(durableStore, env) {
874
902
  }
875
903
  }
876
904
 
905
+ function buildRevivingStagingStore(upstreamStore) {
906
+ const localStore = new StringKeyInMemoryStore();
907
+ const staleEntries = new Set();
908
+ function readEntry(key) {
909
+ if (typeof key !== 'string') {
910
+ return upstreamStore.readEntry(key);
911
+ }
912
+ let storeEntry = localStore.readEntry(key);
913
+ if (!storeEntry) {
914
+ // read from upstream store...
915
+ storeEntry = upstreamStore.readEntry(key);
916
+ // put it in our store to avoid it getting evicted prior to the next durable store read
917
+ localStore.put(key, storeEntry);
918
+ }
919
+ return storeEntry;
920
+ }
921
+ // Entries are marked stale by the durable store change listener. They are not
922
+ // immediately evicted so as to not result in a cache miss during a rebuild.
923
+ // The revive process will clear stale entries and read them from the durable store
924
+ // on the next revive loop.
925
+ function markStale(key) {
926
+ staleEntries.add(key);
927
+ }
928
+ // The revive loop clears stale entries right before reading from the durable store.
929
+ // Any stale entries will be revived to ensure they are present in L1 and match the
930
+ // latest data.
931
+ function clearStale() {
932
+ for (const key of staleEntries) {
933
+ localStore.dealloc(key);
934
+ }
935
+ staleEntries.clear();
936
+ }
937
+ // All functions other than `readEntry` pass through to the upstream store.
938
+ // A reviving store is only "active" during a call to `environment.storeLookup`, and will
939
+ // be used by the reader attempting to build an L1 snapshot. Immediately after the L1 rebuild
940
+ // the reviving store becomes inactive other than receiving change notifications.
941
+ return create$6(upstreamStore, {
942
+ readEntry: { value: readEntry },
943
+ markStale: { value: markStale },
944
+ clearStale: { value: clearStale },
945
+ staleEntries: { value: staleEntries },
946
+ });
947
+ }
948
+
877
949
  const AdapterContextSegment = 'ADAPTER-CONTEXT';
878
950
  const ADAPTER_CONTEXT_ID_SUFFIX = '__NAMED_CONTEXT';
879
951
  async function reviveOrCreateContext(adapterId, durableStore, durableStoreErrorHandler, contextStores, pendingContextStoreKeys, onContextLoaded) {
@@ -929,14 +1001,16 @@ function isUnfulfilledSnapshot$1(cachedSnapshotResult) {
929
1001
  * @param durableStore A DurableStore implementation
930
1002
  * @param instrumentation An instrumentation function implementation
931
1003
  */
932
- function makeDurable(environment, { durableStore, instrumentation }) {
933
- let ingestStagingStore = null;
1004
+ function makeDurable(environment, { durableStore, instrumentation, useRevivingStore, enableDurableMetadataRefresh = false, }) {
1005
+ let stagingStore = null;
934
1006
  const durableTTLStore = new DurableTTLStore(durableStore);
935
1007
  const mergeKeysPromiseMap = new Map();
936
1008
  // When a context store is mutated we write it to L2, which causes DS on change
937
1009
  // event. If this instance of makeDurable caused that L2 write we can ignore that
938
1010
  // on change event. This Set helps us do that.
939
1011
  const pendingContextStoreKeys = new Set();
1012
+ // Reviving stores are tracked so that they can be notified of durable store change notifications.
1013
+ const revivingStores = new Set();
940
1014
  // redirects that need to be flushed to the durable store
941
1015
  const pendingStoreRedirects = new Map();
942
1016
  const contextStores = create$6(null);
@@ -962,6 +1036,7 @@ function makeDurable(environment, { durableStore, instrumentation }) {
962
1036
  const defaultSegmentKeys = [];
963
1037
  const adapterContextSegmentKeys = [];
964
1038
  const redirectSegmentKeys = [];
1039
+ const metadataRefreshSegmentKeys = [];
965
1040
  const messagingSegmentKeys = [];
966
1041
  let shouldBroadcast = false;
967
1042
  for (let i = 0, len = changes.length; i < len; i++) {
@@ -969,7 +1044,12 @@ function makeDurable(environment, { durableStore, instrumentation }) {
969
1044
  // we only care about changes to the data which is stored in the default
970
1045
  // segment or the adapter context
971
1046
  if (change.segment === DefaultDurableSegment) {
972
- defaultSegmentKeys.push(...change.ids);
1047
+ if (change.type === 'setMetadata') {
1048
+ metadataRefreshSegmentKeys.push(...change.ids);
1049
+ }
1050
+ else {
1051
+ defaultSegmentKeys.push(...change.ids);
1052
+ }
973
1053
  }
974
1054
  else if (change.segment === AdapterContextSegment) {
975
1055
  adapterContextSegmentKeys.push(...change.ids);
@@ -1033,9 +1113,26 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1033
1113
  // and go through an entire broadcast/revive cycle for unchanged data
1034
1114
  // call base environment storeEvict so this evict is not tracked for durable deletion
1035
1115
  environment.storeEvict(key);
1116
+ for (const revivingStore of revivingStores) {
1117
+ revivingStore.markStale(key);
1118
+ }
1036
1119
  }
1037
1120
  shouldBroadcast = true;
1038
1121
  }
1122
+ // process metadata only refreshes
1123
+ if (metadataRefreshSegmentKeys.length > 0) {
1124
+ const entries = await durableStore.getMetadata(metadataRefreshSegmentKeys, DefaultDurableSegment);
1125
+ if (entries !== undefined) {
1126
+ const entryKeys = keys$7(entries);
1127
+ for (let i = 0, len = entryKeys.length; i < len; i++) {
1128
+ const entryKey = entryKeys[i];
1129
+ const { metadata } = entries[entryKey];
1130
+ if (metadata !== undefined) {
1131
+ environment.putStoreMetadata(entryKey, metadata, false);
1132
+ }
1133
+ }
1134
+ }
1135
+ }
1039
1136
  if (shouldBroadcast) {
1040
1137
  await environment.storeBroadcast(rebuildSnapshot, environment.snapshotAvailable);
1041
1138
  }
@@ -1061,10 +1158,10 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1061
1158
  };
1062
1159
  const storePublish = function (key, data) {
1063
1160
  validateNotDisposed();
1064
- if (ingestStagingStore === null) {
1065
- ingestStagingStore = buildIngestStagingStore(environment);
1161
+ if (stagingStore === null) {
1162
+ stagingStore = buildIngestStagingStore(environment);
1066
1163
  }
1067
- ingestStagingStore.publish(key, data);
1164
+ stagingStore.publish(key, data);
1068
1165
  // remove record from main luvio L1 cache while we are on the synchronous path
1069
1166
  // because we do not want some other code attempting to use the
1070
1167
  // in-memory values before the durable store onChanged handler
@@ -1073,26 +1170,26 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1073
1170
  };
1074
1171
  const publishStoreMetadata = function (recordId, storeMetadata) {
1075
1172
  validateNotDisposed();
1076
- if (ingestStagingStore === null) {
1077
- ingestStagingStore = buildIngestStagingStore(environment);
1173
+ if (stagingStore === null) {
1174
+ stagingStore = buildIngestStagingStore(environment);
1078
1175
  }
1079
- ingestStagingStore.publishMetadata(recordId, storeMetadata);
1176
+ stagingStore.publishMetadata(recordId, storeMetadata);
1080
1177
  };
1081
1178
  const storeIngest = function (key, ingest, response, luvio) {
1082
1179
  validateNotDisposed();
1083
1180
  // we don't ingest to the luvio L1 store from network directly, we ingest to
1084
1181
  // L2 and let DurableStore on change event revive keys into luvio L1 store
1085
- if (ingestStagingStore === null) {
1086
- ingestStagingStore = buildIngestStagingStore(environment);
1182
+ if (stagingStore === null) {
1183
+ stagingStore = buildIngestStagingStore(environment);
1087
1184
  }
1088
- environment.storeIngest(key, ingest, response, luvio, ingestStagingStore);
1185
+ environment.storeIngest(key, ingest, response, luvio, stagingStore);
1089
1186
  };
1090
1187
  const storeIngestError = function (key, errorSnapshot, storeMetadataParams, _storeOverride) {
1091
1188
  validateNotDisposed();
1092
- if (ingestStagingStore === null) {
1093
- ingestStagingStore = buildIngestStagingStore(environment);
1189
+ if (stagingStore === null) {
1190
+ stagingStore = buildIngestStagingStore(environment);
1094
1191
  }
1095
- environment.storeIngestError(key, errorSnapshot, storeMetadataParams, ingestStagingStore);
1192
+ environment.storeIngestError(key, errorSnapshot, storeMetadataParams, stagingStore);
1096
1193
  };
1097
1194
  const storeBroadcast = function (_rebuildSnapshot, _snapshotDataAvailable) {
1098
1195
  validateNotDisposed();
@@ -1103,19 +1200,19 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1103
1200
  };
1104
1201
  const publishChangesToDurableStore = function (additionalDurableStoreOperations) {
1105
1202
  validateNotDisposed();
1106
- if (ingestStagingStore === null) {
1203
+ if (stagingStore === null) {
1107
1204
  return Promise.resolve();
1108
1205
  }
1109
- const promise = flushInMemoryStoreValuesToDurableStore(ingestStagingStore, durableStore, durableStoreErrorHandler, new Map(pendingStoreRedirects), additionalDurableStoreOperations);
1206
+ const promise = flushInMemoryStoreValuesToDurableStore(stagingStore, durableStore, durableStoreErrorHandler, new Map(pendingStoreRedirects), additionalDurableStoreOperations, enableDurableMetadataRefresh);
1110
1207
  pendingStoreRedirects.clear();
1111
- ingestStagingStore = null;
1208
+ stagingStore = null;
1112
1209
  return promise;
1113
1210
  };
1114
1211
  const storeLookup = function (sel, createSnapshot, refresh, ttlStrategy) {
1115
1212
  validateNotDisposed();
1116
- // if this lookup is right after an ingest there will be a staging store
1117
- if (ingestStagingStore !== null) {
1118
- const reader = new Reader(ingestStagingStore, sel.variables, refresh, undefined, ttlStrategy);
1213
+ // if this lookup is right after an ingest or during a revive there will be a staging store
1214
+ if (stagingStore !== null) {
1215
+ const reader = new Reader(stagingStore, sel.variables, refresh, undefined, ttlStrategy);
1119
1216
  return reader.read(sel);
1120
1217
  }
1121
1218
  // otherwise this is from buildCachedSnapshot and we should use the luvio
@@ -1124,24 +1221,24 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1124
1221
  };
1125
1222
  const storeEvict = function (key) {
1126
1223
  validateNotDisposed();
1127
- if (ingestStagingStore === null) {
1128
- ingestStagingStore = buildIngestStagingStore(environment);
1224
+ if (stagingStore === null) {
1225
+ stagingStore = buildIngestStagingStore(environment);
1129
1226
  }
1130
- ingestStagingStore.evict(key);
1227
+ stagingStore.evict(key);
1131
1228
  };
1132
1229
  const getNode = function (key) {
1133
1230
  validateNotDisposed();
1134
- if (ingestStagingStore === null) {
1135
- ingestStagingStore = buildIngestStagingStore(environment);
1231
+ if (stagingStore === null) {
1232
+ stagingStore = buildIngestStagingStore(environment);
1136
1233
  }
1137
- return environment.getNode(key, ingestStagingStore);
1234
+ return environment.getNode(key, stagingStore);
1138
1235
  };
1139
1236
  const wrapNormalizedGraphNode = function (normalized) {
1140
1237
  validateNotDisposed();
1141
- if (ingestStagingStore === null) {
1142
- ingestStagingStore = buildIngestStagingStore(environment);
1238
+ if (stagingStore === null) {
1239
+ stagingStore = buildIngestStagingStore(environment);
1143
1240
  }
1144
- return environment.wrapNormalizedGraphNode(normalized, ingestStagingStore);
1241
+ return environment.wrapNormalizedGraphNode(normalized, stagingStore);
1145
1242
  };
1146
1243
  const rebuildSnapshot = function (snapshot, onRebuild) {
1147
1244
  validateNotDisposed();
@@ -1153,7 +1250,7 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1153
1250
  return;
1154
1251
  }
1155
1252
  // Do an L2 revive and emit to subscriber using the callback.
1156
- reviveSnapshot(environment, durableStore, rebuilt, durableStoreErrorHandler, () => {
1253
+ reviveSnapshotWrapper(rebuilt, () => {
1157
1254
  // reviveSnapshot will revive into L1, and since "records" is a reference
1158
1255
  // (and not a copy) to the L1 records we can use it for rebuild
1159
1256
  let rebuiltSnap;
@@ -1194,10 +1291,10 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1194
1291
  // the next publishChangesToDurableStore. NOTE: we don't need to call
1195
1292
  // redirect on the base environment store because staging store and base
1196
1293
  // L1 store share the same redirect and reverseRedirectKeys
1197
- if (ingestStagingStore === null) {
1198
- ingestStagingStore = buildIngestStagingStore(environment);
1294
+ if (stagingStore === null) {
1295
+ stagingStore = buildIngestStagingStore(environment);
1199
1296
  }
1200
- ingestStagingStore.redirect(existingKey, canonicalKey);
1297
+ stagingStore.redirect(existingKey, canonicalKey);
1201
1298
  };
1202
1299
  const storeSetTTLOverride = function (namespace, representationName, ttl) {
1203
1300
  validateNotDisposed();
@@ -1238,7 +1335,7 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1238
1335
  if (isUnfulfilledSnapshot$1(snapshot)) {
1239
1336
  const start = Date.now();
1240
1337
  emitDurableEnvironmentAdapterEvent({ type: 'l2-revive-start' }, adapterRequestContext.eventObservers);
1241
- const revivedSnapshot = reviveSnapshot(environment, durableStore, snapshot, durableStoreErrorHandler, () => injectedStoreLookup(snapshot.select, snapshot.refresh)).then((result) => {
1338
+ const revivedSnapshot = reviveSnapshotWrapper(snapshot, () => injectedStoreLookup(snapshot.select, snapshot.refresh)).then((result) => {
1242
1339
  emitDurableEnvironmentAdapterEvent({
1243
1340
  type: 'l2-revive-end',
1244
1341
  snapshot: result.snapshot,
@@ -1263,15 +1360,15 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1263
1360
  };
1264
1361
  const getIngestStagingStoreRecords = function () {
1265
1362
  validateNotDisposed();
1266
- if (ingestStagingStore !== null) {
1267
- return ingestStagingStore.fallbackStringKeyInMemoryStore.records;
1363
+ if (stagingStore !== null) {
1364
+ return stagingStore.fallbackStringKeyInMemoryStore.records;
1268
1365
  }
1269
1366
  return {};
1270
1367
  };
1271
1368
  const getIngestStagingStoreMetadata = function () {
1272
1369
  validateNotDisposed();
1273
- if (ingestStagingStore !== null) {
1274
- return ingestStagingStore.fallbackStringKeyInMemoryStore.metadata;
1370
+ if (stagingStore !== null) {
1371
+ return stagingStore.fallbackStringKeyInMemoryStore.metadata;
1275
1372
  }
1276
1373
  return {};
1277
1374
  };
@@ -1310,22 +1407,20 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1310
1407
  }
1311
1408
  await Promise.all(pendingPromises);
1312
1409
  const entries = await durableStore.getEntries(keysToReviveAsArray, DefaultDurableSegment);
1313
- ingestStagingStore = buildIngestStagingStore(environment);
1410
+ stagingStore = buildIngestStagingStore(environment);
1314
1411
  publishDurableStoreEntries(entries, (key, record) => {
1315
1412
  if (typeof key === 'string') {
1316
- ingestStagingStore.fallbackStringKeyInMemoryStore.records[key] =
1317
- record;
1413
+ stagingStore.fallbackStringKeyInMemoryStore.records[key] = record;
1318
1414
  }
1319
1415
  else {
1320
- ingestStagingStore.recordsMap.set(key, record);
1416
+ stagingStore.recordsMap.set(key, record);
1321
1417
  }
1322
1418
  }, (key, metadata) => {
1323
1419
  if (typeof key === 'string') {
1324
- ingestStagingStore.fallbackStringKeyInMemoryStore.metadata[key] =
1325
- metadata;
1420
+ stagingStore.fallbackStringKeyInMemoryStore.metadata[key] = metadata;
1326
1421
  }
1327
1422
  else {
1328
- ingestStagingStore.metadataMap.set(key, metadata);
1423
+ stagingStore.metadataMap.set(key, metadata);
1329
1424
  }
1330
1425
  });
1331
1426
  snapshotFromMemoryIngest = await ingestAndBroadcastFunc();
@@ -1354,7 +1449,7 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1354
1449
  // we aren't doing any merging so we don't have to synchronize, the
1355
1450
  // underlying DurableStore implementation takes care of R/W sync
1356
1451
  // so all we have to do is ingest then write to L2
1357
- ingestStagingStore = buildIngestStagingStore(environment);
1452
+ stagingStore = buildIngestStagingStore(environment);
1358
1453
  snapshotFromMemoryIngest = await ingestAndBroadcastFunc();
1359
1454
  }
1360
1455
  if (snapshotFromMemoryIngest === undefined) {
@@ -1365,12 +1460,12 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1365
1460
  }
1366
1461
  // if snapshot from staging store lookup is unfulfilled then do an L2 lookup
1367
1462
  const { select, refresh } = snapshotFromMemoryIngest;
1368
- const result = await reviveSnapshot(environment, durableStore, snapshotFromMemoryIngest, durableStoreErrorHandler, () => environment.storeLookup(select, environment.createSnapshot, refresh));
1463
+ const result = await reviveSnapshotWrapper(snapshotFromMemoryIngest, () => environment.storeLookup(select, environment.createSnapshot, refresh));
1369
1464
  return result.snapshot;
1370
1465
  };
1371
1466
  const handleErrorResponse = async function (ingestAndBroadcastFunc) {
1372
1467
  validateNotDisposed();
1373
- ingestStagingStore = buildIngestStagingStore(environment);
1468
+ stagingStore = buildIngestStagingStore(environment);
1374
1469
  return ingestAndBroadcastFunc();
1375
1470
  };
1376
1471
  const getNotifyChangeStoreEntries = function (keys) {
@@ -1421,6 +1516,27 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1421
1516
  await durableStore.setEntries({ notifyStoreUpdateAvailable: { data: entryKeys } }, MessagingDurableSegment);
1422
1517
  return Promise.resolve(undefined);
1423
1518
  };
1519
+ const reviveSnapshotWrapper = function (unavailableSnapshot, buildL1Snapshot) {
1520
+ let revivingStore = undefined;
1521
+ if (useRevivingStore) {
1522
+ // NOTE: `store` is private, there doesn't seem to be a better,
1523
+ // cleaner way of accessing it from a derived environment.
1524
+ let baseStore = environment.store;
1525
+ // If we're rebuilding during an ingest, the existing staging store should be the base store.
1526
+ if (stagingStore) {
1527
+ baseStore = stagingStore;
1528
+ }
1529
+ let revivingStore = buildRevivingStagingStore(baseStore);
1530
+ revivingStores.add(revivingStore);
1531
+ }
1532
+ return reviveSnapshot(environment, durableStore, unavailableSnapshot, durableStoreErrorHandler, () => {
1533
+ const tempStore = stagingStore;
1534
+ const result = buildL1Snapshot();
1535
+ stagingStore = tempStore;
1536
+ return result;
1537
+ }, revivingStore).finally(() => {
1538
+ });
1539
+ };
1424
1540
  // set the default cache policy of the base environment
1425
1541
  environment.setDefaultCachePolicy({
1426
1542
  type: 'stale-while-revalidate',
@@ -1455,6 +1571,72 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1455
1571
  });
1456
1572
  }
1457
1573
 
1574
+ /**
1575
+ * Copyright (c) 2022, Salesforce, Inc.,
1576
+ * All rights reserved.
1577
+ * For full license text, see the LICENSE.txt file
1578
+ */
1579
+
1580
+ const API_NAMESPACE = 'UiApi';
1581
+ const RECORD_REPRESENTATION_NAME = 'RecordRepresentation';
1582
+ const RECORD_VIEW_ENTITY_REPRESENTATION_NAME = 'RecordViewEntityRepresentation';
1583
+ const RECORD_ID_PREFIX = `${API_NAMESPACE}::${RECORD_REPRESENTATION_NAME}:`;
1584
+ const RECORD_VIEW_ENTITY_ID_PREFIX = `${API_NAMESPACE}::${RECORD_VIEW_ENTITY_REPRESENTATION_NAME}:Name:`;
1585
+ const RECORD_FIELDS_KEY_JUNCTION = '__fields__';
1586
+ function isStoreKeyRecordId(key) {
1587
+ return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) === -1;
1588
+ }
1589
+ function isStoreKeyRecordViewEntity(key) {
1590
+ return (key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX) > -1 &&
1591
+ key.indexOf(RECORD_FIELDS_KEY_JUNCTION) === -1);
1592
+ }
1593
+ function isStoreKeyRecordField(key) {
1594
+ return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) > -1;
1595
+ }
1596
+ function extractRecordIdFromStoreKey(key) {
1597
+ if (key === undefined ||
1598
+ (key.indexOf(RECORD_ID_PREFIX) === -1 && key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX) === -1)) {
1599
+ return undefined;
1600
+ }
1601
+ const parts = key.split(':');
1602
+ return parts[parts.length - 1].split('_')[0];
1603
+ }
1604
+ function buildRecordFieldStoreKey(recordKey, fieldName) {
1605
+ return `${recordKey}${RECORD_FIELDS_KEY_JUNCTION}${fieldName}`;
1606
+ }
1607
+ function objectsDeepEqual(lhs, rhs) {
1608
+ if (lhs === rhs)
1609
+ return true;
1610
+ if (typeof lhs !== 'object' || typeof rhs !== 'object' || lhs === null || rhs === null)
1611
+ return false;
1612
+ const lhsKeys = Object.keys(lhs);
1613
+ const rhsKeys = Object.keys(rhs);
1614
+ if (lhsKeys.length !== rhsKeys.length)
1615
+ return false;
1616
+ for (let key of lhsKeys) {
1617
+ if (!rhsKeys.includes(key))
1618
+ return false;
1619
+ if (typeof lhs[key] === 'function' || typeof rhs[key] === 'function') {
1620
+ if (lhs[key].toString() !== rhs[key].toString())
1621
+ return false;
1622
+ }
1623
+ else {
1624
+ if (!objectsDeepEqual(lhs[key], rhs[key]))
1625
+ return false;
1626
+ }
1627
+ }
1628
+ return true;
1629
+ }
1630
+
1631
+ function isStoreRecordError(storeRecord) {
1632
+ return storeRecord.__type === 'error';
1633
+ }
1634
+ function isEntryDurableRecordRepresentation(entry, key) {
1635
+ // Either a DurableRecordRepresentation or StoreRecordError can live at a record key
1636
+ return ((isStoreKeyRecordId(key) || isStoreKeyRecordViewEntity(key)) &&
1637
+ entry.data.__type === undefined);
1638
+ }
1639
+
1458
1640
  /**
1459
1641
  * Copyright (c) 2022, Salesforce, Inc.,
1460
1642
  * All rights reserved.
@@ -1839,7 +2021,7 @@ function isFailure(result) {
1839
2021
  function errors(result) {
1840
2022
  return result.error;
1841
2023
  }
1842
- function values$3(result) {
2024
+ function values$4(result) {
1843
2025
  return result.value;
1844
2026
  }
1845
2027
  function flattenResults(results) {
@@ -1847,7 +2029,7 @@ function flattenResults(results) {
1847
2029
  if (fails.length > 0) {
1848
2030
  return failure(fails);
1849
2031
  }
1850
- return success(results.filter(isSuccess).map(values$3));
2032
+ return success(results.filter(isSuccess).map(values$4));
1851
2033
  }
1852
2034
 
1853
2035
  function getFieldInfo(apiName, fieldName, infoMap) {
@@ -2733,7 +2915,7 @@ function fieldsToFilters(fieldValues, joinAlias, apiName, input, compoundOperato
2733
2915
  if (failures.length > 0) {
2734
2916
  return failure(failures);
2735
2917
  }
2736
- const containers = results.filter(isSuccess).map(values$3);
2918
+ const containers = results.filter(isSuccess).map(values$4);
2737
2919
  const predicates = [];
2738
2920
  containers.forEach((c) => {
2739
2921
  if (c.predicate !== undefined) {
@@ -3025,7 +3207,7 @@ function dateFunctions(operatorNode, extract, dataType) {
3025
3207
  if (fails.length > 0) {
3026
3208
  return failure(fails);
3027
3209
  }
3028
- const vals = results.filter(isSuccess).reduce(flatMap(values$3), []);
3210
+ const vals = results.filter(isSuccess).reduce(flatMap(values$4), []);
3029
3211
  return success(vals);
3030
3212
  }
3031
3213
  function isFilterFunction(name) {
@@ -3035,7 +3217,7 @@ function fieldOperators(operatorNode, dataType) {
3035
3217
  const results = Object.entries(operatorNode.fields)
3036
3218
  .filter(([key, _]) => isFilterFunction(key) === false)
3037
3219
  .map(([key, value]) => operatorWithValue(key, value, dataType));
3038
- const _values = results.filter(isSuccess).map(values$3);
3220
+ const _values = results.filter(isSuccess).map(values$4);
3039
3221
  const fails = results.filter(isFailure).reduce(flatMap(errors), []);
3040
3222
  if (fails.length > 0) {
3041
3223
  return failure(fails);
@@ -3956,7 +4138,7 @@ function selectionToQueryField(node, names, parentApiName, parentAlias, input, j
3956
4138
  }
3957
4139
  function recordFields(luvioSelections, names, parentApiName, parentAlias, input, joins) {
3958
4140
  const results = luvioSelections.map((selection) => selectionToQueryField(selection, names, parentApiName, parentAlias, input, joins));
3959
- const fields = results.filter(isSuccess).reduce(flatMap(values$3), []);
4141
+ const fields = results.filter(isSuccess).reduce(flatMap(values$4), []);
3960
4142
  const fails = results.filter(isFailure).reduce(flatMap(errors), []);
3961
4143
  if (fails.length > 0) {
3962
4144
  return failure(fails);
@@ -4202,7 +4384,7 @@ function rootRecordQuery(selection, input) {
4202
4384
  }
4203
4385
  function rootQuery(recordNodes, input) {
4204
4386
  const results = recordNodes.map((record) => rootRecordQuery(record, input));
4205
- const connections = results.filter(isSuccess).map(values$3);
4387
+ const connections = results.filter(isSuccess).map(values$4);
4206
4388
  const fails = results.filter(isFailure).reduce(flatMap(errors), []);
4207
4389
  if (fails.length > 0) {
4208
4390
  return failure(fails);
@@ -4653,7 +4835,11 @@ function makeStoreEval(preconditioner, objectInfoService, userId, contextProvide
4653
4835
  try {
4654
4836
  const { data, seenRecords } = await queryEvaluator(rootQuery, context, eventEmitter);
4655
4837
  const rebuildWithStoreEval = ((originalSnapshot) => {
4656
- return storeEval(config, originalSnapshot, observers, connectionKeyBuilder);
4838
+ return storeEval(config, originalSnapshot, observers, connectionKeyBuilder).then((rebuiltSnapshot) => {
4839
+ return objectsDeepEqual(originalSnapshot.data, rebuiltSnapshot.data)
4840
+ ? originalSnapshot
4841
+ : rebuiltSnapshot;
4842
+ });
4657
4843
  });
4658
4844
  const recordId = generateUniqueRecordId$1();
4659
4845
  // if the non-eval'ed snapshot was an error then we return a synthetic
@@ -4956,7 +5142,7 @@ function createDraftSynthesisErrorResponse(message = 'failed to synthesize draft
4956
5142
  return new DraftErrorFetchResponse(HttpStatusCode.BadRequest, error);
4957
5143
  }
4958
5144
 
4959
- const { keys: keys$5, create: create$5, assign: assign$5, values: values$2 } = Object;
5145
+ const { keys: keys$5, create: create$5, assign: assign$5, values: values$3 } = Object;
4960
5146
  const { stringify: stringify$5, parse: parse$5 } = JSON;
4961
5147
  const { isArray: isArray$3 } = Array;
4962
5148
 
@@ -5391,7 +5577,7 @@ class DurableDraftQueue {
5391
5577
  const queueOperations = handler.getQueueOperationsForCompletingDrafts(queue, action);
5392
5578
  // write the queue operations to the store prior to ingesting the result
5393
5579
  await this.draftStore.completeAction(queueOperations);
5394
- await handler.handleActionCompleted(action, queueOperations, values$2(this.handlers));
5580
+ await handler.handleActionCompleted(action, queueOperations, values$3(this.handlers));
5395
5581
  this.retryIntervalMilliseconds = 0;
5396
5582
  this.uploadingActionId = undefined;
5397
5583
  await this.notifyChangedListeners({
@@ -6613,49 +6799,6 @@ function makeEnvironmentDraftAware(luvio, env, durableStore, handlers, draftQueu
6613
6799
  });
6614
6800
  }
6615
6801
 
6616
- /**
6617
- * Copyright (c) 2022, Salesforce, Inc.,
6618
- * All rights reserved.
6619
- * For full license text, see the LICENSE.txt file
6620
- */
6621
-
6622
- const API_NAMESPACE = 'UiApi';
6623
- const RECORD_REPRESENTATION_NAME = 'RecordRepresentation';
6624
- const RECORD_VIEW_ENTITY_REPRESENTATION_NAME = 'RecordViewEntityRepresentation';
6625
- const RECORD_ID_PREFIX = `${API_NAMESPACE}::${RECORD_REPRESENTATION_NAME}:`;
6626
- const RECORD_VIEW_ENTITY_ID_PREFIX = `${API_NAMESPACE}::${RECORD_VIEW_ENTITY_REPRESENTATION_NAME}:Name:`;
6627
- const RECORD_FIELDS_KEY_JUNCTION = '__fields__';
6628
- function isStoreKeyRecordId(key) {
6629
- return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) === -1;
6630
- }
6631
- function isStoreKeyRecordViewEntity(key) {
6632
- return (key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX) > -1 &&
6633
- key.indexOf(RECORD_FIELDS_KEY_JUNCTION) === -1);
6634
- }
6635
- function isStoreKeyRecordField(key) {
6636
- return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) > -1;
6637
- }
6638
- function extractRecordIdFromStoreKey(key) {
6639
- if (key === undefined ||
6640
- (key.indexOf(RECORD_ID_PREFIX) === -1 && key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX) === -1)) {
6641
- return undefined;
6642
- }
6643
- const parts = key.split(':');
6644
- return parts[parts.length - 1].split('_')[0];
6645
- }
6646
- function buildRecordFieldStoreKey(recordKey, fieldName) {
6647
- return `${recordKey}${RECORD_FIELDS_KEY_JUNCTION}${fieldName}`;
6648
- }
6649
-
6650
- function isStoreRecordError(storeRecord) {
6651
- return storeRecord.__type === 'error';
6652
- }
6653
- function isEntryDurableRecordRepresentation(entry, key) {
6654
- // Either a DurableRecordRepresentation or StoreRecordError can live at a record key
6655
- return ((isStoreKeyRecordId(key) || isStoreKeyRecordViewEntity(key)) &&
6656
- entry.data.__type === undefined);
6657
- }
6658
-
6659
6802
  function serializeFieldArguments(argumentNodes, variables) {
6660
6803
  const mutableArgumentNodes = Object.assign([], argumentNodes);
6661
6804
  return `args__(${mutableArgumentNodes
@@ -6887,7 +7030,7 @@ function isArrayLike(x) {
6887
7030
  (x.length === 0 || (x.length > 0 && Object.prototype.hasOwnProperty.call(x, x.length - 1))));
6888
7031
  }
6889
7032
 
6890
- const { create: create$4, keys: keys$4, values: values$1, entries: entries$3, assign: assign$4 } = Object;
7033
+ const { create: create$4, keys: keys$4, values: values$2, entries: entries$3, assign: assign$4 } = Object;
6891
7034
  const { stringify: stringify$4, parse: parse$4 } = JSON;
6892
7035
  const { isArray: isArray$2 } = Array;
6893
7036
 
@@ -7220,7 +7363,7 @@ function dateTimePredicate(input, operator, field, alias) {
7220
7363
  return predicate;
7221
7364
  }
7222
7365
  else if (literal !== undefined) {
7223
- const isAvailableLiteral = values$1(DateLiteral).includes(literal);
7366
+ const isAvailableLiteral = values$2(DateLiteral).includes(literal);
7224
7367
  // eslint-disable-next-line @salesforce/lds/no-error-in-production
7225
7368
  if (!isAvailableLiteral)
7226
7369
  throw new Error(`${literal} is not a valid DateLiteral`);
@@ -7894,7 +8037,7 @@ function dedupeJoins(joins) {
7894
8037
  for (const join of joins) {
7895
8038
  deduped[join.alias + join.to] = join;
7896
8039
  }
7897
- return values$1(deduped);
8040
+ return values$2(deduped);
7898
8041
  }
7899
8042
  function buildJoins(config) {
7900
8043
  let sql = '';
@@ -8542,7 +8685,7 @@ function flatten(previous, current) {
8542
8685
  return previous.concat(current);
8543
8686
  }
8544
8687
  function findFieldInfo(objectInfo, fieldName) {
8545
- return values$1(objectInfo.fields).find((field) => field.apiName === fieldName ||
8688
+ return values$2(objectInfo.fields).find((field) => field.apiName === fieldName ||
8546
8689
  (field.dataType === 'Reference' && field.relationshipName === fieldName));
8547
8690
  }
8548
8691
 
@@ -8562,10 +8705,10 @@ function orderByToPredicate(orderBy, recordType, alias, objectInfoMap, joins) {
8562
8705
  for (let i = 0, len = keys$1.length; i < len; i++) {
8563
8706
  const key = keys$1[i];
8564
8707
  const parentFields = objectInfoMap[recordType].fields;
8565
- const fieldInfo = values$1(parentFields).find(findSpanningField(key));
8708
+ const fieldInfo = values$2(parentFields).find(findSpanningField(key));
8566
8709
  if (fieldInfo && fieldInfo.referenceToInfos.length > 0) {
8567
8710
  const { apiName } = fieldInfo.referenceToInfos[0];
8568
- const parentFieldInfo = values$1(objectInfoMap[recordType].fields).find(findSpanningField(fieldInfo.apiName));
8711
+ const parentFieldInfo = values$2(objectInfoMap[recordType].fields).find(findSpanningField(fieldInfo.apiName));
8569
8712
  if (parentFieldInfo !== undefined) {
8570
8713
  const path = {
8571
8714
  leftPath: `$.fields.${parentFieldInfo.apiName}.value`,
@@ -8707,7 +8850,7 @@ function addResolversToSchema(schema, polyFields) {
8707
8850
  let baseRecord = undefined;
8708
8851
  // Concrete types for Polymorphic field
8709
8852
  const polyTypes = [];
8710
- for (const type of values$1(schema.getTypeMap())) {
8853
+ for (const type of values$2(schema.getTypeMap())) {
8711
8854
  if (type.name === 'Record') {
8712
8855
  recordInterface = type;
8713
8856
  }
@@ -8720,7 +8863,7 @@ function addResolversToSchema(schema, polyFields) {
8720
8863
  if (polyFields.find((fieldTypeName) => fieldTypeName === type.name) !== undefined) {
8721
8864
  polyTypes.push(type);
8722
8865
  }
8723
- const fields = values$1(type.getFields());
8866
+ const fields = values$2(type.getFields());
8724
8867
  // initialize the fields of current type with default behavior
8725
8868
  for (const field of fields) {
8726
8869
  field.resolve = defaultFieldResolver;
@@ -9122,26 +9265,20 @@ function generateRecordQueries(objectInfos) {
9122
9265
  let recordConnections = ``;
9123
9266
  const polymorphicFieldTypeNames = new Set();
9124
9267
  let typedScalars = new Set();
9125
- for (const objectInfo of values$1(objectInfos)) {
9268
+ let parentRelationshipFields = new Set();
9269
+ for (const objectInfo of values$2(objectInfos)) {
9126
9270
  const { apiName, childRelationships } = objectInfo;
9127
9271
  let fields = ``;
9128
9272
  typedScalars.add(`${apiName}_Filter`);
9129
9273
  typedScalars.add(`${apiName}_OrderBy`);
9130
- for (const childRelationship of childRelationships) {
9131
- const { childObjectApiName } = childRelationship;
9132
- // Only add the relationship if there is relevant objectinfos for it,
9133
- // otherwise we'd be defining types we cannot satisfy and aren't referenced in
9134
- // the query.
9135
- if (objectInfos[childObjectApiName] !== undefined) {
9136
- fields += `${childRelationship.relationshipName}(first: Int, where: ${childObjectApiName}_Filter, orderBy: ${childObjectApiName}_OrderBy, scope: SupportedScopes): ${childObjectApiName}Connection \n`;
9137
- typedScalars.add(`${childObjectApiName}_Filter`);
9138
- typedScalars.add(`${childObjectApiName}_OrderBy`);
9139
- }
9140
- }
9141
- for (const field of values$1(objectInfo.fields)) {
9274
+ for (const field of values$2(objectInfo.fields)) {
9142
9275
  if (!fieldsStaticallyAdded.includes(field.apiName)) {
9143
9276
  fields += `${field.apiName}: ${dataTypeToType(field.dataType, field.apiName)}\n`;
9144
9277
  }
9278
+ //handles parent relationship
9279
+ if (field.relationshipName === null) {
9280
+ continue;
9281
+ }
9145
9282
  // For spanning parent relationships with no union types
9146
9283
  if (field.referenceToInfos.length === 1) {
9147
9284
  const [relation] = field.referenceToInfos;
@@ -9149,11 +9286,13 @@ function generateRecordQueries(objectInfos) {
9149
9286
  // otherwise we'd be defining types we cannot satisfy and aren't referenced in
9150
9287
  // the query.
9151
9288
  if (objectInfos[relation.apiName] !== undefined) {
9289
+ parentRelationshipFields.add(field.relationshipName);
9152
9290
  fields += `${field.relationshipName}: ${relation.apiName}\n`;
9153
9291
  }
9154
9292
  // For polymorphic field, its type is 'Record' inteface. The concrete entity type name is saved for field resolving of next phase
9155
9293
  }
9156
9294
  else if (field.referenceToInfos.length > 1) {
9295
+ parentRelationshipFields.add(field.relationshipName);
9157
9296
  fields += `${field.relationshipName}: Record\n`;
9158
9297
  for (const relation of field.referenceToInfos) {
9159
9298
  if (objectInfos[relation.apiName] !== undefined) {
@@ -9162,6 +9301,20 @@ function generateRecordQueries(objectInfos) {
9162
9301
  }
9163
9302
  }
9164
9303
  }
9304
+ // handles child relationship
9305
+ for (const childRelationship of childRelationships) {
9306
+ const { childObjectApiName } = childRelationship;
9307
+ // Only add the relationship if there is relevant objectinfos for it,
9308
+ // otherwise we'd be defining types we cannot satisfy and aren't referenced in
9309
+ // the query.
9310
+ // If one field has both parent relationship and child relationship with the same name, the child relationship is ignored. This is how the server GQL has implemented as date of 08/07/2023
9311
+ if (objectInfos[childObjectApiName] !== undefined &&
9312
+ !parentRelationshipFields.has(childRelationship.relationshipName)) {
9313
+ fields += `${childRelationship.relationshipName}(first: Int, where: ${childObjectApiName}_Filter, orderBy: ${childObjectApiName}_OrderBy, scope: SupportedScopes): ${childObjectApiName}Connection \n`;
9314
+ typedScalars.add(`${childObjectApiName}_Filter`);
9315
+ typedScalars.add(`${childObjectApiName}_OrderBy`);
9316
+ }
9317
+ }
9165
9318
  recordQueries += `${apiName}(first: Int, where: ${apiName}_Filter, orderBy: ${apiName}_OrderBy, scope: SupportedScopes): ${apiName}Connection\n`;
9166
9319
  const isServiceAppointment = apiName === 'ServiceAppointment';
9167
9320
  recordConnections += /* GraphQL */ `
@@ -9641,7 +9794,7 @@ function isMineScopeAvailable(apiNamePath, objectInfoApiMap, objectInfos) {
9641
9794
  const objectInfo = objectInfos[apiName[0]];
9642
9795
  if (!objectInfo)
9643
9796
  return false;
9644
- return values$1(objectInfo.fields).some((fieldInfo) => {
9797
+ return values$2(objectInfo.fields).some((fieldInfo) => {
9645
9798
  return (fieldInfo.apiName === 'OwnerId' &&
9646
9799
  fieldInfo.referenceToInfos.some((referenceToInfo) => {
9647
9800
  return referenceToInfo.apiName === 'User';
@@ -10892,7 +11045,7 @@ function referenceIdFieldForRelationship(relationshipName) {
10892
11045
  * For full license text, see the LICENSE.txt file
10893
11046
  */
10894
11047
 
10895
- const { keys: keys$3, values, create: create$3, assign: assign$3, freeze } = Object;
11048
+ const { keys: keys$3, values: values$1, create: create$3, assign: assign$3, freeze } = Object;
10896
11049
  const { stringify: stringify$3, parse: parse$3 } = JSON;
10897
11050
  const { shift } = Array.prototype;
10898
11051
  const { isArray: isArray$1 } = Array;
@@ -11742,7 +11895,7 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
11742
11895
  return;
11743
11896
  }
11744
11897
  const objectInfo = objectInfoMap[apiName];
11745
- const optionalFields = values(objectInfo.fields).map((field) => `${apiName}.${field.apiName}`);
11898
+ const optionalFields = values$1(objectInfo.fields).map((field) => `${apiName}.${field.apiName}`);
11746
11899
  await getAdapterData(this.getRecordAdapter, {
11747
11900
  recordId: referenceFieldInfo.id,
11748
11901
  optionalFields,
@@ -11761,7 +11914,7 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
11761
11914
  const referenceToInfos = fieldInfo.referenceToInfos;
11762
11915
  const apiNames = referenceToInfos.map((referenceToInfo) => referenceToInfo.apiName);
11763
11916
  const objectInfoMap = await this.objectInfoService.getObjectInfos(apiNames);
11764
- for (const objectInfo of values(objectInfoMap)) {
11917
+ for (const objectInfo of values$1(objectInfoMap)) {
11765
11918
  const { apiName, keyPrefix } = objectInfo;
11766
11919
  if (keyPrefix !== null && id.startsWith(keyPrefix)) {
11767
11920
  return apiName;
@@ -12271,14 +12424,30 @@ function makeRecordDenormalizingDurableStore(luvio, durableStore, getStoreRecord
12271
12424
  const operationsWithDenormedRecords = [];
12272
12425
  for (let i = 0, len = operations.length; i < len; i++) {
12273
12426
  const operation = operations[i];
12274
- if (operation.segment !== DefaultDurableSegment || operation.type !== 'setEntries') {
12275
- operationsWithDenormedRecords.push(operation);
12276
- continue;
12427
+ if (durableStore.plugin !== undefined &&
12428
+ durableStore.plugin.supportsBatchUpdates !== undefined &&
12429
+ durableStore.plugin.supportsBatchUpdates() === true) {
12430
+ if (operation.segment !== DefaultDurableSegment ||
12431
+ operation.type !== 'setEntries') {
12432
+ operationsWithDenormedRecords.push(operation);
12433
+ continue;
12434
+ }
12435
+ operationsWithDenormedRecords.push({
12436
+ ...operation,
12437
+ entries: denormalizeEntries(operation.entries),
12438
+ });
12439
+ }
12440
+ else {
12441
+ if (operation.segment !== DefaultDurableSegment ||
12442
+ operation.type === 'evictEntries') {
12443
+ operationsWithDenormedRecords.push(operation);
12444
+ continue;
12445
+ }
12446
+ operationsWithDenormedRecords.push({
12447
+ ...operation,
12448
+ entries: denormalizeEntries(operation.entries),
12449
+ });
12277
12450
  }
12278
- operationsWithDenormedRecords.push({
12279
- ...operation,
12280
- entries: denormalizeEntries(operation.entries),
12281
- });
12282
12451
  }
12283
12452
  return durableStore.batchOperations(operationsWithDenormedRecords);
12284
12453
  };
@@ -12826,6 +12995,9 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
12826
12995
  if (!rebuildResult.errors) {
12827
12996
  rebuildResult = removeSyntheticFields(rebuildResult, config.query);
12828
12997
  }
12998
+ if (objectsDeepEqual(rebuildResult, originalSnapshot.data)) {
12999
+ return originalSnapshot;
13000
+ }
12829
13001
  // 'originalSnapshot' is the local eval snapshot subscribed. It is always in 'Fulfilled' state. This behavior would change once W-1273462(rebuild non-evaluated snapshot when the graphql local eval rebuild is triggered) is resolved.
12830
13002
  return {
12831
13003
  ...originalSnapshot,
@@ -14990,7 +15162,7 @@ function registerReportObserver(reportObserver) {
14990
15162
  };
14991
15163
  }
14992
15164
 
14993
- const { keys, create, assign, entries } = Object;
15165
+ const { keys, create, assign, entries, values } = Object;
14994
15166
  const { stringify, parse } = JSON;
14995
15167
 
14996
15168
  function selectColumnsFromTableWhereKeyIn(columnNames, table, keyColumnName, whereIn) {
@@ -15024,6 +15196,22 @@ class LdsDataTable {
15024
15196
  }, reject);
15025
15197
  });
15026
15198
  }
15199
+ getMetadataByKeys(keys) {
15200
+ const query = selectColumnsFromTableWhereKeyIn([COLUMN_NAME_KEY$2, COLUMN_NAME_METADATA$1], this.tableName, COLUMN_NAME_KEY$2, keys);
15201
+ return new Promise((resolve, reject) => {
15202
+ this.plugin.query(query, keys, (results) => {
15203
+ resolve(results.rows.reduce((entries, row) => {
15204
+ const [key, stringifiedMetadata] = row;
15205
+ if (stringifiedMetadata !== undefined) {
15206
+ entries[key] = {
15207
+ metadata: parse(stringifiedMetadata),
15208
+ };
15209
+ }
15210
+ return entries;
15211
+ }, {}));
15212
+ }, reject);
15213
+ });
15214
+ }
15027
15215
  getAll() {
15028
15216
  return new Promise((resolve, reject) => {
15029
15217
  this.plugin.query(this.getAllQuery, [], (x) => {
@@ -15050,6 +15238,24 @@ class LdsDataTable {
15050
15238
  }, []),
15051
15239
  };
15052
15240
  }
15241
+ metadataToUpdateOperations(entries, segment) {
15242
+ return {
15243
+ type: 'update',
15244
+ table: this.tableName,
15245
+ keyColumn: COLUMN_NAME_KEY$2,
15246
+ context: {
15247
+ segment,
15248
+ type: 'setMetadata',
15249
+ },
15250
+ columns: [COLUMN_NAME_METADATA$1],
15251
+ values: keys(entries).reduce((values, key) => {
15252
+ const { metadata } = entries[key];
15253
+ const row = [metadata ? stringify(metadata) : null];
15254
+ values[key] = row;
15255
+ return values;
15256
+ }, {}),
15257
+ };
15258
+ }
15053
15259
  mapToDurableEntries(sqliteResult) {
15054
15260
  return sqliteResult.rows.reduce((entries, row) => {
15055
15261
  const [key, stringifiedData, stringifiedMetadata] = row;
@@ -15096,6 +15302,25 @@ class LdsInternalDataTable {
15096
15302
  }, reject);
15097
15303
  });
15098
15304
  }
15305
+ getMetadataByKeys(keys, namespace) {
15306
+ if (namespace === undefined) {
15307
+ throw Error('LdsInternalDataTable requires namespace');
15308
+ }
15309
+ const query = selectColumnsFromTableWhereKeyInNamespaced([COLUMN_NAME_KEY$1, COLUMN_NAME_METADATA], this.tableName, COLUMN_NAME_KEY$1, keys, COLUMN_NAME_NAMESPACE);
15310
+ return new Promise((resolve, reject) => {
15311
+ this.plugin.query(query, [namespace].concat(keys), (results) => {
15312
+ resolve(results.rows.reduce((entries, row) => {
15313
+ const [key, stringifiedMetadata] = row;
15314
+ if (stringifiedMetadata !== undefined) {
15315
+ entries[key] = {
15316
+ metadata: parse(stringifiedMetadata),
15317
+ };
15318
+ }
15319
+ return entries;
15320
+ }, {}));
15321
+ }, reject);
15322
+ });
15323
+ }
15099
15324
  getAll(namespace) {
15100
15325
  return new Promise((resolve, reject) => {
15101
15326
  this.plugin.query(this.getAllQuery, [namespace], (x) => {
@@ -15129,6 +15354,42 @@ class LdsInternalDataTable {
15129
15354
  }, []),
15130
15355
  };
15131
15356
  }
15357
+ metadataToUpdateOperations(entries, segment) {
15358
+ return {
15359
+ type: 'update',
15360
+ table: this.tableName,
15361
+ keyColumn: COLUMN_NAME_KEY$1,
15362
+ context: {
15363
+ segment,
15364
+ type: 'setMetadata',
15365
+ },
15366
+ columns: [COLUMN_NAME_METADATA],
15367
+ values: keys(entries).reduce((values, key) => {
15368
+ const { metadata } = entries[key];
15369
+ const row = [metadata ? stringify(metadata) : null];
15370
+ values[key] = row;
15371
+ return values;
15372
+ }, {}),
15373
+ };
15374
+ }
15375
+ metadataToUpdateSQLQueries(entries, segment) {
15376
+ return keys(entries).reduce((accu, key) => {
15377
+ const { metadata } = entries[key];
15378
+ if (metadata !== undefined) {
15379
+ accu.push({
15380
+ sql: `UPDATE ${this.tableName} SET ${COLUMN_NAME_METADATA} = ? WHERE (${COLUMN_NAME_KEY$1} IS ? AND ${COLUMN_NAME_NAMESPACE} IS ?)`,
15381
+ params: [stringify(metadata), key, segment],
15382
+ change: {
15383
+ ids: [key],
15384
+ segment,
15385
+ type: 'setMetadata',
15386
+ isExternalChange: false,
15387
+ },
15388
+ });
15389
+ }
15390
+ return accu;
15391
+ }, []);
15392
+ }
15132
15393
  mapToDurableEntries(sqliteResult) {
15133
15394
  return sqliteResult.rows.reduce((entries, row) => {
15134
15395
  const [key, stringifiedData, stringifiedMetadata] = row;
@@ -15165,9 +15426,16 @@ class NimbusSqliteStore {
15165
15426
  });
15166
15427
  });
15167
15428
  }
15429
+ batchQuery(queries) {
15430
+ const promises = queries.map((q) => this.query(q.sql, q.params));
15431
+ return Promise.all(promises);
15432
+ }
15168
15433
  async getEntries(entryIds, segment) {
15169
15434
  return this.getTable(segment).getByKeys(entryIds, segment);
15170
15435
  }
15436
+ async getMetadata(entryIds, segment) {
15437
+ return this.getTable(segment).getMetadataByKeys(entryIds, segment);
15438
+ }
15171
15439
  getAllEntries(segment) {
15172
15440
  return this.getTable(segment).getAll(segment);
15173
15441
  }
@@ -15176,12 +15444,30 @@ class NimbusSqliteStore {
15176
15444
  const upsertOperation = table.entriesToUpsertOperations(entries, segment);
15177
15445
  return this.batchOperationAsPromise([upsertOperation]);
15178
15446
  }
15447
+ setMetadata(entries, segment) {
15448
+ const table = this.getTable(segment);
15449
+ const operation = this.plugin.supportsBatchUpdates === undefined ||
15450
+ this.plugin.supportsBatchUpdates() === false
15451
+ ? table.entriesToUpsertOperations(entries, segment)
15452
+ : table.metadataToUpdateOperations(entries, segment);
15453
+ return this.batchOperationAsPromise([operation]);
15454
+ }
15179
15455
  batchOperations(operations) {
15180
15456
  const sqliteOperations = operations.reduce((acc, cur) => {
15181
15457
  if (cur.type === 'setEntries') {
15182
15458
  const table = this.getTable(cur.segment);
15183
15459
  acc.push(table.entriesToUpsertOperations(cur.entries, cur.segment));
15184
15460
  }
15461
+ else if (cur.type === 'setMetadata') {
15462
+ const table = this.getTable(cur.segment);
15463
+ if (this.plugin.supportsBatchUpdates === undefined ||
15464
+ this.plugin.supportsBatchUpdates() === false) {
15465
+ acc.push(table.entriesToUpsertOperations(cur.entries, cur.segment));
15466
+ }
15467
+ else {
15468
+ acc.push(table.metadataToUpdateOperations(cur.entries, cur.segment));
15469
+ }
15470
+ }
15185
15471
  else {
15186
15472
  acc.push(this.idsToDeleteOperation(cur.ids, cur.segment));
15187
15473
  }
@@ -15198,8 +15484,15 @@ class NimbusSqliteStore {
15198
15484
  this.plugin
15199
15485
  .registerOnChangedListener(async (changes) => {
15200
15486
  const durableChanges = changes.map((c) => {
15487
+ let type = c.type === 'upsert' ? 'setEntries' : 'evictEntries';
15488
+ // if our context contains a type then set that as our main level type
15489
+ // allows us in the future of updates to specify the segment change happening
15490
+ // example being update call on metadata only or updating data
15491
+ if (c.type === 'update' && c.context.type !== undefined) {
15492
+ type = c.context.type;
15493
+ }
15201
15494
  return {
15202
- type: c.type === 'upsert' ? 'setEntries' : 'evictEntries',
15495
+ type,
15203
15496
  ids: c.keys,
15204
15497
  isExternalChange: false,
15205
15498
  segment: c.context.segment,
@@ -15266,6 +15559,10 @@ class AbstractKeyValueDataTable {
15266
15559
  }, reject);
15267
15560
  });
15268
15561
  }
15562
+ getMetadataByKeys(_keys) {
15563
+ // eslint-disable-next-line @salesforce/lds/no-error-in-production
15564
+ throw new Error(`There is no metadata in the ${this.tableName} table.`);
15565
+ }
15269
15566
  getAll() {
15270
15567
  const getAllQuery = `SELECT ${this.columnNames.join(',')} FROM ${this.tableName}`;
15271
15568
  return new Promise((resolve, reject) => {
@@ -15291,6 +15588,10 @@ class AbstractKeyValueDataTable {
15291
15588
  }, []),
15292
15589
  };
15293
15590
  }
15591
+ metadataToUpdateOperations(_entries, _segment) {
15592
+ // eslint-disable-next-line @salesforce/lds/no-error-in-production
15593
+ throw new Error(`There is no metadata in the ${this.tableName} table.`);
15594
+ }
15294
15595
  mapToDurableEntries(sqliteResult) {
15295
15596
  return sqliteResult.rows.reduce((entries, row) => {
15296
15597
  const [key, stringifiedData] = row;
@@ -16630,6 +16931,7 @@ function getRuntime() {
16630
16931
  const gqlEnv = makeEnvironmentGraphqlAware(baseEnv);
16631
16932
  const durableEnv = makeDurable(gqlEnv, {
16632
16933
  durableStore: recordDenormingStore,
16934
+ enableDurableMetadataRefresh: ldsMetadataRefreshEnabled.isOpen({ fallback: false }),
16633
16935
  });
16634
16936
  getIngestRecords = durableEnv.getIngestStagingStoreRecords;
16635
16937
  getIngestMetadata = durableEnv.getIngestStagingStoreMetadata;
@@ -16731,4 +17033,4 @@ register({
16731
17033
  });
16732
17034
 
16733
17035
  export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
16734
- // version: 1.232.0-968cf099f
17036
+ // version: 1.235.0-3790decf0