@salesforce/lds-runtime-mobile 1.248.0 → 1.250.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -16,7 +16,8 @@ import { setupInstrumentation, instrumentAdapter as instrumentAdapter$1, instrum
16
16
  import { HttpStatusCode, StoreKeySet, serializeStructuredKey, StringKeyInMemoryStore, Reader, deepFreeze, emitAdapterEvent, createCustomAdapterEventEmitter, StoreKeyMap, isFileReference, Environment, Luvio, InMemoryStore } from '@luvio/engine';
17
17
  import excludeStaleRecordsGate from '@salesforce/gate/lds.graphqlEvalExcludeStaleRecords';
18
18
  import { parseAndVisit, Kind, buildSchema, isObjectType, defaultFieldResolver, visit, execute, parse as parse$7, extendSchema, isScalarType } from '@luvio/graphql-parser';
19
- 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, keyBuilderObjectInfo, ObjectInfoDirectoryEntryRepresentationType, getRecordsAdapterFactory } from '@salesforce/lds-adapters-uiapi';
19
+ 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';
20
+ import ldsIdempotencyWriteDisabled from '@salesforce/gate/lds.idempotencyWriteDisabled';
20
21
  import caseSensitiveUserId from '@salesforce/user/Id';
21
22
  import { idleDetector, getInstrumentation } from 'o11y/client';
22
23
  import ldsUseShortUrlGate from '@salesforce/gate/lds.useShortUrl';
@@ -1237,12 +1238,12 @@ function makeDurable(environment, { durableStore, instrumentation, useRevivingSt
1237
1238
  }
1238
1239
  return environment.getNode(key, stagingStore);
1239
1240
  };
1240
- const wrapNormalizedGraphNode = function (normalized) {
1241
+ const wrapNormalizedGraphNode = function (normalized, key) {
1241
1242
  validateNotDisposed();
1242
1243
  if (stagingStore === null) {
1243
1244
  stagingStore = buildIngestStagingStore(environment);
1244
1245
  }
1245
- return environment.wrapNormalizedGraphNode(normalized, stagingStore);
1246
+ return environment.wrapNormalizedGraphNode(normalized, key, stagingStore);
1246
1247
  };
1247
1248
  const rebuildSnapshot = function (snapshot, onRebuild) {
1248
1249
  validateNotDisposed();
@@ -4377,7 +4378,7 @@ function rootQuery(recordNodes, input) {
4377
4378
  if (fails.length > 0) {
4378
4379
  return failure(fails);
4379
4380
  }
4380
- return success({ type: 'root', connections });
4381
+ return success({ type: 'root', connections, queryKeys: input.queryKeys });
4381
4382
  }
4382
4383
  /**
4383
4384
  * Given a connection array of LuvioSelectionCustomFieldNode
@@ -4597,11 +4598,11 @@ class StoreEvalPreconditioner {
4597
4598
  // require at least this top level record present to resolve relationship lookups
4598
4599
  const recordSelections = findRecordSelections(ast);
4599
4600
  let metadata = {};
4601
+ const queryKeys = recordSelections.map((rs) => connectionKeyBuilder(rs, variables));
4600
4602
  if (excludeStaleRecordsGate.isOpen({ fallback: false })) {
4601
- const keys = recordSelections.map((rs) => connectionKeyBuilder(rs, variables));
4602
- let sqlResult = await sqliteStore.query(`select key, metadata from lds_data where key in (${keys
4603
+ let sqlResult = await sqliteStore.query(`select key, metadata from lds_data where key in (${queryKeys
4603
4604
  .map(() => '?')
4604
- .join(',')})`, keys);
4605
+ .join(',')})`, queryKeys);
4605
4606
  metadata = sqlResult.rows.reduce((metadata, row) => {
4606
4607
  metadata[row[0]] = JSON.parse(row[1]);
4607
4608
  return metadata;
@@ -4653,6 +4654,7 @@ class StoreEvalPreconditioner {
4653
4654
  draftFunctions,
4654
4655
  connectionKeyBuilder,
4655
4656
  metadata,
4657
+ queryKeys,
4656
4658
  });
4657
4659
  if (astTransformResult.isSuccess === false) {
4658
4660
  for (const error of astTransformResult.error) {
@@ -4724,6 +4726,11 @@ async function evaluateSqlite(query, eventEmitter, store) {
4724
4726
  eventEmitter({ type: 'graphql-db-read', sql, bindings, duration: Date.now() - start });
4725
4727
  const data = JSON.parse(rawValue);
4726
4728
  const seenRecords = createSeenRecords$1(data);
4729
+ if (query.queryKeys) {
4730
+ for (const queryKey of query.queryKeys) {
4731
+ seenRecords.add(queryKey);
4732
+ }
4733
+ }
4727
4734
  return { data, seenRecords };
4728
4735
  }
4729
4736
  const wrapStartEndEvents = (storeEval) => {
@@ -4950,6 +4957,21 @@ class AsyncWorkerPool {
4950
4957
  }
4951
4958
  }
4952
4959
 
4960
+ /**
4961
+ Use Math.random to generate v4 RFC4122 compliant uuid
4962
+ */
4963
+ function uuidv4() {
4964
+ const uuid = [];
4965
+ for (let i = 0; i < 32; i++) {
4966
+ const random = (Math.random() * 16) | 0;
4967
+ if (i === 8 || i === 12 || i === 16 || i === 20) {
4968
+ uuid.push('-');
4969
+ }
4970
+ uuid.push((i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random).toString(16));
4971
+ }
4972
+ return uuid.join('');
4973
+ }
4974
+
4953
4975
  /**
4954
4976
  * Copyright (c) 2022, Salesforce, Inc.,
4955
4977
  * All rights reserved.
@@ -5163,20 +5185,6 @@ function generateUniqueDraftActionId(existingIds) {
5163
5185
  }
5164
5186
  return newId.toString();
5165
5187
  }
5166
- /**
5167
- Use Math.random to generate v4 RFC4122 compliant uuid
5168
- */
5169
- function uuidv4() {
5170
- const uuid = [];
5171
- for (let i = 0; i < 32; i++) {
5172
- const random = (Math.random() * 16) | 0;
5173
- if (i === 8 || i === 12 || i === 16 || i === 20) {
5174
- uuid.push('-');
5175
- }
5176
- uuid.push((i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random).toString(16));
5177
- }
5178
- return uuid.join('');
5179
- }
5180
5188
 
5181
5189
  const HTTP_HEADER_RETRY_AFTER = 'Retry-After';
5182
5190
  const HTTP_HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';
@@ -6053,7 +6061,12 @@ class AbstractResourceRequestActionHandler {
6053
6061
  // the luvio store redirect table, during which a new draft might be enqueued
6054
6062
  // which would not see a necessary mapping.
6055
6063
  this.ephemeralRedirects = {};
6064
+ // determined by Server setup.
6056
6065
  this.isIdempotencySupported = true;
6066
+ // idempotency write flag set by lds
6067
+ this.isLdsIdempotencyWriteDisabled = ldsIdempotencyWriteDisabled.isOpen({
6068
+ fallback: false,
6069
+ });
6057
6070
  }
6058
6071
  enqueue(data) {
6059
6072
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -6410,7 +6423,7 @@ class AbstractResourceRequestActionHandler {
6410
6423
  return [action.targetId];
6411
6424
  }
6412
6425
  hasIdempotencySupport() {
6413
- return this.isIdempotencySupported;
6426
+ return this.isIdempotencySupported && !this.isLdsIdempotencyWriteDisabled;
6414
6427
  }
6415
6428
  async ingestResponses(responses, action) {
6416
6429
  const luvio = this.getLuvio();
@@ -6941,6 +6954,20 @@ function buildQueryTypeStringKey(args) {
6941
6954
  return `${keyPrefix}::${schemaName}::${queryTypeName}[${serializeOperationNode(operationNode, variables, fragmentMap)}]`;
6942
6955
  }
6943
6956
 
6957
+ /**
6958
+ * @description Spec compliant way to retrieve the correct Operation from the Document that Luvio should operate on. https://spec.graphql.org/June2018/#sec-Named-Operation-Definitions
6959
+ * @param document
6960
+ * @param operationName
6961
+ * @returns The Operation in the GraphQL document we should use for the current call.
6962
+ */
6963
+ function getOperationFromDocument(document, operationName) {
6964
+ const operations = document.definitions.filter((def) => def.kind === 'OperationDefinition');
6965
+ if (operationName) {
6966
+ return operations.find((def) => def.name !== undefined && def.name.value === operationName);
6967
+ }
6968
+ return operations[0]; // If a named operation is not provided, we return the first one
6969
+ }
6970
+
6944
6971
  /**
6945
6972
  * Copyright (c) 2022, Salesforce, Inc.,
6946
6973
  * All rights reserved.
@@ -6948,14 +6975,20 @@ function buildQueryTypeStringKey(args) {
6948
6975
  */
6949
6976
 
6950
6977
 
6978
+ const MAX_BATCH_SIZE = 2000;
6951
6979
  class DataLoader {
6952
- constructor(batchLoadFn) {
6980
+ constructor(batchLoadFn, options) {
6953
6981
  this._batchLoadFn = batchLoadFn;
6954
6982
  this._batch = null;
6955
6983
  this._batchScheduleFn = function (fn) {
6956
6984
  setTimeout(fn, 0);
6957
6985
  };
6958
6986
  this._cacheMap = new Map();
6987
+ this._maxBatchSize = MAX_BATCH_SIZE;
6988
+ if (options !== undefined) {
6989
+ const { maxBatchSize } = options;
6990
+ this._maxBatchSize = maxBatchSize || MAX_BATCH_SIZE;
6991
+ }
6959
6992
  }
6960
6993
  load(key) {
6961
6994
  if (key === null || key === undefined) {
@@ -6985,7 +7018,9 @@ class DataLoader {
6985
7018
  // If there is an existing batch which has not yet dispatched and is within
6986
7019
  // the limit of the batch size, then return it.
6987
7020
  const existingBatch = this._batch;
6988
- if (existingBatch !== null && !existingBatch.hasDispatched && !existingBatch.cacheHits) {
7021
+ if (existingBatch !== null &&
7022
+ !existingBatch.hasDispatched &&
7023
+ existingBatch.keys.length < this._maxBatchSize) {
6989
7024
  return existingBatch;
6990
7025
  }
6991
7026
  // Otherwise, create a new batch for this loader.
@@ -9378,7 +9413,9 @@ function extendSchemaWithObjectInfos(cache, objectInfoMap) {
9378
9413
  ];
9379
9414
  // extend the schema and add resolvers
9380
9415
  const schema = addResolversToSchema(extendSchema(cache.getSchema(), extensions), polymorphicFieldTypeNames);
9416
+ const polymorphicFieldTypeNamesSet = new Set(polymorphicFieldTypeNames);
9381
9417
  cache.setSchema(schema);
9418
+ cache.setPolymorphicFieldTypeNames([...polymorphicFieldTypeNamesSet]);
9382
9419
  return cache;
9383
9420
  }
9384
9421
  /**
@@ -9534,8 +9571,11 @@ function extendExistingRecordType(schema, type, objectInfo, objectInfoMap) {
9534
9571
  let typedScalars = new Set();
9535
9572
  let parentRelationshipFields = new Set();
9536
9573
  const existingFields = keys$4(type.getFields());
9537
- const missingFields = values$2(objectInfo.fields).filter((field) => existingFields.includes(field.apiName) === false);
9538
- const { fields, polymorphicFieldTypeNames } = makeRecordField(missingFields, objectInfoMap, parentRelationshipFields, 'Cached');
9574
+ const missingFields = values$2(objectInfo.fields).filter((field) => {
9575
+ return (existingFields.includes(field.apiName) === false ||
9576
+ (field.relationshipName !== null && field.referenceToInfos.length > 0));
9577
+ });
9578
+ const { fields, polymorphicFieldTypeNames } = makeRecordField(missingFields, objectInfoMap, parentRelationshipFields, 'Cached', existingFields);
9539
9579
  const { apiName, childRelationships } = objectInfo;
9540
9580
  // handles child relationship
9541
9581
  const { spanningRecordConnections, typedScalars: spanningConnectionTypedScalars } = makeSpanningRecordConnections(schema, childRelationships, objectInfoMap, parentRelationshipFields, existingFields);
@@ -9610,7 +9650,7 @@ function makeSpanningRecordConnections(schema, childRelationships, objectInfoMap
9610
9650
  * @param recordTypeInSchema
9611
9651
  * @returns
9612
9652
  */
9613
- function makeRecordField(fieldRepresentations, objectInfoMap, existingParentRelationships, recordTypeInSchema) {
9653
+ function makeRecordField(fieldRepresentations, objectInfoMap, existingParentRelationships, recordTypeInSchema, existingFields = []) {
9614
9654
  const polymorphicFieldTypeNames = new Set();
9615
9655
  let fields = ``;
9616
9656
  for (const field of values$2(fieldRepresentations)) {
@@ -9627,15 +9667,18 @@ function makeRecordField(fieldRepresentations, objectInfoMap, existingParentRela
9627
9667
  // Only add the relationship if there is relevant objectinfos for it,
9628
9668
  // otherwise we'd be defining types we cannot satisfy and aren't referenced in
9629
9669
  // the query.
9630
- if (objectInfoMap[relation.apiName] !== undefined) {
9670
+ if (objectInfoMap[relation.apiName] !== undefined &&
9671
+ existingFields.includes(field.relationshipName) === false) {
9631
9672
  existingParentRelationships.add(field.relationshipName);
9632
9673
  fields += `${field.relationshipName}: ${relation.apiName}\n`;
9633
9674
  }
9634
9675
  // For polymorphic field, its type is 'Record' inteface. The concrete entity type name is saved for field resolving of next phase
9635
9676
  }
9636
9677
  else if (field.referenceToInfos.length > 1) {
9637
- existingParentRelationships.add(field.relationshipName);
9638
- fields += `${field.relationshipName}: Record\n`;
9678
+ if (recordTypeInSchema === 'Missing') {
9679
+ existingParentRelationships.add(field.relationshipName);
9680
+ fields += `${field.relationshipName}: Record\n`;
9681
+ }
9639
9682
  for (const relation of field.referenceToInfos) {
9640
9683
  if (objectInfoMap[relation.apiName] !== undefined) {
9641
9684
  polymorphicFieldTypeNames.add(relation.apiName);
@@ -9698,6 +9741,8 @@ async function evaluate(config, observers, settings, objectInfos, store, snapsho
9698
9741
  // this is only wrapped in a try to execute the event after the result was returned
9699
9742
  try {
9700
9743
  eventEmitter({ type: 'graphql-eval-start' });
9744
+ const operationNode = getOperationFromDocument(config.query);
9745
+ let topLevelQueries = [];
9701
9746
  // assume that 'config.query' has required injected fields.
9702
9747
  const modifiedAST = visit(config.query, {
9703
9748
  Field: {
@@ -9727,6 +9772,25 @@ async function evaluate(config, observers, settings, objectInfos, store, snapsho
9727
9772
  };
9728
9773
  },
9729
9774
  },
9775
+ Directive: {
9776
+ enter(node, _key, _parent, _path, ancester) {
9777
+ if (node.name.value === 'category' && node.arguments !== undefined) {
9778
+ for (let i = 0, len = node.arguments.length; i < len; i++) {
9779
+ const argument = node.arguments[i];
9780
+ if (isStringValueNode(argument.value) &&
9781
+ argument.value.value === 'recordQuery') {
9782
+ const parentNode = ancester[ancester.length - 1];
9783
+ if (isFieldNode(parentNode)) {
9784
+ topLevelQueries.push({
9785
+ argumentNodes: parentNode.arguments || [],
9786
+ recordName: parentNode.name.value,
9787
+ });
9788
+ }
9789
+ }
9790
+ }
9791
+ }
9792
+ },
9793
+ },
9730
9794
  });
9731
9795
  eventEmitter({ type: 'graphql-preconditions-met' });
9732
9796
  // create the resolver request context, runtime values and functions for
@@ -9745,7 +9809,18 @@ async function evaluate(config, observers, settings, objectInfos, store, snapsho
9745
9809
  rootValue: {},
9746
9810
  }));
9747
9811
  eventEmitter({ type: 'graphql-evaluated' });
9748
- return { result, seenRecordIds: [...contextValue.seenRecordIds] };
9812
+ // add record key to seen ids
9813
+ const seenRecordIds = [...contextValue.seenRecordIds].map((id) => `UiApi::RecordRepresentation:${id}`);
9814
+ // if we have all the data to build the top level query
9815
+ // add it to the seen ids
9816
+ if (operationNode !== undefined && topLevelQueries.length > 0) {
9817
+ topLevelQueries.forEach((query) => {
9818
+ const { recordName, argumentNodes } = query;
9819
+ const queryString = buildKeyStringForRecordQuery(operationNode, config.variables || {}, argumentNodes, recordName);
9820
+ seenRecordIds.push(queryString);
9821
+ });
9822
+ }
9823
+ return { result, seenRecordIds };
9749
9824
  }
9750
9825
  finally {
9751
9826
  eventEmitter({ type: 'graphql-eval-end' });
@@ -12084,34 +12159,42 @@ function applyReferenceLinksToDraft(record, draftMetadata) {
12084
12159
  }
12085
12160
  const { dataType, relationshipName, referenceToInfos } = fieldInfo;
12086
12161
  const draftFieldValue = record.fields[draftField].value;
12087
- if (dataType === 'Reference' && relationshipName !== null && draftFieldValue !== null) {
12088
- if (typeof draftFieldValue !== 'string') {
12089
- throw Error('reference field value is not a string');
12162
+ if (dataType === 'Reference' && relationshipName !== null) {
12163
+ if (draftFieldValue === null) {
12164
+ recordFields[relationshipName] = {
12165
+ displayValue: null,
12166
+ value: null,
12167
+ };
12090
12168
  }
12091
- const key = getRecordKeyForId(luvio, draftFieldValue);
12092
- const referencedRecord = referencedRecords.get(key);
12093
- recordFields[relationshipName] = {
12094
- displayValue: null,
12095
- value: createLink(key),
12096
- };
12097
- // for custom objects, we select the 'Name' field
12098
- // otherwise we check the object info for name fields.
12099
- //if there are multiple we select 'Name' if it exists, otherwise the first one
12100
- if (referencedRecord !== undefined && referenceToInfos.length > 0) {
12101
- let nameField;
12102
- const referenceToInfo = referenceToInfos[0];
12103
- const nameFields = referenceToInfo.nameFields;
12104
- if (nameFields.length !== 0) {
12105
- nameField = nameFields.find((x) => x === 'Name');
12106
- if (nameField === undefined) {
12107
- nameField = nameFields[0];
12108
- }
12169
+ else {
12170
+ if (typeof draftFieldValue !== 'string') {
12171
+ throw Error('reference field value is not a string');
12109
12172
  }
12110
- if (nameField !== undefined) {
12111
- const nameFieldRef = referencedRecord.fields[nameField];
12112
- if (nameFieldRef) {
12113
- recordFields[relationshipName].displayValue =
12114
- (_a = nameFieldRef.displayValue) !== null && _a !== void 0 ? _a : nameFieldRef.value;
12173
+ const key = getRecordKeyForId(luvio, draftFieldValue);
12174
+ const referencedRecord = referencedRecords.get(key);
12175
+ recordFields[relationshipName] = {
12176
+ displayValue: null,
12177
+ value: createLink(key),
12178
+ };
12179
+ // for custom objects, we select the 'Name' field
12180
+ // otherwise we check the object info for name fields.
12181
+ //if there are multiple we select 'Name' if it exists, otherwise the first one
12182
+ if (referencedRecord !== undefined && referenceToInfos.length > 0) {
12183
+ let nameField;
12184
+ const referenceToInfo = referenceToInfos[0];
12185
+ const nameFields = referenceToInfo.nameFields;
12186
+ if (nameFields.length !== 0) {
12187
+ nameField = nameFields.find((x) => x === 'Name');
12188
+ if (nameField === undefined) {
12189
+ nameField = nameFields[0];
12190
+ }
12191
+ }
12192
+ if (nameField !== undefined) {
12193
+ const nameFieldRef = referencedRecord.fields[nameField];
12194
+ if (nameFieldRef) {
12195
+ recordFields[relationshipName].displayValue =
12196
+ (_a = nameFieldRef.displayValue) !== null && _a !== void 0 ? _a : nameFieldRef.value;
12197
+ }
12115
12198
  }
12116
12199
  }
12117
12200
  }
@@ -12404,17 +12487,8 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
12404
12487
  };
12405
12488
  for (const fieldName of keys$3(recordWithSpanningRefLinks.fields)) {
12406
12489
  const fieldKey = buildRecordFieldStoreKey(key, fieldName);
12407
- if (this.collectedFields[fieldKey] !== undefined) {
12408
- const fieldData = recordWithSpanningRefLinks.fields[fieldName];
12409
- normalizedRecord.fields[fieldName] = { __ref: fieldKey };
12410
- publishData(fieldKey, fieldData);
12411
- }
12412
- else if (recordWithSpanningRefLinks.fields[fieldName] &&
12413
- recordWithSpanningRefLinks.fields[fieldName].value &&
12414
- recordWithSpanningRefLinks.fields[fieldName].value.__ref !== undefined) {
12415
- normalizedRecord.fields[fieldName] = { __ref: fieldKey };
12416
- publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
12417
- }
12490
+ normalizedRecord.fields[fieldName] = { __ref: fieldKey };
12491
+ publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
12418
12492
  }
12419
12493
  // publish the normalized record
12420
12494
  publishData(key, normalizedRecord);
@@ -13126,14 +13200,12 @@ function isLocalEvalSnapshot(snapshot) {
13126
13200
  return 'rebuildWithLocalEval' in snapshot;
13127
13201
  }
13128
13202
  function createSeenRecords(ids, currentSnapshot) {
13129
- let seenRecords = ids
13130
- .map((id) => `UiApi::RecordRepresentation:${id}`)
13131
- .reduce((acc, curr) => (acc.add(curr), acc), new StoreKeySet());
13203
+ let seenRecords = ids.reduce((acc, curr) => (acc.add(curr), acc), new StoreKeySet());
13132
13204
  if (currentSnapshot.state !== 'Error') {
13133
13205
  currentSnapshot.seenRecords.forEach((record) => {
13134
13206
  let keyString = typeof record !== 'string' ? serializeStructuredKey(record) : record;
13135
- if (keyString !== 'UiApi::GraphQLRepresentation__uiapi' &&
13136
- keyString !== 'UiApi::GraphQLRepresentation__uiapi__query' &&
13207
+ if (keyString !== 'UiApi::uiapi::Query[uiapi]__uiapi' &&
13208
+ keyString !== 'UiApi::uiapi::Query[uiapi]__uiapi__query' &&
13137
13209
  seenRecords.has(record) === false) {
13138
13210
  seenRecords.add(record);
13139
13211
  }
@@ -15375,6 +15447,14 @@ class ObjectInfoService {
15375
15447
  return this.updateObjectInfoMapping(keyPrefix, apiName);
15376
15448
  }
15377
15449
  };
15450
+ this.getCachedObjectInfoStatus = async () => {
15451
+ const infos = await this.readObjectInfoDataFromDurableStore();
15452
+ const map = new Map();
15453
+ infos.forEach(({ apiName, expirationTimestamp }) => {
15454
+ map.set(apiName, { expiration: expirationTimestamp });
15455
+ });
15456
+ return map;
15457
+ };
15378
15458
  this.isObjectInfoInDurableStore = async (apiName) => {
15379
15459
  if (this.apiNameToKeyPrefixMemoryCache[apiName] !== undefined) {
15380
15460
  return Promise.resolve(true);
@@ -15383,12 +15463,10 @@ class ObjectInfoService {
15383
15463
  return this.apiNameToKeyPrefixMemoryCache[apiName] !== undefined;
15384
15464
  };
15385
15465
  this.loadObjectInfoMaps = async () => {
15386
- const rows = (await this.durableStore.query(`SELECT json_extract(data, '$.apiName') as ApiName, json_extract(data, '$.keyPrefix') as keyPrefix from lds_data where key like '%ObjectInfoRepresentation%'`, [])).rows;
15387
- for (const row of rows) {
15388
- const apiName = row[0];
15389
- const keyPrefix = row[1];
15466
+ const infos = await this.readObjectInfoDataFromDurableStore();
15467
+ infos.forEach(({ keyPrefix, apiName }) => {
15390
15468
  this.updateObjectInfoMapping(keyPrefix, apiName);
15391
- }
15469
+ });
15392
15470
  };
15393
15471
  this.updateObjectInfoMapping = (keyPrefix, apiName) => {
15394
15472
  this.apiNameToKeyPrefixMemoryCache[apiName] = keyPrefix;
@@ -15432,6 +15510,24 @@ class ObjectInfoService {
15432
15510
  }
15433
15511
  return snapshot.data;
15434
15512
  }
15513
+ async readObjectInfoDataFromDurableStore() {
15514
+ const rows = (await this.durableStore.query(`
15515
+ SELECT
15516
+ json_extract(data, '$.apiName') as ApiName,
15517
+ json_extract(data, '$.keyPrefix') as keyPrefix,
15518
+ JSON_EXTRACT(metadata, '$.expirationTimestamp') AS expirationTimestamp
15519
+ from
15520
+ lds_data
15521
+ where
15522
+ key like '%ObjectInfoRepresentation%'`, [])).rows;
15523
+ return rows.map((row) => {
15524
+ return {
15525
+ apiName: row[0],
15526
+ keyPrefix: row[1],
15527
+ expirationTimestamp: row[2],
15528
+ };
15529
+ });
15530
+ }
15435
15531
  }
15436
15532
 
15437
15533
  function instrumentGraphQLEval(adapter) {
@@ -16530,7 +16626,7 @@ class ConflictPool {
16530
16626
  }
16531
16627
  }
16532
16628
 
16533
- const DEFAULT_BATCH_SIZE = 500;
16629
+ const DEFAULT_BATCH_SIZE$1 = 500;
16534
16630
  const DEFAULT_CONCURRENCY = 6;
16535
16631
  const DEFAULT_GQL_QUERY_BATCH_SIZE = 5;
16536
16632
  class PrimingSession extends EventEmitter {
@@ -16538,7 +16634,7 @@ class PrimingSession extends EventEmitter {
16538
16634
  var _a, _b;
16539
16635
  super();
16540
16636
  this.useBatchGQL = false;
16541
- this.batchSize = (_a = config.batchSize) !== null && _a !== void 0 ? _a : DEFAULT_BATCH_SIZE;
16637
+ this.batchSize = (_a = config.batchSize) !== null && _a !== void 0 ? _a : DEFAULT_BATCH_SIZE$1;
16542
16638
  this.concurrency = (_b = config.concurrency) !== null && _b !== void 0 ? _b : DEFAULT_CONCURRENCY;
16543
16639
  this.recordLoader = config.recordLoader;
16544
16640
  this.recordIngestor = config.recordIngestor;
@@ -17159,10 +17255,6 @@ class NimbusPrimingNetworkAdapter {
17159
17255
  // ref: https://gnome.pages.gitlab.gnome.org/tracker/docs/developer/limits.html?gi-language=c
17160
17256
  const SQLITE_MAX_VARIABLE_NUMBER = 999;
17161
17257
  const PARAMS_PER_RECORD = 3;
17162
- /**
17163
- * No key builder (or adapter) exists for the object info directory, we need to build the key manually
17164
- */
17165
- const ObjectInfoDirectoryKey = `${UiApiNamespace}::${ObjectInfoDirectoryEntryRepresentationType}:`;
17166
17258
  // We need to batch the records to avoid hitting the SQLITE_MAX_VARIABLE_NUMBER limit. Each record has 3 parameters
17167
17259
  const BATCH_SIZE = Math.floor(SQLITE_MAX_VARIABLE_NUMBER / PARAMS_PER_RECORD);
17168
17260
  class SqlitePrimingStore {
@@ -17227,44 +17319,6 @@ class SqlitePrimingStore {
17227
17319
  };
17228
17320
  }
17229
17321
  }
17230
- async readObjectInfoDirectory() {
17231
- const sql = 'SELECT data FROM lds_data WHERE key = ?';
17232
- const params = [ObjectInfoDirectoryKey];
17233
- const result = await this.store.query(sql, params);
17234
- if (result.rows.length === 1) {
17235
- return JSON.parse(result.rows[0][0]);
17236
- }
17237
- return undefined;
17238
- }
17239
- async readObjectApiNames() {
17240
- const sql = 'SELECT key FROM lds_data WHERE key like ?';
17241
- const params = [`%${ObjectInfoRepresentationType}%`];
17242
- const result = await this.store.query(sql, params);
17243
- const apiNames = new Set();
17244
- result.rows.forEach((row) => {
17245
- const key = row[0];
17246
- const parts = key.split(':');
17247
- apiNames.add(parts[parts.length - 1]);
17248
- });
17249
- return apiNames;
17250
- }
17251
- writeObjectInfoDirectory(directory) {
17252
- const sql = 'INSERT or IGNORE into lds_data (key, data) values (?, ?)';
17253
- const params = [ObjectInfoDirectoryKey, JSON.stringify(directory)];
17254
- return this.store.query(sql, params).then(() => { });
17255
- }
17256
- writeObjectInfos(objectInfos) {
17257
- const sql = `INSERT or IGNORE into lds_data (key, data) values ${objectInfos
17258
- .map(() => '(?, ?)')
17259
- .join(',')};`;
17260
- const params = [];
17261
- objectInfos.forEach((objectInfo) => {
17262
- const key = keyBuilderObjectInfo(this.getLuvio(), { apiName: objectInfo.apiName });
17263
- params.push(key);
17264
- params.push(JSON.stringify(objectInfo));
17265
- });
17266
- return this.store.query(sql, params).then(() => { });
17267
- }
17268
17322
  }
17269
17323
  function batchArray(arr, batchSize = BATCH_SIZE) {
17270
17324
  const batches = [];
@@ -17339,9 +17393,7 @@ function primingSessionFactory(config) {
17339
17393
  recordLoader,
17340
17394
  recordIngestor,
17341
17395
  store: primingStore,
17342
- objectInfoLoader: {
17343
- getObjectInfos: objectInfoService.getObjectInfos.bind(objectInfoService),
17344
- },
17396
+ objectInfoLoader: objectInfoService,
17345
17397
  concurrency: config.concurrency,
17346
17398
  batchSize: config.batchSize,
17347
17399
  ldsRecordRefresher: new LdsPrimingRecordRefresher(config.getRecords),
@@ -17507,4 +17559,4 @@ register({
17507
17559
  });
17508
17560
 
17509
17561
  export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
17510
- // version: 1.248.0-1f7f01112
17562
+ // version: 1.250.0-9df9bc3d1
@@ -1,6 +1,5 @@
1
1
  import type { PrimingStore, RecordWithMetadata, WriteResult } from '@salesforce/lds-priming';
2
2
  import type { Luvio } from '@luvio/engine';
3
- import type { ObjectInfoDirectoryRepresentation, ObjectInfoRepresentation } from '@salesforce/lds-adapters-uiapi';
4
3
  import type { SqliteStore } from '@salesforce/lds-store-sql';
5
4
  export declare class SqlitePrimingStore implements PrimingStore {
6
5
  private readonly getLuvio;
@@ -9,8 +8,4 @@ export declare class SqlitePrimingStore implements PrimingStore {
9
8
  readRecords(ids: string[]): Promise<RecordWithMetadata[]>;
10
9
  writeRecords(records: RecordWithMetadata[], overwrite: boolean): Promise<WriteResult>;
11
10
  private writeBatch;
12
- readObjectInfoDirectory(): Promise<ObjectInfoDirectoryRepresentation | undefined>;
13
- readObjectApiNames(): Promise<Set<string>>;
14
- writeObjectInfoDirectory(directory: ObjectInfoDirectoryRepresentation): Promise<void>;
15
- writeObjectInfos(objectInfos: ObjectInfoRepresentation[]): Promise<void>;
16
11
  }
@@ -10,6 +10,9 @@ type ObjectInfoDirectoryConfig = Parameters<ObjectInfoDirectoryAdapterReturn>[0]
10
10
  export type ObjectInfoMap = {
11
11
  [apiName: string]: ObjectInfoRepresentation;
12
12
  };
13
+ interface ObjectInfoStatus {
14
+ expiration: number;
15
+ }
13
16
  export declare class ObjectInfoService {
14
17
  private getObjectInfoAdapter;
15
18
  private getObjectInfosAdapter;
@@ -31,8 +34,10 @@ export declare class ObjectInfoService {
31
34
  getObjectInfos(apiNames: string[]): Promise<ObjectInfoMap>;
32
35
  getObjectInfoDirectory(): Promise<ObjectInfoDirectoryRepresentation | undefined>;
33
36
  ensureObjectInfoCached: (apiName: string, entry?: ObjectInfoRepresentation) => Promise<void>;
37
+ getCachedObjectInfoStatus: () => Promise<Map<string, ObjectInfoStatus>>;
34
38
  private isObjectInfoInDurableStore;
35
39
  private loadObjectInfoMaps;
40
+ private readObjectInfoDataFromDurableStore;
36
41
  private updateObjectInfoMapping;
37
42
  }
38
43
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/lds-runtime-mobile",
3
- "version": "1.248.0",
3
+ "version": "1.250.0",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "description": "LDS runtime for mobile/hybrid environments.",
6
6
  "main": "dist/main.js",
package/sfdc/main.js CHANGED
@@ -16,7 +16,8 @@ import { setupInstrumentation, instrumentAdapter as instrumentAdapter$1, instrum
16
16
  import { HttpStatusCode, StoreKeySet, serializeStructuredKey, StringKeyInMemoryStore, Reader, deepFreeze, emitAdapterEvent, createCustomAdapterEventEmitter, StoreKeyMap, isFileReference, Environment, Luvio, InMemoryStore } from 'force/luvioEngine';
17
17
  import excludeStaleRecordsGate from '@salesforce/gate/lds.graphqlEvalExcludeStaleRecords';
18
18
  import { parseAndVisit, Kind, buildSchema, isObjectType, defaultFieldResolver, visit, execute, parse as parse$7, extendSchema, isScalarType } from 'force/ldsGraphqlParser';
19
- 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, keyBuilderObjectInfo, ObjectInfoDirectoryEntryRepresentationType, getRecordsAdapterFactory } from 'force/ldsAdaptersUiapi';
19
+ 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';
20
+ import ldsIdempotencyWriteDisabled from '@salesforce/gate/lds.idempotencyWriteDisabled';
20
21
  import caseSensitiveUserId from '@salesforce/user/Id';
21
22
  import { idleDetector, getInstrumentation } from 'o11y/client';
22
23
  import ldsUseShortUrlGate from '@salesforce/gate/lds.useShortUrl';
@@ -1237,12 +1238,12 @@ function makeDurable(environment, { durableStore, instrumentation, useRevivingSt
1237
1238
  }
1238
1239
  return environment.getNode(key, stagingStore);
1239
1240
  };
1240
- const wrapNormalizedGraphNode = function (normalized) {
1241
+ const wrapNormalizedGraphNode = function (normalized, key) {
1241
1242
  validateNotDisposed();
1242
1243
  if (stagingStore === null) {
1243
1244
  stagingStore = buildIngestStagingStore(environment);
1244
1245
  }
1245
- return environment.wrapNormalizedGraphNode(normalized, stagingStore);
1246
+ return environment.wrapNormalizedGraphNode(normalized, key, stagingStore);
1246
1247
  };
1247
1248
  const rebuildSnapshot = function (snapshot, onRebuild) {
1248
1249
  validateNotDisposed();
@@ -4377,7 +4378,7 @@ function rootQuery(recordNodes, input) {
4377
4378
  if (fails.length > 0) {
4378
4379
  return failure(fails);
4379
4380
  }
4380
- return success({ type: 'root', connections });
4381
+ return success({ type: 'root', connections, queryKeys: input.queryKeys });
4381
4382
  }
4382
4383
  /**
4383
4384
  * Given a connection array of LuvioSelectionCustomFieldNode
@@ -4597,11 +4598,11 @@ class StoreEvalPreconditioner {
4597
4598
  // require at least this top level record present to resolve relationship lookups
4598
4599
  const recordSelections = findRecordSelections(ast);
4599
4600
  let metadata = {};
4601
+ const queryKeys = recordSelections.map((rs) => connectionKeyBuilder(rs, variables));
4600
4602
  if (excludeStaleRecordsGate.isOpen({ fallback: false })) {
4601
- const keys = recordSelections.map((rs) => connectionKeyBuilder(rs, variables));
4602
- let sqlResult = await sqliteStore.query(`select key, metadata from lds_data where key in (${keys
4603
+ let sqlResult = await sqliteStore.query(`select key, metadata from lds_data where key in (${queryKeys
4603
4604
  .map(() => '?')
4604
- .join(',')})`, keys);
4605
+ .join(',')})`, queryKeys);
4605
4606
  metadata = sqlResult.rows.reduce((metadata, row) => {
4606
4607
  metadata[row[0]] = JSON.parse(row[1]);
4607
4608
  return metadata;
@@ -4653,6 +4654,7 @@ class StoreEvalPreconditioner {
4653
4654
  draftFunctions,
4654
4655
  connectionKeyBuilder,
4655
4656
  metadata,
4657
+ queryKeys,
4656
4658
  });
4657
4659
  if (astTransformResult.isSuccess === false) {
4658
4660
  for (const error of astTransformResult.error) {
@@ -4724,6 +4726,11 @@ async function evaluateSqlite(query, eventEmitter, store) {
4724
4726
  eventEmitter({ type: 'graphql-db-read', sql, bindings, duration: Date.now() - start });
4725
4727
  const data = JSON.parse(rawValue);
4726
4728
  const seenRecords = createSeenRecords$1(data);
4729
+ if (query.queryKeys) {
4730
+ for (const queryKey of query.queryKeys) {
4731
+ seenRecords.add(queryKey);
4732
+ }
4733
+ }
4727
4734
  return { data, seenRecords };
4728
4735
  }
4729
4736
  const wrapStartEndEvents = (storeEval) => {
@@ -4950,6 +4957,21 @@ class AsyncWorkerPool {
4950
4957
  }
4951
4958
  }
4952
4959
 
4960
+ /**
4961
+ Use Math.random to generate v4 RFC4122 compliant uuid
4962
+ */
4963
+ function uuidv4() {
4964
+ const uuid = [];
4965
+ for (let i = 0; i < 32; i++) {
4966
+ const random = (Math.random() * 16) | 0;
4967
+ if (i === 8 || i === 12 || i === 16 || i === 20) {
4968
+ uuid.push('-');
4969
+ }
4970
+ uuid.push((i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random).toString(16));
4971
+ }
4972
+ return uuid.join('');
4973
+ }
4974
+
4953
4975
  /**
4954
4976
  * Copyright (c) 2022, Salesforce, Inc.,
4955
4977
  * All rights reserved.
@@ -5163,20 +5185,6 @@ function generateUniqueDraftActionId(existingIds) {
5163
5185
  }
5164
5186
  return newId.toString();
5165
5187
  }
5166
- /**
5167
- Use Math.random to generate v4 RFC4122 compliant uuid
5168
- */
5169
- function uuidv4() {
5170
- const uuid = [];
5171
- for (let i = 0; i < 32; i++) {
5172
- const random = (Math.random() * 16) | 0;
5173
- if (i === 8 || i === 12 || i === 16 || i === 20) {
5174
- uuid.push('-');
5175
- }
5176
- uuid.push((i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random).toString(16));
5177
- }
5178
- return uuid.join('');
5179
- }
5180
5188
 
5181
5189
  const HTTP_HEADER_RETRY_AFTER = 'Retry-After';
5182
5190
  const HTTP_HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';
@@ -6053,7 +6061,12 @@ class AbstractResourceRequestActionHandler {
6053
6061
  // the luvio store redirect table, during which a new draft might be enqueued
6054
6062
  // which would not see a necessary mapping.
6055
6063
  this.ephemeralRedirects = {};
6064
+ // determined by Server setup.
6056
6065
  this.isIdempotencySupported = true;
6066
+ // idempotency write flag set by lds
6067
+ this.isLdsIdempotencyWriteDisabled = ldsIdempotencyWriteDisabled.isOpen({
6068
+ fallback: false,
6069
+ });
6057
6070
  }
6058
6071
  enqueue(data) {
6059
6072
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -6410,7 +6423,7 @@ class AbstractResourceRequestActionHandler {
6410
6423
  return [action.targetId];
6411
6424
  }
6412
6425
  hasIdempotencySupport() {
6413
- return this.isIdempotencySupported;
6426
+ return this.isIdempotencySupported && !this.isLdsIdempotencyWriteDisabled;
6414
6427
  }
6415
6428
  async ingestResponses(responses, action) {
6416
6429
  const luvio = this.getLuvio();
@@ -6941,6 +6954,20 @@ function buildQueryTypeStringKey(args) {
6941
6954
  return `${keyPrefix}::${schemaName}::${queryTypeName}[${serializeOperationNode(operationNode, variables, fragmentMap)}]`;
6942
6955
  }
6943
6956
 
6957
+ /**
6958
+ * @description Spec compliant way to retrieve the correct Operation from the Document that Luvio should operate on. https://spec.graphql.org/June2018/#sec-Named-Operation-Definitions
6959
+ * @param document
6960
+ * @param operationName
6961
+ * @returns The Operation in the GraphQL document we should use for the current call.
6962
+ */
6963
+ function getOperationFromDocument(document, operationName) {
6964
+ const operations = document.definitions.filter((def) => def.kind === 'OperationDefinition');
6965
+ if (operationName) {
6966
+ return operations.find((def) => def.name !== undefined && def.name.value === operationName);
6967
+ }
6968
+ return operations[0]; // If a named operation is not provided, we return the first one
6969
+ }
6970
+
6944
6971
  /**
6945
6972
  * Copyright (c) 2022, Salesforce, Inc.,
6946
6973
  * All rights reserved.
@@ -6948,14 +6975,20 @@ function buildQueryTypeStringKey(args) {
6948
6975
  */
6949
6976
 
6950
6977
 
6978
+ const MAX_BATCH_SIZE = 2000;
6951
6979
  class DataLoader {
6952
- constructor(batchLoadFn) {
6980
+ constructor(batchLoadFn, options) {
6953
6981
  this._batchLoadFn = batchLoadFn;
6954
6982
  this._batch = null;
6955
6983
  this._batchScheduleFn = function (fn) {
6956
6984
  setTimeout(fn, 0);
6957
6985
  };
6958
6986
  this._cacheMap = new Map();
6987
+ this._maxBatchSize = MAX_BATCH_SIZE;
6988
+ if (options !== undefined) {
6989
+ const { maxBatchSize } = options;
6990
+ this._maxBatchSize = maxBatchSize || MAX_BATCH_SIZE;
6991
+ }
6959
6992
  }
6960
6993
  load(key) {
6961
6994
  if (key === null || key === undefined) {
@@ -6985,7 +7018,9 @@ class DataLoader {
6985
7018
  // If there is an existing batch which has not yet dispatched and is within
6986
7019
  // the limit of the batch size, then return it.
6987
7020
  const existingBatch = this._batch;
6988
- if (existingBatch !== null && !existingBatch.hasDispatched && !existingBatch.cacheHits) {
7021
+ if (existingBatch !== null &&
7022
+ !existingBatch.hasDispatched &&
7023
+ existingBatch.keys.length < this._maxBatchSize) {
6989
7024
  return existingBatch;
6990
7025
  }
6991
7026
  // Otherwise, create a new batch for this loader.
@@ -9378,7 +9413,9 @@ function extendSchemaWithObjectInfos(cache, objectInfoMap) {
9378
9413
  ];
9379
9414
  // extend the schema and add resolvers
9380
9415
  const schema = addResolversToSchema(extendSchema(cache.getSchema(), extensions), polymorphicFieldTypeNames);
9416
+ const polymorphicFieldTypeNamesSet = new Set(polymorphicFieldTypeNames);
9381
9417
  cache.setSchema(schema);
9418
+ cache.setPolymorphicFieldTypeNames([...polymorphicFieldTypeNamesSet]);
9382
9419
  return cache;
9383
9420
  }
9384
9421
  /**
@@ -9534,8 +9571,11 @@ function extendExistingRecordType(schema, type, objectInfo, objectInfoMap) {
9534
9571
  let typedScalars = new Set();
9535
9572
  let parentRelationshipFields = new Set();
9536
9573
  const existingFields = keys$4(type.getFields());
9537
- const missingFields = values$2(objectInfo.fields).filter((field) => existingFields.includes(field.apiName) === false);
9538
- const { fields, polymorphicFieldTypeNames } = makeRecordField(missingFields, objectInfoMap, parentRelationshipFields, 'Cached');
9574
+ const missingFields = values$2(objectInfo.fields).filter((field) => {
9575
+ return (existingFields.includes(field.apiName) === false ||
9576
+ (field.relationshipName !== null && field.referenceToInfos.length > 0));
9577
+ });
9578
+ const { fields, polymorphicFieldTypeNames } = makeRecordField(missingFields, objectInfoMap, parentRelationshipFields, 'Cached', existingFields);
9539
9579
  const { apiName, childRelationships } = objectInfo;
9540
9580
  // handles child relationship
9541
9581
  const { spanningRecordConnections, typedScalars: spanningConnectionTypedScalars } = makeSpanningRecordConnections(schema, childRelationships, objectInfoMap, parentRelationshipFields, existingFields);
@@ -9610,7 +9650,7 @@ function makeSpanningRecordConnections(schema, childRelationships, objectInfoMap
9610
9650
  * @param recordTypeInSchema
9611
9651
  * @returns
9612
9652
  */
9613
- function makeRecordField(fieldRepresentations, objectInfoMap, existingParentRelationships, recordTypeInSchema) {
9653
+ function makeRecordField(fieldRepresentations, objectInfoMap, existingParentRelationships, recordTypeInSchema, existingFields = []) {
9614
9654
  const polymorphicFieldTypeNames = new Set();
9615
9655
  let fields = ``;
9616
9656
  for (const field of values$2(fieldRepresentations)) {
@@ -9627,15 +9667,18 @@ function makeRecordField(fieldRepresentations, objectInfoMap, existingParentRela
9627
9667
  // Only add the relationship if there is relevant objectinfos for it,
9628
9668
  // otherwise we'd be defining types we cannot satisfy and aren't referenced in
9629
9669
  // the query.
9630
- if (objectInfoMap[relation.apiName] !== undefined) {
9670
+ if (objectInfoMap[relation.apiName] !== undefined &&
9671
+ existingFields.includes(field.relationshipName) === false) {
9631
9672
  existingParentRelationships.add(field.relationshipName);
9632
9673
  fields += `${field.relationshipName}: ${relation.apiName}\n`;
9633
9674
  }
9634
9675
  // For polymorphic field, its type is 'Record' inteface. The concrete entity type name is saved for field resolving of next phase
9635
9676
  }
9636
9677
  else if (field.referenceToInfos.length > 1) {
9637
- existingParentRelationships.add(field.relationshipName);
9638
- fields += `${field.relationshipName}: Record\n`;
9678
+ if (recordTypeInSchema === 'Missing') {
9679
+ existingParentRelationships.add(field.relationshipName);
9680
+ fields += `${field.relationshipName}: Record\n`;
9681
+ }
9639
9682
  for (const relation of field.referenceToInfos) {
9640
9683
  if (objectInfoMap[relation.apiName] !== undefined) {
9641
9684
  polymorphicFieldTypeNames.add(relation.apiName);
@@ -9698,6 +9741,8 @@ async function evaluate(config, observers, settings, objectInfos, store, snapsho
9698
9741
  // this is only wrapped in a try to execute the event after the result was returned
9699
9742
  try {
9700
9743
  eventEmitter({ type: 'graphql-eval-start' });
9744
+ const operationNode = getOperationFromDocument(config.query);
9745
+ let topLevelQueries = [];
9701
9746
  // assume that 'config.query' has required injected fields.
9702
9747
  const modifiedAST = visit(config.query, {
9703
9748
  Field: {
@@ -9727,6 +9772,25 @@ async function evaluate(config, observers, settings, objectInfos, store, snapsho
9727
9772
  };
9728
9773
  },
9729
9774
  },
9775
+ Directive: {
9776
+ enter(node, _key, _parent, _path, ancester) {
9777
+ if (node.name.value === 'category' && node.arguments !== undefined) {
9778
+ for (let i = 0, len = node.arguments.length; i < len; i++) {
9779
+ const argument = node.arguments[i];
9780
+ if (isStringValueNode(argument.value) &&
9781
+ argument.value.value === 'recordQuery') {
9782
+ const parentNode = ancester[ancester.length - 1];
9783
+ if (isFieldNode(parentNode)) {
9784
+ topLevelQueries.push({
9785
+ argumentNodes: parentNode.arguments || [],
9786
+ recordName: parentNode.name.value,
9787
+ });
9788
+ }
9789
+ }
9790
+ }
9791
+ }
9792
+ },
9793
+ },
9730
9794
  });
9731
9795
  eventEmitter({ type: 'graphql-preconditions-met' });
9732
9796
  // create the resolver request context, runtime values and functions for
@@ -9745,7 +9809,18 @@ async function evaluate(config, observers, settings, objectInfos, store, snapsho
9745
9809
  rootValue: {},
9746
9810
  }));
9747
9811
  eventEmitter({ type: 'graphql-evaluated' });
9748
- return { result, seenRecordIds: [...contextValue.seenRecordIds] };
9812
+ // add record key to seen ids
9813
+ const seenRecordIds = [...contextValue.seenRecordIds].map((id) => `UiApi::RecordRepresentation:${id}`);
9814
+ // if we have all the data to build the top level query
9815
+ // add it to the seen ids
9816
+ if (operationNode !== undefined && topLevelQueries.length > 0) {
9817
+ topLevelQueries.forEach((query) => {
9818
+ const { recordName, argumentNodes } = query;
9819
+ const queryString = buildKeyStringForRecordQuery(operationNode, config.variables || {}, argumentNodes, recordName);
9820
+ seenRecordIds.push(queryString);
9821
+ });
9822
+ }
9823
+ return { result, seenRecordIds };
9749
9824
  }
9750
9825
  finally {
9751
9826
  eventEmitter({ type: 'graphql-eval-end' });
@@ -12084,34 +12159,42 @@ function applyReferenceLinksToDraft(record, draftMetadata) {
12084
12159
  }
12085
12160
  const { dataType, relationshipName, referenceToInfos } = fieldInfo;
12086
12161
  const draftFieldValue = record.fields[draftField].value;
12087
- if (dataType === 'Reference' && relationshipName !== null && draftFieldValue !== null) {
12088
- if (typeof draftFieldValue !== 'string') {
12089
- throw Error('reference field value is not a string');
12162
+ if (dataType === 'Reference' && relationshipName !== null) {
12163
+ if (draftFieldValue === null) {
12164
+ recordFields[relationshipName] = {
12165
+ displayValue: null,
12166
+ value: null,
12167
+ };
12090
12168
  }
12091
- const key = getRecordKeyForId(luvio, draftFieldValue);
12092
- const referencedRecord = referencedRecords.get(key);
12093
- recordFields[relationshipName] = {
12094
- displayValue: null,
12095
- value: createLink(key),
12096
- };
12097
- // for custom objects, we select the 'Name' field
12098
- // otherwise we check the object info for name fields.
12099
- //if there are multiple we select 'Name' if it exists, otherwise the first one
12100
- if (referencedRecord !== undefined && referenceToInfos.length > 0) {
12101
- let nameField;
12102
- const referenceToInfo = referenceToInfos[0];
12103
- const nameFields = referenceToInfo.nameFields;
12104
- if (nameFields.length !== 0) {
12105
- nameField = nameFields.find((x) => x === 'Name');
12106
- if (nameField === undefined) {
12107
- nameField = nameFields[0];
12108
- }
12169
+ else {
12170
+ if (typeof draftFieldValue !== 'string') {
12171
+ throw Error('reference field value is not a string');
12109
12172
  }
12110
- if (nameField !== undefined) {
12111
- const nameFieldRef = referencedRecord.fields[nameField];
12112
- if (nameFieldRef) {
12113
- recordFields[relationshipName].displayValue =
12114
- (_a = nameFieldRef.displayValue) !== null && _a !== void 0 ? _a : nameFieldRef.value;
12173
+ const key = getRecordKeyForId(luvio, draftFieldValue);
12174
+ const referencedRecord = referencedRecords.get(key);
12175
+ recordFields[relationshipName] = {
12176
+ displayValue: null,
12177
+ value: createLink(key),
12178
+ };
12179
+ // for custom objects, we select the 'Name' field
12180
+ // otherwise we check the object info for name fields.
12181
+ //if there are multiple we select 'Name' if it exists, otherwise the first one
12182
+ if (referencedRecord !== undefined && referenceToInfos.length > 0) {
12183
+ let nameField;
12184
+ const referenceToInfo = referenceToInfos[0];
12185
+ const nameFields = referenceToInfo.nameFields;
12186
+ if (nameFields.length !== 0) {
12187
+ nameField = nameFields.find((x) => x === 'Name');
12188
+ if (nameField === undefined) {
12189
+ nameField = nameFields[0];
12190
+ }
12191
+ }
12192
+ if (nameField !== undefined) {
12193
+ const nameFieldRef = referencedRecord.fields[nameField];
12194
+ if (nameFieldRef) {
12195
+ recordFields[relationshipName].displayValue =
12196
+ (_a = nameFieldRef.displayValue) !== null && _a !== void 0 ? _a : nameFieldRef.value;
12197
+ }
12115
12198
  }
12116
12199
  }
12117
12200
  }
@@ -12404,17 +12487,8 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
12404
12487
  };
12405
12488
  for (const fieldName of keys$3(recordWithSpanningRefLinks.fields)) {
12406
12489
  const fieldKey = buildRecordFieldStoreKey(key, fieldName);
12407
- if (this.collectedFields[fieldKey] !== undefined) {
12408
- const fieldData = recordWithSpanningRefLinks.fields[fieldName];
12409
- normalizedRecord.fields[fieldName] = { __ref: fieldKey };
12410
- publishData(fieldKey, fieldData);
12411
- }
12412
- else if (recordWithSpanningRefLinks.fields[fieldName] &&
12413
- recordWithSpanningRefLinks.fields[fieldName].value &&
12414
- recordWithSpanningRefLinks.fields[fieldName].value.__ref !== undefined) {
12415
- normalizedRecord.fields[fieldName] = { __ref: fieldKey };
12416
- publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
12417
- }
12490
+ normalizedRecord.fields[fieldName] = { __ref: fieldKey };
12491
+ publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
12418
12492
  }
12419
12493
  // publish the normalized record
12420
12494
  publishData(key, normalizedRecord);
@@ -13126,14 +13200,12 @@ function isLocalEvalSnapshot(snapshot) {
13126
13200
  return 'rebuildWithLocalEval' in snapshot;
13127
13201
  }
13128
13202
  function createSeenRecords(ids, currentSnapshot) {
13129
- let seenRecords = ids
13130
- .map((id) => `UiApi::RecordRepresentation:${id}`)
13131
- .reduce((acc, curr) => (acc.add(curr), acc), new StoreKeySet());
13203
+ let seenRecords = ids.reduce((acc, curr) => (acc.add(curr), acc), new StoreKeySet());
13132
13204
  if (currentSnapshot.state !== 'Error') {
13133
13205
  currentSnapshot.seenRecords.forEach((record) => {
13134
13206
  let keyString = typeof record !== 'string' ? serializeStructuredKey(record) : record;
13135
- if (keyString !== 'UiApi::GraphQLRepresentation__uiapi' &&
13136
- keyString !== 'UiApi::GraphQLRepresentation__uiapi__query' &&
13207
+ if (keyString !== 'UiApi::uiapi::Query[uiapi]__uiapi' &&
13208
+ keyString !== 'UiApi::uiapi::Query[uiapi]__uiapi__query' &&
13137
13209
  seenRecords.has(record) === false) {
13138
13210
  seenRecords.add(record);
13139
13211
  }
@@ -15375,6 +15447,14 @@ class ObjectInfoService {
15375
15447
  return this.updateObjectInfoMapping(keyPrefix, apiName);
15376
15448
  }
15377
15449
  };
15450
+ this.getCachedObjectInfoStatus = async () => {
15451
+ const infos = await this.readObjectInfoDataFromDurableStore();
15452
+ const map = new Map();
15453
+ infos.forEach(({ apiName, expirationTimestamp }) => {
15454
+ map.set(apiName, { expiration: expirationTimestamp });
15455
+ });
15456
+ return map;
15457
+ };
15378
15458
  this.isObjectInfoInDurableStore = async (apiName) => {
15379
15459
  if (this.apiNameToKeyPrefixMemoryCache[apiName] !== undefined) {
15380
15460
  return Promise.resolve(true);
@@ -15383,12 +15463,10 @@ class ObjectInfoService {
15383
15463
  return this.apiNameToKeyPrefixMemoryCache[apiName] !== undefined;
15384
15464
  };
15385
15465
  this.loadObjectInfoMaps = async () => {
15386
- const rows = (await this.durableStore.query(`SELECT json_extract(data, '$.apiName') as ApiName, json_extract(data, '$.keyPrefix') as keyPrefix from lds_data where key like '%ObjectInfoRepresentation%'`, [])).rows;
15387
- for (const row of rows) {
15388
- const apiName = row[0];
15389
- const keyPrefix = row[1];
15466
+ const infos = await this.readObjectInfoDataFromDurableStore();
15467
+ infos.forEach(({ keyPrefix, apiName }) => {
15390
15468
  this.updateObjectInfoMapping(keyPrefix, apiName);
15391
- }
15469
+ });
15392
15470
  };
15393
15471
  this.updateObjectInfoMapping = (keyPrefix, apiName) => {
15394
15472
  this.apiNameToKeyPrefixMemoryCache[apiName] = keyPrefix;
@@ -15432,6 +15510,24 @@ class ObjectInfoService {
15432
15510
  }
15433
15511
  return snapshot.data;
15434
15512
  }
15513
+ async readObjectInfoDataFromDurableStore() {
15514
+ const rows = (await this.durableStore.query(`
15515
+ SELECT
15516
+ json_extract(data, '$.apiName') as ApiName,
15517
+ json_extract(data, '$.keyPrefix') as keyPrefix,
15518
+ JSON_EXTRACT(metadata, '$.expirationTimestamp') AS expirationTimestamp
15519
+ from
15520
+ lds_data
15521
+ where
15522
+ key like '%ObjectInfoRepresentation%'`, [])).rows;
15523
+ return rows.map((row) => {
15524
+ return {
15525
+ apiName: row[0],
15526
+ keyPrefix: row[1],
15527
+ expirationTimestamp: row[2],
15528
+ };
15529
+ });
15530
+ }
15435
15531
  }
15436
15532
 
15437
15533
  function instrumentGraphQLEval(adapter) {
@@ -16530,7 +16626,7 @@ class ConflictPool {
16530
16626
  }
16531
16627
  }
16532
16628
 
16533
- const DEFAULT_BATCH_SIZE = 500;
16629
+ const DEFAULT_BATCH_SIZE$1 = 500;
16534
16630
  const DEFAULT_CONCURRENCY = 6;
16535
16631
  const DEFAULT_GQL_QUERY_BATCH_SIZE = 5;
16536
16632
  class PrimingSession extends EventEmitter {
@@ -16538,7 +16634,7 @@ class PrimingSession extends EventEmitter {
16538
16634
  var _a, _b;
16539
16635
  super();
16540
16636
  this.useBatchGQL = false;
16541
- this.batchSize = (_a = config.batchSize) !== null && _a !== void 0 ? _a : DEFAULT_BATCH_SIZE;
16637
+ this.batchSize = (_a = config.batchSize) !== null && _a !== void 0 ? _a : DEFAULT_BATCH_SIZE$1;
16542
16638
  this.concurrency = (_b = config.concurrency) !== null && _b !== void 0 ? _b : DEFAULT_CONCURRENCY;
16543
16639
  this.recordLoader = config.recordLoader;
16544
16640
  this.recordIngestor = config.recordIngestor;
@@ -17159,10 +17255,6 @@ class NimbusPrimingNetworkAdapter {
17159
17255
  // ref: https://gnome.pages.gitlab.gnome.org/tracker/docs/developer/limits.html?gi-language=c
17160
17256
  const SQLITE_MAX_VARIABLE_NUMBER = 999;
17161
17257
  const PARAMS_PER_RECORD = 3;
17162
- /**
17163
- * No key builder (or adapter) exists for the object info directory, we need to build the key manually
17164
- */
17165
- const ObjectInfoDirectoryKey = `${UiApiNamespace}::${ObjectInfoDirectoryEntryRepresentationType}:`;
17166
17258
  // We need to batch the records to avoid hitting the SQLITE_MAX_VARIABLE_NUMBER limit. Each record has 3 parameters
17167
17259
  const BATCH_SIZE = Math.floor(SQLITE_MAX_VARIABLE_NUMBER / PARAMS_PER_RECORD);
17168
17260
  class SqlitePrimingStore {
@@ -17227,44 +17319,6 @@ class SqlitePrimingStore {
17227
17319
  };
17228
17320
  }
17229
17321
  }
17230
- async readObjectInfoDirectory() {
17231
- const sql = 'SELECT data FROM lds_data WHERE key = ?';
17232
- const params = [ObjectInfoDirectoryKey];
17233
- const result = await this.store.query(sql, params);
17234
- if (result.rows.length === 1) {
17235
- return JSON.parse(result.rows[0][0]);
17236
- }
17237
- return undefined;
17238
- }
17239
- async readObjectApiNames() {
17240
- const sql = 'SELECT key FROM lds_data WHERE key like ?';
17241
- const params = [`%${ObjectInfoRepresentationType}%`];
17242
- const result = await this.store.query(sql, params);
17243
- const apiNames = new Set();
17244
- result.rows.forEach((row) => {
17245
- const key = row[0];
17246
- const parts = key.split(':');
17247
- apiNames.add(parts[parts.length - 1]);
17248
- });
17249
- return apiNames;
17250
- }
17251
- writeObjectInfoDirectory(directory) {
17252
- const sql = 'INSERT or IGNORE into lds_data (key, data) values (?, ?)';
17253
- const params = [ObjectInfoDirectoryKey, JSON.stringify(directory)];
17254
- return this.store.query(sql, params).then(() => { });
17255
- }
17256
- writeObjectInfos(objectInfos) {
17257
- const sql = `INSERT or IGNORE into lds_data (key, data) values ${objectInfos
17258
- .map(() => '(?, ?)')
17259
- .join(',')};`;
17260
- const params = [];
17261
- objectInfos.forEach((objectInfo) => {
17262
- const key = keyBuilderObjectInfo(this.getLuvio(), { apiName: objectInfo.apiName });
17263
- params.push(key);
17264
- params.push(JSON.stringify(objectInfo));
17265
- });
17266
- return this.store.query(sql, params).then(() => { });
17267
- }
17268
17322
  }
17269
17323
  function batchArray(arr, batchSize = BATCH_SIZE) {
17270
17324
  const batches = [];
@@ -17339,9 +17393,7 @@ function primingSessionFactory(config) {
17339
17393
  recordLoader,
17340
17394
  recordIngestor,
17341
17395
  store: primingStore,
17342
- objectInfoLoader: {
17343
- getObjectInfos: objectInfoService.getObjectInfos.bind(objectInfoService),
17344
- },
17396
+ objectInfoLoader: objectInfoService,
17345
17397
  concurrency: config.concurrency,
17346
17398
  batchSize: config.batchSize,
17347
17399
  ldsRecordRefresher: new LdsPrimingRecordRefresher(config.getRecords),
@@ -17507,4 +17559,4 @@ register({
17507
17559
  });
17508
17560
 
17509
17561
  export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
17510
- // version: 1.248.0-1f7f01112
17562
+ // version: 1.250.0-9df9bc3d1
@@ -1,6 +1,5 @@
1
1
  import type { PrimingStore, RecordWithMetadata, WriteResult } from '@salesforce/lds-priming';
2
2
  import type { Luvio } from '@luvio/engine';
3
- import type { ObjectInfoDirectoryRepresentation, ObjectInfoRepresentation } from '@salesforce/lds-adapters-uiapi';
4
3
  import type { SqliteStore } from '@salesforce/lds-store-sql';
5
4
  export declare class SqlitePrimingStore implements PrimingStore {
6
5
  private readonly getLuvio;
@@ -9,8 +8,4 @@ export declare class SqlitePrimingStore implements PrimingStore {
9
8
  readRecords(ids: string[]): Promise<RecordWithMetadata[]>;
10
9
  writeRecords(records: RecordWithMetadata[], overwrite: boolean): Promise<WriteResult>;
11
10
  private writeBatch;
12
- readObjectInfoDirectory(): Promise<ObjectInfoDirectoryRepresentation | undefined>;
13
- readObjectApiNames(): Promise<Set<string>>;
14
- writeObjectInfoDirectory(directory: ObjectInfoDirectoryRepresentation): Promise<void>;
15
- writeObjectInfos(objectInfos: ObjectInfoRepresentation[]): Promise<void>;
16
11
  }
@@ -10,6 +10,9 @@ type ObjectInfoDirectoryConfig = Parameters<ObjectInfoDirectoryAdapterReturn>[0]
10
10
  export type ObjectInfoMap = {
11
11
  [apiName: string]: ObjectInfoRepresentation;
12
12
  };
13
+ interface ObjectInfoStatus {
14
+ expiration: number;
15
+ }
13
16
  export declare class ObjectInfoService {
14
17
  private getObjectInfoAdapter;
15
18
  private getObjectInfosAdapter;
@@ -31,8 +34,10 @@ export declare class ObjectInfoService {
31
34
  getObjectInfos(apiNames: string[]): Promise<ObjectInfoMap>;
32
35
  getObjectInfoDirectory(): Promise<ObjectInfoDirectoryRepresentation | undefined>;
33
36
  ensureObjectInfoCached: (apiName: string, entry?: ObjectInfoRepresentation) => Promise<void>;
37
+ getCachedObjectInfoStatus: () => Promise<Map<string, ObjectInfoStatus>>;
34
38
  private isObjectInfoInDurableStore;
35
39
  private loadObjectInfoMaps;
40
+ private readObjectInfoDataFromDurableStore;
36
41
  private updateObjectInfoMapping;
37
42
  }
38
43
  export {};