@salesforce/lds-runtime-mobile 1.287.0-dev15 → 1.287.0-dev17

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 +147 -38
  2. package/package.json +16 -16
  3. package/sfdc/main.js +147 -38
package/dist/main.js CHANGED
@@ -20,7 +20,7 @@ import { setupInstrumentation, instrumentAdapter as instrumentAdapter$1, instrum
20
20
  import { HttpStatusCode, setBypassDeepFreeze, StoreKeySet, serializeStructuredKey, StringKeyInMemoryStore, Reader, deepFreeze, emitAdapterEvent, createCustomAdapterEventEmitter, StoreKeyMap, isFileReference, Environment, Luvio, InMemoryStore } from '@luvio/engine';
21
21
  import excludeStaleRecordsGate from '@salesforce/gate/lds.graphqlEvalExcludeStaleRecords';
22
22
  import { parseAndVisit, Kind, buildSchema, isObjectType, defaultFieldResolver, visit, execute, parse as parse$7, extendSchema, isScalarType } from '@luvio/graphql-parser';
23
- import { RECORD_ID_PREFIX, RECORD_FIELDS_KEY_JUNCTION, isStoreKeyRecordViewEntity, getRecordId18, RECORD_REPRESENTATION_NAME, extractRecordIdFromStoreKey, keyBuilderQuickActionExecutionRepresentation, ingestQuickActionExecutionRepresentation, keyBuilderContentDocumentCompositeRepresentation, getResponseCacheKeysContentDocumentCompositeRepresentation, keyBuilderFromTypeContentDocumentCompositeRepresentation, ingestContentDocumentCompositeRepresentation, keyBuilderRecord, RECORD_VIEW_ENTITY_ID_PREFIX, getTypeCacheKeysRecord, keyBuilderFromTypeRecordRepresentation, ingestRecord, RecordRepresentationRepresentationType, ObjectInfoRepresentationType, getRecordAdapterFactory, getObjectInfoAdapterFactory, getObjectInfosAdapterFactory, getObjectInfoDirectoryAdapterFactory, UiApiNamespace, RecordRepresentationType, RecordRepresentationTTL, RecordRepresentationVersion, getRecordsAdapterFactory } from '@salesforce/lds-adapters-uiapi';
23
+ import { RECORD_ID_PREFIX, RECORD_FIELDS_KEY_JUNCTION, isStoreKeyRecordViewEntity, getRecordId18, RECORD_REPRESENTATION_NAME, extractRecordIdFromStoreKey, keyBuilderQuickActionExecutionRepresentation, ingestQuickActionExecutionRepresentation, keyBuilderContentDocumentCompositeRepresentation, getResponseCacheKeysContentDocumentCompositeRepresentation, keyBuilderFromTypeContentDocumentCompositeRepresentation, ingestContentDocumentCompositeRepresentation, keyBuilderRecord, RECORD_VIEW_ENTITY_ID_PREFIX, getTypeCacheKeysRecord, keyBuilderFromTypeRecordRepresentation, ingestRecord, getRecordsAdapterFactory, RecordRepresentationRepresentationType, ObjectInfoRepresentationType, getRecordAdapterFactory, getObjectInfoAdapterFactory, getObjectInfosAdapterFactory, getObjectInfoDirectoryAdapterFactory, UiApiNamespace, RecordRepresentationType, RecordRepresentationTTL, RecordRepresentationVersion } from '@salesforce/lds-adapters-uiapi';
24
24
  import ldsIdempotencyWriteDisabled from '@salesforce/gate/lds.idempotencyWriteDisabled';
25
25
  import ldsBackdatingEnabled from '@salesforce/gate/lds.backdatingEnabled';
26
26
  import FIRST_DAY_OF_WEEK from '@salesforce/i18n/firstDayOfWeek';
@@ -1566,6 +1566,32 @@ function makeDurable(environment, { durableStore, instrumentation, useRevivingSt
1566
1566
  }, revivingStore).finally(() => {
1567
1567
  });
1568
1568
  };
1569
+ const expirePossibleStaleRecords = async function (keys$1, config, refresh) {
1570
+ validateNotDisposed();
1571
+ const metadataKeys = keys$1.map(serializeStructuredKey);
1572
+ const now = Date.now();
1573
+ const entries = await durableStore.getMetadata(metadataKeys, DefaultDurableSegment);
1574
+ if (entries === undefined || keys$7(entries).length === 0) {
1575
+ return environment.expirePossibleStaleRecords(keys$1);
1576
+ }
1577
+ let metaDataChanged = false;
1578
+ const metadataEntries = metadataKeys.reduce((accu, key) => {
1579
+ const metadataEntry = entries[key];
1580
+ if (metadataEntry.metadata !== undefined) {
1581
+ const metadata = { ...metadataEntry.metadata, expirationTimestamp: now };
1582
+ accu[key] = { metadata };
1583
+ metaDataChanged = true;
1584
+ }
1585
+ return accu;
1586
+ }, {});
1587
+ if (metaDataChanged) {
1588
+ await durableStore.setMetadata(metadataEntries, DefaultDurableSegment);
1589
+ }
1590
+ if (config !== undefined && refresh !== undefined) {
1591
+ return environment.refreshPossibleStaleRecords(config, refresh);
1592
+ }
1593
+ return Promise.resolve();
1594
+ };
1569
1595
  // set the default cache policy of the base environment
1570
1596
  environment.setDefaultCachePolicy({
1571
1597
  type: 'stale-while-revalidate',
@@ -1598,6 +1624,7 @@ function makeDurable(environment, { durableStore, instrumentation, useRevivingSt
1598
1624
  handleErrorResponse: { value: handleErrorResponse },
1599
1625
  getNotifyChangeStoreEntries: { value: getNotifyChangeStoreEntries },
1600
1626
  notifyStoreUpdateAvailable: { value: notifyStoreUpdateAvailable },
1627
+ expirePossibleStaleRecords: { value: expirePossibleStaleRecords },
1601
1628
  });
1602
1629
  }
1603
1630
 
@@ -7230,6 +7257,7 @@ function createContext(store, objectInfos, eventEmitter, settings, snapshot, dra
7230
7257
  Record,
7231
7258
  snapshot,
7232
7259
  seenRecordIds: new Set(),
7260
+ possibleStaleRecordMap: new Map(),
7233
7261
  draftFunctions,
7234
7262
  };
7235
7263
  }
@@ -7841,7 +7869,6 @@ function isTodayStartOfWeek() {
7841
7869
 
7842
7870
  const JSON_EXTRACT_PATH_INGESTION_TIMESTAMP = '$.ingestionTimestamp';
7843
7871
  const JSON_EXTRACT_PATH_INGESTION_APINAME = '$.apiName';
7844
- const JSON_EXTRACT_PATH_DRAFTS = '$.drafts';
7845
7872
 
7846
7873
  const MultiPickListValueSeparator = ';';
7847
7874
  function filterToPredicates(where, recordType, alias, objectInfoMap, joins, draftFunctions) {
@@ -8371,18 +8398,11 @@ function buildQuery(config) {
8371
8398
  const joins = buildJoins(config);
8372
8399
  const predicates = buildPredicates(config);
8373
8400
  const orderBy = buildOrderBy(config);
8374
- const staleRecordsSql = excludeStaleRecordsGate.isOpen({ fallback: false })
8375
- ? `AND (
8376
- json_extract("${config.alias}".metadata, '${JSON_EXTRACT_PATH_INGESTION_TIMESTAMP}') >= ?
8377
- OR json_extract("${config.alias}".data, '${JSON_EXTRACT_PATH_DRAFTS}') IS NOT NULL
8378
- )`
8379
- : '';
8380
8401
  const sql = `
8381
- SELECT "${config.alias}".data
8402
+ SELECT "${config.alias}".data, "${config.alias}".metadata
8382
8403
  FROM lds_data "${config.alias}" ${joins.sql}
8383
8404
  WHERE "${config.alias}".key like 'UiApi::RecordRepresentation:%'
8384
8405
  AND json_extract("${config.alias}".data, '${JSON_EXTRACT_PATH_INGESTION_APINAME}') = '${config.alias}'
8385
- ${staleRecordsSql}
8386
8406
  ${predicates.sql}
8387
8407
  ${orderBy.sql}
8388
8408
  LIMIT ?
@@ -8393,7 +8413,6 @@ function buildQuery(config) {
8393
8413
  const bindings = [
8394
8414
  // bindings from predicates on joins
8395
8415
  ...joins.bindings,
8396
- ...(excludeStaleRecordsGate.isOpen({ fallback: false }) ? [config.ingestionTimestamp] : []),
8397
8416
  // where clause and parent scope bindings
8398
8417
  ...predicates.bindings,
8399
8418
  // limit binding
@@ -8419,33 +8438,19 @@ function buildJoins(config) {
8419
8438
  if (allJoins.length === 0)
8420
8439
  return { sql, bindings };
8421
8440
  sql = allJoins.reduce((joinAccumulator, join) => {
8422
- let timestampAdded = false;
8423
8441
  const joinConditions = join.conditions.reduce((conditionAccumulator, condition) => {
8424
8442
  let joined_sql;
8425
- const joinMetadataTimestamp = excludeStaleRecordsGate.isOpen({ fallback: false })
8426
- ? ` AND (json_extract("${join.alias}".metadata, '${JSON_EXTRACT_PATH_INGESTION_TIMESTAMP}') >= ? OR json_extract("${join.alias}".data, '${JSON_EXTRACT_PATH_DRAFTS}') IS NOT NULL)`
8427
- : '';
8428
8443
  // predicate on a value, use the newly joined table
8429
8444
  if ('type' in condition) {
8430
8445
  const { sql, binding } = predicateToSQL(condition, join.alias);
8431
- joined_sql = ` AND ${sql}${timestampAdded ? '' : joinMetadataTimestamp}`;
8446
+ joined_sql = ` AND ${sql}`;
8432
8447
  bindings.push(...binding);
8433
- if (excludeStaleRecordsGate.isOpen({ fallback: false }) &&
8434
- timestampAdded === false) {
8435
- bindings.push(config.ingestionTimestamp);
8436
- timestampAdded = true;
8437
- }
8438
8448
  }
8439
8449
  else {
8440
8450
  // predicate on a path
8441
8451
  const left = ` AND json_extract("${join.to}".data, '${condition.leftPath}')`;
8442
8452
  const right = `json_extract("${join.alias}".data, '${condition.rightPath}')`;
8443
- joined_sql = `${left} = ${right}${timestampAdded ? '' : joinMetadataTimestamp}`;
8444
- if (excludeStaleRecordsGate.isOpen({ fallback: false }) &&
8445
- timestampAdded === false) {
8446
- bindings.push(config.ingestionTimestamp);
8447
- timestampAdded = true;
8448
- }
8453
+ joined_sql = `${left} = ${right}`;
8449
8454
  }
8450
8455
  conditionAccumulator += joined_sql;
8451
8456
  return conditionAccumulator;
@@ -9390,8 +9395,7 @@ function addResolversToSchema(schema, polyFields) {
9390
9395
  for (const field of fields) {
9391
9396
  if (field.name === 'node') {
9392
9397
  field.resolve = function nodeResolver(obj, _args, { seenRecordIds }) {
9393
- const { record, ingestionTimestamp } = obj;
9394
- const recordRepresentation = parse$4(record);
9398
+ const { recordRepresentation, ingestionTimestamp } = obj;
9395
9399
  seenRecordIds.add(recordRepresentation.id);
9396
9400
  return { recordRepresentation, ingestionTimestamp };
9397
9401
  };
@@ -9605,16 +9609,30 @@ async function connectionEdgeResolver(obj, _args, context) {
9605
9609
  predicates,
9606
9610
  orderBy: orderByToPredicate(parentArgs.orderBy, alias, alias, context.objectInfos),
9607
9611
  limit: parentArgs.first,
9608
- ingestionTimestamp,
9609
9612
  };
9610
9613
  const { sql, bindings } = buildQuery(queryConfig);
9611
9614
  const results = await query(sql, bindings);
9612
9615
  //map each sql result with the ingestion timestamp to pass it down a level
9613
- return results.rows
9614
- .map((row) => row[0])
9615
- .map((record) => {
9616
+ return results.rows.map((row) => {
9617
+ const recordMetadataResult = {
9618
+ recordRepresentation: parse$4(row[0]),
9619
+ metadata: parse$4(row[1]),
9620
+ };
9621
+ const { recordRepresentation, metadata } = recordMetadataResult;
9622
+ context.seenRecordIds.add(recordRepresentation.id);
9623
+ if (metadata.ingestionTimestamp < ingestionTimestamp &&
9624
+ recordRepresentation.drafts === undefined) {
9625
+ if (context.possibleStaleRecordMap.has(recordRepresentation.apiName) === false) {
9626
+ context.possibleStaleRecordMap.set(recordRepresentation.apiName, []);
9627
+ }
9628
+ const ids = context.possibleStaleRecordMap.get(recordRepresentation.apiName);
9629
+ if (ids !== undefined) {
9630
+ ids.push(recordRepresentation.id);
9631
+ context.possibleStaleRecordMap.set(recordRepresentation.apiName, ids);
9632
+ }
9633
+ }
9616
9634
  return {
9617
- record,
9635
+ recordRepresentation,
9618
9636
  ingestionTimestamp,
9619
9637
  };
9620
9638
  });
@@ -10179,7 +10197,11 @@ async function evaluate(config, observers, settings, objectInfos, store, snapsho
10179
10197
  seenRecordIds.push(queryString);
10180
10198
  });
10181
10199
  }
10182
- return { result, seenRecordIds };
10200
+ return {
10201
+ result,
10202
+ seenRecordIds,
10203
+ possibleStaleRecordMap: contextValue.possibleStaleRecordMap,
10204
+ };
10183
10205
  }
10184
10206
  finally {
10185
10207
  eventEmitter({ type: 'graphql-eval-end' });
@@ -13813,6 +13835,60 @@ function hasGraphQlErrors(response) {
13813
13835
  response.errors.length > 0);
13814
13836
  }
13815
13837
 
13838
+ const MAX_ID_CHUNK_LENGTH = 100;
13839
+ /**
13840
+ * Adapter for limiting the number of record ids to a maximum of 100 and sending them
13841
+ * out in batches to the getRecords adapter
13842
+ *
13843
+ * @param luvio
13844
+ * @returns
13845
+ */
13846
+ function batchingGetRecordsAdapterFactory(luvio) {
13847
+ const getRecordsAdapter = getRecordsAdapterFactory(luvio);
13848
+ const batchGetRecords = (config, requestContext) => {
13849
+ const seenRecords = new StoreKeySet();
13850
+ const chunks = chunkConfig(config);
13851
+ const promises = chunks.map((conf) => {
13852
+ return getRecordsAdapter(conf, requestContext);
13853
+ });
13854
+ return Promise.all(promises).then((results) => {
13855
+ const data = results.reduce((accu, item) => {
13856
+ if (item !== null && item.data !== undefined) {
13857
+ accu.results = accu.results.concat(item.data.results);
13858
+ seenRecords.merge(item.seenRecords);
13859
+ }
13860
+ return accu;
13861
+ }, { results: [] });
13862
+ return {
13863
+ data: data,
13864
+ seenRecords,
13865
+ state: 'Fulfilled',
13866
+ };
13867
+ });
13868
+ };
13869
+ return batchGetRecords;
13870
+ }
13871
+ /**
13872
+ * Given a GetRecordsConfig it will chunk it into multiple configs with a maximum of 100
13873
+ * record ids per config.
13874
+ *
13875
+ * @param config
13876
+ * @returns
13877
+ */
13878
+ function chunkConfig(config) {
13879
+ const chunks = [];
13880
+ config.records.forEach((record) => {
13881
+ const { recordIds, fields } = record;
13882
+ for (let i = 0, len = recordIds.length; i < len; i += MAX_ID_CHUNK_LENGTH) {
13883
+ const chunk = recordIds.slice(i, i + MAX_ID_CHUNK_LENGTH);
13884
+ chunks.push({
13885
+ records: [{ recordIds: chunk, fields: fields !== undefined ? fields : [] }],
13886
+ });
13887
+ }
13888
+ });
13889
+ return chunks;
13890
+ }
13891
+
13816
13892
  function generateUniqueRecordId() {
13817
13893
  return `UiApi::GraphQLRepresentation:${Date.now() + Math.random().toFixed(5).split('.')[1]}`;
13818
13894
  }
@@ -13922,8 +13998,13 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
13922
13998
  : [];
13923
13999
  let gqlResult;
13924
14000
  let seenRecordIds;
14001
+ let possibleStaleRecordMap;
13925
14002
  try {
13926
- ({ result: gqlResult, seenRecordIds } = await evaluate({
14003
+ ({
14004
+ result: gqlResult,
14005
+ seenRecordIds,
14006
+ possibleStaleRecordMap,
14007
+ } = await evaluate({
13927
14008
  ...config,
13928
14009
  //need to create another copy of the ast for future writes
13929
14010
  query: parse$3(stringify$3(injectedAST)),
@@ -13953,13 +14034,18 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
13953
14034
  const seenRecords = createSeenRecords(seenRecordIds, nonEvaluatedSnapshot);
13954
14035
  const recordId = generateUniqueRecordId();
13955
14036
  const rebuildWithLocalEval = async (originalSnapshot) => {
13956
- let { result: rebuildResult, seenRecordIds } = await evaluate({
14037
+ let { result: rebuildResult, seenRecordIds, possibleStaleRecordMap, } = await evaluate({
13957
14038
  ...config,
13958
14039
  query: injectedAST,
13959
14040
  }, observers, { userId }, objectInfoNeeded, store, originalSnapshot, graphqlSchemaCache, draftFunctions);
13960
14041
  if (!rebuildResult.errors) {
13961
14042
  rebuildResult = removeSyntheticFields(rebuildResult, config.query);
13962
14043
  }
14044
+ let snapshotState = 'Fulfilled';
14045
+ if (possibleStaleRecordMap.size > 0) {
14046
+ initiateStaleRecordRefresh(luvio, possibleStaleRecordMap);
14047
+ snapshotState = 'Stale';
14048
+ }
13963
14049
  if (objectsDeepEqual(rebuildResult, originalSnapshot.data)) {
13964
14050
  return originalSnapshot;
13965
14051
  }
@@ -13968,6 +14054,7 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
13968
14054
  ...originalSnapshot,
13969
14055
  data: rebuildResult,
13970
14056
  recordId,
14057
+ state: snapshotState,
13971
14058
  seenRecords: createSeenRecords(seenRecordIds, nonEvaluatedSnapshot),
13972
14059
  rebuildWithLocalEval,
13973
14060
  };
@@ -14005,6 +14092,10 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
14005
14092
  },
14006
14093
  };
14007
14094
  }
14095
+ if (possibleStaleRecordMap.size > 0) {
14096
+ initiateStaleRecordRefresh(luvio, possibleStaleRecordMap);
14097
+ resultSnapshot.state = 'Stale';
14098
+ }
14008
14099
  return resultSnapshot;
14009
14100
  };
14010
14101
  }
@@ -14065,6 +14156,24 @@ function environmentAwareGraphQLBatchAdapterFactory(objectInfoService, luvio, is
14065
14156
  };
14066
14157
  };
14067
14158
  }
14159
+ function initiateStaleRecordRefresh(luvio, keyMap) {
14160
+ const staleRecordKeys = from$1(keyMap.values())
14161
+ .flat()
14162
+ .map((id) => `UiApi::RecordRepresentation:${id}`);
14163
+ luvio.storeExpirePossibleStaleRecords(staleRecordKeys, makeGetRecordsConfig(keyMap), batchingGetRecordsAdapterFactory(luvio));
14164
+ }
14165
+ function makeGetRecordsConfig(keyMap) {
14166
+ const records = [];
14167
+ keyMap.forEach((recordIds, apiName) => {
14168
+ records.push({
14169
+ recordIds,
14170
+ fields: [`${apiName}.Id`],
14171
+ });
14172
+ });
14173
+ return {
14174
+ records,
14175
+ };
14176
+ }
14068
14177
 
14069
14178
  const CONTENT_DOCUMENT_DRAFT_ID_KEY = 'CONTENT_DOCUMENT_DRAFT_ID';
14070
14179
  const CONTENT_VERSION_DRAFT_ID_KEY = 'CONTENT_VERSION_DRAFT_ID';
@@ -18374,4 +18483,4 @@ register({
18374
18483
  });
18375
18484
 
18376
18485
  export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, registerReportObserver, reportGraphqlQueryParseError };
18377
- // version: 1.287.0-dev15-a292df40d2
18486
+ // version: 1.287.0-dev17-670374cbf3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-runtime-mobile",
3
- "version": "1.287.0-dev15",
3
+ "version": "1.287.0-dev17",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "LDS runtime for mobile/hybrid environments.",
6
6
  "main": "dist/main.js",
@@ -32,25 +32,25 @@
32
32
  "release:corejar": "yarn build && ../core-build/scripts/core.js --name=lds-runtime-mobile"
33
33
  },
34
34
  "dependencies": {
35
- "@salesforce/lds-adapters-uiapi": "^1.287.0-dev15",
36
- "@salesforce/lds-bindings": "^1.287.0-dev15",
37
- "@salesforce/lds-instrumentation": "^1.287.0-dev15",
38
- "@salesforce/lds-priming": "^1.287.0-dev15",
35
+ "@salesforce/lds-adapters-uiapi": "^1.287.0-dev17",
36
+ "@salesforce/lds-bindings": "^1.287.0-dev17",
37
+ "@salesforce/lds-instrumentation": "^1.287.0-dev17",
38
+ "@salesforce/lds-priming": "^1.287.0-dev17",
39
39
  "@salesforce/user": "0.0.21",
40
40
  "o11y": "250.7.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@salesforce/lds-adapters-graphql": "^1.287.0-dev15",
44
- "@salesforce/lds-drafts": "^1.287.0-dev15",
45
- "@salesforce/lds-drafts-adapters-uiapi": "^1.287.0-dev15",
46
- "@salesforce/lds-graphql-eval": "^1.287.0-dev15",
47
- "@salesforce/lds-network-adapter": "^1.287.0-dev15",
48
- "@salesforce/lds-network-nimbus": "^1.287.0-dev15",
49
- "@salesforce/lds-store-binary": "^1.287.0-dev15",
50
- "@salesforce/lds-store-nimbus": "^1.287.0-dev15",
51
- "@salesforce/lds-store-sql": "^1.287.0-dev15",
52
- "@salesforce/lds-utils-adapters": "^1.287.0-dev15",
53
- "@salesforce/nimbus-plugin-lds": "^1.287.0-dev15",
43
+ "@salesforce/lds-adapters-graphql": "^1.287.0-dev17",
44
+ "@salesforce/lds-drafts": "^1.287.0-dev17",
45
+ "@salesforce/lds-drafts-adapters-uiapi": "^1.287.0-dev17",
46
+ "@salesforce/lds-graphql-eval": "^1.287.0-dev17",
47
+ "@salesforce/lds-network-adapter": "^1.287.0-dev17",
48
+ "@salesforce/lds-network-nimbus": "^1.287.0-dev17",
49
+ "@salesforce/lds-store-binary": "^1.287.0-dev17",
50
+ "@salesforce/lds-store-nimbus": "^1.287.0-dev17",
51
+ "@salesforce/lds-store-sql": "^1.287.0-dev17",
52
+ "@salesforce/lds-utils-adapters": "^1.287.0-dev17",
53
+ "@salesforce/nimbus-plugin-lds": "^1.287.0-dev17",
54
54
  "babel-plugin-dynamic-import-node": "^2.3.3",
55
55
  "wait-for-expect": "^3.0.2"
56
56
  },
package/sfdc/main.js CHANGED
@@ -20,7 +20,7 @@ import { setupInstrumentation, instrumentAdapter as instrumentAdapter$1, instrum
20
20
  import { HttpStatusCode, setBypassDeepFreeze, StoreKeySet, serializeStructuredKey, StringKeyInMemoryStore, Reader, deepFreeze, emitAdapterEvent, createCustomAdapterEventEmitter, StoreKeyMap, isFileReference, Environment, Luvio, InMemoryStore } from 'force/luvioEngine';
21
21
  import excludeStaleRecordsGate from '@salesforce/gate/lds.graphqlEvalExcludeStaleRecords';
22
22
  import { parseAndVisit, Kind, buildSchema, isObjectType, defaultFieldResolver, visit, execute, parse as parse$7, extendSchema, isScalarType } from 'force/ldsGraphqlParser';
23
- import { RECORD_ID_PREFIX, RECORD_FIELDS_KEY_JUNCTION, isStoreKeyRecordViewEntity, getRecordId18, RECORD_REPRESENTATION_NAME, extractRecordIdFromStoreKey, keyBuilderQuickActionExecutionRepresentation, ingestQuickActionExecutionRepresentation, keyBuilderContentDocumentCompositeRepresentation, getResponseCacheKeysContentDocumentCompositeRepresentation, keyBuilderFromTypeContentDocumentCompositeRepresentation, ingestContentDocumentCompositeRepresentation, keyBuilderRecord, RECORD_VIEW_ENTITY_ID_PREFIX, getTypeCacheKeysRecord, keyBuilderFromTypeRecordRepresentation, ingestRecord, RecordRepresentationRepresentationType, ObjectInfoRepresentationType, getRecordAdapterFactory, getObjectInfoAdapterFactory, getObjectInfosAdapterFactory, getObjectInfoDirectoryAdapterFactory, UiApiNamespace, RecordRepresentationType, RecordRepresentationTTL, RecordRepresentationVersion, getRecordsAdapterFactory } from 'force/ldsAdaptersUiapi';
23
+ import { RECORD_ID_PREFIX, RECORD_FIELDS_KEY_JUNCTION, isStoreKeyRecordViewEntity, getRecordId18, RECORD_REPRESENTATION_NAME, extractRecordIdFromStoreKey, keyBuilderQuickActionExecutionRepresentation, ingestQuickActionExecutionRepresentation, keyBuilderContentDocumentCompositeRepresentation, getResponseCacheKeysContentDocumentCompositeRepresentation, keyBuilderFromTypeContentDocumentCompositeRepresentation, ingestContentDocumentCompositeRepresentation, keyBuilderRecord, RECORD_VIEW_ENTITY_ID_PREFIX, getTypeCacheKeysRecord, keyBuilderFromTypeRecordRepresentation, ingestRecord, getRecordsAdapterFactory, RecordRepresentationRepresentationType, ObjectInfoRepresentationType, getRecordAdapterFactory, getObjectInfoAdapterFactory, getObjectInfosAdapterFactory, getObjectInfoDirectoryAdapterFactory, UiApiNamespace, RecordRepresentationType, RecordRepresentationTTL, RecordRepresentationVersion } from 'force/ldsAdaptersUiapi';
24
24
  import ldsIdempotencyWriteDisabled from '@salesforce/gate/lds.idempotencyWriteDisabled';
25
25
  import ldsBackdatingEnabled from '@salesforce/gate/lds.backdatingEnabled';
26
26
  import FIRST_DAY_OF_WEEK from '@salesforce/i18n/firstDayOfWeek';
@@ -1566,6 +1566,32 @@ function makeDurable(environment, { durableStore, instrumentation, useRevivingSt
1566
1566
  }, revivingStore).finally(() => {
1567
1567
  });
1568
1568
  };
1569
+ const expirePossibleStaleRecords = async function (keys$1, config, refresh) {
1570
+ validateNotDisposed();
1571
+ const metadataKeys = keys$1.map(serializeStructuredKey);
1572
+ const now = Date.now();
1573
+ const entries = await durableStore.getMetadata(metadataKeys, DefaultDurableSegment);
1574
+ if (entries === undefined || keys$7(entries).length === 0) {
1575
+ return environment.expirePossibleStaleRecords(keys$1);
1576
+ }
1577
+ let metaDataChanged = false;
1578
+ const metadataEntries = metadataKeys.reduce((accu, key) => {
1579
+ const metadataEntry = entries[key];
1580
+ if (metadataEntry.metadata !== undefined) {
1581
+ const metadata = { ...metadataEntry.metadata, expirationTimestamp: now };
1582
+ accu[key] = { metadata };
1583
+ metaDataChanged = true;
1584
+ }
1585
+ return accu;
1586
+ }, {});
1587
+ if (metaDataChanged) {
1588
+ await durableStore.setMetadata(metadataEntries, DefaultDurableSegment);
1589
+ }
1590
+ if (config !== undefined && refresh !== undefined) {
1591
+ return environment.refreshPossibleStaleRecords(config, refresh);
1592
+ }
1593
+ return Promise.resolve();
1594
+ };
1569
1595
  // set the default cache policy of the base environment
1570
1596
  environment.setDefaultCachePolicy({
1571
1597
  type: 'stale-while-revalidate',
@@ -1598,6 +1624,7 @@ function makeDurable(environment, { durableStore, instrumentation, useRevivingSt
1598
1624
  handleErrorResponse: { value: handleErrorResponse },
1599
1625
  getNotifyChangeStoreEntries: { value: getNotifyChangeStoreEntries },
1600
1626
  notifyStoreUpdateAvailable: { value: notifyStoreUpdateAvailable },
1627
+ expirePossibleStaleRecords: { value: expirePossibleStaleRecords },
1601
1628
  });
1602
1629
  }
1603
1630
 
@@ -7230,6 +7257,7 @@ function createContext(store, objectInfos, eventEmitter, settings, snapshot, dra
7230
7257
  Record,
7231
7258
  snapshot,
7232
7259
  seenRecordIds: new Set(),
7260
+ possibleStaleRecordMap: new Map(),
7233
7261
  draftFunctions,
7234
7262
  };
7235
7263
  }
@@ -7841,7 +7869,6 @@ function isTodayStartOfWeek() {
7841
7869
 
7842
7870
  const JSON_EXTRACT_PATH_INGESTION_TIMESTAMP = '$.ingestionTimestamp';
7843
7871
  const JSON_EXTRACT_PATH_INGESTION_APINAME = '$.apiName';
7844
- const JSON_EXTRACT_PATH_DRAFTS = '$.drafts';
7845
7872
 
7846
7873
  const MultiPickListValueSeparator = ';';
7847
7874
  function filterToPredicates(where, recordType, alias, objectInfoMap, joins, draftFunctions) {
@@ -8371,18 +8398,11 @@ function buildQuery(config) {
8371
8398
  const joins = buildJoins(config);
8372
8399
  const predicates = buildPredicates(config);
8373
8400
  const orderBy = buildOrderBy(config);
8374
- const staleRecordsSql = excludeStaleRecordsGate.isOpen({ fallback: false })
8375
- ? `AND (
8376
- json_extract("${config.alias}".metadata, '${JSON_EXTRACT_PATH_INGESTION_TIMESTAMP}') >= ?
8377
- OR json_extract("${config.alias}".data, '${JSON_EXTRACT_PATH_DRAFTS}') IS NOT NULL
8378
- )`
8379
- : '';
8380
8401
  const sql = `
8381
- SELECT "${config.alias}".data
8402
+ SELECT "${config.alias}".data, "${config.alias}".metadata
8382
8403
  FROM lds_data "${config.alias}" ${joins.sql}
8383
8404
  WHERE "${config.alias}".key like 'UiApi::RecordRepresentation:%'
8384
8405
  AND json_extract("${config.alias}".data, '${JSON_EXTRACT_PATH_INGESTION_APINAME}') = '${config.alias}'
8385
- ${staleRecordsSql}
8386
8406
  ${predicates.sql}
8387
8407
  ${orderBy.sql}
8388
8408
  LIMIT ?
@@ -8393,7 +8413,6 @@ function buildQuery(config) {
8393
8413
  const bindings = [
8394
8414
  // bindings from predicates on joins
8395
8415
  ...joins.bindings,
8396
- ...(excludeStaleRecordsGate.isOpen({ fallback: false }) ? [config.ingestionTimestamp] : []),
8397
8416
  // where clause and parent scope bindings
8398
8417
  ...predicates.bindings,
8399
8418
  // limit binding
@@ -8419,33 +8438,19 @@ function buildJoins(config) {
8419
8438
  if (allJoins.length === 0)
8420
8439
  return { sql, bindings };
8421
8440
  sql = allJoins.reduce((joinAccumulator, join) => {
8422
- let timestampAdded = false;
8423
8441
  const joinConditions = join.conditions.reduce((conditionAccumulator, condition) => {
8424
8442
  let joined_sql;
8425
- const joinMetadataTimestamp = excludeStaleRecordsGate.isOpen({ fallback: false })
8426
- ? ` AND (json_extract("${join.alias}".metadata, '${JSON_EXTRACT_PATH_INGESTION_TIMESTAMP}') >= ? OR json_extract("${join.alias}".data, '${JSON_EXTRACT_PATH_DRAFTS}') IS NOT NULL)`
8427
- : '';
8428
8443
  // predicate on a value, use the newly joined table
8429
8444
  if ('type' in condition) {
8430
8445
  const { sql, binding } = predicateToSQL(condition, join.alias);
8431
- joined_sql = ` AND ${sql}${timestampAdded ? '' : joinMetadataTimestamp}`;
8446
+ joined_sql = ` AND ${sql}`;
8432
8447
  bindings.push(...binding);
8433
- if (excludeStaleRecordsGate.isOpen({ fallback: false }) &&
8434
- timestampAdded === false) {
8435
- bindings.push(config.ingestionTimestamp);
8436
- timestampAdded = true;
8437
- }
8438
8448
  }
8439
8449
  else {
8440
8450
  // predicate on a path
8441
8451
  const left = ` AND json_extract("${join.to}".data, '${condition.leftPath}')`;
8442
8452
  const right = `json_extract("${join.alias}".data, '${condition.rightPath}')`;
8443
- joined_sql = `${left} = ${right}${timestampAdded ? '' : joinMetadataTimestamp}`;
8444
- if (excludeStaleRecordsGate.isOpen({ fallback: false }) &&
8445
- timestampAdded === false) {
8446
- bindings.push(config.ingestionTimestamp);
8447
- timestampAdded = true;
8448
- }
8453
+ joined_sql = `${left} = ${right}`;
8449
8454
  }
8450
8455
  conditionAccumulator += joined_sql;
8451
8456
  return conditionAccumulator;
@@ -9390,8 +9395,7 @@ function addResolversToSchema(schema, polyFields) {
9390
9395
  for (const field of fields) {
9391
9396
  if (field.name === 'node') {
9392
9397
  field.resolve = function nodeResolver(obj, _args, { seenRecordIds }) {
9393
- const { record, ingestionTimestamp } = obj;
9394
- const recordRepresentation = parse$4(record);
9398
+ const { recordRepresentation, ingestionTimestamp } = obj;
9395
9399
  seenRecordIds.add(recordRepresentation.id);
9396
9400
  return { recordRepresentation, ingestionTimestamp };
9397
9401
  };
@@ -9605,16 +9609,30 @@ async function connectionEdgeResolver(obj, _args, context) {
9605
9609
  predicates,
9606
9610
  orderBy: orderByToPredicate(parentArgs.orderBy, alias, alias, context.objectInfos),
9607
9611
  limit: parentArgs.first,
9608
- ingestionTimestamp,
9609
9612
  };
9610
9613
  const { sql, bindings } = buildQuery(queryConfig);
9611
9614
  const results = await query(sql, bindings);
9612
9615
  //map each sql result with the ingestion timestamp to pass it down a level
9613
- return results.rows
9614
- .map((row) => row[0])
9615
- .map((record) => {
9616
+ return results.rows.map((row) => {
9617
+ const recordMetadataResult = {
9618
+ recordRepresentation: parse$4(row[0]),
9619
+ metadata: parse$4(row[1]),
9620
+ };
9621
+ const { recordRepresentation, metadata } = recordMetadataResult;
9622
+ context.seenRecordIds.add(recordRepresentation.id);
9623
+ if (metadata.ingestionTimestamp < ingestionTimestamp &&
9624
+ recordRepresentation.drafts === undefined) {
9625
+ if (context.possibleStaleRecordMap.has(recordRepresentation.apiName) === false) {
9626
+ context.possibleStaleRecordMap.set(recordRepresentation.apiName, []);
9627
+ }
9628
+ const ids = context.possibleStaleRecordMap.get(recordRepresentation.apiName);
9629
+ if (ids !== undefined) {
9630
+ ids.push(recordRepresentation.id);
9631
+ context.possibleStaleRecordMap.set(recordRepresentation.apiName, ids);
9632
+ }
9633
+ }
9616
9634
  return {
9617
- record,
9635
+ recordRepresentation,
9618
9636
  ingestionTimestamp,
9619
9637
  };
9620
9638
  });
@@ -10179,7 +10197,11 @@ async function evaluate(config, observers, settings, objectInfos, store, snapsho
10179
10197
  seenRecordIds.push(queryString);
10180
10198
  });
10181
10199
  }
10182
- return { result, seenRecordIds };
10200
+ return {
10201
+ result,
10202
+ seenRecordIds,
10203
+ possibleStaleRecordMap: contextValue.possibleStaleRecordMap,
10204
+ };
10183
10205
  }
10184
10206
  finally {
10185
10207
  eventEmitter({ type: 'graphql-eval-end' });
@@ -13813,6 +13835,60 @@ function hasGraphQlErrors(response) {
13813
13835
  response.errors.length > 0);
13814
13836
  }
13815
13837
 
13838
+ const MAX_ID_CHUNK_LENGTH = 100;
13839
+ /**
13840
+ * Adapter for limiting the number of record ids to a maximum of 100 and sending them
13841
+ * out in batches to the getRecords adapter
13842
+ *
13843
+ * @param luvio
13844
+ * @returns
13845
+ */
13846
+ function batchingGetRecordsAdapterFactory(luvio) {
13847
+ const getRecordsAdapter = getRecordsAdapterFactory(luvio);
13848
+ const batchGetRecords = (config, requestContext) => {
13849
+ const seenRecords = new StoreKeySet();
13850
+ const chunks = chunkConfig(config);
13851
+ const promises = chunks.map((conf) => {
13852
+ return getRecordsAdapter(conf, requestContext);
13853
+ });
13854
+ return Promise.all(promises).then((results) => {
13855
+ const data = results.reduce((accu, item) => {
13856
+ if (item !== null && item.data !== undefined) {
13857
+ accu.results = accu.results.concat(item.data.results);
13858
+ seenRecords.merge(item.seenRecords);
13859
+ }
13860
+ return accu;
13861
+ }, { results: [] });
13862
+ return {
13863
+ data: data,
13864
+ seenRecords,
13865
+ state: 'Fulfilled',
13866
+ };
13867
+ });
13868
+ };
13869
+ return batchGetRecords;
13870
+ }
13871
+ /**
13872
+ * Given a GetRecordsConfig it will chunk it into multiple configs with a maximum of 100
13873
+ * record ids per config.
13874
+ *
13875
+ * @param config
13876
+ * @returns
13877
+ */
13878
+ function chunkConfig(config) {
13879
+ const chunks = [];
13880
+ config.records.forEach((record) => {
13881
+ const { recordIds, fields } = record;
13882
+ for (let i = 0, len = recordIds.length; i < len; i += MAX_ID_CHUNK_LENGTH) {
13883
+ const chunk = recordIds.slice(i, i + MAX_ID_CHUNK_LENGTH);
13884
+ chunks.push({
13885
+ records: [{ recordIds: chunk, fields: fields !== undefined ? fields : [] }],
13886
+ });
13887
+ }
13888
+ });
13889
+ return chunks;
13890
+ }
13891
+
13816
13892
  function generateUniqueRecordId() {
13817
13893
  return `UiApi::GraphQLRepresentation:${Date.now() + Math.random().toFixed(5).split('.')[1]}`;
13818
13894
  }
@@ -13922,8 +13998,13 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
13922
13998
  : [];
13923
13999
  let gqlResult;
13924
14000
  let seenRecordIds;
14001
+ let possibleStaleRecordMap;
13925
14002
  try {
13926
- ({ result: gqlResult, seenRecordIds } = await evaluate({
14003
+ ({
14004
+ result: gqlResult,
14005
+ seenRecordIds,
14006
+ possibleStaleRecordMap,
14007
+ } = await evaluate({
13927
14008
  ...config,
13928
14009
  //need to create another copy of the ast for future writes
13929
14010
  query: parse$3(stringify$3(injectedAST)),
@@ -13953,13 +14034,18 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
13953
14034
  const seenRecords = createSeenRecords(seenRecordIds, nonEvaluatedSnapshot);
13954
14035
  const recordId = generateUniqueRecordId();
13955
14036
  const rebuildWithLocalEval = async (originalSnapshot) => {
13956
- let { result: rebuildResult, seenRecordIds } = await evaluate({
14037
+ let { result: rebuildResult, seenRecordIds, possibleStaleRecordMap, } = await evaluate({
13957
14038
  ...config,
13958
14039
  query: injectedAST,
13959
14040
  }, observers, { userId }, objectInfoNeeded, store, originalSnapshot, graphqlSchemaCache, draftFunctions);
13960
14041
  if (!rebuildResult.errors) {
13961
14042
  rebuildResult = removeSyntheticFields(rebuildResult, config.query);
13962
14043
  }
14044
+ let snapshotState = 'Fulfilled';
14045
+ if (possibleStaleRecordMap.size > 0) {
14046
+ initiateStaleRecordRefresh(luvio, possibleStaleRecordMap);
14047
+ snapshotState = 'Stale';
14048
+ }
13963
14049
  if (objectsDeepEqual(rebuildResult, originalSnapshot.data)) {
13964
14050
  return originalSnapshot;
13965
14051
  }
@@ -13968,6 +14054,7 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
13968
14054
  ...originalSnapshot,
13969
14055
  data: rebuildResult,
13970
14056
  recordId,
14057
+ state: snapshotState,
13971
14058
  seenRecords: createSeenRecords(seenRecordIds, nonEvaluatedSnapshot),
13972
14059
  rebuildWithLocalEval,
13973
14060
  };
@@ -14005,6 +14092,10 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
14005
14092
  },
14006
14093
  };
14007
14094
  }
14095
+ if (possibleStaleRecordMap.size > 0) {
14096
+ initiateStaleRecordRefresh(luvio, possibleStaleRecordMap);
14097
+ resultSnapshot.state = 'Stale';
14098
+ }
14008
14099
  return resultSnapshot;
14009
14100
  };
14010
14101
  }
@@ -14065,6 +14156,24 @@ function environmentAwareGraphQLBatchAdapterFactory(objectInfoService, luvio, is
14065
14156
  };
14066
14157
  };
14067
14158
  }
14159
+ function initiateStaleRecordRefresh(luvio, keyMap) {
14160
+ const staleRecordKeys = from$1(keyMap.values())
14161
+ .flat()
14162
+ .map((id) => `UiApi::RecordRepresentation:${id}`);
14163
+ luvio.storeExpirePossibleStaleRecords(staleRecordKeys, makeGetRecordsConfig(keyMap), batchingGetRecordsAdapterFactory(luvio));
14164
+ }
14165
+ function makeGetRecordsConfig(keyMap) {
14166
+ const records = [];
14167
+ keyMap.forEach((recordIds, apiName) => {
14168
+ records.push({
14169
+ recordIds,
14170
+ fields: [`${apiName}.Id`],
14171
+ });
14172
+ });
14173
+ return {
14174
+ records,
14175
+ };
14176
+ }
14068
14177
 
14069
14178
  const CONTENT_DOCUMENT_DRAFT_ID_KEY = 'CONTENT_DOCUMENT_DRAFT_ID';
14070
14179
  const CONTENT_VERSION_DRAFT_ID_KEY = 'CONTENT_VERSION_DRAFT_ID';
@@ -18374,4 +18483,4 @@ register({
18374
18483
  });
18375
18484
 
18376
18485
  export { O11Y_NAMESPACE_LDS_MOBILE, getRuntime, registerReportObserver, reportGraphqlQueryParseError };
18377
- // version: 1.287.0-dev15-a292df40d2
18486
+ // version: 1.287.0-dev17-670374cbf3