@salesforce/lds-runtime-bridge 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.
- package/dist/ldsRuntimeBridge.js +153 -13
- package/package.json +9 -9
package/dist/ldsRuntimeBridge.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
38
|
-
"@salesforce/lds-durable-records": "^1.
|
|
39
|
-
"@salesforce/lds-instrumentation": "^1.
|
|
40
|
-
"@salesforce/lds-runtime-mobile": "^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.
|
|
46
|
-
"@salesforce/lds-runtime-aura": "^1.
|
|
47
|
-
"@salesforce/lds-store-nimbus": "^1.
|
|
48
|
-
"@salesforce/nimbus-plugin-lds": "^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": [
|