@salesforce/lds-runtime-bridge 1.414.1 → 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.
@@ -136,7 +136,7 @@ function publishDurableStoreEntries(durableRecords, put, publishMetadata) {
136
136
  * will refresh the snapshot from network, and then run the results from network
137
137
  * through L2 ingestion, returning the subsequent revived snapshot.
138
138
  */
139
- function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics = { l2Trips: [] }) {
139
+ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics = { l2Trips: [] }, shouldFilterFields) {
140
140
  const { recordId, select, missingLinks, seenRecords, state } = unavailableSnapshot;
141
141
  // L2 can only revive Unfulfilled snapshots that have a selector since they have the
142
142
  // info needed to revive (like missingLinks) and rebuild. Otherwise return L1 snapshot.
@@ -163,19 +163,55 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
163
163
  }
164
164
  keysToReviveSet.merge(missingLinks);
165
165
  const keysToRevive = keysToReviveSet.keysAsArray();
166
- const canonicalKeys = keysToRevive.map((x) => serializeStructuredKey(baseEnvironment.storeGetCanonicalKey(x)));
167
166
  const start = Date.now();
168
167
  const { l2Trips } = reviveMetrics;
169
- return durableStore.getEntries(canonicalKeys, DefaultDurableSegment).then((durableRecords) => {
168
+ // Extract requested fields first to determine if filtering is possible
169
+ let requestedFields;
170
+ if (select.node.kind === 'Fragment' && 'selections' in select.node && select.node.selections) {
171
+ requestedFields = extractRequestedFieldNames(select.node.selections);
172
+ }
173
+ const canonicalKeys = [];
174
+ const filteredCanonicalKeys = [];
175
+ for (let i = 0, len = keysToRevive.length; i < len; i += 1) {
176
+ const canonicalKey = serializeStructuredKey(baseEnvironment.storeGetCanonicalKey(keysToRevive[i]));
177
+ // Only filter if we have fields to filter and shouldFilterFields returns true
178
+ if (requestedFields !== undefined &&
179
+ requestedFields.size > 0 &&
180
+ shouldFilterFields(canonicalKey)) {
181
+ filteredCanonicalKeys.push(canonicalKey);
182
+ }
183
+ else {
184
+ canonicalKeys.push(canonicalKey);
185
+ }
186
+ }
187
+ const fetchPromises = [
188
+ durableStore.getEntries(canonicalKeys, DefaultDurableSegment),
189
+ ];
190
+ if (requestedFields !== undefined &&
191
+ requestedFields.size > 0 &&
192
+ filteredCanonicalKeys.length > 0) {
193
+ fetchPromises.push(durableStore.getEntriesWithSpecificFields(filteredCanonicalKeys, requestedFields, DefaultDurableSegment));
194
+ }
195
+ return Promise.all(fetchPromises).then(([durableRecords, filteredDurableRecords]) => {
170
196
  l2Trips.push({
171
197
  duration: Date.now() - start,
172
- keysRequestedCount: canonicalKeys.length,
198
+ keysRequestedCount: canonicalKeys.length + filteredCanonicalKeys.length,
173
199
  });
174
- const { revivedKeys, hadUnexpectedShape } = publishDurableStoreEntries(durableRecords,
175
- // TODO [W-10072584]: instead of implicitly using L1 we should take in
176
- // publish and publishMetadata funcs, so callers can decide where to
177
- // revive to (like they pass in how to do the buildL1Snapshot)
178
- baseEnvironment.storePut.bind(baseEnvironment), baseEnvironment.publishStoreMetadata.bind(baseEnvironment));
200
+ // Process both normal and filtered records in a single pass
201
+ const revivedKeys = new StoreKeySet();
202
+ let hadUnexpectedShape = false;
203
+ // Process normal records
204
+ if (durableRecords !== undefined) {
205
+ const normalResult = publishDurableStoreEntries(durableRecords, baseEnvironment.storePut.bind(baseEnvironment), baseEnvironment.publishStoreMetadata.bind(baseEnvironment));
206
+ revivedKeys.merge(normalResult.revivedKeys);
207
+ hadUnexpectedShape = hadUnexpectedShape || normalResult.hadUnexpectedShape;
208
+ }
209
+ // Process filtered records with merging
210
+ if (filteredDurableRecords !== undefined) {
211
+ const filteredResult = publishDurableStoreEntries(filteredDurableRecords, createMergeFilteredPut((key) => baseEnvironment.store.readEntry(key), baseEnvironment.storePut.bind(baseEnvironment)), baseEnvironment.publishStoreMetadata.bind(baseEnvironment));
212
+ revivedKeys.merge(filteredResult.revivedKeys);
213
+ hadUnexpectedShape = hadUnexpectedShape || filteredResult.hadUnexpectedShape;
214
+ }
179
215
  // if the data coming back from DS had an unexpected shape then just
180
216
  // return the L1 snapshot
181
217
  if (hadUnexpectedShape === true) {
@@ -210,7 +246,7 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
210
246
  for (let i = 0, len = newKeys.length; i < len; i++) {
211
247
  const newSnapshotSeenKey = newKeys[i];
212
248
  if (!alreadyRequestedOrRevivedSet.has(newSnapshotSeenKey)) {
213
- return reviveSnapshot(baseEnvironment, durableStore, snapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics);
249
+ return reviveSnapshot(baseEnvironment, durableStore, snapshot, durableStoreErrorHandler, buildL1Snapshot, revivingStore, reviveMetrics, shouldFilterFields);
214
250
  }
215
251
  }
216
252
  }
@@ -221,6 +257,59 @@ function reviveSnapshot(baseEnvironment, durableStore, unavailableSnapshot, dura
221
257
  return { snapshot: unavailableSnapshot, metrics: reviveMetrics };
222
258
  });
223
259
  }
260
+ /**
261
+ * Creates a put function that merges filtered fields with existing L1 records instead of replacing them.
262
+ * This is used when reviving filtered fields from L2 to preserve existing data in L1.
263
+ */
264
+ function createMergeFilteredPut(readEntry, storePut) {
265
+ return (key, filteredData) => {
266
+ const existingRecord = readEntry(key);
267
+ if (existingRecord !== undefined &&
268
+ existingRecord !== null &&
269
+ typeof filteredData === 'object' &&
270
+ filteredData !== null &&
271
+ typeof existingRecord === 'object') {
272
+ const filteredFields = filteredData;
273
+ const existingObj = existingRecord;
274
+ // Check if object is frozen (can happen after deepFreeze)
275
+ // If frozen, create a shallow copy to merge fields into
276
+ let targetObj = existingObj;
277
+ if (Object.isFrozen(existingObj)) {
278
+ targetObj = { ...existingObj };
279
+ }
280
+ const keys = Object.keys(filteredFields);
281
+ for (let i = 0, len = keys.length; i < len; i += 1) {
282
+ const fieldKey = keys[i];
283
+ targetObj[fieldKey] = filteredFields[fieldKey];
284
+ }
285
+ storePut(key, targetObj);
286
+ }
287
+ else {
288
+ // No existing record, just put the filtered data
289
+ storePut(key, filteredData);
290
+ }
291
+ };
292
+ }
293
+ /**
294
+ * Extracts the requested field names from the selections of a 'fields' ObjectSelection.
295
+ * Returns undefined if no 'fields' selection is found or if it doesn't have selections.
296
+ */
297
+ function extractRequestedFieldNames(selections) {
298
+ if (!selections) {
299
+ return undefined;
300
+ }
301
+ // Find the 'fields' ObjectSelection
302
+ const fieldsSelection = selections.find((sel) => sel.kind === 'Object' && sel.name === 'fields');
303
+ if (!fieldsSelection || !fieldsSelection.selections) {
304
+ return undefined;
305
+ }
306
+ // Extract all field names from the fields selections
307
+ const fieldNames = new Set();
308
+ for (const fieldSel of fieldsSelection.selections) {
309
+ fieldNames.add(fieldSel.name);
310
+ }
311
+ return fieldNames.size > 0 ? fieldNames : undefined;
312
+ }
224
313
 
225
314
  const TTL_DURABLE_SEGMENT = 'TTL_DURABLE_SEGMENT';
226
315
  const TTL_DEFAULT_KEY = 'TTL_DEFAULT_KEY';
@@ -537,7 +626,7 @@ function isUnfulfilledSnapshot(cachedSnapshotResult) {
537
626
  * @param durableStore A DurableStore implementation
538
627
  * @param instrumentation An instrumentation function implementation
539
628
  */
540
- function makeDurable(environment, { durableStore, instrumentation, useRevivingStore, shouldFlush, enableDurableMetadataRefresh = false, disableDeepFreeze = false, }) {
629
+ function makeDurable(environment, { durableStore, instrumentation, useRevivingStore, shouldFlush, enableDurableMetadataRefresh = false, disableDeepFreeze = false, shouldFilterFieldsOnRevive, }) {
541
630
  // runtimes can choose to disable deepFreeze, e.g. headless mobile runtime
542
631
  setBypassDeepFreeze(disableDeepFreeze);
543
632
  let stagingStore = null;
@@ -1079,7 +1168,7 @@ function makeDurable(environment, { durableStore, instrumentation, useRevivingSt
1079
1168
  const result = buildL1Snapshot();
1080
1169
  stagingStore = tempStore;
1081
1170
  return result;
1082
- }, revivingStore).finally(() => {
1171
+ }, revivingStore, { l2Trips: [] }, shouldFilterFieldsOnRevive !== null && shouldFilterFieldsOnRevive !== void 0 ? shouldFilterFieldsOnRevive : (() => false)).finally(() => {
1083
1172
  });
1084
1173
  };
1085
1174
  const expirePossibleStaleRecords = async function (keys$1, config, refresh) {
@@ -1178,6 +1267,27 @@ class LdsDataTable {
1178
1267
  }, reject);
1179
1268
  });
1180
1269
  }
1270
+ getByKeysWithSpecificFields(keys, fields, _segment) {
1271
+ // SQLite json_object has a max argument limit (~127 pairs = 254 args)
1272
+ // If too many fields requested, return undefined to indicate filtering not possible
1273
+ if (fields.size > 100) {
1274
+ return Promise.resolve(undefined);
1275
+ }
1276
+ return new Promise((resolve, reject) => {
1277
+ // Build JSON object with only the specified fields from the nested 'fields' property
1278
+ const jsonFields = Array.from(fields)
1279
+ .map((field) => `'${field}', json_extract(${COLUMN_NAME_DATA$2}, '$.fields.${field}')`)
1280
+ .join(', ');
1281
+ const filteredFieldsExpr = `json_object(${jsonFields})`;
1282
+ // Use json_set to replace the 'fields' property in the data with the filtered version
1283
+ const filteredDataExpr = `json_set(${COLUMN_NAME_DATA$2}, '$.fields', ${filteredFieldsExpr})`;
1284
+ const paramList = keys.map(() => '?').join(',');
1285
+ 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})`;
1286
+ this.plugin.query(getQuery, keys, (x) => {
1287
+ resolve(this.mapToDurableEntries(x));
1288
+ }, reject);
1289
+ });
1290
+ }
1181
1291
  getMetadataByKeys(keys) {
1182
1292
  const query = selectColumnsFromTableWhereKeyIn([COLUMN_NAME_KEY$2, COLUMN_NAME_METADATA$1], this.tableName, COLUMN_NAME_KEY$2, keys);
1183
1293
  return new Promise((resolve, reject) => {
@@ -1284,6 +1394,30 @@ class LdsInternalDataTable {
1284
1394
  }, reject);
1285
1395
  });
1286
1396
  }
1397
+ getByKeysWithSpecificFields(keys, fields, namespace) {
1398
+ if (namespace === undefined) {
1399
+ throw Error('LdsInternalDataTable requires namespace');
1400
+ }
1401
+ // SQLite json_object has a max argument limit (~127 pairs = 254 args)
1402
+ // If too many fields requested, return undefined to indicate filtering not possible
1403
+ if (fields.size > 100) {
1404
+ return Promise.resolve(undefined);
1405
+ }
1406
+ return new Promise((resolve, reject) => {
1407
+ // Build JSON object with only the specified fields from the nested 'fields' property
1408
+ const jsonFields = Array.from(fields)
1409
+ .map((field) => `'${field}', json_extract(${COLUMN_NAME_DATA$1}, '$.fields.${field}')`)
1410
+ .join(', ');
1411
+ const filteredFieldsExpr = `json_object(${jsonFields})`;
1412
+ // Use json_set to replace the 'fields' property in the data with the filtered version
1413
+ const filteredDataExpr = `json_set(${COLUMN_NAME_DATA$1}, '$.fields', ${filteredFieldsExpr})`;
1414
+ const paramList = keys.map(() => '?').join(',');
1415
+ 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})`;
1416
+ this.plugin.query(getQuery, [namespace].concat(keys), (x) => {
1417
+ resolve(this.mapToDurableEntries(x));
1418
+ }, reject);
1419
+ });
1420
+ }
1287
1421
  getMetadataByKeys(keys, namespace) {
1288
1422
  if (namespace === undefined) {
1289
1423
  throw Error('LdsInternalDataTable requires namespace');
@@ -1424,6 +1558,12 @@ class NimbusSqliteStore {
1424
1558
  .getByKeys(entryIds, segment)
1425
1559
  .finally(() => tasker.done());
1426
1560
  }
1561
+ async getEntriesWithSpecificFields(entryIds, fields, segment) {
1562
+ tasker.add();
1563
+ return this.getTable(segment)
1564
+ .getByKeysWithSpecificFields(entryIds, fields, segment)
1565
+ .finally(() => tasker.done());
1566
+ }
1427
1567
  async getMetadata(entryIds, segment) {
1428
1568
  tasker.add();
1429
1569
  return this.getTable(segment)
@@ -2011,4 +2151,4 @@ function ldsRuntimeBridge() {
2011
2151
  }
2012
2152
 
2013
2153
  export { ldsRuntimeBridge as default };
2014
- // version: 1.414.1-a1d9b7b404
2154
+ // version: 1.416.1-0ee8f9a6ba
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-runtime-bridge",
3
- "version": "1.414.1",
3
+ "version": "1.416.1",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "LDS runtime for bridge.app.",
6
6
  "main": "dist/ldsRuntimeBridge.js",
@@ -34,18 +34,18 @@
34
34
  "release:corejar": "yarn build && ../core-build/scripts/core.js --name=lds-runtime-bridge"
35
35
  },
36
36
  "dependencies": {
37
- "@salesforce/lds-bindings": "^1.414.1",
38
- "@salesforce/lds-durable-records": "^1.414.1",
39
- "@salesforce/lds-instrumentation": "^1.414.1",
40
- "@salesforce/lds-runtime-mobile": "^1.414.1",
37
+ "@salesforce/lds-bindings": "^1.416.1",
38
+ "@salesforce/lds-durable-records": "^1.416.1",
39
+ "@salesforce/lds-instrumentation": "^1.416.1",
40
+ "@salesforce/lds-runtime-mobile": "^1.416.1",
41
41
  "@salesforce/user": "0.0.21",
42
42
  "o11y": "250.7.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@salesforce/lds-network-aura": "^1.414.1",
46
- "@salesforce/lds-runtime-aura": "^1.414.1",
47
- "@salesforce/lds-store-nimbus": "^1.414.1",
48
- "@salesforce/nimbus-plugin-lds": "^1.414.1",
45
+ "@salesforce/lds-network-aura": "^1.416.1",
46
+ "@salesforce/lds-runtime-aura": "^1.416.1",
47
+ "@salesforce/lds-store-nimbus": "^1.416.1",
48
+ "@salesforce/nimbus-plugin-lds": "^1.416.1",
49
49
  "babel-plugin-dynamic-import-node": "^2.3.3"
50
50
  },
51
51
  "luvioBundlesize": [