@salesforce/lds-runtime-mobile 1.415.0 → 1.416.1

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 +184 -15
  2. package/package.json +16 -16
  3. package/sfdc/main.js +184 -15
package/dist/main.js CHANGED
@@ -47,6 +47,7 @@ import { setServices } from '@conduit-client/service-provisioner/v1';
47
47
  import '@conduit-client/type-normalization/v1';
48
48
  import { Kind as Kind$2, visit as visit$2, print as print$1, resolveAndValidateGraphQLConfig } from '@conduit-client/onestore-graphql-parser/v1';
49
49
  import productConsumedSideEffects from '@salesforce/gate/com.salesforce.fieldservice.vanStockLDSBypass256';
50
+ import reviveOnlyRequestedFields from '@salesforce/gate/lmr.reviveOnlyRequestedFields';
50
51
 
51
52
  /**
52
53
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -633,7 +634,7 @@ function publishDurableStoreEntries(durableRecords, put, publishMetadata) {
633
634
  * will refresh the snapshot from network, and then run the results from network
634
635
  * through L2 ingestion, returning the subsequent revived snapshot.
635
636
  */
636
- function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics = { l2Trips: [] }) {
637
+ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics = { l2Trips: [] }, shouldFilterFields) {
637
638
  const { recordId, select, missingLinks, seenRecords, state } = unavailableSnapshot;
638
639
  // L2 can only revive Unfulfilled snapshots that have a selector since they have the
639
640
  // info needed to revive (like missingLinks) and rebuild. Otherwise return L1 snapshot.
@@ -660,19 +661,55 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
660
661
  }
661
662
  keysToReviveSet.merge(missingLinks);
662
663
  const keysToRevive = keysToReviveSet.keysAsArray();
663
- const canonicalKeys = keysToRevive.map((x) => serializeStructuredKey(baseEnvironment.storeGetCanonicalKey(x)));
664
664
  const start = Date.now();
665
665
  const { l2Trips } = reviveMetrics;
666
- return durableStore.getEntries(canonicalKeys, DefaultDurableSegment).then((durableRecords) => {
666
+ // Extract requested fields first to determine if filtering is possible
667
+ let requestedFields;
668
+ if (select.node.kind === 'Fragment' && 'selections' in select.node && select.node.selections) {
669
+ requestedFields = extractRequestedFieldNames(select.node.selections);
670
+ }
671
+ const canonicalKeys = [];
672
+ const filteredCanonicalKeys = [];
673
+ for (let i = 0, len = keysToRevive.length; i < len; i += 1) {
674
+ const canonicalKey = serializeStructuredKey(baseEnvironment.storeGetCanonicalKey(keysToRevive[i]));
675
+ // Only filter if we have fields to filter and shouldFilterFields returns true
676
+ if (requestedFields !== undefined &&
677
+ requestedFields.size > 0 &&
678
+ shouldFilterFields(canonicalKey)) {
679
+ filteredCanonicalKeys.push(canonicalKey);
680
+ }
681
+ else {
682
+ canonicalKeys.push(canonicalKey);
683
+ }
684
+ }
685
+ const fetchPromises = [
686
+ durableStore.getEntries(canonicalKeys, DefaultDurableSegment),
687
+ ];
688
+ if (requestedFields !== undefined &&
689
+ requestedFields.size > 0 &&
690
+ filteredCanonicalKeys.length > 0) {
691
+ fetchPromises.push(durableStore.getEntriesWithSpecificFields(filteredCanonicalKeys, requestedFields, DefaultDurableSegment));
692
+ }
693
+ return Promise.all(fetchPromises).then(([durableRecords, filteredDurableRecords]) => {
667
694
  l2Trips.push({
668
695
  duration: Date.now() - start,
669
- keysRequestedCount: canonicalKeys.length,
696
+ keysRequestedCount: canonicalKeys.length + filteredCanonicalKeys.length,
670
697
  });
671
- const { revivedKeys, hadUnexpectedShape } = publishDurableStoreEntries(durableRecords,
672
- // TODO [W-10072584]: instead of implicitly using L1 we should take in
673
- // publish and publishMetadata funcs, so callers can decide where to
674
- // revive to (like they pass in how to do the buildL1Snapshot)
675
- baseEnvironment.storePut.bind(baseEnvironment), baseEnvironment.publishStoreMetadata.bind(baseEnvironment));
698
+ // Process both normal and filtered records in a single pass
699
+ const revivedKeys = new StoreKeySet();
700
+ let hadUnexpectedShape = false;
701
+ // Process normal records
702
+ if (durableRecords !== undefined) {
703
+ const normalResult = publishDurableStoreEntries(durableRecords, baseEnvironment.storePut.bind(baseEnvironment), baseEnvironment.publishStoreMetadata.bind(baseEnvironment));
704
+ revivedKeys.merge(normalResult.revivedKeys);
705
+ hadUnexpectedShape = hadUnexpectedShape || normalResult.hadUnexpectedShape;
706
+ }
707
+ // Process filtered records with merging
708
+ if (filteredDurableRecords !== undefined) {
709
+ const filteredResult = publishDurableStoreEntries(filteredDurableRecords, createMergeFilteredPut((key) => baseEnvironment.store.readEntry(key), baseEnvironment.storePut.bind(baseEnvironment)), baseEnvironment.publishStoreMetadata.bind(baseEnvironment));
710
+ revivedKeys.merge(filteredResult.revivedKeys);
711
+ hadUnexpectedShape = hadUnexpectedShape || filteredResult.hadUnexpectedShape;
712
+ }
676
713
  // if the data coming back from DS had an unexpected shape then just
677
714
  // return the L1 snapshot
678
715
  if (hadUnexpectedShape === true) {
@@ -707,7 +744,7 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
707
744
  for (let i = 0, len = newKeys.length; i < len; i++) {
708
745
  const newSnapshotSeenKey = newKeys[i];
709
746
  if (!alreadyRequestedOrRevivedSet.has(newSnapshotSeenKey)) {
710
- return reviveSnapshot(baseEnvironment, durableStore, snapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics);
747
+ return reviveSnapshot(baseEnvironment, durableStore, snapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics, shouldFilterFields);
711
748
  }
712
749
  }
713
750
  }
@@ -718,6 +755,59 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
718
755
  return { snapshot: unavailableSnapshot, metrics: reviveMetrics };
719
756
  });
720
757
  }
758
+ /**
759
+ * Creates a put function that merges filtered fields with existing L1 records instead of replacing them.
760
+ * This is used when reviving filtered fields from L2 to preserve existing data in L1.
761
+ */
762
+ function createMergeFilteredPut(readEntry, storePut) {
763
+ return (key, filteredData) => {
764
+ const existingRecord = readEntry(key);
765
+ if (existingRecord !== undefined &&
766
+ existingRecord !== null &&
767
+ typeof filteredData === 'object' &&
768
+ filteredData !== null &&
769
+ typeof existingRecord === 'object') {
770
+ const filteredFields = filteredData;
771
+ const existingObj = existingRecord;
772
+ // Check if object is frozen (can happen after deepFreeze)
773
+ // If frozen, create a shallow copy to merge fields into
774
+ let targetObj = existingObj;
775
+ if (Object.isFrozen(existingObj)) {
776
+ targetObj = { ...existingObj };
777
+ }
778
+ const keys = Object.keys(filteredFields);
779
+ for (let i = 0, len = keys.length; i < len; i += 1) {
780
+ const fieldKey = keys[i];
781
+ targetObj[fieldKey] = filteredFields[fieldKey];
782
+ }
783
+ storePut(key, targetObj);
784
+ }
785
+ else {
786
+ // No existing record, just put the filtered data
787
+ storePut(key, filteredData);
788
+ }
789
+ };
790
+ }
791
+ /**
792
+ * Extracts the requested field names from the selections of a 'fields' ObjectSelection.
793
+ * Returns undefined if no 'fields' selection is found or if it doesn't have selections.
794
+ */
795
+ function extractRequestedFieldNames(selections) {
796
+ if (!selections) {
797
+ return undefined;
798
+ }
799
+ // Find the 'fields' ObjectSelection
800
+ const fieldsSelection = selections.find((sel) => sel.kind === 'Object' && sel.name === 'fields');
801
+ if (!fieldsSelection || !fieldsSelection.selections) {
802
+ return undefined;
803
+ }
804
+ // Extract all field names from the fields selections
805
+ const fieldNames = new Set();
806
+ for (const fieldSel of fieldsSelection.selections) {
807
+ fieldNames.add(fieldSel.name);
808
+ }
809
+ return fieldNames.size > 0 ? fieldNames : undefined;
810
+ }
721
811
 
722
812
  const TTL_DURABLE_SEGMENT = 'TTL_DURABLE_SEGMENT';
723
813
  const TTL_DEFAULT_KEY = 'TTL_DEFAULT_KEY';
@@ -1034,7 +1124,7 @@ function isUnfulfilledSnapshot$3(cachedSnapshotResult) {
1034
1124
  * @param durableStore A DurableStore implementation
1035
1125
  * @param instrumentation An instrumentation function implementation
1036
1126
  */
1037
- function makeDurable(environment, { durableStore, instrumentation, useRevivingStore, shouldFlush, enableDurableMetadataRefresh = false, disableDeepFreeze = false, }) {
1127
+ function makeDurable(environment, { durableStore, instrumentation, useRevivingStore, shouldFlush, enableDurableMetadataRefresh = false, disableDeepFreeze = false, shouldFilterFieldsOnRevive, }) {
1038
1128
  // runtimes can choose to disable deepFreeze, e.g. headless mobile runtime
1039
1129
  setBypassDeepFreeze(disableDeepFreeze);
1040
1130
  let stagingStore = null;
@@ -1576,7 +1666,7 @@ function makeDurable(environment, { durableStore, instrumentation, useRevivingSt
1576
1666
  const result = buildL1Snapshot();
1577
1667
  stagingStore = tempStore;
1578
1668
  return result;
1579
- }, revivingStore).finally(() => {
1669
+ }, revivingStore, { l2Trips: [] }, shouldFilterFieldsOnRevive !== null && shouldFilterFieldsOnRevive !== void 0 ? shouldFilterFieldsOnRevive : (() => false)).finally(() => {
1580
1670
  });
1581
1671
  };
1582
1672
  const expirePossibleStaleRecords = async function (keys$1, config, refresh) {
@@ -52140,6 +52230,27 @@ class LdsDataTable {
52140
52230
  }, reject);
52141
52231
  });
52142
52232
  }
52233
+ getByKeysWithSpecificFields(keys, fields, _segment) {
52234
+ // SQLite json_object has a max argument limit (~127 pairs = 254 args)
52235
+ // If too many fields requested, return undefined to indicate filtering not possible
52236
+ if (fields.size > 100) {
52237
+ return Promise.resolve(undefined);
52238
+ }
52239
+ return new Promise((resolve, reject) => {
52240
+ // Build JSON object with only the specified fields from the nested 'fields' property
52241
+ const jsonFields = Array.from(fields)
52242
+ .map((field) => `'${field}', json_extract(${COLUMN_NAME_DATA$2}, '$.fields.${field}')`)
52243
+ .join(', ');
52244
+ const filteredFieldsExpr = `json_object(${jsonFields})`;
52245
+ // Use json_set to replace the 'fields' property in the data with the filtered version
52246
+ const filteredDataExpr = `json_set(${COLUMN_NAME_DATA$2}, '$.fields', ${filteredFieldsExpr})`;
52247
+ const paramList = keys.map(() => '?').join(',');
52248
+ const getQuery = `SELECT ${COLUMN_NAME_KEY$2}, ${filteredDataExpr} as ${COLUMN_NAME_DATA$2}, ${COLUMN_NAME_METADATA$1} FROM ${this.tableName} WHERE ${COLUMN_NAME_KEY$2} IN (${paramList})`;
52249
+ this.plugin.query(getQuery, keys, (x) => {
52250
+ resolve(this.mapToDurableEntries(x));
52251
+ }, reject);
52252
+ });
52253
+ }
52143
52254
  getMetadataByKeys(keys) {
52144
52255
  const query = selectColumnsFromTableWhereKeyIn([COLUMN_NAME_KEY$2, COLUMN_NAME_METADATA$1], this.tableName, COLUMN_NAME_KEY$2, keys);
52145
52256
  return new Promise((resolve, reject) => {
@@ -52246,6 +52357,30 @@ class LdsInternalDataTable {
52246
52357
  }, reject);
52247
52358
  });
52248
52359
  }
52360
+ getByKeysWithSpecificFields(keys, fields, namespace) {
52361
+ if (namespace === undefined) {
52362
+ throw Error('LdsInternalDataTable requires namespace');
52363
+ }
52364
+ // SQLite json_object has a max argument limit (~127 pairs = 254 args)
52365
+ // If too many fields requested, return undefined to indicate filtering not possible
52366
+ if (fields.size > 100) {
52367
+ return Promise.resolve(undefined);
52368
+ }
52369
+ return new Promise((resolve, reject) => {
52370
+ // Build JSON object with only the specified fields from the nested 'fields' property
52371
+ const jsonFields = Array.from(fields)
52372
+ .map((field) => `'${field}', json_extract(${COLUMN_NAME_DATA$1}, '$.fields.${field}')`)
52373
+ .join(', ');
52374
+ const filteredFieldsExpr = `json_object(${jsonFields})`;
52375
+ // Use json_set to replace the 'fields' property in the data with the filtered version
52376
+ const filteredDataExpr = `json_set(${COLUMN_NAME_DATA$1}, '$.fields', ${filteredFieldsExpr})`;
52377
+ const paramList = keys.map(() => '?').join(',');
52378
+ const getQuery = `SELECT ${COLUMN_NAME_KEY$1}, ${filteredDataExpr} as ${COLUMN_NAME_DATA$1}, ${COLUMN_NAME_METADATA}, ${COLUMN_NAME_NAMESPACE} FROM ${this.tableName} WHERE ${COLUMN_NAME_NAMESPACE} = ? AND ${COLUMN_NAME_KEY$1} IN (${paramList})`;
52379
+ this.plugin.query(getQuery, [namespace].concat(keys), (x) => {
52380
+ resolve(this.mapToDurableEntries(x));
52381
+ }, reject);
52382
+ });
52383
+ }
52249
52384
  getMetadataByKeys(keys, namespace) {
52250
52385
  if (namespace === undefined) {
52251
52386
  throw Error('LdsInternalDataTable requires namespace');
@@ -52386,6 +52521,12 @@ class NimbusSqliteStore {
52386
52521
  .getByKeys(entryIds, segment)
52387
52522
  .finally(() => tasker.done());
52388
52523
  }
52524
+ async getEntriesWithSpecificFields(entryIds, fields, segment) {
52525
+ tasker.add();
52526
+ return this.getTable(segment)
52527
+ .getByKeysWithSpecificFields(entryIds, fields, segment)
52528
+ .finally(() => tasker.done());
52529
+ }
52389
52530
  async getMetadata(entryIds, segment) {
52390
52531
  tasker.add();
52391
52532
  return this.getTable(segment)
@@ -52545,6 +52686,27 @@ class AbstractKeyValueDataTable {
52545
52686
  }, reject);
52546
52687
  });
52547
52688
  }
52689
+ getByKeysWithSpecificFields(keys, fields, _segment) {
52690
+ // SQLite json_object has a max argument limit (~127 pairs = 254 args)
52691
+ // If too many fields requested, return undefined to indicate filtering not possible
52692
+ if (fields.size > 100) {
52693
+ return Promise.resolve(undefined);
52694
+ }
52695
+ return new Promise((resolve, reject) => {
52696
+ // Build JSON object with only the specified fields from the nested 'fields' property
52697
+ const jsonFields = Array.from(fields)
52698
+ .map((field) => `'${field}', json_extract(${COLUMN_NAME_DATA}, '$.fields.${field}')`)
52699
+ .join(', ');
52700
+ const filteredFieldsExpr = `json_object(${jsonFields})`;
52701
+ // Use json_set to replace the 'fields' property in the data with the filtered version
52702
+ const filteredDataExpr = `json_set(${COLUMN_NAME_DATA}, '$.fields', ${filteredFieldsExpr})`;
52703
+ const paramList = keys.map(() => '?').join(',');
52704
+ const getQuery = `SELECT ${COLUMN_NAME_KEY}, ${filteredDataExpr} as ${COLUMN_NAME_DATA} FROM ${this.tableName} WHERE ${COLUMN_NAME_KEY} IN (${paramList})`;
52705
+ this.plugin.query(getQuery, keys, (x) => {
52706
+ resolve(this.mapToDurableEntries(x));
52707
+ }, reject);
52708
+ });
52709
+ }
52548
52710
  getMetadataByKeys(_keys) {
52549
52711
  // eslint-disable-next-line @salesforce/lds/no-error-in-production
52550
52712
  throw new Error(`There is no metadata in the ${this.tableName} table.`);
@@ -58420,7 +58582,7 @@ function buildServiceDescriptor$b(luvio) {
58420
58582
  },
58421
58583
  };
58422
58584
  }
58423
- // version: 1.415.0-caf45a5d7e
58585
+ // version: 1.416.1-c10be671f4
58424
58586
 
58425
58587
  /**
58426
58588
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -58446,7 +58608,7 @@ function buildServiceDescriptor$a(notifyRecordUpdateAvailable, getNormalizedLuvi
58446
58608
  },
58447
58609
  };
58448
58610
  }
58449
- // version: 1.415.0-caf45a5d7e
58611
+ // version: 1.416.1-c10be671f4
58450
58612
 
58451
58613
  /*!
58452
58614
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -60960,6 +61122,12 @@ function getRuntime({ primeUser = false } = {}) {
60960
61122
  lazyInternalLuvio = internalLuvio;
60961
61123
  lazyObjectInfoService = new ObjectInfoService(getObjectInfo, getObjectInfos, getObjectInfoDirectory, lazyDurableStore);
60962
61124
  const baseEnv = new Environment(store, lazyNetworkAdapter);
61125
+ const shouldFilterFieldsOnRevive = (key) => {
61126
+ if (reviveOnlyRequestedFields.isOpen({ fallback: false })) {
61127
+ return isStoreKeyRecordId(key);
61128
+ }
61129
+ return false;
61130
+ };
60963
61131
  const gqlEnv = makeEnvironmentGraphqlAware(baseEnv);
60964
61132
  const durableEnv = makeDurable(gqlEnv, {
60965
61133
  durableStore: lazyDurableStore,
@@ -60967,6 +61135,7 @@ function getRuntime({ primeUser = false } = {}) {
60967
61135
  // disable luvio deep freeze in headless environments
60968
61136
  disableDeepFreeze: typeof window === 'undefined',
60969
61137
  shouldFlush,
61138
+ shouldFilterFieldsOnRevive,
60970
61139
  });
60971
61140
  // draft queue
60972
61141
  lazyDraftQueue = buildLdsDraftQueue(lazyDurableStore);
@@ -61087,4 +61256,4 @@ register({
61087
61256
  });
61088
61257
 
61089
61258
  export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, ingest$1o as ingestDenormalizedRecordRepresentation, initializeOneStore, registerReportObserver, reportGraphqlQueryParseError };
61090
- // version: 1.415.0-3636f2a7b2
61259
+ // version: 1.416.1-0ee8f9a6ba
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-runtime-mobile",
3
- "version": "1.415.0",
3
+ "version": "1.416.1",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "LDS runtime for mobile/hybrid environments.",
6
6
  "main": "dist/main.js",
@@ -35,11 +35,11 @@
35
35
  "@conduit-client/service-bindings-imperative": "3.9.0",
36
36
  "@conduit-client/service-bindings-lwc": "3.9.0",
37
37
  "@conduit-client/service-provisioner": "3.9.0",
38
- "@salesforce/lds-adapters-uiapi": "^1.415.0",
39
- "@salesforce/lds-bindings": "^1.415.0",
40
- "@salesforce/lds-instrumentation": "^1.415.0",
41
- "@salesforce/lds-luvio-service": "^1.415.0",
42
- "@salesforce/lds-luvio-uiapi-records-service": "^1.415.0",
38
+ "@salesforce/lds-adapters-uiapi": "^1.416.1",
39
+ "@salesforce/lds-bindings": "^1.416.1",
40
+ "@salesforce/lds-instrumentation": "^1.416.1",
41
+ "@salesforce/lds-luvio-service": "^1.416.1",
42
+ "@salesforce/lds-luvio-uiapi-records-service": "^1.416.1",
43
43
  "@salesforce/user": "0.0.21",
44
44
  "o11y": "250.7.0",
45
45
  "o11y_schema": "256.126.0"
@@ -60,16 +60,16 @@
60
60
  "@conduit-client/service-pubsub": "3.9.0",
61
61
  "@conduit-client/service-store": "3.9.0",
62
62
  "@conduit-client/utils": "3.9.0",
63
- "@salesforce/lds-adapters-graphql": "^1.415.0",
64
- "@salesforce/lds-drafts": "^1.415.0",
65
- "@salesforce/lds-durable-records": "^1.415.0",
66
- "@salesforce/lds-network-adapter": "^1.415.0",
67
- "@salesforce/lds-network-nimbus": "^1.415.0",
68
- "@salesforce/lds-store-binary": "^1.415.0",
69
- "@salesforce/lds-store-nimbus": "^1.415.0",
70
- "@salesforce/lds-store-sql": "^1.415.0",
71
- "@salesforce/lds-utils-adapters": "^1.415.0",
72
- "@salesforce/nimbus-plugin-lds": "^1.415.0",
63
+ "@salesforce/lds-adapters-graphql": "^1.416.1",
64
+ "@salesforce/lds-drafts": "^1.416.1",
65
+ "@salesforce/lds-durable-records": "^1.416.1",
66
+ "@salesforce/lds-network-adapter": "^1.416.1",
67
+ "@salesforce/lds-network-nimbus": "^1.416.1",
68
+ "@salesforce/lds-store-binary": "^1.416.1",
69
+ "@salesforce/lds-store-nimbus": "^1.416.1",
70
+ "@salesforce/lds-store-sql": "^1.416.1",
71
+ "@salesforce/lds-utils-adapters": "^1.416.1",
72
+ "@salesforce/nimbus-plugin-lds": "^1.416.1",
73
73
  "babel-plugin-dynamic-import-node": "^2.3.3",
74
74
  "wait-for-expect": "^3.0.2"
75
75
  },
package/sfdc/main.js CHANGED
@@ -47,6 +47,7 @@ import { setServices } from 'force/luvioServiceProvisioner1';
47
47
  import 'force/luvioTypeNormalization1';
48
48
  import { Kind as Kind$2, visit as visit$2, print as print$1, resolveAndValidateGraphQLConfig } from 'force/luvioOnestoreGraphqlParser';
49
49
  import productConsumedSideEffects from '@salesforce/gate/com.salesforce.fieldservice.vanStockLDSBypass256';
50
+ import reviveOnlyRequestedFields from '@salesforce/gate/lmr.reviveOnlyRequestedFields';
50
51
 
51
52
  /**
52
53
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -633,7 +634,7 @@ function publishDurableStoreEntries(durableRecords, put, publishMetadata) {
633
634
  * will refresh the snapshot from network, and then run the results from network
634
635
  * through L2 ingestion, returning the subsequent revived snapshot.
635
636
  */
636
- function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics = { l2Trips: [] }) {
637
+ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics = { l2Trips: [] }, shouldFilterFields) {
637
638
  const { recordId, select, missingLinks, seenRecords, state } = unavailableSnapshot;
638
639
  // L2 can only revive Unfulfilled snapshots that have a selector since they have the
639
640
  // info needed to revive (like missingLinks) and rebuild. Otherwise return L1 snapshot.
@@ -660,19 +661,55 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
660
661
  }
661
662
  keysToReviveSet.merge(missingLinks);
662
663
  const keysToRevive = keysToReviveSet.keysAsArray();
663
- const canonicalKeys = keysToRevive.map((x) => serializeStructuredKey(baseEnvironment.storeGetCanonicalKey(x)));
664
664
  const start = Date.now();
665
665
  const { l2Trips } = reviveMetrics;
666
- return durableStore.getEntries(canonicalKeys, DefaultDurableSegment).then((durableRecords) => {
666
+ // Extract requested fields first to determine if filtering is possible
667
+ let requestedFields;
668
+ if (select.node.kind === 'Fragment' && 'selections' in select.node && select.node.selections) {
669
+ requestedFields = extractRequestedFieldNames(select.node.selections);
670
+ }
671
+ const canonicalKeys = [];
672
+ const filteredCanonicalKeys = [];
673
+ for (let i = 0, len = keysToRevive.length; i < len; i += 1) {
674
+ const canonicalKey = serializeStructuredKey(baseEnvironment.storeGetCanonicalKey(keysToRevive[i]));
675
+ // Only filter if we have fields to filter and shouldFilterFields returns true
676
+ if (requestedFields !== undefined &&
677
+ requestedFields.size > 0 &&
678
+ shouldFilterFields(canonicalKey)) {
679
+ filteredCanonicalKeys.push(canonicalKey);
680
+ }
681
+ else {
682
+ canonicalKeys.push(canonicalKey);
683
+ }
684
+ }
685
+ const fetchPromises = [
686
+ durableStore.getEntries(canonicalKeys, DefaultDurableSegment),
687
+ ];
688
+ if (requestedFields !== undefined &&
689
+ requestedFields.size > 0 &&
690
+ filteredCanonicalKeys.length > 0) {
691
+ fetchPromises.push(durableStore.getEntriesWithSpecificFields(filteredCanonicalKeys, requestedFields, DefaultDurableSegment));
692
+ }
693
+ return Promise.all(fetchPromises).then(([durableRecords, filteredDurableRecords]) => {
667
694
  l2Trips.push({
668
695
  duration: Date.now() - start,
669
- keysRequestedCount: canonicalKeys.length,
696
+ keysRequestedCount: canonicalKeys.length + filteredCanonicalKeys.length,
670
697
  });
671
- const { revivedKeys, hadUnexpectedShape } = publishDurableStoreEntries(durableRecords,
672
- // TODO [W-10072584]: instead of implicitly using L1 we should take in
673
- // publish and publishMetadata funcs, so callers can decide where to
674
- // revive to (like they pass in how to do the buildL1Snapshot)
675
- baseEnvironment.storePut.bind(baseEnvironment), baseEnvironment.publishStoreMetadata.bind(baseEnvironment));
698
+ // Process both normal and filtered records in a single pass
699
+ const revivedKeys = new StoreKeySet();
700
+ let hadUnexpectedShape = false;
701
+ // Process normal records
702
+ if (durableRecords !== undefined) {
703
+ const normalResult = publishDurableStoreEntries(durableRecords, baseEnvironment.storePut.bind(baseEnvironment), baseEnvironment.publishStoreMetadata.bind(baseEnvironment));
704
+ revivedKeys.merge(normalResult.revivedKeys);
705
+ hadUnexpectedShape = hadUnexpectedShape || normalResult.hadUnexpectedShape;
706
+ }
707
+ // Process filtered records with merging
708
+ if (filteredDurableRecords !== undefined) {
709
+ const filteredResult = publishDurableStoreEntries(filteredDurableRecords, createMergeFilteredPut((key) => baseEnvironment.store.readEntry(key), baseEnvironment.storePut.bind(baseEnvironment)), baseEnvironment.publishStoreMetadata.bind(baseEnvironment));
710
+ revivedKeys.merge(filteredResult.revivedKeys);
711
+ hadUnexpectedShape = hadUnexpectedShape || filteredResult.hadUnexpectedShape;
712
+ }
676
713
  // if the data coming back from DS had an unexpected shape then just
677
714
  // return the L1 snapshot
678
715
  if (hadUnexpectedShape === true) {
@@ -707,7 +744,7 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
707
744
  for (let i = 0, len = newKeys.length; i < len; i++) {
708
745
  const newSnapshotSeenKey = newKeys[i];
709
746
  if (!alreadyRequestedOrRevivedSet.has(newSnapshotSeenKey)) {
710
- return reviveSnapshot(baseEnvironment, durableStore, snapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics);
747
+ return reviveSnapshot(baseEnvironment, durableStore, snapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics, shouldFilterFields);
711
748
  }
712
749
  }
713
750
  }
@@ -718,6 +755,59 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
718
755
  return { snapshot: unavailableSnapshot, metrics: reviveMetrics };
719
756
  });
720
757
  }
758
+ /**
759
+ * Creates a put function that merges filtered fields with existing L1 records instead of replacing them.
760
+ * This is used when reviving filtered fields from L2 to preserve existing data in L1.
761
+ */
762
+ function createMergeFilteredPut(readEntry, storePut) {
763
+ return (key, filteredData) => {
764
+ const existingRecord = readEntry(key);
765
+ if (existingRecord !== undefined &&
766
+ existingRecord !== null &&
767
+ typeof filteredData === 'object' &&
768
+ filteredData !== null &&
769
+ typeof existingRecord === 'object') {
770
+ const filteredFields = filteredData;
771
+ const existingObj = existingRecord;
772
+ // Check if object is frozen (can happen after deepFreeze)
773
+ // If frozen, create a shallow copy to merge fields into
774
+ let targetObj = existingObj;
775
+ if (Object.isFrozen(existingObj)) {
776
+ targetObj = { ...existingObj };
777
+ }
778
+ const keys = Object.keys(filteredFields);
779
+ for (let i = 0, len = keys.length; i < len; i += 1) {
780
+ const fieldKey = keys[i];
781
+ targetObj[fieldKey] = filteredFields[fieldKey];
782
+ }
783
+ storePut(key, targetObj);
784
+ }
785
+ else {
786
+ // No existing record, just put the filtered data
787
+ storePut(key, filteredData);
788
+ }
789
+ };
790
+ }
791
+ /**
792
+ * Extracts the requested field names from the selections of a 'fields' ObjectSelection.
793
+ * Returns undefined if no 'fields' selection is found or if it doesn't have selections.
794
+ */
795
+ function extractRequestedFieldNames(selections) {
796
+ if (!selections) {
797
+ return undefined;
798
+ }
799
+ // Find the 'fields' ObjectSelection
800
+ const fieldsSelection = selections.find((sel) => sel.kind === 'Object' && sel.name === 'fields');
801
+ if (!fieldsSelection || !fieldsSelection.selections) {
802
+ return undefined;
803
+ }
804
+ // Extract all field names from the fields selections
805
+ const fieldNames = new Set();
806
+ for (const fieldSel of fieldsSelection.selections) {
807
+ fieldNames.add(fieldSel.name);
808
+ }
809
+ return fieldNames.size > 0 ? fieldNames : undefined;
810
+ }
721
811
 
722
812
  const TTL_DURABLE_SEGMENT = 'TTL_DURABLE_SEGMENT';
723
813
  const TTL_DEFAULT_KEY = 'TTL_DEFAULT_KEY';
@@ -1034,7 +1124,7 @@ function isUnfulfilledSnapshot$3(cachedSnapshotResult) {
1034
1124
  * @param durableStore A DurableStore implementation
1035
1125
  * @param instrumentation An instrumentation function implementation
1036
1126
  */
1037
- function makeDurable(environment, { durableStore, instrumentation, useRevivingStore, shouldFlush, enableDurableMetadataRefresh = false, disableDeepFreeze = false, }) {
1127
+ function makeDurable(environment, { durableStore, instrumentation, useRevivingStore, shouldFlush, enableDurableMetadataRefresh = false, disableDeepFreeze = false, shouldFilterFieldsOnRevive, }) {
1038
1128
  // runtimes can choose to disable deepFreeze, e.g. headless mobile runtime
1039
1129
  setBypassDeepFreeze(disableDeepFreeze);
1040
1130
  let stagingStore = null;
@@ -1576,7 +1666,7 @@ function makeDurable(environment, { durableStore, instrumentation, useRevivingSt
1576
1666
  const result = buildL1Snapshot();
1577
1667
  stagingStore = tempStore;
1578
1668
  return result;
1579
- }, revivingStore).finally(() => {
1669
+ }, revivingStore, { l2Trips: [] }, shouldFilterFieldsOnRevive !== null && shouldFilterFieldsOnRevive !== void 0 ? shouldFilterFieldsOnRevive : (() => false)).finally(() => {
1580
1670
  });
1581
1671
  };
1582
1672
  const expirePossibleStaleRecords = async function (keys$1, config, refresh) {
@@ -52140,6 +52230,27 @@ class LdsDataTable {
52140
52230
  }, reject);
52141
52231
  });
52142
52232
  }
52233
+ getByKeysWithSpecificFields(keys, fields, _segment) {
52234
+ // SQLite json_object has a max argument limit (~127 pairs = 254 args)
52235
+ // If too many fields requested, return undefined to indicate filtering not possible
52236
+ if (fields.size > 100) {
52237
+ return Promise.resolve(undefined);
52238
+ }
52239
+ return new Promise((resolve, reject) => {
52240
+ // Build JSON object with only the specified fields from the nested 'fields' property
52241
+ const jsonFields = Array.from(fields)
52242
+ .map((field) => `'${field}', json_extract(${COLUMN_NAME_DATA$2}, '$.fields.${field}')`)
52243
+ .join(', ');
52244
+ const filteredFieldsExpr = `json_object(${jsonFields})`;
52245
+ // Use json_set to replace the 'fields' property in the data with the filtered version
52246
+ const filteredDataExpr = `json_set(${COLUMN_NAME_DATA$2}, '$.fields', ${filteredFieldsExpr})`;
52247
+ const paramList = keys.map(() => '?').join(',');
52248
+ const getQuery = `SELECT ${COLUMN_NAME_KEY$2}, ${filteredDataExpr} as ${COLUMN_NAME_DATA$2}, ${COLUMN_NAME_METADATA$1} FROM ${this.tableName} WHERE ${COLUMN_NAME_KEY$2} IN (${paramList})`;
52249
+ this.plugin.query(getQuery, keys, (x) => {
52250
+ resolve(this.mapToDurableEntries(x));
52251
+ }, reject);
52252
+ });
52253
+ }
52143
52254
  getMetadataByKeys(keys) {
52144
52255
  const query = selectColumnsFromTableWhereKeyIn([COLUMN_NAME_KEY$2, COLUMN_NAME_METADATA$1], this.tableName, COLUMN_NAME_KEY$2, keys);
52145
52256
  return new Promise((resolve, reject) => {
@@ -52246,6 +52357,30 @@ class LdsInternalDataTable {
52246
52357
  }, reject);
52247
52358
  });
52248
52359
  }
52360
+ getByKeysWithSpecificFields(keys, fields, namespace) {
52361
+ if (namespace === undefined) {
52362
+ throw Error('LdsInternalDataTable requires namespace');
52363
+ }
52364
+ // SQLite json_object has a max argument limit (~127 pairs = 254 args)
52365
+ // If too many fields requested, return undefined to indicate filtering not possible
52366
+ if (fields.size > 100) {
52367
+ return Promise.resolve(undefined);
52368
+ }
52369
+ return new Promise((resolve, reject) => {
52370
+ // Build JSON object with only the specified fields from the nested 'fields' property
52371
+ const jsonFields = Array.from(fields)
52372
+ .map((field) => `'${field}', json_extract(${COLUMN_NAME_DATA$1}, '$.fields.${field}')`)
52373
+ .join(', ');
52374
+ const filteredFieldsExpr = `json_object(${jsonFields})`;
52375
+ // Use json_set to replace the 'fields' property in the data with the filtered version
52376
+ const filteredDataExpr = `json_set(${COLUMN_NAME_DATA$1}, '$.fields', ${filteredFieldsExpr})`;
52377
+ const paramList = keys.map(() => '?').join(',');
52378
+ const getQuery = `SELECT ${COLUMN_NAME_KEY$1}, ${filteredDataExpr} as ${COLUMN_NAME_DATA$1}, ${COLUMN_NAME_METADATA}, ${COLUMN_NAME_NAMESPACE} FROM ${this.tableName} WHERE ${COLUMN_NAME_NAMESPACE} = ? AND ${COLUMN_NAME_KEY$1} IN (${paramList})`;
52379
+ this.plugin.query(getQuery, [namespace].concat(keys), (x) => {
52380
+ resolve(this.mapToDurableEntries(x));
52381
+ }, reject);
52382
+ });
52383
+ }
52249
52384
  getMetadataByKeys(keys, namespace) {
52250
52385
  if (namespace === undefined) {
52251
52386
  throw Error('LdsInternalDataTable requires namespace');
@@ -52386,6 +52521,12 @@ class NimbusSqliteStore {
52386
52521
  .getByKeys(entryIds, segment)
52387
52522
  .finally(() => tasker.done());
52388
52523
  }
52524
+ async getEntriesWithSpecificFields(entryIds, fields, segment) {
52525
+ tasker.add();
52526
+ return this.getTable(segment)
52527
+ .getByKeysWithSpecificFields(entryIds, fields, segment)
52528
+ .finally(() => tasker.done());
52529
+ }
52389
52530
  async getMetadata(entryIds, segment) {
52390
52531
  tasker.add();
52391
52532
  return this.getTable(segment)
@@ -52545,6 +52686,27 @@ class AbstractKeyValueDataTable {
52545
52686
  }, reject);
52546
52687
  });
52547
52688
  }
52689
+ getByKeysWithSpecificFields(keys, fields, _segment) {
52690
+ // SQLite json_object has a max argument limit (~127 pairs = 254 args)
52691
+ // If too many fields requested, return undefined to indicate filtering not possible
52692
+ if (fields.size > 100) {
52693
+ return Promise.resolve(undefined);
52694
+ }
52695
+ return new Promise((resolve, reject) => {
52696
+ // Build JSON object with only the specified fields from the nested 'fields' property
52697
+ const jsonFields = Array.from(fields)
52698
+ .map((field) => `'${field}', json_extract(${COLUMN_NAME_DATA}, '$.fields.${field}')`)
52699
+ .join(', ');
52700
+ const filteredFieldsExpr = `json_object(${jsonFields})`;
52701
+ // Use json_set to replace the 'fields' property in the data with the filtered version
52702
+ const filteredDataExpr = `json_set(${COLUMN_NAME_DATA}, '$.fields', ${filteredFieldsExpr})`;
52703
+ const paramList = keys.map(() => '?').join(',');
52704
+ const getQuery = `SELECT ${COLUMN_NAME_KEY}, ${filteredDataExpr} as ${COLUMN_NAME_DATA} FROM ${this.tableName} WHERE ${COLUMN_NAME_KEY} IN (${paramList})`;
52705
+ this.plugin.query(getQuery, keys, (x) => {
52706
+ resolve(this.mapToDurableEntries(x));
52707
+ }, reject);
52708
+ });
52709
+ }
52548
52710
  getMetadataByKeys(_keys) {
52549
52711
  // eslint-disable-next-line @salesforce/lds/no-error-in-production
52550
52712
  throw new Error(`There is no metadata in the ${this.tableName} table.`);
@@ -58420,7 +58582,7 @@ function buildServiceDescriptor$b(luvio) {
58420
58582
  },
58421
58583
  };
58422
58584
  }
58423
- // version: 1.415.0-caf45a5d7e
58585
+ // version: 1.416.1-c10be671f4
58424
58586
 
58425
58587
  /**
58426
58588
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -58446,7 +58608,7 @@ function buildServiceDescriptor$a(notifyRecordUpdateAvailable, getNormalizedLuvi
58446
58608
  },
58447
58609
  };
58448
58610
  }
58449
- // version: 1.415.0-caf45a5d7e
58611
+ // version: 1.416.1-c10be671f4
58450
58612
 
58451
58613
  /*!
58452
58614
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -60960,6 +61122,12 @@ function getRuntime({ primeUser = false } = {}) {
60960
61122
  lazyInternalLuvio = internalLuvio;
60961
61123
  lazyObjectInfoService = new ObjectInfoService(getObjectInfo, getObjectInfos, getObjectInfoDirectory, lazyDurableStore);
60962
61124
  const baseEnv = new Environment(store, lazyNetworkAdapter);
61125
+ const shouldFilterFieldsOnRevive = (key) => {
61126
+ if (reviveOnlyRequestedFields.isOpen({ fallback: false })) {
61127
+ return isStoreKeyRecordId(key);
61128
+ }
61129
+ return false;
61130
+ };
60963
61131
  const gqlEnv = makeEnvironmentGraphqlAware(baseEnv);
60964
61132
  const durableEnv = makeDurable(gqlEnv, {
60965
61133
  durableStore: lazyDurableStore,
@@ -60967,6 +61135,7 @@ function getRuntime({ primeUser = false } = {}) {
60967
61135
  // disable luvio deep freeze in headless environments
60968
61136
  disableDeepFreeze: typeof window === 'undefined',
60969
61137
  shouldFlush,
61138
+ shouldFilterFieldsOnRevive,
60970
61139
  });
60971
61140
  // draft queue
60972
61141
  lazyDraftQueue = buildLdsDraftQueue(lazyDurableStore);
@@ -61087,4 +61256,4 @@ register({
61087
61256
  });
61088
61257
 
61089
61258
  export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, ingest$1o as ingestDenormalizedRecordRepresentation, initializeOneStore, registerReportObserver, reportGraphqlQueryParseError };
61090
- // version: 1.415.0-3636f2a7b2
61259
+ // version: 1.416.1-0ee8f9a6ba