@salesforce/lds-worker-api 1.228.1 → 1.229.0-dev10

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.
@@ -25,12 +25,15 @@ const { hasOwnProperty: hasOwnProperty$3 } = Object.prototype;
25
25
  const { isArray: isArray$9 } = Array;
26
26
  const { push: push$5, indexOf, slice: slice$2 } = Array.prototype;
27
27
  const { parse: parse$a, stringify: stringify$a } = JSON;
28
+ const WeakSetCtor = WeakSet;
28
29
 
30
+ const deeplyFrozen = new WeakSetCtor();
29
31
  function deepFreeze(value) {
30
- // No need to freeze primitives
31
- if (typeof value !== 'object' || value === null) {
32
+ // No need to freeze primitives or already frozen stuff
33
+ if (typeof value !== 'object' || value === null || deeplyFrozen.has(value)) {
32
34
  return;
33
35
  }
36
+ deeplyFrozen.add(value);
34
37
  if (isArray$9(value)) {
35
38
  for (let i = 0, len = value.length; i < len; i += 1) {
36
39
  deepFreeze(value[i]);
@@ -1727,6 +1730,10 @@ class InMemoryStore {
1727
1730
  }
1728
1731
  }
1729
1732
  markVisited(canonicalKey) {
1733
+ if (typeof canonicalKey === 'string') {
1734
+ this.fallbackStringKeyInMemoryStore.markVisited(canonicalKey);
1735
+ return;
1736
+ }
1730
1737
  const { visitedIdsSet, reverseRedirectKeysMap } = this;
1731
1738
  let redirectKey = canonicalKey;
1732
1739
  // mark all redirects leading up to the canonical key as visited so
@@ -2038,7 +2045,7 @@ class GraphLink {
2038
2045
  if (isStoreRecordError$1(linked)) {
2039
2046
  return new GraphNodeError(this.store, linked);
2040
2047
  }
2041
- return new GraphNode(this.store, linked);
2048
+ return new GraphNode(this.store, linked, __ref);
2042
2049
  }
2043
2050
  linkData() {
2044
2051
  return this.data.data;
@@ -2048,10 +2055,11 @@ class GraphLink {
2048
2055
  }
2049
2056
  }
2050
2057
  class GraphNode {
2051
- constructor(store, data) {
2058
+ constructor(store, data, storeKey) {
2052
2059
  this.type = GraphNodeType$1.Node;
2053
2060
  this.store = store;
2054
2061
  this.data = data;
2062
+ this.storeKey = storeKey;
2055
2063
  }
2056
2064
  object(propertyName) {
2057
2065
  const value = this.data[propertyName];
@@ -2061,7 +2069,8 @@ class GraphNode {
2061
2069
  if (typeof value !== 'object' || value === null) {
2062
2070
  throw new Error(`Cannot walk to path ${String(propertyName)}. "${String(propertyName)}" is a scalar: "${value}"`);
2063
2071
  }
2064
- return new GraphNode(this.store, value);
2072
+ // We're walking to an object property on the current store record, pass the storeKey down.
2073
+ return new GraphNode(this.store, value, this.storeKey);
2065
2074
  }
2066
2075
  link(propertyName) {
2067
2076
  const value = this.data[propertyName];
@@ -2091,6 +2100,8 @@ class GraphNode {
2091
2100
  }
2092
2101
  write(propertyName, value) {
2093
2102
  this.data[propertyName] = value;
2103
+ const canonicalKey = this.store.getCanonicalRecordId(this.storeKey);
2104
+ this.store.markVisited(canonicalKey);
2094
2105
  }
2095
2106
  isUndefined(propertyName) {
2096
2107
  return this.data[propertyName] === undefined;
@@ -2275,6 +2286,34 @@ var FragmentReadResultState$1;
2275
2286
  const FRAGMENT_READ_RESULT_MISSING = {
2276
2287
  state: FragmentReadResultState$1.Missing,
2277
2288
  };
2289
+ function resolveLink$1(reader, storeLink, version) {
2290
+ const { StoreLinkStateValues } = reader;
2291
+ const linkState = reader.getLinkState(storeLink);
2292
+ switch (linkState.state) {
2293
+ case StoreLinkStateValues.RefNotPresent:
2294
+ case StoreLinkStateValues.NotPresent:
2295
+ case StoreLinkStateValues.Missing:
2296
+ reader.markMissingLink(storeLink.__ref);
2297
+ reader.markMissing();
2298
+ return;
2299
+ case StoreLinkStateValues.Pending:
2300
+ reader.markPending();
2301
+ return;
2302
+ case StoreLinkStateValues.Null:
2303
+ return;
2304
+ }
2305
+ const { key: __ref } = linkState;
2306
+ return reader.read({
2307
+ recordId: __ref,
2308
+ node: {
2309
+ kind: 'Fragment',
2310
+ private: [],
2311
+ opaque: true,
2312
+ version,
2313
+ },
2314
+ variables: {},
2315
+ });
2316
+ }
2278
2317
  class Reader {
2279
2318
  constructor(store, variables, refresh, baseSnapshot, ttlStrategy) {
2280
2319
  this.store = store;
@@ -3225,9 +3264,9 @@ class Environment {
3225
3264
  if (value === undefined) {
3226
3265
  return null;
3227
3266
  }
3228
- return this.wrapNormalizedGraphNode(value, store);
3267
+ return this.wrapNormalizedGraphNode(value, key, store);
3229
3268
  }
3230
- wrapNormalizedGraphNode(normalized, storeOverride) {
3269
+ wrapNormalizedGraphNode(normalized, key, storeOverride) {
3231
3270
  if (normalized === null) {
3232
3271
  return null;
3233
3272
  }
@@ -3235,7 +3274,7 @@ class Environment {
3235
3274
  if (isStoreRecordError$1(normalized)) {
3236
3275
  return new GraphNodeError(store, normalized);
3237
3276
  }
3238
- return new GraphNode(store, normalized);
3277
+ return new GraphNode(store, normalized, key);
3239
3278
  }
3240
3279
  withContext(adapter, options) {
3241
3280
  const { contextId, onContextLoaded } = options;
@@ -3530,8 +3569,8 @@ class Luvio {
3530
3569
  getNode(key) {
3531
3570
  return this.environment.getNode(key);
3532
3571
  }
3533
- wrapNormalizedGraphNode(normalized) {
3534
- return this.environment.wrapNormalizedGraphNode(normalized);
3572
+ wrapNormalizedGraphNode(normalized, key) {
3573
+ return this.environment.wrapNormalizedGraphNode(normalized, key);
3535
3574
  }
3536
3575
  instrument(paramsBuilder) {
3537
3576
  const { instrument } = this.options;
@@ -3841,7 +3880,7 @@ function createResourceParamsImpl(config, configMetadata) {
3841
3880
  }
3842
3881
  return resourceParams;
3843
3882
  }
3844
- // engine version: 0.145.2-6a13677c
3883
+ // engine version: 0.146.0-dev5-a2ec6e3f
3845
3884
 
3846
3885
  /**
3847
3886
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -3968,7 +4007,7 @@ function withDefaultLuvio(callback) {
3968
4007
  }
3969
4008
  callbacks.push(callback);
3970
4009
  }
3971
- // version: 1.228.1-4e6356f71
4010
+ // version: 1.229.0-dev10-bc9ef2513
3972
4011
 
3973
4012
  // TODO [TD-0081508]: once that TD is fulfilled we can probably change this file
3974
4013
  function instrumentAdapter$1(createFunction, _metadata) {
@@ -15429,7 +15468,7 @@ function gql(literals, ...subs) {
15429
15468
  }
15430
15469
  return superResult;
15431
15470
  }
15432
- // version: 1.228.1-4e6356f71
15471
+ // version: 1.229.0-dev10-bc9ef2513
15433
15472
 
15434
15473
  function unwrap(data) {
15435
15474
  // The lwc-luvio bindings import a function from lwc called "unwrap".
@@ -15530,14 +15569,15 @@ function refreshData(data, dataToTuple, luvio) {
15530
15569
  return undefined;
15531
15570
  });
15532
15571
  }
15533
- const { isArray: isArray$8 } = Array;
15534
- const { stringify: stringify$9 } = JSON;
15535
15572
 
15536
15573
  function isPromise$1(value) {
15537
15574
  // check for Thenable due to test frameworks using custom Promise impls
15538
15575
  return value.then !== undefined;
15539
15576
  }
15540
15577
 
15578
+ const { isArray: isArray$8 } = Array;
15579
+ const { stringify: stringify$9 } = JSON;
15580
+
15541
15581
  /**
15542
15582
  * (Re)throws an error after adding a prefix to the message.
15543
15583
  *
@@ -16352,7 +16392,7 @@ function createGraphQLWireAdapterConstructor(luvio, adapter, metadata, astResolv
16352
16392
  const { apiFamily, name } = metadata;
16353
16393
  return createGraphQLWireAdapterConstructor$1(adapter, `${apiFamily}.${name}`, luvio, astResolver);
16354
16394
  }
16355
- // version: 1.228.1-4e6356f71
16395
+ // version: 1.229.0-dev10-bc9ef2513
16356
16396
 
16357
16397
  /**
16358
16398
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -16451,11 +16491,11 @@ var TypeCheckShapes;
16451
16491
  TypeCheckShapes[TypeCheckShapes["Integer"] = 3] = "Integer";
16452
16492
  TypeCheckShapes[TypeCheckShapes["Unsupported"] = 4] = "Unsupported";
16453
16493
  })(TypeCheckShapes || (TypeCheckShapes = {}));
16454
- // engine version: 0.145.2-6a13677c
16494
+ // engine version: 0.146.0-dev5-a2ec6e3f
16455
16495
 
16456
16496
  const { keys: ObjectKeys$3, create: ObjectCreate$3 } = Object;
16457
16497
 
16458
- const { assign: assign$9, create: create$9, freeze: freeze$4, keys: keys$b } = Object;
16498
+ const { assign: assign$9, create: create$9, freeze: freeze$4, isFrozen: isFrozen$2, keys: keys$b } = Object;
16459
16499
 
16460
16500
  ObjectCreate$3(null);
16461
16501
 
@@ -16825,7 +16865,7 @@ function buildAdapterValidationConfig$1(displayName, paramsMeta) {
16825
16865
  }
16826
16866
  const keyPrefix$1 = 'UiApi';
16827
16867
 
16828
- const { assign: assign$8, create: create$8, freeze: freeze$3, keys: keys$a } = Object;
16868
+ const { assign: assign$8, create: create$8, freeze: freeze$3, isFrozen: isFrozen$1, keys: keys$a } = Object;
16829
16869
  const { hasOwnProperty: hasOwnProperty$1 } = Object.prototype;
16830
16870
  const { split, endsWith } = String.prototype;
16831
16871
  const { isArray: isArray$7 } = Array;
@@ -19794,7 +19834,7 @@ function extractTrackedFieldsToTrie(recordId, node, root, config, visitedRecordI
19794
19834
  extractTrackedFieldsToTrie(spanningLink.data.__ref, spanning, next, config, spanningVisitedRecordIds, depth + 1);
19795
19835
  // For a spanning record that is detected to be a circular reference, we add the field along with Id and Name.
19796
19836
  // It's possible for spanning record lookup fields to sometimes be circular, and sometimes not - depending on the value of the lookup field.
19797
- // For more information on scenarios that caused this fix: https://salesforce.quip.com/OvzNAh3eNIWY
19837
+ // For more information on scenarios that caused this fix: search "LDS Recursive Spanning Fields Problem" in Quip
19798
19838
  if (keys$a(next.children).length === 0) {
19799
19839
  addScalarFieldId(next);
19800
19840
  addScalarFieldName(next);
@@ -19947,7 +19987,11 @@ function markNulledOutPath(record, path) {
19947
19987
  }
19948
19988
  const link = fieldValueRepresentation.link(fieldName);
19949
19989
  const resolved = link.follow();
19950
- if (isGraphNode(resolved) && resolved.isScalar('value') && path.length > 0) {
19990
+ if (isGraphNode(resolved) &&
19991
+ resolved.isScalar('value') &&
19992
+ path.length > 0 &&
19993
+ // TODO [W-14082782]: temporary fix
19994
+ !isFrozen$1(link.data)) {
19951
19995
  const linkState = link.linkData();
19952
19996
  const fields = linkState === undefined ? [] : linkState.fields;
19953
19997
  link.writeLinkData({
@@ -19975,22 +20019,12 @@ function _markMissingPath(record, path) {
19975
20019
  const fieldValueRepresentation = record.object('fields');
19976
20020
  const fieldName = path.shift();
19977
20021
  if (fieldValueRepresentation.isUndefined(fieldName) === true) {
19978
- // TODO [W-6900046]: remove cast, make RecordRepresentationNormalized['fields'] accept
19979
- // an undefined/non-present __ref if isMissing is present
19980
- fieldValueRepresentation.write(fieldName, {
19981
- __ref: undefined,
19982
- isMissing: true,
19983
- });
20022
+ writeMissingFieldToStore(fieldValueRepresentation, fieldName);
19984
20023
  return;
19985
20024
  }
19986
20025
  const link = fieldValueRepresentation.link(fieldName);
19987
20026
  if (link.isPending()) {
19988
- // TODO [W-6900046]: remove cast, make RecordRepresentationNormalized['fields'] accept
19989
- // an undefined/non-present __ref if isMissing is present
19990
- fieldValueRepresentation.write(fieldName, {
19991
- __ref: undefined,
19992
- isMissing: true,
19993
- });
20027
+ writeMissingFieldToStore(fieldValueRepresentation, fieldName);
19994
20028
  }
19995
20029
  else if (path.length > 0 && link.isMissing() === false) {
19996
20030
  const fieldValue = link.follow();
@@ -20006,6 +20040,19 @@ function _markMissingPath(record, path) {
20006
20040
  }
20007
20041
  }
20008
20042
  }
20043
+ /**
20044
+ * Graph Node Directly modifies store entries, which is generally a non-starter.
20045
+ * Until we can refactor this mess, you need to use this function to safely mark the RecordRepresentation
20046
+ * as a seenId in the store when you perform this mutation.
20047
+ */
20048
+ function writeMissingFieldToStore(field, fieldName) {
20049
+ // TODO [W-6900046]: remove cast, make RecordRepresentationNormalized['fields'] accept
20050
+ // an undefined/non-present __ref if isMissing is present
20051
+ field.write(fieldName, {
20052
+ __ref: undefined,
20053
+ isMissing: true,
20054
+ });
20055
+ }
20009
20056
  /**
20010
20057
  * Tells you if an objectApiName is supported by UI API or not.
20011
20058
  * Note: Luvio does not currently support all the entities, the list is limited to UI API supported entities
@@ -20137,8 +20184,11 @@ function mergeAndRefreshLowerVersionRecord(luvio, incoming, existing, incomingTr
20137
20184
  return existing;
20138
20185
  }
20139
20186
  function mergeRecordConflict(luvio, incoming, existing, recordConflictMap) {
20140
- const incomingNode = luvio.wrapNormalizedGraphNode(incoming);
20141
- const existingNode = luvio.wrapNormalizedGraphNode(existing);
20187
+ const recordKey = keyBuilder$1U(luvio, {
20188
+ recordId: incoming.id,
20189
+ });
20190
+ const incomingNode = luvio.wrapNormalizedGraphNode(incoming, recordKey);
20191
+ const existingNode = luvio.wrapNormalizedGraphNode(existing, recordKey);
20142
20192
  const incomingTrackedFieldsTrieRoot = {
20143
20193
  name: incoming.apiName,
20144
20194
  children: {},
@@ -20147,9 +20197,6 @@ function mergeRecordConflict(luvio, incoming, existing, recordConflictMap) {
20147
20197
  name: existing.apiName,
20148
20198
  children: {},
20149
20199
  };
20150
- const recordKey = keyBuilder$1U(luvio, {
20151
- recordId: incoming.id,
20152
- });
20153
20200
  const trackedFieldsConfig = {
20154
20201
  maxDepth: configurationForRestAdapters$1.getTrackedFieldDepthOnCacheMergeConflict(),
20155
20202
  onlyFetchLeafNodeIdAndName: configurationForRestAdapters$1.getTrackedFieldLeafNodeIdAndNameOnly(),
@@ -20455,7 +20502,7 @@ function fulfill(existing, incoming) {
20455
20502
  const batchRequestWithSingleRequest = isSingleBatchRecordRequest(existingUrlParams) &&
20456
20503
  isSingleRecordRequest(urlParams) &&
20457
20504
  incomingUrlRecords[0] === existingUrlRecords[0];
20458
- if (!batchRequestWithSingleRequest) {
20505
+ if (!batchRequestWithSingleRequest || isRestrictedPathCondition(existingPath, path)) {
20459
20506
  return false;
20460
20507
  }
20461
20508
  }
@@ -20498,6 +20545,12 @@ function isSingleBatchRecordRequest(urlParams) {
20498
20545
  function isSingleRecordRequest(urlParams) {
20499
20546
  return hasOwnProperty$1.call(urlParams, 'recordId');
20500
20547
  }
20548
+ function isRestrictedPathCondition(existingPath, path) {
20549
+ // should not dedupe getRecordUi and getRecord as both of their representation is different
20550
+ // records call cannot digest response of getRecordUi
20551
+ return ((existingPath.includes('/record-ui') && path.includes('/records')) ||
20552
+ (existingPath.includes('/records') && path.includes('/record-ui')));
20553
+ }
20501
20554
 
20502
20555
  const createResourceRequest$12 = function getUiApiRecordsByRecordIdCreateResourceRequest(config) {
20503
20556
  return {
@@ -24854,7 +24907,7 @@ function getLayoutMapAndObjectInfo(recordId, data) {
24854
24907
  // Temp fix until we can mimic the server behavior for non-layoutable entities.
24855
24908
  let layoutMap = {};
24856
24909
  if (hasOwnProperty$1.call(layouts, apiName)) {
24857
- layoutMap = layouts[apiName][recordTypeId];
24910
+ layoutMap = layouts[apiName][recordTypeId] || {};
24858
24911
  }
24859
24912
  return {
24860
24913
  layoutMap,
@@ -25032,18 +25085,28 @@ const buildLayoutModeCacheSnapshot = (apiName, recordTypeId, layoutType, mode) =
25032
25085
  * These are intermediate lookups to check if the record is in the L2 cache
25033
25086
  * @param {Luvio} luvio
25034
25087
  * @param {GetRecordLayoutTypeConfig} config
25035
- * @param {BuildCachedSnapshot<BuildSnapshotContext} cachedSnapshot
25088
+ * @param {BuildCachedSnapshot<BuildSnapshotContext>} cachedSnapshot
25036
25089
  */
25037
25090
  function makeCacheOnlySnapshot(luvio, config, adapterContext, cachedSnapshot) {
25038
- return luvio.applyCachePolicy({
25039
- cachePolicy: {
25040
- // only looking in the cache so we can check for L2 data offline
25041
- type: 'only-if-cached',
25042
- },
25043
- }, { config, luvio, adapterContext }, cachedSnapshot,
25044
- // this won't be invoked since we're requesting only-if-cached
25091
+ return luvio.applyCachePolicy(
25092
+ // Pass empty context so environment will use its default cache-policy
25093
+ {}, { config, luvio, adapterContext }, cachedSnapshot,
25094
+ // disallow hitting the network by returning a gateway timeout
25045
25095
  () => {
25046
- throw Error('buildNetworkSnapshot should not be called for only-if-cached policy');
25096
+ return new Promise((resolve) => {
25097
+ resolve({
25098
+ state: 'Error',
25099
+ data: undefined,
25100
+ error: {
25101
+ body: undefined,
25102
+ headers: {},
25103
+ ok: false,
25104
+ status: 504,
25105
+ statusText: 'Gateway Timeout',
25106
+ errorType: 'fetchResponse',
25107
+ },
25108
+ });
25109
+ });
25047
25110
  });
25048
25111
  }
25049
25112
  /**
@@ -25254,7 +25317,7 @@ const notifyChangeFactory = (luvio) => {
25254
25317
  const responsePromises = [];
25255
25318
  for (let i = 0, len = entries.length; i < len; i++) {
25256
25319
  const { key, record } = entries[i];
25257
- const node = luvio.wrapNormalizedGraphNode(record);
25320
+ const node = luvio.wrapNormalizedGraphNode(record, key);
25258
25321
  const optionalFields = getTrackedFields(key, node, {
25259
25322
  maxDepth: configurationForRestAdapters$1.getTrackedFieldDepthOnNotifyChange(),
25260
25323
  onlyFetchLeafNodeIdAndName: configurationForRestAdapters$1.getTrackedFieldLeafNodeIdAndNameOnly(),
@@ -41152,7 +41215,16 @@ withDefaultLuvio((luvio) => {
41152
41215
  throttle(60, 60000, createLDSAdapter(luvio, 'notifyListInfoUpdateAvailable', notifyUpdateAvailableFactory$1));
41153
41216
  throttle(60, 60000, createLDSAdapter(luvio, 'notifyQuickActionDefaultsUpdateAvailable', notifyUpdateAvailableFactory));
41154
41217
  });
41155
- // version: 1.228.1-0cb6f94f1
41218
+ // version: 1.229.0-dev10-abb060196
41219
+
41220
+ var ldsIdempotencyWriteDisabled = {
41221
+ isOpen: function (e) {
41222
+ return e.fallback;
41223
+ },
41224
+ hasError: function () {
41225
+ return !0;
41226
+ },
41227
+ };
41156
41228
 
41157
41229
  var caseSensitiveUserId = '005B0000000GR4OIAW';
41158
41230
 
@@ -41783,6 +41855,9 @@ function handleDurableStoreRejection(instrument) {
41783
41855
  }
41784
41856
 
41785
41857
  function isStoreEntryError(storeRecord) {
41858
+ if (!storeRecord || typeof storeRecord !== 'object') {
41859
+ return false;
41860
+ }
41786
41861
  return storeRecord.__type === 'error';
41787
41862
  }
41788
41863
 
@@ -42367,12 +42442,12 @@ function makeDurable(environment, { durableStore, instrumentation }) {
42367
42442
  }
42368
42443
  return environment.getNode(key, ingestStagingStore);
42369
42444
  };
42370
- const wrapNormalizedGraphNode = function (normalized) {
42445
+ const wrapNormalizedGraphNode = function (normalized, key) {
42371
42446
  validateNotDisposed();
42372
42447
  if (ingestStagingStore === null) {
42373
42448
  ingestStagingStore = buildIngestStagingStore(environment);
42374
42449
  }
42375
- return environment.wrapNormalizedGraphNode(normalized, ingestStagingStore);
42450
+ return environment.wrapNormalizedGraphNode(normalized, key, ingestStagingStore);
42376
42451
  };
42377
42452
  const rebuildSnapshot = function (snapshot, onRebuild) {
42378
42453
  validateNotDisposed();
@@ -42686,6 +42761,72 @@ function makeDurable(environment, { durableStore, instrumentation }) {
42686
42761
  });
42687
42762
  }
42688
42763
 
42764
+ /**
42765
+ * Copyright (c) 2022, Salesforce, Inc.,
42766
+ * All rights reserved.
42767
+ * For full license text, see the LICENSE.txt file
42768
+ */
42769
+
42770
+ const API_NAMESPACE$1 = 'UiApi';
42771
+ const RECORD_REPRESENTATION_NAME$2 = 'RecordRepresentation';
42772
+ const RECORD_VIEW_ENTITY_REPRESENTATION_NAME$1 = 'RecordViewEntityRepresentation';
42773
+ const RECORD_ID_PREFIX$1 = `${API_NAMESPACE$1}::${RECORD_REPRESENTATION_NAME$2}:`;
42774
+ const RECORD_VIEW_ENTITY_ID_PREFIX$1 = `${API_NAMESPACE$1}::${RECORD_VIEW_ENTITY_REPRESENTATION_NAME$1}:Name:`;
42775
+ const RECORD_FIELDS_KEY_JUNCTION$1 = '__fields__';
42776
+ function isStoreKeyRecordId(key) {
42777
+ return key.indexOf(RECORD_ID_PREFIX$1) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) === -1;
42778
+ }
42779
+ function isStoreKeyRecordViewEntity$1(key) {
42780
+ return (key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX$1) > -1 &&
42781
+ key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) === -1);
42782
+ }
42783
+ function isStoreKeyRecordField(key) {
42784
+ return key.indexOf(RECORD_ID_PREFIX$1) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) > -1;
42785
+ }
42786
+ function extractRecordIdFromStoreKey$1(key) {
42787
+ if (key === undefined ||
42788
+ (key.indexOf(RECORD_ID_PREFIX$1) === -1 && key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX$1) === -1)) {
42789
+ return undefined;
42790
+ }
42791
+ const parts = key.split(':');
42792
+ return parts[parts.length - 1].split('_')[0];
42793
+ }
42794
+ function buildRecordFieldStoreKey(recordKey, fieldName) {
42795
+ return `${recordKey}${RECORD_FIELDS_KEY_JUNCTION$1}${fieldName}`;
42796
+ }
42797
+ function objectsDeepEqual(lhs, rhs) {
42798
+ if (lhs === rhs)
42799
+ return true;
42800
+ if (typeof lhs !== 'object' || typeof rhs !== 'object' || lhs === null || rhs === null)
42801
+ return false;
42802
+ const lhsKeys = Object.keys(lhs);
42803
+ const rhsKeys = Object.keys(rhs);
42804
+ if (lhsKeys.length !== rhsKeys.length)
42805
+ return false;
42806
+ for (let key of lhsKeys) {
42807
+ if (!rhsKeys.includes(key))
42808
+ return false;
42809
+ if (typeof lhs[key] === 'function' || typeof rhs[key] === 'function') {
42810
+ if (lhs[key].toString() !== rhs[key].toString())
42811
+ return false;
42812
+ }
42813
+ else {
42814
+ if (!objectsDeepEqual(lhs[key], rhs[key]))
42815
+ return false;
42816
+ }
42817
+ }
42818
+ return true;
42819
+ }
42820
+
42821
+ function isStoreRecordError(storeRecord) {
42822
+ return storeRecord.__type === 'error';
42823
+ }
42824
+ function isEntryDurableRecordRepresentation(entry, key) {
42825
+ // Either a DurableRecordRepresentation or StoreRecordError can live at a record key
42826
+ return ((isStoreKeyRecordId(key) || isStoreKeyRecordViewEntity$1(key)) &&
42827
+ entry.data.__type === undefined);
42828
+ }
42829
+
42689
42830
  /**
42690
42831
  * Copyright (c) 2022, Salesforce, Inc.,
42691
42832
  * All rights reserved.
@@ -43945,6 +44086,7 @@ function isScalarDataType(type) {
43945
44086
  'Email',
43946
44087
  'TextArea',
43947
44088
  'Percent',
44089
+ 'EncryptedString',
43948
44090
  ].includes(type);
43949
44091
  }
43950
44092
 
@@ -45425,8 +45567,12 @@ function rootRecordQuery(selection, input) {
45425
45567
  // If there is no metadata for this query or it somehow lacks a timestamp
45426
45568
  // skip setting the root timestamp
45427
45569
  if (queryMetadata !== undefined && queryMetadata.ingestionTimestamp !== undefined) {
45428
- // subtract 10ms from timestamp to account for ingestion processing time
45429
- input.rootTimestamp = queryMetadata.ingestionTimestamp - 10;
45570
+ const timestamp = Number(queryMetadata.ingestionTimestamp);
45571
+ if (!isNaN(timestamp)) {
45572
+ // adjust the timestamp to account for ingestion processing time
45573
+ // 30s is used because this is the default record TTL
45574
+ input.rootTimestamp = timestamp - 30000;
45575
+ }
45430
45576
  }
45431
45577
  }
45432
45578
  return recordQuery(selection, alias, apiName, [], input);
@@ -45884,7 +46030,11 @@ function makeStoreEval(preconditioner, objectInfoService, userId, contextProvide
45884
46030
  try {
45885
46031
  const { data, seenRecords } = await queryEvaluator(rootQuery, context, eventEmitter);
45886
46032
  const rebuildWithStoreEval = ((originalSnapshot) => {
45887
- return storeEval(config, originalSnapshot, observers, connectionKeyBuilder);
46033
+ return storeEval(config, originalSnapshot, observers, connectionKeyBuilder).then((rebuiltSnapshot) => {
46034
+ return objectsDeepEqual(originalSnapshot.data, rebuiltSnapshot.data)
46035
+ ? originalSnapshot
46036
+ : rebuiltSnapshot;
46037
+ });
45888
46038
  });
45889
46039
  const recordId = generateUniqueRecordId$1();
45890
46040
  // if the non-eval'ed snapshot was an error then we return a synthetic
@@ -46235,8 +46385,12 @@ function uuidv4() {
46235
46385
 
46236
46386
  const HTTP_HEADER_RETRY_AFTER = 'Retry-After';
46237
46387
  const HTTP_HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';
46388
+ const ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED = 'IDEMPOTENCY_FEATURE_NOT_ENABLED';
46389
+ const ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED = 'IDEMPOTENCY_NOT_SUPPORTED';
46238
46390
  const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = 'IDEMPOTENCY_KEY_USED_DIFFERENT_USER';
46239
46391
  const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = 'IDEMPOTENCY_CONCURRENT_REQUEST';
46392
+ const ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED = 'IDEMPOTENCY_KEY_ALREADY_USED';
46393
+ const ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR = 'IDEMPOTENCY_BACKEND_OPERATION_ERROR';
46240
46394
  /**
46241
46395
  * Get the retry after in milliseconds from the response headers, undefined if not specified.
46242
46396
  * The header could have two different format.
@@ -46266,7 +46420,9 @@ function buildLuvioOverrideForDraftAdapters(luvio, handler, extractTargetIdFromC
46266
46420
  const dispatchResourceRequest = async function (resourceRequest, _context) {
46267
46421
  const resourceRequestCopy = clone$1(resourceRequest);
46268
46422
  resourceRequestCopy.headers = resourceRequestCopy.headers || {};
46269
- resourceRequestCopy.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
46423
+ if (handler.hasIdempotencySupport()) {
46424
+ resourceRequestCopy.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
46425
+ }
46270
46426
  // enable return extra fields for record creation and record update http call
46271
46427
  if (resourceRequest.basePath === '/ui-api/records' &&
46272
46428
  (resourceRequest.method === 'post' || resourceRequest.method === 'patch')) {
@@ -47088,6 +47244,12 @@ class AbstractResourceRequestActionHandler {
47088
47244
  // the luvio store redirect table, during which a new draft might be enqueued
47089
47245
  // which would not see a necessary mapping.
47090
47246
  this.ephemeralRedirects = {};
47247
+ // determined by Server setup.
47248
+ this.isIdempotencySupported = true;
47249
+ // idempotency write flag set by lds
47250
+ this.isLdsIdempotencyWriteDisabled = ldsIdempotencyWriteDisabled.isOpen({
47251
+ fallback: false,
47252
+ });
47091
47253
  }
47092
47254
  enqueue(data) {
47093
47255
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -47117,21 +47279,43 @@ class AbstractResourceRequestActionHandler {
47117
47279
  retryDelayInMs = getRetryAfterInMs(response.headers);
47118
47280
  shouldRetry = true;
47119
47281
  break;
47120
- case HttpStatusCode$1.ServerError:
47282
+ case HttpStatusCode$1.ServerError: {
47121
47283
  shouldRetry = true;
47284
+ if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR)) {
47285
+ this.isIdempotencySupported = false;
47286
+ retryDelayInMs = 0;
47287
+ actionDataChanged = true;
47288
+ }
47122
47289
  break;
47290
+ }
47123
47291
  case 409 /* IdempotentWriteSpecificHttpStatusCode.Conflict */: {
47124
- const errorCode = response.body[0].errorCode;
47125
- if (errorCode === ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER) {
47126
- updatedAction.data.headers = updatedAction.data.headers || {};
47127
- updatedAction.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
47292
+ if (this.isUiApiErrors(response.body)) {
47293
+ const errorCode = response.body[0].errorCode;
47294
+ if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER)) {
47295
+ retryDelayInMs = 0;
47296
+ actionDataChanged = true;
47297
+ }
47298
+ else if (errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST) {
47299
+ retryDelayInMs = getRetryAfterInMs(response.headers);
47300
+ }
47301
+ shouldRetry = true;
47302
+ }
47303
+ break;
47304
+ }
47305
+ case HttpStatusCode$1.BadRequest: {
47306
+ if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED, ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED)) {
47128
47307
  retryDelayInMs = 0;
47129
47308
  actionDataChanged = true;
47309
+ shouldRetry = true;
47130
47310
  }
47131
- else if (errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST) {
47132
- retryDelayInMs = getRetryAfterInMs(response.headers);
47311
+ break;
47312
+ }
47313
+ case 422 /* IdempotentWriteSpecificHttpStatusCode.UnProcessableEntity */: {
47314
+ if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED)) {
47315
+ retryDelayInMs = 0;
47316
+ actionDataChanged = true;
47317
+ shouldRetry = true;
47133
47318
  }
47134
- shouldRetry = true;
47135
47319
  break;
47136
47320
  }
47137
47321
  }
@@ -47150,6 +47334,27 @@ class AbstractResourceRequestActionHandler {
47150
47334
  return ProcessActionResult.NETWORK_ERROR;
47151
47335
  }
47152
47336
  }
47337
+ // true if response is an idempotency server error. updates or deletes idempotency key if the reponse is idempotency related error. Idempotency related error is in format of UiApiError array.
47338
+ handleIdempotencyServerError(responseBody, action, updateIdempotencyKey, ...targetErrorCodes) {
47339
+ if (this.isUiApiErrors(responseBody)) {
47340
+ const errorCode = responseBody[0].errorCode;
47341
+ if (targetErrorCodes.includes(errorCode)) {
47342
+ action.data.headers = action.data.headers || {};
47343
+ if (updateIdempotencyKey) {
47344
+ action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
47345
+ }
47346
+ else {
47347
+ delete action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY];
47348
+ }
47349
+ return true;
47350
+ }
47351
+ }
47352
+ return false;
47353
+ }
47354
+ // checks if the body is an array of UiApiError. Sometimes the body has `enhancedErrorType` field as an error indicator(one example is the field validation failure). In such case Action being processed updates to an Error Action.
47355
+ isUiApiErrors(body) {
47356
+ return body !== undefined && Array.isArray(body) && body.length > 0 && body[0].errorCode;
47357
+ }
47153
47358
  async buildPendingAction(request, queue) {
47154
47359
  const targetId = await this.getIdFromRequest(request);
47155
47360
  if (targetId === undefined) {
@@ -47363,6 +47568,10 @@ class AbstractResourceRequestActionHandler {
47363
47568
  ...targetData,
47364
47569
  body: this.mergeRequestBody(targetBody, sourceBody),
47365
47570
  };
47571
+ // Updates Idempotency key if target has one
47572
+ if (targetData.headers && targetData.headers[HTTP_HEADER_IDEMPOTENCY_KEY]) {
47573
+ merged.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
47574
+ }
47366
47575
  // overlay metadata
47367
47576
  merged.metadata = { ...targetMetadata, ...sourceMetadata };
47368
47577
  // put status back to pending to auto upload if queue is active and targed is at the head.
@@ -47399,6 +47608,9 @@ class AbstractResourceRequestActionHandler {
47399
47608
  getDraftIdsFromAction(action) {
47400
47609
  return [action.targetId];
47401
47610
  }
47611
+ hasIdempotencySupport() {
47612
+ return this.isIdempotencySupported && !this.isLdsIdempotencyWriteDisabled;
47613
+ }
47402
47614
  async ingestResponses(responses, action) {
47403
47615
  const luvio = this.getLuvio();
47404
47616
  await luvio.handleSuccessResponse(() => {
@@ -47830,49 +48042,6 @@ function makeEnvironmentDraftAware(luvio, env, durableStore, handlers, draftQueu
47830
48042
  });
47831
48043
  }
47832
48044
 
47833
- /**
47834
- * Copyright (c) 2022, Salesforce, Inc.,
47835
- * All rights reserved.
47836
- * For full license text, see the LICENSE.txt file
47837
- */
47838
-
47839
- const API_NAMESPACE$1 = 'UiApi';
47840
- const RECORD_REPRESENTATION_NAME$2 = 'RecordRepresentation';
47841
- const RECORD_VIEW_ENTITY_REPRESENTATION_NAME$1 = 'RecordViewEntityRepresentation';
47842
- const RECORD_ID_PREFIX$1 = `${API_NAMESPACE$1}::${RECORD_REPRESENTATION_NAME$2}:`;
47843
- const RECORD_VIEW_ENTITY_ID_PREFIX$1 = `${API_NAMESPACE$1}::${RECORD_VIEW_ENTITY_REPRESENTATION_NAME$1}:Name:`;
47844
- const RECORD_FIELDS_KEY_JUNCTION$1 = '__fields__';
47845
- function isStoreKeyRecordId(key) {
47846
- return key.indexOf(RECORD_ID_PREFIX$1) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) === -1;
47847
- }
47848
- function isStoreKeyRecordViewEntity$1(key) {
47849
- return (key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX$1) > -1 &&
47850
- key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) === -1);
47851
- }
47852
- function isStoreKeyRecordField(key) {
47853
- return key.indexOf(RECORD_ID_PREFIX$1) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) > -1;
47854
- }
47855
- function extractRecordIdFromStoreKey$1(key) {
47856
- if (key === undefined ||
47857
- (key.indexOf(RECORD_ID_PREFIX$1) === -1 && key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX$1) === -1)) {
47858
- return undefined;
47859
- }
47860
- const parts = key.split(':');
47861
- return parts[parts.length - 1].split('_')[0];
47862
- }
47863
- function buildRecordFieldStoreKey(recordKey, fieldName) {
47864
- return `${recordKey}${RECORD_FIELDS_KEY_JUNCTION$1}${fieldName}`;
47865
- }
47866
-
47867
- function isStoreRecordError(storeRecord) {
47868
- return storeRecord.__type === 'error';
47869
- }
47870
- function isEntryDurableRecordRepresentation(entry, key) {
47871
- // Either a DurableRecordRepresentation or StoreRecordError can live at a record key
47872
- return ((isStoreKeyRecordId(key) || isStoreKeyRecordViewEntity$1(key)) &&
47873
- entry.data.__type === undefined);
47874
- }
47875
-
47876
48045
  function serializeFieldArguments$1(argumentNodes, variables) {
47877
48046
  const mutableArgumentNodes = Object.assign([], argumentNodes);
47878
48047
  return `args__(${mutableArgumentNodes
@@ -49247,6 +49416,10 @@ function isOperationDefinitionNode(node) {
49247
49416
  return node.kind === 'OperationDefinition';
49248
49417
  }
49249
49418
 
49419
+ const POLYMORPHIC_PARENT_RELATIONSHIP = 'polymorphicParentRelationship';
49420
+ const PARENT_RELATIONSHIP = 'parentRelationship';
49421
+ const CHILD_RELATIONSHIP = 'childRelationship';
49422
+ const RECORD_QUERY = 'recordQuery';
49250
49423
  function requestsDraftsField(recordFieldNode) {
49251
49424
  if (!recordFieldNode.selectionSet)
49252
49425
  return false;
@@ -49262,18 +49435,41 @@ function isRecordQuery(recordQueryField) {
49262
49435
  directive.arguments
49263
49436
  .map((argument) => argument.value)
49264
49437
  .filter(isStringValueNode)
49265
- .some((categoryName) => categoryName.value === 'recordQuery'));
49438
+ .some((categoryName) => categoryName.value === RECORD_QUERY));
49266
49439
  });
49267
49440
  }
49268
49441
  return false;
49269
49442
  }
49270
- // finds field with 'recordQuery' and 'childRelationship' directive
49271
- function findNearestRecordQuery(ancestors) {
49272
- const recordQueryAncester = findNearestAncesterPath(ancestors, true).node;
49273
- return recordQueryAncester === undefined ? undefined : recordQueryAncester;
49443
+ // finds connection field with 'recordQuery' and 'childRelationship' directive.
49444
+ function findNearestConnection(ancestors) {
49445
+ const connectionAncestor = findNearestAncesterPath(ancestors, true).node;
49446
+ return connectionAncestor === undefined ? undefined : connectionAncestor;
49447
+ }
49448
+ // convinient method to find nearest connection with its path
49449
+ function findNearestConnectionWithPath(ancestors) {
49450
+ const closestAncestorPath = findNearestAncesterPath(ancestors, true);
49451
+ let connection = undefined;
49452
+ let connectionPath = undefined;
49453
+ if (closestAncestorPath.parentIndex > 0) {
49454
+ const connectionAncestor = closestAncestorPath.node;
49455
+ const connectionAncestors = ancestors.slice(0, closestAncestorPath.parentIndex);
49456
+ connection =
49457
+ connectionAncestor === undefined ? undefined : connectionAncestor;
49458
+ if (connection !== undefined) {
49459
+ const ancesterPath = findAncesterPath(connectionAncestors);
49460
+ connectionPath =
49461
+ ancesterPath === ''
49462
+ ? connection.name.value
49463
+ : `${ancesterPath}#${connection.name.value}`;
49464
+ }
49465
+ }
49466
+ return {
49467
+ connection,
49468
+ path: connectionPath,
49469
+ };
49274
49470
  }
49275
- // finds cloeset ancester. If 'parentRelationship' is allowed, it could be 'InlineFragmentNode' since it inherits the 'parent' relationship. 'InlineFragmentNode' makes sure that only one 'apiName' returns when tree is traversed.
49276
- function findNearestAncesterPath(ancestors, recordQueryOnly) {
49471
+ // finds closest ancestor. If node with 'parentRelationship' is the ancester, the end result could be 'InlineFragmentNode' since it inherits the 'parent' relationship. 'InlineFragmentNode' makes sure that only one 'apiName' returns when tree is traversed.
49472
+ function findNearestAncesterPath(ancestors, connectionOnly) {
49277
49473
  let recordQueryPath = { node: undefined, parentIndex: -1 };
49278
49474
  let relationship = '';
49279
49475
  for (let i = ancestors.length - 1; i >= 0; i--) {
@@ -49287,9 +49483,11 @@ function findNearestAncesterPath(ancestors, recordQueryOnly) {
49287
49483
  continue;
49288
49484
  for (let arg of directive.arguments) {
49289
49485
  if (arg.value &&
49290
- (arg.value.value === 'recordQuery' ||
49291
- arg.value.value === 'childRelationship' ||
49292
- (!recordQueryOnly && arg.value.value === 'parentRelationship'))) {
49486
+ (arg.value.value === RECORD_QUERY ||
49487
+ arg.value.value === CHILD_RELATIONSHIP ||
49488
+ (!connectionOnly &&
49489
+ (arg.value.value === PARENT_RELATIONSHIP ||
49490
+ arg.value.value === POLYMORPHIC_PARENT_RELATIONSHIP)))) {
49293
49491
  recordQueryPath = { node: node, parentIndex: i };
49294
49492
  relationship = arg.value.value;
49295
49493
  break;
@@ -49304,17 +49502,19 @@ function findNearestAncesterPath(ancestors, recordQueryOnly) {
49304
49502
  //checks if nearest ancester could be an inline fragment
49305
49503
  if (recordQueryPath.node !== undefined &&
49306
49504
  recordQueryPath.node.selectionSet &&
49307
- relationship === 'parentRelationship') {
49308
- //
49309
- if (recordQueryPath.node.selectionSet.selections.every(isInlineFragmentNode)) {
49310
- //
49311
- const inlineFragmentLoc = recordQueryPath.parentIndex + 2;
49312
- if (inlineFragmentLoc < ancestors.length && ancestors[inlineFragmentLoc]) {
49505
+ (relationship === PARENT_RELATIONSHIP || relationship === POLYMORPHIC_PARENT_RELATIONSHIP)) {
49506
+ // InlineFragment is usually 3 steps aways from its FieldNode parent within ancester hierarchy if it exists. The below search
49507
+ // is applied to adapt to future AST structure change
49508
+ let parentIndex = recordQueryPath.parentIndex + 1;
49509
+ while (parentIndex < ancestors.length) {
49510
+ if (isInlineFragmentNode(ancestors[parentIndex])) {
49313
49511
  recordQueryPath = {
49314
- node: ancestors[inlineFragmentLoc],
49315
- parentIndex: inlineFragmentLoc,
49512
+ node: ancestors[parentIndex],
49513
+ parentIndex,
49316
49514
  };
49515
+ break;
49317
49516
  }
49517
+ parentIndex++;
49318
49518
  }
49319
49519
  }
49320
49520
  return recordQueryPath;
@@ -49338,7 +49538,7 @@ function findAncesterPath(ancesters) {
49338
49538
  ? sectionPath
49339
49539
  : sectionPath === ''
49340
49540
  ? path
49341
- : `${sectionPath}_${path}`;
49541
+ : `${sectionPath}#${path}`;
49342
49542
  }
49343
49543
  }
49344
49544
  boundaryIndex = parentIndex;
@@ -49396,9 +49596,9 @@ function getRelation(node) {
49396
49596
  const relationships = args
49397
49597
  .map((arg) => arg.value)
49398
49598
  .filter(isStringValueNode)
49399
- .filter((valueNode) => valueNode.value === 'childRelationship' ||
49400
- valueNode.value === 'parentRelationship' ||
49401
- valueNode.value === 'polymorphicParentRelationship')
49599
+ .filter((valueNode) => valueNode.value === CHILD_RELATIONSHIP ||
49600
+ valueNode.value === PARENT_RELATIONSHIP ||
49601
+ valueNode.value === POLYMORPHIC_PARENT_RELATIONSHIP)
49402
49602
  .map((relationshipNode) => relationshipNode.value);
49403
49603
  if (relationships.length > 0) {
49404
49604
  return relationships[0];
@@ -49455,8 +49655,8 @@ function isFieldSpanning(node, parentNode) {
49455
49655
  */
49456
49656
  function isParentRelationship(node) {
49457
49657
  return (node &&
49458
- (isRelationship(node, 'parentRelationship') ||
49459
- isRelationship(node, 'polymorphicParentRelationship')));
49658
+ (isRelationship(node, PARENT_RELATIONSHIP) ||
49659
+ isRelationship(node, POLYMORPHIC_PARENT_RELATIONSHIP)));
49460
49660
  }
49461
49661
  /*
49462
49662
  checks if the InlineFragment spans
@@ -49762,6 +49962,26 @@ function findFieldInfo(objectInfo, fieldName) {
49762
49962
  return values$1(objectInfo.fields).find((field) => field.apiName === fieldName ||
49763
49963
  (field.dataType === 'Reference' && field.relationshipName === fieldName));
49764
49964
  }
49965
+ async function readIngestionTimestampForKey(key, query) {
49966
+ let ingestionTimestamp = 0;
49967
+ const sql = `
49968
+ SELECT json_extract(metadata, '${JSON_EXTRACT_PATH_INGESTION_TIMESTAMP}')
49969
+ FROM lds_data
49970
+ WHERE key IS ?
49971
+ `;
49972
+ const results = await query(sql, [key]);
49973
+ const [timestamp] = results.rows.map((row) => row[0]);
49974
+ if (timestamp !== null) {
49975
+ const numericalTimestamp = Number(timestamp);
49976
+ if (isNaN(numericalTimestamp)) {
49977
+ return ingestionTimestamp;
49978
+ }
49979
+ // adjust the timestamp to account for ingestion processing time
49980
+ // 30s is used because this is the default record TTL
49981
+ ingestionTimestamp = numericalTimestamp - 30000;
49982
+ }
49983
+ return ingestionTimestamp;
49984
+ }
49765
49985
 
49766
49986
  function findSpanningField(name) {
49767
49987
  return (field) => {
@@ -50281,17 +50501,7 @@ async function fetchIngestionTimeStampFromDatabase(apiName, info, args, query) {
50281
50501
  const key = buildKeyStringForRecordQuery(operation,
50282
50502
  // join varables passed from query to the argument variables given from the AST
50283
50503
  { ...variableValues, ...args }, info.fieldNodes[0].arguments, apiName);
50284
- const sql = `
50285
- SELECT json_extract(metadata, '${JSON_EXTRACT_PATH_INGESTION_TIMESTAMP}')
50286
- FROM lds_data
50287
- WHERE key IS ?
50288
- `;
50289
- const results = await query(sql, [key]);
50290
- const [timestamp] = results.rows.map((row) => row[0]);
50291
- if (timestamp !== null && typeof timestamp === 'number') {
50292
- //go back 10 ms to adjust for margin of error when top level query is stored and when raml objects are stored
50293
- ingestionTimestamp = timestamp - 10;
50294
- }
50504
+ return readIngestionTimestampForKey(key, query);
50295
50505
  }
50296
50506
  return ingestionTimestamp;
50297
50507
  }
@@ -50339,26 +50549,20 @@ function generateRecordQueries(objectInfos) {
50339
50549
  let recordConnections = ``;
50340
50550
  const polymorphicFieldTypeNames = new Set();
50341
50551
  let typedScalars = new Set();
50552
+ let parentRelationshipFields = new Set();
50342
50553
  for (const objectInfo of values$1(objectInfos)) {
50343
50554
  const { apiName, childRelationships } = objectInfo;
50344
50555
  let fields = ``;
50345
50556
  typedScalars.add(`${apiName}_Filter`);
50346
50557
  typedScalars.add(`${apiName}_OrderBy`);
50347
- for (const childRelationship of childRelationships) {
50348
- const { childObjectApiName } = childRelationship;
50349
- // Only add the relationship if there is relevant objectinfos for it,
50350
- // otherwise we'd be defining types we cannot satisfy and aren't referenced in
50351
- // the query.
50352
- if (objectInfos[childObjectApiName] !== undefined) {
50353
- fields += `${childRelationship.relationshipName}(first: Int, where: ${childObjectApiName}_Filter, orderBy: ${childObjectApiName}_OrderBy, scope: SupportedScopes): ${childObjectApiName}Connection \n`;
50354
- typedScalars.add(`${childObjectApiName}_Filter`);
50355
- typedScalars.add(`${childObjectApiName}_OrderBy`);
50356
- }
50357
- }
50358
50558
  for (const field of values$1(objectInfo.fields)) {
50359
50559
  if (!fieldsStaticallyAdded.includes(field.apiName)) {
50360
50560
  fields += `${field.apiName}: ${dataTypeToType(field.dataType, field.apiName)}\n`;
50361
50561
  }
50562
+ //handles parent relationship
50563
+ if (field.relationshipName === null) {
50564
+ continue;
50565
+ }
50362
50566
  // For spanning parent relationships with no union types
50363
50567
  if (field.referenceToInfos.length === 1) {
50364
50568
  const [relation] = field.referenceToInfos;
@@ -50366,11 +50570,13 @@ function generateRecordQueries(objectInfos) {
50366
50570
  // otherwise we'd be defining types we cannot satisfy and aren't referenced in
50367
50571
  // the query.
50368
50572
  if (objectInfos[relation.apiName] !== undefined) {
50573
+ parentRelationshipFields.add(field.relationshipName);
50369
50574
  fields += `${field.relationshipName}: ${relation.apiName}\n`;
50370
50575
  }
50371
50576
  // For polymorphic field, its type is 'Record' inteface. The concrete entity type name is saved for field resolving of next phase
50372
50577
  }
50373
50578
  else if (field.referenceToInfos.length > 1) {
50579
+ parentRelationshipFields.add(field.relationshipName);
50374
50580
  fields += `${field.relationshipName}: Record\n`;
50375
50581
  for (const relation of field.referenceToInfos) {
50376
50582
  if (objectInfos[relation.apiName] !== undefined) {
@@ -50379,6 +50585,20 @@ function generateRecordQueries(objectInfos) {
50379
50585
  }
50380
50586
  }
50381
50587
  }
50588
+ // handles child relationship
50589
+ for (const childRelationship of childRelationships) {
50590
+ const { childObjectApiName } = childRelationship;
50591
+ // Only add the relationship if there is relevant objectinfos for it,
50592
+ // otherwise we'd be defining types we cannot satisfy and aren't referenced in
50593
+ // the query.
50594
+ // If one field has both parent relationship and child relationship with the same name, the child relationship is ignored. This is how the server GQL has implemented as date of 08/07/2023
50595
+ if (objectInfos[childObjectApiName] !== undefined &&
50596
+ !parentRelationshipFields.has(childRelationship.relationshipName)) {
50597
+ fields += `${childRelationship.relationshipName}(first: Int, where: ${childObjectApiName}_Filter, orderBy: ${childObjectApiName}_OrderBy, scope: SupportedScopes): ${childObjectApiName}Connection \n`;
50598
+ typedScalars.add(`${childObjectApiName}_Filter`);
50599
+ typedScalars.add(`${childObjectApiName}_OrderBy`);
50600
+ }
50601
+ }
50382
50602
  recordQueries += `${apiName}(first: Int, where: ${apiName}_Filter, orderBy: ${apiName}_OrderBy, scope: SupportedScopes): ${apiName}Connection\n`;
50383
50603
  const isServiceAppointment = apiName === 'ServiceAppointment';
50384
50604
  recordConnections += /* GraphQL */ `
@@ -50453,6 +50673,8 @@ function dataTypeToType(objectInfoDataType, apiName) {
50453
50673
  return 'PercentValue';
50454
50674
  case 'Int':
50455
50675
  return 'IntValue';
50676
+ case 'EncryptedString':
50677
+ return 'EncryptedStringValue';
50456
50678
  // ! do the rest of the custom types
50457
50679
  default:
50458
50680
  return 'String';
@@ -50538,7 +50760,7 @@ const parentRelationshipDirective = {
50538
50760
  },
50539
50761
  value: {
50540
50762
  kind: Kind.STRING,
50541
- value: 'parentRelationship',
50763
+ value: PARENT_RELATIONSHIP,
50542
50764
  block: false,
50543
50765
  },
50544
50766
  },
@@ -50552,8 +50774,8 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50552
50774
  // example 2 'ServiceAppointment' -> ['Owner']; 'Owner' -> ['User', 'Group']
50553
50775
  const objectNodeInfoTree = {};
50554
50776
  // save the field path to apiName map
50555
- // example 1: 'ServiceAppointment' -> ['ServiceAppointment']; 'ServiceAppointment_Account' -> ['Account']; 'ServiceAppointment_Account_Owner' -> ['User']
50556
- const objectInfoApiMap = {};
50777
+ // example 1: 'ServiceAppointment' -> ['ServiceAppointment']; 'ServiceAppointment#ccount' -> ['Account']; 'ServiceAppointment#Account#Owner' -> ['User']
50778
+ const pathToObjectApiNamesMap = {};
50557
50779
  let startNodes = new Set();
50558
50780
  let totalNodes = new Set();
50559
50781
  let objectInfos = {};
@@ -50567,11 +50789,11 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50567
50789
  visit(originalAST, {
50568
50790
  Argument: {
50569
50791
  enter(node, key, parent, path, ancestors) {
50570
- const recordQueryNode = findNearestRecordQuery(ancestors);
50571
- if (!recordQueryNode)
50792
+ const { connection: recordConnectionNode, path: ancesterPath } = findNearestConnectionWithPath(ancestors);
50793
+ if (!recordConnectionNode || !ancesterPath)
50572
50794
  return;
50573
- if (!objectNodeInfoTree[recordQueryNode.name.value]) {
50574
- objectNodeInfoTree[recordQueryNode.name.value] = [];
50795
+ if (!objectNodeInfoTree[ancesterPath]) {
50796
+ objectNodeInfoTree[ancesterPath] = [];
50575
50797
  }
50576
50798
  switch (node.name.value) {
50577
50799
  case 'orderBy':
@@ -50579,12 +50801,12 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50579
50801
  if (node.value.kind !== 'ObjectValue') {
50580
50802
  return;
50581
50803
  }
50582
- totalNodes.add(recordQueryNode.name.value);
50804
+ totalNodes.add(ancesterPath);
50583
50805
  // 'childRelationship' node is not taken as the startNode of the 'NodeInfoTree' graph. The field scanning will construct the graph which lead here.
50584
- if (isRecordQuery(recordQueryNode)) {
50585
- startNodes.add(recordQueryNode.name.value);
50806
+ if (isRecordQuery(recordConnectionNode)) {
50807
+ startNodes.add(recordConnectionNode.name.value);
50586
50808
  }
50587
- growObjectFieldTree(objectNodeInfoTree, recordQueryNode.name.value, node.value, totalNodes, startNodes);
50809
+ growObjectFieldTree(objectNodeInfoTree, ancesterPath, node.value, totalNodes, startNodes);
50588
50810
  break;
50589
50811
  case 'scope':
50590
50812
  if (!isScopeArgumentNodeWithType(node, 'ASSIGNEDTOME', variables)) {
@@ -50599,17 +50821,16 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50599
50821
  name: 'ServiceResources',
50600
50822
  });
50601
50823
  }
50602
- if (objectNodeInfoTree['ServiceResources'] === undefined) {
50603
- objectNodeInfoTree['ServiceResources'] = [];
50604
- }
50605
- if (!objectNodeInfoTree['ServiceResources'].some((child) => child.name === 'ServiceResource')) {
50606
- objectNodeInfoTree['ServiceResources'].push({
50607
- relation: 'parent',
50608
- name: 'ServiceResource',
50609
- });
50824
+ if (objectNodeInfoTree['ServiceAppointment#ServiceResources'] === undefined) {
50825
+ objectNodeInfoTree['ServiceAppointment#ServiceResources'] = [
50826
+ {
50827
+ relation: 'parent',
50828
+ name: 'ServiceResource',
50829
+ },
50830
+ ];
50610
50831
  }
50611
- if (objectNodeInfoTree['ServiceResource'] === undefined) {
50612
- objectNodeInfoTree['ServiceResource'] = [];
50832
+ if (objectNodeInfoTree['ServiceAppointment#ServiceResources#ServiceResource'] === undefined) {
50833
+ objectNodeInfoTree['ServiceAppointment#ServiceResources#ServiceResource'] = [];
50613
50834
  }
50614
50835
  break;
50615
50836
  default:
@@ -50623,7 +50844,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50623
50844
  return;
50624
50845
  if (!node.selectionSet)
50625
50846
  return;
50626
- const recordQueryField = findNearestRecordQuery(ancestors);
50847
+ const recordQueryField = findNearestConnection(ancestors);
50627
50848
  //only injects fields for 'recordQuery' field. ignores the 'childRelationship' field since it will be traversed as the child of the 'recordQuery'
50628
50849
  if (isRecordQuery(recordQueryField) && recordQueryField) {
50629
50850
  totalNodes.add(recordQueryField.name.value);
@@ -50634,21 +50855,21 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50634
50855
  },
50635
50856
  });
50636
50857
  if (objectInfoService && startNodes.size > 0) {
50637
- objectInfos = await resolveObjectInfos(objectNodeInfoTree, objectInfoApiMap, startNodes, objectInfoService);
50858
+ objectInfos = await resolveObjectInfos(objectNodeInfoTree, pathToObjectApiNamesMap, startNodes, objectInfoService);
50638
50859
  }
50639
50860
  // read pass; gather whats needed
50640
50861
  visit(originalAST, {
50641
50862
  Argument: {
50642
50863
  leave(node, key, parent, path, ancestors) {
50643
- const recordQueryField = findNearestRecordQuery(ancestors);
50864
+ const recordQueryField = findNearestConnection(ancestors);
50644
50865
  if (!recordQueryField)
50645
50866
  return;
50646
50867
  const ancestorPath = findAncesterPath(ancestors);
50647
50868
  if (!inlineFragmentSelections[ancestorPath]) {
50648
50869
  inlineFragmentSelections[ancestorPath] = [];
50649
50870
  }
50650
- const recordQueryApiName = objectInfoApiMap[ancestorPath]
50651
- ? objectInfoApiMap[ancestorPath][0]
50871
+ const recordQueryApiName = pathToObjectApiNamesMap[ancestorPath]
50872
+ ? pathToObjectApiNamesMap[ancestorPath][0]
50652
50873
  : recordQueryField.name.value;
50653
50874
  // The record node acts as the reference. The duplicated field in the record node is not injected
50654
50875
  const recordReferenceNode = [recordQueryField]
@@ -50662,7 +50883,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50662
50883
  case 'scope':
50663
50884
  // Hanle 'MINE' field
50664
50885
  if (isScopeArgumentNodeWithType(node, 'MINE', variables)) {
50665
- if (isMineScopeAvailable(ancestorPath, objectInfoApiMap, objectInfos)) {
50886
+ if (isMineScopeAvailable(ancestorPath, pathToObjectApiNamesMap, objectInfos)) {
50666
50887
  // 'typeConditon' is added when the 'InlineFragmentNode' is appended at the write pass
50667
50888
  inlineFragmentSelections[ancestorPath].push(...mineFragmentSelections);
50668
50889
  }
@@ -50688,7 +50909,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50688
50909
  case 'where': {
50689
50910
  inlineFragmentSelections[ancestorPath] = [
50690
50911
  ...inlineFragmentSelections[ancestorPath],
50691
- ...injectFilter(node, idState, ancestorPath, objectInfos, objectInfoApiMap, draftFunctions, recordReferenceNode),
50912
+ ...injectFilter(node, idState, ancestorPath, false, objectInfos, pathToObjectApiNamesMap, draftFunctions, recordReferenceNode),
50692
50913
  ];
50693
50914
  break;
50694
50915
  }
@@ -50704,7 +50925,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50704
50925
  if (!node.selectionSet)
50705
50926
  return;
50706
50927
  // it could be 'recordQuery' or 'childRelationship'
50707
- const recordQueryField = findNearestRecordQuery(ancestors);
50928
+ const recordQueryField = findNearestConnection(ancestors);
50708
50929
  if (!recordQueryField)
50709
50930
  return;
50710
50931
  const ancestorPath = findAncesterPath(ancestors);
@@ -50716,7 +50937,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50716
50937
  spanningSelections.push(selection);
50717
50938
  }
50718
50939
  }
50719
- const injectedFields = injectFields(spanningSelections, node, ancestors, objectInfos, objectInfoApiMap);
50940
+ const injectedFields = injectFields(spanningSelections, node, ancestorPath, ancestors, objectInfos, pathToObjectApiNamesMap);
50720
50941
  const mergedInjectedFields = mergeSelectionNodes$1(inlineFragmentSelections[ancestorPath], injectedFields);
50721
50942
  inlineFragmentSelections[ancestorPath] = mergedInjectedFields;
50722
50943
  },
@@ -50729,7 +50950,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50729
50950
  // removes 'ServicesResources' query field node if 'assignedtome' scope shows up
50730
50951
  if (assignedtomeQueryFieldNode !== undefined &&
50731
50952
  node.name.value === 'ServiceResources') {
50732
- const serviceResourcesAncestor = findNearestRecordQuery(ancestors);
50953
+ const serviceResourcesAncestor = findNearestConnection(ancestors);
50733
50954
  if (serviceResourcesAncestor === assignedtomeQueryFieldNode) {
50734
50955
  return null;
50735
50956
  }
@@ -50738,7 +50959,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50738
50959
  return;
50739
50960
  if (!node.selectionSet)
50740
50961
  return;
50741
- const recordQueryField = findNearestRecordQuery(ancestors);
50962
+ const recordQueryField = findNearestConnection(ancestors);
50742
50963
  if (!recordQueryField)
50743
50964
  return;
50744
50965
  const ancestorPath = findAncesterPath(ancestors);
@@ -50747,8 +50968,8 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50747
50968
  return;
50748
50969
  //const recordQueryPath = findAncesterPath(ancestors);
50749
50970
  // 'apiName' has to be at index 0 since 'node' record type could only be of 'recordQuery' or 'childRelationship'. They can not have the 'InlineFragmentNode' as its children.
50750
- const recordQueryApiName = objectInfoApiMap[ancestorPath]
50751
- ? objectInfoApiMap[ancestorPath][0]
50971
+ const recordQueryApiName = pathToObjectApiNamesMap[ancestorPath]
50972
+ ? pathToObjectApiNamesMap[ancestorPath][0]
50752
50973
  : recordQueryField.name.value;
50753
50974
  const nodeWithFragments = {
50754
50975
  ...node,
@@ -50785,7 +51006,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
50785
51006
  if (node.name.value === 'where') {
50786
51007
  const ancestorPath = findAncesterPath(ancestors);
50787
51008
  if (idState.paths.includes(ancestorPath)) {
50788
- const apiName = objectInfoApiMap[ancestorPath][0];
51009
+ const apiName = pathToObjectApiNamesMap[ancestorPath][0];
50789
51010
  const objectInfo = objectInfos[apiName];
50790
51011
  const swappedIdFilter = swapIdField(node.value, objectInfo, false, idState, draftFunctions);
50791
51012
  return {
@@ -50851,8 +51072,8 @@ function swapIdField(filterFields, objectInfo, swapped, idState, draftFunctions)
50851
51072
  };
50852
51073
  }
50853
51074
  }
50854
- function isMineScopeAvailable(apiNamePath, objectInfoApiMap, objectInfos) {
50855
- const apiName = objectInfoApiMap[apiNamePath];
51075
+ function isMineScopeAvailable(apiNamePath, pathToObjectApiNamesMap, objectInfos) {
51076
+ const apiName = pathToObjectApiNamesMap[apiNamePath];
50856
51077
  if (!apiName)
50857
51078
  return false;
50858
51079
  const objectInfo = objectInfos[apiName[0]];
@@ -50941,15 +51162,16 @@ function growObjectFieldTree(tree, parentNode, entryNode, totalNodes, startNodes
50941
51162
  }
50942
51163
  // example: 'Account'
50943
51164
  const childNode = objectFieldNode.name.value;
51165
+ const childNodepath = `${parentNode}#${childNode}`;
50944
51166
  if (!tree[parentNode].some((child) => child.name === childNode)) {
50945
51167
  tree[parentNode].push({
50946
51168
  relation: 'parent',
50947
51169
  name: childNode,
50948
51170
  });
50949
- totalNodes.add(childNode);
51171
+ totalNodes.add(childNodepath);
50950
51172
  }
50951
51173
  // recursively go to deeper level of filter.
50952
- growObjectFieldTree(tree, childNode, objectFieldNode.value, totalNodes, startNodes);
51174
+ growObjectFieldTree(tree, childNodepath, objectFieldNode.value, totalNodes, startNodes);
50953
51175
  }
50954
51176
  }
50955
51177
  }
@@ -50984,19 +51206,20 @@ function growFieldTree(tree, parentSectionPath, entryNode, parentNode, totalNode
50984
51206
  }
50985
51207
  if (!tree[parentSectionPath].some((field) => field.name === fieldName)) {
50986
51208
  tree[parentSectionPath].push({
50987
- relation: relationType === 'parentRelationship' ||
50988
- relationType === 'polymorphicParentRelationship'
51209
+ relation: relationType === PARENT_RELATIONSHIP ||
51210
+ relationType === POLYMORPHIC_PARENT_RELATIONSHIP
50989
51211
  ? 'parent'
50990
51212
  : 'child',
50991
51213
  name: fieldName,
50992
51214
  });
50993
- totalNodes.add(fieldName);
51215
+ totalNodes.add(`${parentSectionPath}#${fieldName}`);
50994
51216
  }
50995
51217
  if (entryNode.selectionSet && entryNode.selectionSet.selections) {
50996
51218
  const childNodes = entryNode.selectionSet.selections.filter(isFieldOrInlineFragmentNode);
50997
51219
  // recursively build the traversal tree
50998
51220
  for (const child of childNodes) {
50999
- growFieldTree(tree, fieldName, child, entryNode, totalNodes, startNodes);
51221
+ const path = `${parentSectionPath}#${fieldName}`;
51222
+ growFieldTree(tree, path, child, entryNode, totalNodes, startNodes);
51000
51223
  }
51001
51224
  }
51002
51225
  }
@@ -51026,23 +51249,23 @@ function growFieldTree(tree, parentSectionPath, entryNode, parentNode, totalNode
51026
51249
  }
51027
51250
  if (!tree[parentSectionPath].some((field) => field.name === conditionName)) {
51028
51251
  tree[parentSectionPath].push({
51029
- relation: relationType === 'parentRelationship' ||
51030
- relationType === 'polymorphicParentRelationship'
51252
+ relation: relationType === PARENT_RELATIONSHIP ||
51253
+ relationType === POLYMORPHIC_PARENT_RELATIONSHIP
51031
51254
  ? 'parent'
51032
51255
  : 'child',
51033
51256
  name: conditionName,
51034
51257
  });
51035
- totalNodes.add(conditionName);
51258
+ const path = `${parentSectionPath}#${conditionName}`;
51259
+ totalNodes.add(path);
51036
51260
  }
51037
51261
  }
51038
51262
  }
51039
51263
  // dive deep immediately for 'InlineFragment'
51040
51264
  const childNodes = entryNode.selectionSet.selections.filter(isFieldOrInlineFragmentNode);
51265
+ const path = `${parentSectionPath}${entryNode.typeCondition ? '#' + entryNode.typeCondition.name.value : ''}`;
51041
51266
  // Navigates into InLineFragment
51042
51267
  for (const child of childNodes) {
51043
- growFieldTree(tree, entryNode.typeCondition
51044
- ? entryNode.typeCondition.name.value
51045
- : parentSectionPath, child, entryNode, totalNodes, startNodes);
51268
+ growFieldTree(tree, path, child, entryNode, totalNodes, startNodes);
51046
51269
  }
51047
51270
  }
51048
51271
  }
@@ -51054,7 +51277,7 @@ function growFieldTree(tree, parentSectionPath, entryNode, parentNode, totalNode
51054
51277
  * @param startNodes start nodes of the tree. It can be used to fetch ObjectInfo immediately
51055
51278
  * @param path
51056
51279
  */
51057
- async function resolveObjectInfos(objectInfotree, objectInfoApiMap, startNodes, objectInfoService) {
51280
+ async function resolveObjectInfos(objectInfotree, pathToObjectApiNamesMap, startNodes, objectInfoService) {
51058
51281
  let objectInfos;
51059
51282
  try {
51060
51283
  objectInfos = await objectInfoService.getObjectInfos(Array.from(startNodes));
@@ -51068,9 +51291,9 @@ async function resolveObjectInfos(objectInfotree, objectInfoApiMap, startNodes,
51068
51291
  throw new Error(`Unable to resolve ObjectInfo(s) for ${Array.from(startNodes)}`);
51069
51292
  }
51070
51293
  for (const startNode of startNodes) {
51071
- objectInfoApiMap[startNode] = [startNode];
51294
+ pathToObjectApiNamesMap[startNode] = [startNode];
51072
51295
  const children = objectInfotree[startNode];
51073
- const subObjectInfoMap = await fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfos, children, startNode, objectInfoService);
51296
+ const subObjectInfoMap = await fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, objectInfos, children, startNode, objectInfoService);
51074
51297
  objectInfos = { ...objectInfos, ...subObjectInfoMap };
51075
51298
  }
51076
51299
  return objectInfos;
@@ -51078,15 +51301,15 @@ async function resolveObjectInfos(objectInfotree, objectInfoApiMap, startNodes,
51078
51301
  // example 1: 'parentPath': 'ServiceAppointment', 'nodesAtSameLevel': ['Account']
51079
51302
  // example 2: 'parentPath': 'ServiceAppointment', 'nodesAtSameLevel': ['Owner'], this example has 2 apiName for the node 'Owner'
51080
51303
  // example 3: 'parentPath': 'ServiceAppointment_Owner', 'nodesAtSameLevel': ['User', 'Group']
51081
- async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap, nodesAtSameLevel, parentPath, objectInfoService) {
51082
- const objectInfoApiNames = objectInfoApiMap[parentPath];
51304
+ async function fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, objectInfoMap, nodesAtSameLevel, parentPath, objectInfoService) {
51305
+ const objectInfoApiNames = pathToObjectApiNamesMap[parentPath];
51083
51306
  if (!objectInfoApiNames) {
51084
51307
  // eslint-disable-next-line
51085
51308
  throw new Error(`Object Info does not exist for ${parentPath}`);
51086
51309
  }
51087
51310
  const validObjectInfoNodes = [];
51088
51311
  let updatedObjectInfoMap = {};
51089
- // InlineFragment and polymorphic field support fits into this scenario ObjectInfoApiMap Entry: 'ServiceAppointment_Owner' -> ['User', 'Group']; ServiceAppointment_Owner_User' -> ['User']
51312
+ // InlineFragment and polymorphic field support fits into this scenario pathToObjectApiNamesMap Entry: 'ServiceAppointment#Owner' -> ['User', 'Group']; ServiceAppointment#Owner#User' -> ['User']
51090
51313
  if (objectInfoApiNames.length > 0 &&
51091
51314
  nodesAtSameLevel.length > 0 &&
51092
51315
  objectInfoApiNames.includes(nodesAtSameLevel[0].name)) {
@@ -51098,8 +51321,8 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
51098
51321
  // eslint-disable-next-line
51099
51322
  throw new Error(`Condition ${field.name} does not exists for ${parentPath}`);
51100
51323
  }
51101
- const path = `${parentPath}_${field.name}`;
51102
- objectInfoApiMap[path] = [field.name];
51324
+ const path = `${parentPath}#${field.name}`;
51325
+ pathToObjectApiNamesMap[path] = [field.name];
51103
51326
  }
51104
51327
  validObjectInfoNodes.push(...nodesAtSameLevel);
51105
51328
  updatedObjectInfoMap = { ...objectInfoMap };
@@ -51114,7 +51337,7 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
51114
51337
  let apiNames = [];
51115
51338
  for (const nodeInfo of nodesAtSameLevel) {
51116
51339
  const field = nodeInfo.name;
51117
- const path = `${parentPath}_${field}`;
51340
+ const path = `${parentPath}#${field}`;
51118
51341
  // Handle 'parentRelationship'
51119
51342
  if (nodeInfo.relation === 'parent') {
51120
51343
  const relationshipId = referenceIdFieldForRelationship(field);
@@ -51132,21 +51355,21 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
51132
51355
  }
51133
51356
  }
51134
51357
  // This is a polymorphic field
51135
- if (fieldDefinition.referenceToInfos.length > 1 && objectInfotree[field]) {
51358
+ if (fieldDefinition.referenceToInfos.length > 1 && objectInfotree[path]) {
51136
51359
  // Fields needs to expand and heterogenous entity ObjectInfo needs to be fetched
51137
- const referencedNodeInfos = objectInfotree[field];
51360
+ const referencedNodeInfos = objectInfotree[path];
51138
51361
  const requestedApiNames = referencedNodeInfos.map((referenceNodeInfo) => referenceNodeInfo.name);
51139
51362
  // Fetches requested ObjectInfo only. Some entity's relation field could define more than 6 references. Only references show up in query need to be handled.
51140
- if (requestedApiNames.length > 0 && objectInfotree[field]) {
51363
+ if (requestedApiNames.length > 0 && objectInfotree[path]) {
51141
51364
  fieldDefinition.referenceToInfos
51142
51365
  .filter((referenceToInfo) => requestedApiNames.includes(referenceToInfo.apiName))
51143
51366
  .forEach((ref) => {
51144
- if (!objectInfoApiMap[path]) {
51145
- objectInfoApiMap[path] = [];
51367
+ if (!pathToObjectApiNamesMap[path]) {
51368
+ pathToObjectApiNamesMap[path] = [];
51146
51369
  }
51147
51370
  // 'ServiceAppointment_Owner' ->['User', 'Group']
51148
- if (!objectInfoApiMap[path].includes(ref.apiName)) {
51149
- objectInfoApiMap[path].push(ref.apiName);
51371
+ if (!pathToObjectApiNamesMap[path].includes(ref.apiName)) {
51372
+ pathToObjectApiNamesMap[path].push(ref.apiName);
51150
51373
  }
51151
51374
  if (!apiNames.includes(ref.apiName)) {
51152
51375
  apiNames.push(ref.apiName);
@@ -51156,11 +51379,11 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
51156
51379
  }
51157
51380
  else if (fieldDefinition.referenceToInfos.length === 1) {
51158
51381
  const ref = fieldDefinition.referenceToInfos[0];
51159
- if (!objectInfoApiMap[path]) {
51160
- objectInfoApiMap[path] = [];
51382
+ if (!pathToObjectApiNamesMap[path]) {
51383
+ pathToObjectApiNamesMap[path] = [];
51161
51384
  }
51162
- if (!objectInfoApiMap[path].includes(ref.apiName)) {
51163
- objectInfoApiMap[path].push(ref.apiName);
51385
+ if (!pathToObjectApiNamesMap[path].includes(ref.apiName)) {
51386
+ pathToObjectApiNamesMap[path].push(ref.apiName);
51164
51387
  }
51165
51388
  if (!apiNames.includes(ref.apiName)) {
51166
51389
  apiNames.push(ref.apiName);
@@ -51171,11 +51394,11 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
51171
51394
  // handles 'childRelationship'
51172
51395
  const childRelationship = parentObjectInfo.childRelationships.find((childRelationship) => childRelationship.relationshipName === field);
51173
51396
  if (childRelationship) {
51174
- if (!objectInfoApiMap[path]) {
51175
- objectInfoApiMap[path] = [];
51397
+ if (!pathToObjectApiNamesMap[path]) {
51398
+ pathToObjectApiNamesMap[path] = [];
51176
51399
  }
51177
- if (!objectInfoApiMap[path].includes(childRelationship.childObjectApiName)) {
51178
- objectInfoApiMap[path].push(childRelationship.childObjectApiName);
51400
+ if (!pathToObjectApiNamesMap[path].includes(childRelationship.childObjectApiName)) {
51401
+ pathToObjectApiNamesMap[path].push(childRelationship.childObjectApiName);
51179
51402
  }
51180
51403
  if (!apiNames.includes(childRelationship.childObjectApiName)) {
51181
51404
  apiNames.push(childRelationship.childObjectApiName);
@@ -51197,10 +51420,10 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
51197
51420
  }
51198
51421
  for (const nodeInfo of validObjectInfoNodes) {
51199
51422
  const field = nodeInfo.name;
51200
- const subLevelFields = objectInfotree[field];
51201
- const path = `${parentPath}_${field}`;
51423
+ const path = `${parentPath}#${field}`;
51424
+ const subLevelFields = objectInfotree[path];
51202
51425
  if (subLevelFields && subLevelFields.length > 0) {
51203
- const subObjectInfos = await fetchObjectInfos(objectInfotree, objectInfoApiMap, updatedObjectInfoMap, subLevelFields, path, objectInfoService);
51426
+ const subObjectInfos = await fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, updatedObjectInfoMap, subLevelFields, path, objectInfoService);
51204
51427
  updatedObjectInfoMap = { ...updatedObjectInfoMap, ...subObjectInfos };
51205
51428
  }
51206
51429
  }
@@ -51215,27 +51438,29 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
51215
51438
  * 'path' and 'queryNode' is 1 level above the 'filterNode'
51216
51439
  * @param filterNode filter node which needs to be injected. For example, 'State' ObjectFieldNode within filter 'where: { State: { eq: "Nova Scotia" }}'
51217
51440
  * @param idState ID state will be updated to determine if the ID fields in AST need to be swapped. The swapping happens later.
51218
- * @param path path to the current filterNode's parent. For example, path could be 'ServiceApointment' when filterNode is 'State'. If the path does not exist in 'objectInfoApiMap', parent node is not an field of relationship or recordQuery
51441
+ * @param parentPath path to the current filterNode's parent. For example, path could be 'ServiceApointment' when filterNode is 'State'. If the path does not exist in 'pathToObjectApiNamesMap', parent node is not an field of relationship or recordQuery
51442
+ * @param isParentPolymorphic true if parent points to a polymorphic field.
51219
51443
  * @param queryNode referece FieldNode which provides the information if 'filterNode' exist in it nor not.
51220
51444
  * @param objectInfos ObjectInfo map used in injection. If ObjectInfo misses or field does not exist in ObjectInfo, the error will be thrown
51221
- * @param objectInfoApiMap map used to locate the ObjectInfo. The key is path to a field, value is the ObjectInfo's apiName array. In the case of polymorphic fields, the apiName array have 2 or more elements. For example, 'ServiceAppointment' -> ['ServiceAppointment']; 'ServiceAppointment_Account' -> ['Account'], 'ServiceAppointment_Owner' -> ['User', 'Group'].
51445
+ * @param pathToObjectApiNamesMap map used to locate the ObjectInfo. The key is path to a field, value is the ObjectInfo's apiName array. In the case of polymorphic fields, the apiName array have 2 or more elements. For example, 'ServiceAppointment' -> ['ServiceAppointment']; 'ServiceAppointment_Account' -> ['Account'], 'ServiceAppointment_Owner' -> ['User', 'Group'].
51222
51446
  * @param draftFunctions functions for working with record ids that may be draft-created ids
51223
51447
  * @returns an array of nodes with injected fields
51224
51448
  */
51225
- function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode) {
51449
+ function injectFilter(filterNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode) {
51226
51450
  const injectedSelections = [];
51451
+ let isPolymorphicField = false;
51227
51452
  switch (filterNode.kind) {
51228
51453
  case Kind.ARGUMENT:
51229
51454
  if (filterNode.value.kind !== 'ObjectValue')
51230
51455
  return [];
51231
51456
  filterNode.value.fields.forEach((objectFieldNode) => {
51232
- let subResults = injectFilter(objectFieldNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode);
51457
+ let subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
51233
51458
  for (const subResult of subResults) {
51234
51459
  mergeOrAddToGroup(injectedSelections, subResult);
51235
51460
  }
51236
51461
  // multiple Ids might need to be swapped. remember their paths for faster write.
51237
51462
  if (idState.swapNeeded) {
51238
- idState.paths.push(path);
51463
+ idState.paths.push(parentPath);
51239
51464
  }
51240
51465
  });
51241
51466
  return injectedSelections;
@@ -51244,7 +51469,7 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
51244
51469
  case Kind.LIST: {
51245
51470
  filterNode.value.values.filter(isObjectValueNode).forEach((objectValueNode) => {
51246
51471
  objectValueNode.fields.forEach((objectFieldNode) => {
51247
- const subResults = injectFilter(objectFieldNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode);
51472
+ const subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
51248
51473
  for (const subResult of subResults) {
51249
51474
  mergeOrAddToGroup(injectedSelections, subResult);
51250
51475
  }
@@ -51255,7 +51480,7 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
51255
51480
  case Kind.OBJECT: {
51256
51481
  if (filterNode.name.value === 'not') {
51257
51482
  filterNode.value.fields.forEach((objectFieldNode) => {
51258
- const subResults = injectFilter(objectFieldNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode);
51483
+ const subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
51259
51484
  for (const subResult of subResults) {
51260
51485
  mergeOrAddToGroup(injectedSelections, subResult);
51261
51486
  }
@@ -51265,15 +51490,15 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
51265
51490
  let apiNames = [];
51266
51491
  let isScalarField = false;
51267
51492
  //It is possible that this is a polymorphic field
51268
- apiNames = objectInfoApiMap[path];
51269
- // example: path: 'ServiceAppointment_LastModifiedDate'; filterNode: '{eq: {literal: LAST_WEEK}}'. queryNode: 'LastModifedDate { value}' FilterNode's parent has been verifed as a valid node
51493
+ apiNames = pathToObjectApiNamesMap[parentPath];
51494
+ // example: path: 'ServiceAppointment#LastModifiedDate'; filterNode: '{eq: {literal: LAST_WEEK}}'. queryNode: 'LastModifedDate { value}' FilterNode's parent has been verifed as a valid node
51270
51495
  if (apiNames === undefined) {
51271
51496
  isScalarField = true;
51272
51497
  }
51273
51498
  else {
51274
51499
  if (apiNames.some((apiName) => objectInfos[apiName] === undefined)) {
51275
51500
  // eslint-disable-next-line
51276
- throw new Error(`ObjectInfo is missing for ${path}`);
51501
+ throw new Error(`ObjectInfo is missing for ${parentPath}`);
51277
51502
  }
51278
51503
  }
51279
51504
  if (isScalarField) {
@@ -51295,29 +51520,19 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
51295
51520
  }
51296
51521
  });
51297
51522
  let isSpanning = false;
51523
+ // if true, current node is a polymorphic concrete type node. For example, field node `User` under `Owner`
51298
51524
  let isInlineFragment = false;
51299
- let isPolymorphicField = false;
51300
51525
  let isTypeNameExisting = false;
51301
51526
  let curPath;
51302
51527
  let fieldName = filterNode.name.value;
51303
- curPath = `${path}_${fieldName}`;
51304
- if (objectInfoApiMap[curPath] && objectInfoApiMap[curPath].length > 0) {
51528
+ curPath = `${parentPath}#${fieldName}`;
51529
+ if (pathToObjectApiNamesMap[curPath] &&
51530
+ pathToObjectApiNamesMap[curPath].length > 0) {
51305
51531
  isSpanning = true;
51306
- if (objectInfoApiMap[curPath].length === 1) {
51307
- if (objectInfoApiMap[path] &&
51308
- objectInfoApiMap[path].length >= 1 &&
51309
- objectInfoApiMap[path].includes(objectInfoApiMap[curPath][0])) {
51310
- isInlineFragment = true;
51311
- }
51312
- }
51313
- // Checks if the current filter node is a polymorphic field. 'ServiceAppointment_Owner' --> ['User']; 'ServiceAppointment_Owner_User' --> ['User']
51314
- const childApiName = objectInfoApiMap[curPath][0];
51315
- const trialApiNames = objectInfoApiMap[`${curPath}_${childApiName}`];
51316
- if (trialApiNames !== undefined &&
51317
- trialApiNames.length === 1 &&
51318
- trialApiNames[0] === childApiName) {
51319
- isPolymorphicField = true;
51320
- }
51532
+ isInlineFragment =
51533
+ isParentPolymorphic &&
51534
+ pathToObjectApiNamesMap[curPath].length === 1;
51535
+ isPolymorphicField = isPolymorphicFieldPath(curPath, pathToObjectApiNamesMap, objectInfos);
51321
51536
  }
51322
51537
  // When filter node is at InLineFragment Level(a concrete entity of polymorphic field), query node is one level up. For example, ObjectFieldNode is ...{User:{...}}, queryNode is Owner:[User]
51323
51538
  if (isInlineFragment) {
@@ -51361,23 +51576,25 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
51361
51576
  throw new Error(`Field ${fieldName} does not exist in ${apiNames[0]}`);
51362
51577
  }
51363
51578
  }
51364
- const objectInfoName = objectInfoApiMap[curPath] !== undefined
51365
- ? objectInfoApiMap[curPath][0]
51366
- : objectInfoApiMap[path][0];
51579
+ const objectInfoName = pathToObjectApiNamesMap[curPath] !== undefined
51580
+ ? pathToObjectApiNamesMap[curPath][0]
51581
+ : pathToObjectApiNamesMap[parentPath][0];
51367
51582
  const isIdField = isFieldAnIdField(filterNode.name.value, objectInfos[objectInfoName]);
51368
51583
  if (!isIdField) {
51369
51584
  let subSelectionNodes = [];
51370
51585
  let subFieldsHasId = false;
51371
- filterNode.value.fields.forEach((subFieldNode) => {
51372
- // Check if the filter field has the 'Id'
51373
- if (isFieldAnIdField(subFieldNode.name.value, objectInfos[objectInfoName])) {
51374
- subFieldsHasId = true;
51375
- updateIDInfo(subFieldNode, idState, draftFunctions);
51376
- }
51377
- // try injecting the fields within predicate no matter it has relation or not.
51378
- let subResults = injectFilter(subFieldNode, idState, curPath, objectInfos, objectInfoApiMap, draftFunctions, existingFields ? existingFields[0] : undefined);
51379
- subSelectionNodes = subSelectionNodes.concat(subResults);
51380
- });
51586
+ if (isSpanning) {
51587
+ filterNode.value.fields.forEach((subFieldNode) => {
51588
+ // Check if the filter field has the 'Id'
51589
+ if (isFieldAnIdField(subFieldNode.name.value, objectInfos[objectInfoName])) {
51590
+ subFieldsHasId = true;
51591
+ updateIDInfo(subFieldNode, idState, draftFunctions);
51592
+ }
51593
+ // try injecting the fields within predicate no matter it has relation or not.
51594
+ let subResults = injectFilter(subFieldNode, idState, curPath, isPolymorphicField, objectInfos, pathToObjectApiNamesMap, draftFunctions, existingFields ? existingFields[0] : undefined);
51595
+ subSelectionNodes = subSelectionNodes.concat(subResults);
51596
+ });
51597
+ }
51381
51598
  if (!subFieldsHasId) {
51382
51599
  // Check if the query field has the 'Id'
51383
51600
  const existingIdsInQuery = existingFields &&
@@ -51406,6 +51623,7 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
51406
51623
  }
51407
51624
  //Inject Conditions: 1. Same field does not exist 2. Same fields has different children. 3. Filter spanning field does not have Id. 4. InLineFragment does not have the '__typename' field
51408
51625
  if (!existingFields ||
51626
+ existingFields.length === 0 ||
51409
51627
  subSelectionNodes.length > 0 ||
51410
51628
  (isSpanning && !subFieldsHasId) ||
51411
51629
  (isInlineFragment && !isTypeNameExisting)) {
@@ -51536,6 +51754,44 @@ function mergeOrAddToGroup(group, element) {
51536
51754
  }
51537
51755
  group.push(element);
51538
51756
  }
51757
+ // checks if the path points to a polymorphic field. For example, for the below `pathToObjectApiNamesMap`
51758
+ // {
51759
+ // 'ServiceAppointment' -> ['ServiceAppointment']
51760
+ // 'ServiceAppointment#Owner' --> ['User'],
51761
+ // 'ServiceAppointment#Owner#User' --> ['User']
51762
+ // }
51763
+ // path `ServiceAppointment#Owner` points to a polymorphic field, but path `ServiceAppointment#Owner#User` does not.
51764
+ function isPolymorphicFieldPath(path, pathToObjectApiNamesMap, objectInfos) {
51765
+ const lastSegmentIndex = path.lastIndexOf('#');
51766
+ if (lastSegmentIndex < 0) {
51767
+ return false;
51768
+ }
51769
+ const lastSegment = path.slice(lastSegmentIndex + 1);
51770
+ const parentApiPath = path.slice(0, lastSegmentIndex);
51771
+ if (pathToObjectApiNamesMap[parentApiPath] === undefined) {
51772
+ return false;
51773
+ }
51774
+ const parentObjectApiNames = pathToObjectApiNamesMap[parentApiPath];
51775
+ // If the last segment is a Polymorphic field, its immediate parent is a concrete object entity, which has 1 objectApiName mapped to the parent path in `pathToObjectApiNamesMap`.
51776
+ // For example, we like to check if `ServiceAppointment#Owner` path is polymorphic. The last segment is `Owner` and its parent `ServiceAppointment` has one element (which is also `ServiceAppointment`) array as its value.
51777
+ // Below are the entries in `pathToObjectApiNamesMap`
51778
+ // {
51779
+ // `ServiceAppointmen`t: [`ServiceAppointment`],
51780
+ // `ServiceAppointment#Owner`: [`User`, `Group`],
51781
+ // `ServiceAppointment#Owner#User`: [`User`],
51782
+ // `ServiceAppointment#Owner#Group`: [`Group`],
51783
+ // }
51784
+ if (parentObjectApiNames.length !== 1) {
51785
+ return false;
51786
+ }
51787
+ const parentObjectInfo = objectInfos[parentObjectApiNames[0]];
51788
+ const relationshipField = referenceIdFieldForRelationship(lastSegment);
51789
+ let fieldDefinition = parentObjectInfo.fields[relationshipField];
51790
+ if (fieldDefinition === undefined) {
51791
+ return false;
51792
+ }
51793
+ return fieldDefinition.polymorphicForeignKey;
51794
+ }
51539
51795
  function isFieldAnIdField(fieldName, objectInfo) {
51540
51796
  if (fieldName === 'Id')
51541
51797
  return true;
@@ -51588,10 +51844,10 @@ function updateIDInfo(fieldNode, idState, draftFunctions) {
51588
51844
  * @param parentNode parent node of param 1
51589
51845
  * @param ancestors ancester of param 1
51590
51846
  * @param objectInfos ObjectInfo map used in injection. If ObjectInfo misses or field does not exist in ObjectInfo, the error will be thrown
51591
- * @param objectInfoApiMap map used to locate the ObjectInfo. The key is path to a field, value is the ObjectInfo's apiName array. In the case of polymorphic fields, the apiName array have 2 or more elements. For example, 'ServiceAppointment' -> ['ServiceAppointment']; 'ServiceAppointment_Account' -> ['Account'], 'ServiceAppointment_Owner' -> ['User', 'Group'].
51847
+ * @param pathToObjectApiNamesMap map used to locate the ObjectInfo. The key is path to a field, value is the ObjectInfo's apiName array. In the case of polymorphic fields, the apiName array have 2 or more elements. For example, 'ServiceAppointment' -> ['ServiceAppointment']; 'ServiceAppointment#Account' -> ['Account'], 'ServiceAppointment#Owner' -> ['User', 'Group'].
51592
51848
  * @return injected SelectionNodes used to construct the InlineFragment.
51593
51849
  */
51594
- function injectFields(selections, parentNode, ancestors, objectInfos, objectInfoApiMap) {
51850
+ function injectFields(selections, parentNode, parentPath, ancestors, objectInfos, pathToObjectApiNamesMap) {
51595
51851
  /**
51596
51852
  * 1 parentship can return 2 FieldNode which need to be flattened
51597
51853
  * Concact: { ** Contact { ** ContactId {
@@ -51609,6 +51865,10 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
51609
51865
  if (!selection.selectionSet) {
51610
51866
  return selection;
51611
51867
  }
51868
+ const segment = isFieldNode(selection)
51869
+ ? selection.name.value
51870
+ : selection.typeCondition.name.value;
51871
+ const curPath = `${parentPath}#${segment}`;
51612
51872
  const spanningSubSelections = [];
51613
51873
  for (const subSelection of selection.selectionSet.selections) {
51614
51874
  if (isFieldSpanning(subSelection, selection)) {
@@ -51616,7 +51876,7 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
51616
51876
  }
51617
51877
  }
51618
51878
  // Handles multiple level field injection like 'ServiceAppointment' --> 'Account' --> 'Owner'
51619
- const subInjectedSelections = injectFields(spanningSubSelections, selection, ancestors, objectInfos, objectInfoApiMap);
51879
+ const subInjectedSelections = injectFields(spanningSubSelections, selection, curPath, ancestors, objectInfos, pathToObjectApiNamesMap);
51620
51880
  if (!selection.selectionSet) {
51621
51881
  return selection;
51622
51882
  }
@@ -51659,7 +51919,7 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
51659
51919
  }
51660
51920
  }
51661
51921
  // For polymorphic fields, the Id field is excluded.
51662
- const excludeId = selection.selectionSet.selections.every(isInlineFragmentNode);
51922
+ const excludeId = isPolymorphicFieldPath(curPath, pathToObjectApiNamesMap, objectInfos);
51663
51923
  const idSelection = [];
51664
51924
  if (!excludeId && !hasIdAlready) {
51665
51925
  idSelection.push({
@@ -51670,8 +51930,8 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
51670
51930
  },
51671
51931
  });
51672
51932
  }
51673
- // Inject '__typename' for polymorphic fields. '__typename' field acts as a reference to concrete type of a polymorphic field and is used to match JSON response with AST node. For more detail,
51674
- // please reference 'removeSyntheticFields'.
51933
+ // Inject '__typename' for InlineFragment. '__typename' field acts as a reference to concrete type of a polymorphic field or a standard field in the returned GQL response, which equals to
51934
+ // `typedCondition` of the InlineFragment in the query AST. It is used to match JSON response with AST node. For more detail, please reference 'removeSyntheticFields'.
51675
51935
  if (isInlineFragmentNode(selection) &&
51676
51936
  !selection.selectionSet.selections.find((selection) => isFieldNode(selection) && selection.name.value === '__typename')) {
51677
51937
  idSelection.push({
@@ -51705,7 +51965,7 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
51705
51965
  if (isFieldNode(parentNode) && parentNode.selectionSet && parentNode.name.value === 'node') {
51706
51966
  if (parentNode.selectionSet.selections
51707
51967
  .filter(isFieldOrInlineFragmentNode)
51708
- .some((selectionNode) => isRelationship(selectionNode, 'childRelationship'))) {
51968
+ .some((selectionNode) => isRelationship(selectionNode, CHILD_RELATIONSHIP))) {
51709
51969
  if (!parentNode.selectionSet.selections
51710
51970
  .filter(isFieldNode)
51711
51971
  .some((sibling) => sibling.name.value === 'Id')) {
@@ -51724,15 +51984,15 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
51724
51984
  if (parentInfo.parentIndex >= 0) {
51725
51985
  // example node { TimeSheetEntries { edges { node { Id }}}}
51726
51986
  const parent = parentInfo.node;
51727
- if (isRelationship(parent, 'childRelationship')) {
51987
+ if (isRelationship(parent, CHILD_RELATIONSHIP)) {
51728
51988
  const unVisitedAncestors = ancestors.slice(0, parentInfo.parentIndex);
51729
51989
  // path : "TimeSheet"
51730
51990
  const grandParentPath = findAncesterPath(unVisitedAncestors);
51731
- if (objectInfoApiMap &&
51732
- objectInfoApiMap[grandParentPath] &&
51991
+ if (pathToObjectApiNamesMap &&
51992
+ pathToObjectApiNamesMap[grandParentPath] &&
51733
51993
  objectInfos &&
51734
- objectInfos[objectInfoApiMap[grandParentPath][0]]) {
51735
- const grandParentObjectInfo = objectInfos[objectInfoApiMap[grandParentPath][0]];
51994
+ objectInfos[pathToObjectApiNamesMap[grandParentPath][0]]) {
51995
+ const grandParentObjectInfo = objectInfos[pathToObjectApiNamesMap[grandParentPath][0]];
51736
51996
  // exmaple "TimeSheetEntries"
51737
51997
  const parentFieldName = parent.name.value;
51738
51998
  const targetRelationship = grandParentObjectInfo.childRelationships.find((childRelationship) => {
@@ -51853,7 +52113,7 @@ const assignedToMeFragmentSelections = [
51853
52113
  },
51854
52114
  value: {
51855
52115
  kind: 'StringValue',
51856
- value: 'childRelationship',
52116
+ value: CHILD_RELATIONSHIP,
51857
52117
  block: false,
51858
52118
  },
51859
52119
  },
@@ -51945,7 +52205,7 @@ const assignedToMeFragmentSelections = [
51945
52205
  },
51946
52206
  value: {
51947
52207
  kind: 'StringValue',
51948
- value: 'parentRelationship',
52208
+ value: PARENT_RELATIONSHIP,
51949
52209
  block: false,
51950
52210
  },
51951
52211
  },
@@ -52082,7 +52342,9 @@ function handleNonArrayJsonProperty(selection, fieldName, jsonInput, jsonOutput)
52082
52342
  jsonOutput[fieldName] = null;
52083
52343
  return;
52084
52344
  }
52085
- jsonOutput[fieldName] = {};
52345
+ if (jsonOutput[fieldName] === undefined) {
52346
+ jsonOutput[fieldName] = {};
52347
+ }
52086
52348
  createUserJsonOutput(selection, jsonInput[fieldName], jsonOutput[fieldName]);
52087
52349
  }
52088
52350
  else {
@@ -52814,34 +53076,42 @@ function applyReferenceLinksToDraft(record, draftMetadata) {
52814
53076
  }
52815
53077
  const { dataType, relationshipName, referenceToInfos } = fieldInfo;
52816
53078
  const draftFieldValue = record.fields[draftField].value;
52817
- if (dataType === 'Reference' && relationshipName !== null && draftFieldValue !== null) {
52818
- if (typeof draftFieldValue !== 'string') {
52819
- throw Error('reference field value is not a string');
53079
+ if (dataType === 'Reference' && relationshipName !== null) {
53080
+ if (draftFieldValue === null) {
53081
+ recordFields[relationshipName] = {
53082
+ displayValue: null,
53083
+ value: null,
53084
+ };
52820
53085
  }
52821
- const key = getRecordKeyForId(luvio, draftFieldValue);
52822
- const referencedRecord = referencedRecords.get(key);
52823
- recordFields[relationshipName] = {
52824
- displayValue: null,
52825
- value: createLink$2(key),
52826
- };
52827
- // for custom objects, we select the 'Name' field
52828
- // otherwise we check the object info for name fields.
52829
- //if there are multiple we select 'Name' if it exists, otherwise the first one
52830
- if (referencedRecord !== undefined && referenceToInfos.length > 0) {
52831
- let nameField;
52832
- const referenceToInfo = referenceToInfos[0];
52833
- const nameFields = referenceToInfo.nameFields;
52834
- if (nameFields.length !== 0) {
52835
- nameField = nameFields.find((x) => x === 'Name');
52836
- if (nameField === undefined) {
52837
- nameField = nameFields[0];
52838
- }
53086
+ else {
53087
+ if (typeof draftFieldValue !== 'string') {
53088
+ throw Error('reference field value is not a string');
52839
53089
  }
52840
- if (nameField !== undefined) {
52841
- const nameFieldRef = referencedRecord.fields[nameField];
52842
- if (nameFieldRef) {
52843
- recordFields[relationshipName].displayValue =
52844
- (_a = nameFieldRef.displayValue) !== null && _a !== void 0 ? _a : nameFieldRef.value;
53090
+ const key = getRecordKeyForId(luvio, draftFieldValue);
53091
+ const referencedRecord = referencedRecords.get(key);
53092
+ recordFields[relationshipName] = {
53093
+ displayValue: null,
53094
+ value: createLink$2(key),
53095
+ };
53096
+ // for custom objects, we select the 'Name' field
53097
+ // otherwise we check the object info for name fields.
53098
+ //if there are multiple we select 'Name' if it exists, otherwise the first one
53099
+ if (referencedRecord !== undefined && referenceToInfos.length > 0) {
53100
+ let nameField;
53101
+ const referenceToInfo = referenceToInfos[0];
53102
+ const nameFields = referenceToInfo.nameFields;
53103
+ if (nameFields.length !== 0) {
53104
+ nameField = nameFields.find((x) => x === 'Name');
53105
+ if (nameField === undefined) {
53106
+ nameField = nameFields[0];
53107
+ }
53108
+ }
53109
+ if (nameField !== undefined) {
53110
+ const nameFieldRef = referencedRecord.fields[nameField];
53111
+ if (nameFieldRef) {
53112
+ recordFields[relationshipName].displayValue =
53113
+ (_a = nameFieldRef.displayValue) !== null && _a !== void 0 ? _a : nameFieldRef.value;
53114
+ }
52845
53115
  }
52846
53116
  }
52847
53117
  }
@@ -53134,17 +53404,8 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
53134
53404
  };
53135
53405
  for (const fieldName of keys$3(recordWithSpanningRefLinks.fields)) {
53136
53406
  const fieldKey = buildRecordFieldStoreKey(key, fieldName);
53137
- if (this.collectedFields[fieldKey] !== undefined) {
53138
- const fieldData = recordWithSpanningRefLinks.fields[fieldName];
53139
- normalizedRecord.fields[fieldName] = { __ref: fieldKey };
53140
- publishData(fieldKey, fieldData);
53141
- }
53142
- else if (recordWithSpanningRefLinks.fields[fieldName] &&
53143
- recordWithSpanningRefLinks.fields[fieldName].value &&
53144
- recordWithSpanningRefLinks.fields[fieldName].value.__ref !== undefined) {
53145
- normalizedRecord.fields[fieldName] = { __ref: fieldKey };
53146
- publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
53147
- }
53407
+ normalizedRecord.fields[fieldName] = { __ref: fieldKey };
53408
+ publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
53148
53409
  }
53149
53410
  // publish the normalized record
53150
53411
  publishData(key, normalizedRecord);
@@ -54043,6 +54304,9 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
54043
54304
  if (!rebuildResult.errors) {
54044
54305
  rebuildResult = removeSyntheticFields(rebuildResult, config.query);
54045
54306
  }
54307
+ if (objectsDeepEqual(rebuildResult, originalSnapshot.data)) {
54308
+ return originalSnapshot;
54309
+ }
54046
54310
  // 'originalSnapshot' is the local eval snapshot subscribed. It is always in 'Fulfilled' state. This behavior would change once W-1273462(rebuild non-evaluated snapshot when the graphql local eval rebuild is triggered) is resolved.
54047
54311
  return {
54048
54312
  ...originalSnapshot,
@@ -54061,7 +54325,7 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
54061
54325
  // Fulfilled snapshot (this only happens in this code path if
54062
54326
  // the error is network error or 504), otherwise we spread over
54063
54327
  // the non-eval'ed snapshot (which will be either Fulfilled or Stale)
54064
- return nonEvaluatedSnapshot.state === 'Error'
54328
+ const resultSnapshot = nonEvaluatedSnapshot.state === 'Error'
54065
54329
  ? createLocalEvalSnapshot(gqlResult, seenRecords, recordId, rebuildWithLocalEval)
54066
54330
  : {
54067
54331
  ...nonEvaluatedSnapshot,
@@ -54070,6 +54334,22 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
54070
54334
  seenRecords,
54071
54335
  rebuildWithLocalEval,
54072
54336
  };
54337
+ const { refresh, state } = resultSnapshot;
54338
+ if (state !== 'Error' && refresh) {
54339
+ // after refreshing a graphql snapshot, we want to force a rebuild regardless
54340
+ // of if the call failed or not or if the data changed or not because we want
54341
+ // to make sure any potential new drafts are picked up
54342
+ resultSnapshot.refresh = {
54343
+ ...refresh,
54344
+ resolve: (config) => {
54345
+ return refresh.resolve(config).finally(() => {
54346
+ luvio.storePublish(resultSnapshot.recordId, undefined);
54347
+ luvio.storeBroadcast();
54348
+ });
54349
+ },
54350
+ };
54351
+ }
54352
+ return resultSnapshot;
54073
54353
  };
54074
54354
  }
54075
54355
 
@@ -55123,7 +55403,10 @@ function isErrorResponse(response) {
55123
55403
  * @returns the merged record
55124
55404
  */
55125
55405
  function mergeAggregateUiResponse(response, mergeFunc) {
55126
- const { body } = response;
55406
+ if (response.ok === false) {
55407
+ return response;
55408
+ }
55409
+ const body = response.body;
55127
55410
  try {
55128
55411
  if (body === null ||
55129
55412
  body === undefined ||
@@ -56954,6 +57237,9 @@ class PrimingSession extends EventEmitter {
56954
57237
  this.ldsRecordRefresher = config.ldsRecordRefresher;
56955
57238
  this.networkWorkerPool = new AsyncWorkerPool(this.concurrency);
56956
57239
  this.useBatchGQL = ldsPrimingGraphqlBatch.isOpen({ fallback: false });
57240
+ if (this.useBatchGQL) {
57241
+ this.batchSize = this.batchSize / DEFAULT_GQL_QUERY_BATCH_SIZE;
57242
+ }
56957
57243
  this.conflictPool = new ConflictPool(config.store, this.objectInfoLoader);
56958
57244
  }
56959
57245
  // function that enqueues priming work
@@ -57810,7 +58096,7 @@ register({
57810
58096
  id: '@salesforce/lds-network-adapter',
57811
58097
  instrument: instrument$1,
57812
58098
  });
57813
- // version: 1.228.1-4e6356f71
58099
+ // version: 1.229.0-dev10-bc9ef2513
57814
58100
 
57815
58101
  const { create: create$2, keys: keys$2 } = Object;
57816
58102
  const { stringify: stringify$1, parse: parse$1 } = JSON;
@@ -71134,7 +71420,7 @@ function getFieldType$6(field) {
71134
71420
  }
71135
71421
  }
71136
71422
 
71137
- const { assign, create: create$1, freeze: freeze$1, keys: keys$1 } = Object;
71423
+ const { assign, create: create$1, freeze: freeze$1, isFrozen, keys: keys$1 } = Object;
71138
71424
  const { isArray: isArray$1 } = Array;
71139
71425
  const { concat, filter, includes, push, reduce } = Array.prototype;
71140
71426
 
@@ -72573,6 +72859,7 @@ function buildSelectionForField$7(source, reader, sel, variables, fragments, isC
72573
72859
  }
72574
72860
  if (fieldData === null) {
72575
72861
  reader.assignScalar(requestedFieldName, sink, fieldData);
72862
+ reader.exitPath();
72576
72863
  return sink;
72577
72864
  }
72578
72865
  const fieldType = getFieldType(sel);
@@ -72598,17 +72885,8 @@ function buildSelectionForField$7(source, reader, sel, variables, fragments, isC
72598
72885
  return sink;
72599
72886
  }
72600
72887
  function selectTypeLink(sel, fieldData, reader, key, sink, variables, fragments, version, selectFn, isCursorConnection) {
72601
- const resolvedLink = reader.read({
72602
- recordId: fieldData.__ref,
72603
- node: {
72604
- kind: 'Fragment',
72605
- private: [],
72606
- opaque: true,
72607
- version,
72608
- },
72609
- variables: {}
72610
- });
72611
- if (resolvedLink.data !== undefined) {
72888
+ const resolvedLink = resolveLink$1(reader, fieldData, version);
72889
+ if (resolvedLink && resolvedLink.data !== undefined) {
72612
72890
  if (isCursorConnection) {
72613
72891
  selectTypeLinkWithPagination(resolvedLink, sel, fieldData, reader, key, sink, variables, fragments, selectFn);
72614
72892
  }
@@ -73184,19 +73462,11 @@ function selectType$6(typename, sel, fieldData, reader, key, sink, variables, fr
73184
73462
  }
73185
73463
  case 'PolymorphicParentRelationship':
73186
73464
  case 'RecordRepresentation': {
73187
- const spanningFieldLink = reader.read({
73188
- recordId: fieldData.__ref,
73189
- node: {
73190
- kind: 'Fragment',
73191
- private: [],
73192
- opaque: true,
73193
- version: VERSION$f,
73194
- },
73195
- variables: {},
73196
- });
73197
- reader.markSeenId(fieldData.__ref);
73198
- const resolvedSpanningFieldValue = spanningFieldLink.data;
73465
+ const spanningFieldLink = resolveLink$1(reader, fieldData, VERSION$f);
73466
+ const resolvedSpanningFieldValue = spanningFieldLink && spanningFieldLink.data;
73467
+ const fieldDataRef = fieldData.__ref;
73199
73468
  if (resolvedSpanningFieldValue !== undefined) {
73469
+ reader.markSeenId(fieldDataRef);
73200
73470
  const { value: spanningFieldResult } = resolvedSpanningFieldValue;
73201
73471
  // Handle null values - graphql will return it at the field level, not return nested { value: null }
73202
73472
  if (spanningFieldResult === null || typeof spanningFieldResult !== 'object') {
@@ -73220,7 +73490,9 @@ function selectType$6(typename, sel, fieldData, reader, key, sink, variables, fr
73220
73490
  }
73221
73491
  }
73222
73492
  else {
73223
- reader.markMissingLink(fieldData.__ref);
73493
+ if (fieldDataRef !== undefined) {
73494
+ reader.markMissingLink(fieldDataRef);
73495
+ }
73224
73496
  reader.markMissing();
73225
73497
  }
73226
73498
  break;
@@ -76256,7 +76528,7 @@ register({
76256
76528
  configuration: { ...configurationForGraphQLAdapters },
76257
76529
  instrument,
76258
76530
  });
76259
- // version: 1.228.1-0cb6f94f1
76531
+ // version: 1.229.0-dev10-abb060196
76260
76532
 
76261
76533
  // On core the unstable adapters are re-exported with different names,
76262
76534
 
@@ -78503,7 +78775,7 @@ withDefaultLuvio((luvio) => {
78503
78775
  unstable_graphQL_imperative = createImperativeAdapter(luvio, createInstrumentedAdapter(ldsAdapter, adapterMetadata), adapterMetadata);
78504
78776
  graphQLImperative = ldsAdapter;
78505
78777
  });
78506
- // version: 1.228.1-0cb6f94f1
78778
+ // version: 1.229.0-dev10-abb060196
78507
78779
 
78508
78780
  var gqlApi = /*#__PURE__*/Object.freeze({
78509
78781
  __proto__: null,
@@ -79217,4 +79489,4 @@ const { luvio } = getRuntime();
79217
79489
  setDefaultLuvio({ luvio });
79218
79490
 
79219
79491
  export { createPrimingSession, draftManager, draftQueue, executeAdapter, executeMutatingAdapter, getImperativeAdapterNames, invokeAdapter, invokeAdapterWithDraftToReplace, invokeAdapterWithMetadata, nimbusDraftQueue, registerReportObserver, setMetadataTTL, setUiApiRecordTTL, subscribeToAdapter };
79220
- // version: 1.228.1-4e6356f71
79492
+ // version: 1.229.0-dev10-bc9ef2513