@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.
@@ -31,12 +31,15 @@
31
31
  const { isArray: isArray$9 } = Array;
32
32
  const { push: push$5, indexOf, slice: slice$2 } = Array.prototype;
33
33
  const { parse: parse$a, stringify: stringify$a } = JSON;
34
+ const WeakSetCtor = WeakSet;
34
35
 
36
+ const deeplyFrozen = new WeakSetCtor();
35
37
  function deepFreeze(value) {
36
- // No need to freeze primitives
37
- if (typeof value !== 'object' || value === null) {
38
+ // No need to freeze primitives or already frozen stuff
39
+ if (typeof value !== 'object' || value === null || deeplyFrozen.has(value)) {
38
40
  return;
39
41
  }
42
+ deeplyFrozen.add(value);
40
43
  if (isArray$9(value)) {
41
44
  for (let i = 0, len = value.length; i < len; i += 1) {
42
45
  deepFreeze(value[i]);
@@ -1733,6 +1736,10 @@
1733
1736
  }
1734
1737
  }
1735
1738
  markVisited(canonicalKey) {
1739
+ if (typeof canonicalKey === 'string') {
1740
+ this.fallbackStringKeyInMemoryStore.markVisited(canonicalKey);
1741
+ return;
1742
+ }
1736
1743
  const { visitedIdsSet, reverseRedirectKeysMap } = this;
1737
1744
  let redirectKey = canonicalKey;
1738
1745
  // mark all redirects leading up to the canonical key as visited so
@@ -2044,7 +2051,7 @@
2044
2051
  if (isStoreRecordError$1(linked)) {
2045
2052
  return new GraphNodeError(this.store, linked);
2046
2053
  }
2047
- return new GraphNode(this.store, linked);
2054
+ return new GraphNode(this.store, linked, __ref);
2048
2055
  }
2049
2056
  linkData() {
2050
2057
  return this.data.data;
@@ -2054,10 +2061,11 @@
2054
2061
  }
2055
2062
  }
2056
2063
  class GraphNode {
2057
- constructor(store, data) {
2064
+ constructor(store, data, storeKey) {
2058
2065
  this.type = GraphNodeType$1.Node;
2059
2066
  this.store = store;
2060
2067
  this.data = data;
2068
+ this.storeKey = storeKey;
2061
2069
  }
2062
2070
  object(propertyName) {
2063
2071
  const value = this.data[propertyName];
@@ -2067,7 +2075,8 @@
2067
2075
  if (typeof value !== 'object' || value === null) {
2068
2076
  throw new Error(`Cannot walk to path ${String(propertyName)}. "${String(propertyName)}" is a scalar: "${value}"`);
2069
2077
  }
2070
- return new GraphNode(this.store, value);
2078
+ // We're walking to an object property on the current store record, pass the storeKey down.
2079
+ return new GraphNode(this.store, value, this.storeKey);
2071
2080
  }
2072
2081
  link(propertyName) {
2073
2082
  const value = this.data[propertyName];
@@ -2097,6 +2106,8 @@
2097
2106
  }
2098
2107
  write(propertyName, value) {
2099
2108
  this.data[propertyName] = value;
2109
+ const canonicalKey = this.store.getCanonicalRecordId(this.storeKey);
2110
+ this.store.markVisited(canonicalKey);
2100
2111
  }
2101
2112
  isUndefined(propertyName) {
2102
2113
  return this.data[propertyName] === undefined;
@@ -2281,6 +2292,34 @@
2281
2292
  const FRAGMENT_READ_RESULT_MISSING = {
2282
2293
  state: FragmentReadResultState$1.Missing,
2283
2294
  };
2295
+ function resolveLink$1(reader, storeLink, version) {
2296
+ const { StoreLinkStateValues } = reader;
2297
+ const linkState = reader.getLinkState(storeLink);
2298
+ switch (linkState.state) {
2299
+ case StoreLinkStateValues.RefNotPresent:
2300
+ case StoreLinkStateValues.NotPresent:
2301
+ case StoreLinkStateValues.Missing:
2302
+ reader.markMissingLink(storeLink.__ref);
2303
+ reader.markMissing();
2304
+ return;
2305
+ case StoreLinkStateValues.Pending:
2306
+ reader.markPending();
2307
+ return;
2308
+ case StoreLinkStateValues.Null:
2309
+ return;
2310
+ }
2311
+ const { key: __ref } = linkState;
2312
+ return reader.read({
2313
+ recordId: __ref,
2314
+ node: {
2315
+ kind: 'Fragment',
2316
+ private: [],
2317
+ opaque: true,
2318
+ version,
2319
+ },
2320
+ variables: {},
2321
+ });
2322
+ }
2284
2323
  class Reader {
2285
2324
  constructor(store, variables, refresh, baseSnapshot, ttlStrategy) {
2286
2325
  this.store = store;
@@ -3231,9 +3270,9 @@
3231
3270
  if (value === undefined) {
3232
3271
  return null;
3233
3272
  }
3234
- return this.wrapNormalizedGraphNode(value, store);
3273
+ return this.wrapNormalizedGraphNode(value, key, store);
3235
3274
  }
3236
- wrapNormalizedGraphNode(normalized, storeOverride) {
3275
+ wrapNormalizedGraphNode(normalized, key, storeOverride) {
3237
3276
  if (normalized === null) {
3238
3277
  return null;
3239
3278
  }
@@ -3241,7 +3280,7 @@
3241
3280
  if (isStoreRecordError$1(normalized)) {
3242
3281
  return new GraphNodeError(store, normalized);
3243
3282
  }
3244
- return new GraphNode(store, normalized);
3283
+ return new GraphNode(store, normalized, key);
3245
3284
  }
3246
3285
  withContext(adapter, options) {
3247
3286
  const { contextId, onContextLoaded } = options;
@@ -3536,8 +3575,8 @@
3536
3575
  getNode(key) {
3537
3576
  return this.environment.getNode(key);
3538
3577
  }
3539
- wrapNormalizedGraphNode(normalized) {
3540
- return this.environment.wrapNormalizedGraphNode(normalized);
3578
+ wrapNormalizedGraphNode(normalized, key) {
3579
+ return this.environment.wrapNormalizedGraphNode(normalized, key);
3541
3580
  }
3542
3581
  instrument(paramsBuilder) {
3543
3582
  const { instrument } = this.options;
@@ -3847,7 +3886,7 @@
3847
3886
  }
3848
3887
  return resourceParams;
3849
3888
  }
3850
- // engine version: 0.145.2-6a13677c
3889
+ // engine version: 0.146.0-dev5-a2ec6e3f
3851
3890
 
3852
3891
  /**
3853
3892
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -3974,7 +4013,7 @@
3974
4013
  }
3975
4014
  callbacks.push(callback);
3976
4015
  }
3977
- // version: 1.228.1-4e6356f71
4016
+ // version: 1.229.0-dev10-bc9ef2513
3978
4017
 
3979
4018
  // TODO [TD-0081508]: once that TD is fulfilled we can probably change this file
3980
4019
  function instrumentAdapter$1(createFunction, _metadata) {
@@ -15435,7 +15474,7 @@
15435
15474
  }
15436
15475
  return superResult;
15437
15476
  }
15438
- // version: 1.228.1-4e6356f71
15477
+ // version: 1.229.0-dev10-bc9ef2513
15439
15478
 
15440
15479
  function unwrap(data) {
15441
15480
  // The lwc-luvio bindings import a function from lwc called "unwrap".
@@ -15536,14 +15575,15 @@
15536
15575
  return undefined;
15537
15576
  });
15538
15577
  }
15539
- const { isArray: isArray$8 } = Array;
15540
- const { stringify: stringify$9 } = JSON;
15541
15578
 
15542
15579
  function isPromise$1(value) {
15543
15580
  // check for Thenable due to test frameworks using custom Promise impls
15544
15581
  return value.then !== undefined;
15545
15582
  }
15546
15583
 
15584
+ const { isArray: isArray$8 } = Array;
15585
+ const { stringify: stringify$9 } = JSON;
15586
+
15547
15587
  /**
15548
15588
  * (Re)throws an error after adding a prefix to the message.
15549
15589
  *
@@ -16358,7 +16398,7 @@
16358
16398
  const { apiFamily, name } = metadata;
16359
16399
  return createGraphQLWireAdapterConstructor$1(adapter, `${apiFamily}.${name}`, luvio, astResolver);
16360
16400
  }
16361
- // version: 1.228.1-4e6356f71
16401
+ // version: 1.229.0-dev10-bc9ef2513
16362
16402
 
16363
16403
  /**
16364
16404
  * Copyright (c) 2022, Salesforce, Inc.,
@@ -16457,11 +16497,11 @@
16457
16497
  TypeCheckShapes[TypeCheckShapes["Integer"] = 3] = "Integer";
16458
16498
  TypeCheckShapes[TypeCheckShapes["Unsupported"] = 4] = "Unsupported";
16459
16499
  })(TypeCheckShapes || (TypeCheckShapes = {}));
16460
- // engine version: 0.145.2-6a13677c
16500
+ // engine version: 0.146.0-dev5-a2ec6e3f
16461
16501
 
16462
16502
  const { keys: ObjectKeys$3, create: ObjectCreate$3 } = Object;
16463
16503
 
16464
- const { assign: assign$9, create: create$9, freeze: freeze$4, keys: keys$b } = Object;
16504
+ const { assign: assign$9, create: create$9, freeze: freeze$4, isFrozen: isFrozen$2, keys: keys$b } = Object;
16465
16505
 
16466
16506
  ObjectCreate$3(null);
16467
16507
 
@@ -16831,7 +16871,7 @@
16831
16871
  }
16832
16872
  const keyPrefix$1 = 'UiApi';
16833
16873
 
16834
- const { assign: assign$8, create: create$8, freeze: freeze$3, keys: keys$a } = Object;
16874
+ const { assign: assign$8, create: create$8, freeze: freeze$3, isFrozen: isFrozen$1, keys: keys$a } = Object;
16835
16875
  const { hasOwnProperty: hasOwnProperty$1 } = Object.prototype;
16836
16876
  const { split, endsWith } = String.prototype;
16837
16877
  const { isArray: isArray$7 } = Array;
@@ -19800,7 +19840,7 @@
19800
19840
  extractTrackedFieldsToTrie(spanningLink.data.__ref, spanning, next, config, spanningVisitedRecordIds, depth + 1);
19801
19841
  // For a spanning record that is detected to be a circular reference, we add the field along with Id and Name.
19802
19842
  // It's possible for spanning record lookup fields to sometimes be circular, and sometimes not - depending on the value of the lookup field.
19803
- // For more information on scenarios that caused this fix: https://salesforce.quip.com/OvzNAh3eNIWY
19843
+ // For more information on scenarios that caused this fix: search "LDS Recursive Spanning Fields Problem" in Quip
19804
19844
  if (keys$a(next.children).length === 0) {
19805
19845
  addScalarFieldId(next);
19806
19846
  addScalarFieldName(next);
@@ -19953,7 +19993,11 @@
19953
19993
  }
19954
19994
  const link = fieldValueRepresentation.link(fieldName);
19955
19995
  const resolved = link.follow();
19956
- if (isGraphNode(resolved) && resolved.isScalar('value') && path.length > 0) {
19996
+ if (isGraphNode(resolved) &&
19997
+ resolved.isScalar('value') &&
19998
+ path.length > 0 &&
19999
+ // TODO [W-14082782]: temporary fix
20000
+ !isFrozen$1(link.data)) {
19957
20001
  const linkState = link.linkData();
19958
20002
  const fields = linkState === undefined ? [] : linkState.fields;
19959
20003
  link.writeLinkData({
@@ -19981,22 +20025,12 @@
19981
20025
  const fieldValueRepresentation = record.object('fields');
19982
20026
  const fieldName = path.shift();
19983
20027
  if (fieldValueRepresentation.isUndefined(fieldName) === true) {
19984
- // TODO [W-6900046]: remove cast, make RecordRepresentationNormalized['fields'] accept
19985
- // an undefined/non-present __ref if isMissing is present
19986
- fieldValueRepresentation.write(fieldName, {
19987
- __ref: undefined,
19988
- isMissing: true,
19989
- });
20028
+ writeMissingFieldToStore(fieldValueRepresentation, fieldName);
19990
20029
  return;
19991
20030
  }
19992
20031
  const link = fieldValueRepresentation.link(fieldName);
19993
20032
  if (link.isPending()) {
19994
- // TODO [W-6900046]: remove cast, make RecordRepresentationNormalized['fields'] accept
19995
- // an undefined/non-present __ref if isMissing is present
19996
- fieldValueRepresentation.write(fieldName, {
19997
- __ref: undefined,
19998
- isMissing: true,
19999
- });
20033
+ writeMissingFieldToStore(fieldValueRepresentation, fieldName);
20000
20034
  }
20001
20035
  else if (path.length > 0 && link.isMissing() === false) {
20002
20036
  const fieldValue = link.follow();
@@ -20012,6 +20046,19 @@
20012
20046
  }
20013
20047
  }
20014
20048
  }
20049
+ /**
20050
+ * Graph Node Directly modifies store entries, which is generally a non-starter.
20051
+ * Until we can refactor this mess, you need to use this function to safely mark the RecordRepresentation
20052
+ * as a seenId in the store when you perform this mutation.
20053
+ */
20054
+ function writeMissingFieldToStore(field, fieldName) {
20055
+ // TODO [W-6900046]: remove cast, make RecordRepresentationNormalized['fields'] accept
20056
+ // an undefined/non-present __ref if isMissing is present
20057
+ field.write(fieldName, {
20058
+ __ref: undefined,
20059
+ isMissing: true,
20060
+ });
20061
+ }
20015
20062
  /**
20016
20063
  * Tells you if an objectApiName is supported by UI API or not.
20017
20064
  * Note: Luvio does not currently support all the entities, the list is limited to UI API supported entities
@@ -20143,8 +20190,11 @@
20143
20190
  return existing;
20144
20191
  }
20145
20192
  function mergeRecordConflict(luvio, incoming, existing, recordConflictMap) {
20146
- const incomingNode = luvio.wrapNormalizedGraphNode(incoming);
20147
- const existingNode = luvio.wrapNormalizedGraphNode(existing);
20193
+ const recordKey = keyBuilder$1U(luvio, {
20194
+ recordId: incoming.id,
20195
+ });
20196
+ const incomingNode = luvio.wrapNormalizedGraphNode(incoming, recordKey);
20197
+ const existingNode = luvio.wrapNormalizedGraphNode(existing, recordKey);
20148
20198
  const incomingTrackedFieldsTrieRoot = {
20149
20199
  name: incoming.apiName,
20150
20200
  children: {},
@@ -20153,9 +20203,6 @@
20153
20203
  name: existing.apiName,
20154
20204
  children: {},
20155
20205
  };
20156
- const recordKey = keyBuilder$1U(luvio, {
20157
- recordId: incoming.id,
20158
- });
20159
20206
  const trackedFieldsConfig = {
20160
20207
  maxDepth: configurationForRestAdapters$1.getTrackedFieldDepthOnCacheMergeConflict(),
20161
20208
  onlyFetchLeafNodeIdAndName: configurationForRestAdapters$1.getTrackedFieldLeafNodeIdAndNameOnly(),
@@ -20461,7 +20508,7 @@
20461
20508
  const batchRequestWithSingleRequest = isSingleBatchRecordRequest(existingUrlParams) &&
20462
20509
  isSingleRecordRequest(urlParams) &&
20463
20510
  incomingUrlRecords[0] === existingUrlRecords[0];
20464
- if (!batchRequestWithSingleRequest) {
20511
+ if (!batchRequestWithSingleRequest || isRestrictedPathCondition(existingPath, path)) {
20465
20512
  return false;
20466
20513
  }
20467
20514
  }
@@ -20504,6 +20551,12 @@
20504
20551
  function isSingleRecordRequest(urlParams) {
20505
20552
  return hasOwnProperty$1.call(urlParams, 'recordId');
20506
20553
  }
20554
+ function isRestrictedPathCondition(existingPath, path) {
20555
+ // should not dedupe getRecordUi and getRecord as both of their representation is different
20556
+ // records call cannot digest response of getRecordUi
20557
+ return ((existingPath.includes('/record-ui') && path.includes('/records')) ||
20558
+ (existingPath.includes('/records') && path.includes('/record-ui')));
20559
+ }
20507
20560
 
20508
20561
  const createResourceRequest$12 = function getUiApiRecordsByRecordIdCreateResourceRequest(config) {
20509
20562
  return {
@@ -24860,7 +24913,7 @@
24860
24913
  // Temp fix until we can mimic the server behavior for non-layoutable entities.
24861
24914
  let layoutMap = {};
24862
24915
  if (hasOwnProperty$1.call(layouts, apiName)) {
24863
- layoutMap = layouts[apiName][recordTypeId];
24916
+ layoutMap = layouts[apiName][recordTypeId] || {};
24864
24917
  }
24865
24918
  return {
24866
24919
  layoutMap,
@@ -25038,18 +25091,28 @@
25038
25091
  * These are intermediate lookups to check if the record is in the L2 cache
25039
25092
  * @param {Luvio} luvio
25040
25093
  * @param {GetRecordLayoutTypeConfig} config
25041
- * @param {BuildCachedSnapshot<BuildSnapshotContext} cachedSnapshot
25094
+ * @param {BuildCachedSnapshot<BuildSnapshotContext>} cachedSnapshot
25042
25095
  */
25043
25096
  function makeCacheOnlySnapshot(luvio, config, adapterContext, cachedSnapshot) {
25044
- return luvio.applyCachePolicy({
25045
- cachePolicy: {
25046
- // only looking in the cache so we can check for L2 data offline
25047
- type: 'only-if-cached',
25048
- },
25049
- }, { config, luvio, adapterContext }, cachedSnapshot,
25050
- // this won't be invoked since we're requesting only-if-cached
25097
+ return luvio.applyCachePolicy(
25098
+ // Pass empty context so environment will use its default cache-policy
25099
+ {}, { config, luvio, adapterContext }, cachedSnapshot,
25100
+ // disallow hitting the network by returning a gateway timeout
25051
25101
  () => {
25052
- throw Error('buildNetworkSnapshot should not be called for only-if-cached policy');
25102
+ return new Promise((resolve) => {
25103
+ resolve({
25104
+ state: 'Error',
25105
+ data: undefined,
25106
+ error: {
25107
+ body: undefined,
25108
+ headers: {},
25109
+ ok: false,
25110
+ status: 504,
25111
+ statusText: 'Gateway Timeout',
25112
+ errorType: 'fetchResponse',
25113
+ },
25114
+ });
25115
+ });
25053
25116
  });
25054
25117
  }
25055
25118
  /**
@@ -25260,7 +25323,7 @@
25260
25323
  const responsePromises = [];
25261
25324
  for (let i = 0, len = entries.length; i < len; i++) {
25262
25325
  const { key, record } = entries[i];
25263
- const node = luvio.wrapNormalizedGraphNode(record);
25326
+ const node = luvio.wrapNormalizedGraphNode(record, key);
25264
25327
  const optionalFields = getTrackedFields(key, node, {
25265
25328
  maxDepth: configurationForRestAdapters$1.getTrackedFieldDepthOnNotifyChange(),
25266
25329
  onlyFetchLeafNodeIdAndName: configurationForRestAdapters$1.getTrackedFieldLeafNodeIdAndNameOnly(),
@@ -41158,7 +41221,16 @@
41158
41221
  throttle(60, 60000, createLDSAdapter(luvio, 'notifyListInfoUpdateAvailable', notifyUpdateAvailableFactory$1));
41159
41222
  throttle(60, 60000, createLDSAdapter(luvio, 'notifyQuickActionDefaultsUpdateAvailable', notifyUpdateAvailableFactory));
41160
41223
  });
41161
- // version: 1.228.1-0cb6f94f1
41224
+ // version: 1.229.0-dev10-abb060196
41225
+
41226
+ var ldsIdempotencyWriteDisabled = {
41227
+ isOpen: function (e) {
41228
+ return e.fallback;
41229
+ },
41230
+ hasError: function () {
41231
+ return !0;
41232
+ },
41233
+ };
41162
41234
 
41163
41235
  var caseSensitiveUserId = '005B0000000GR4OIAW';
41164
41236
 
@@ -41789,6 +41861,9 @@
41789
41861
  }
41790
41862
 
41791
41863
  function isStoreEntryError(storeRecord) {
41864
+ if (!storeRecord || typeof storeRecord !== 'object') {
41865
+ return false;
41866
+ }
41792
41867
  return storeRecord.__type === 'error';
41793
41868
  }
41794
41869
 
@@ -42373,12 +42448,12 @@
42373
42448
  }
42374
42449
  return environment.getNode(key, ingestStagingStore);
42375
42450
  };
42376
- const wrapNormalizedGraphNode = function (normalized) {
42451
+ const wrapNormalizedGraphNode = function (normalized, key) {
42377
42452
  validateNotDisposed();
42378
42453
  if (ingestStagingStore === null) {
42379
42454
  ingestStagingStore = buildIngestStagingStore(environment);
42380
42455
  }
42381
- return environment.wrapNormalizedGraphNode(normalized, ingestStagingStore);
42456
+ return environment.wrapNormalizedGraphNode(normalized, key, ingestStagingStore);
42382
42457
  };
42383
42458
  const rebuildSnapshot = function (snapshot, onRebuild) {
42384
42459
  validateNotDisposed();
@@ -42692,6 +42767,72 @@
42692
42767
  });
42693
42768
  }
42694
42769
 
42770
+ /**
42771
+ * Copyright (c) 2022, Salesforce, Inc.,
42772
+ * All rights reserved.
42773
+ * For full license text, see the LICENSE.txt file
42774
+ */
42775
+
42776
+ const API_NAMESPACE$1 = 'UiApi';
42777
+ const RECORD_REPRESENTATION_NAME$2 = 'RecordRepresentation';
42778
+ const RECORD_VIEW_ENTITY_REPRESENTATION_NAME$1 = 'RecordViewEntityRepresentation';
42779
+ const RECORD_ID_PREFIX$1 = `${API_NAMESPACE$1}::${RECORD_REPRESENTATION_NAME$2}:`;
42780
+ const RECORD_VIEW_ENTITY_ID_PREFIX$1 = `${API_NAMESPACE$1}::${RECORD_VIEW_ENTITY_REPRESENTATION_NAME$1}:Name:`;
42781
+ const RECORD_FIELDS_KEY_JUNCTION$1 = '__fields__';
42782
+ function isStoreKeyRecordId(key) {
42783
+ return key.indexOf(RECORD_ID_PREFIX$1) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) === -1;
42784
+ }
42785
+ function isStoreKeyRecordViewEntity$1(key) {
42786
+ return (key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX$1) > -1 &&
42787
+ key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) === -1);
42788
+ }
42789
+ function isStoreKeyRecordField(key) {
42790
+ return key.indexOf(RECORD_ID_PREFIX$1) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) > -1;
42791
+ }
42792
+ function extractRecordIdFromStoreKey$1(key) {
42793
+ if (key === undefined ||
42794
+ (key.indexOf(RECORD_ID_PREFIX$1) === -1 && key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX$1) === -1)) {
42795
+ return undefined;
42796
+ }
42797
+ const parts = key.split(':');
42798
+ return parts[parts.length - 1].split('_')[0];
42799
+ }
42800
+ function buildRecordFieldStoreKey(recordKey, fieldName) {
42801
+ return `${recordKey}${RECORD_FIELDS_KEY_JUNCTION$1}${fieldName}`;
42802
+ }
42803
+ function objectsDeepEqual(lhs, rhs) {
42804
+ if (lhs === rhs)
42805
+ return true;
42806
+ if (typeof lhs !== 'object' || typeof rhs !== 'object' || lhs === null || rhs === null)
42807
+ return false;
42808
+ const lhsKeys = Object.keys(lhs);
42809
+ const rhsKeys = Object.keys(rhs);
42810
+ if (lhsKeys.length !== rhsKeys.length)
42811
+ return false;
42812
+ for (let key of lhsKeys) {
42813
+ if (!rhsKeys.includes(key))
42814
+ return false;
42815
+ if (typeof lhs[key] === 'function' || typeof rhs[key] === 'function') {
42816
+ if (lhs[key].toString() !== rhs[key].toString())
42817
+ return false;
42818
+ }
42819
+ else {
42820
+ if (!objectsDeepEqual(lhs[key], rhs[key]))
42821
+ return false;
42822
+ }
42823
+ }
42824
+ return true;
42825
+ }
42826
+
42827
+ function isStoreRecordError(storeRecord) {
42828
+ return storeRecord.__type === 'error';
42829
+ }
42830
+ function isEntryDurableRecordRepresentation(entry, key) {
42831
+ // Either a DurableRecordRepresentation or StoreRecordError can live at a record key
42832
+ return ((isStoreKeyRecordId(key) || isStoreKeyRecordViewEntity$1(key)) &&
42833
+ entry.data.__type === undefined);
42834
+ }
42835
+
42695
42836
  /**
42696
42837
  * Copyright (c) 2022, Salesforce, Inc.,
42697
42838
  * All rights reserved.
@@ -43951,6 +44092,7 @@
43951
44092
  'Email',
43952
44093
  'TextArea',
43953
44094
  'Percent',
44095
+ 'EncryptedString',
43954
44096
  ].includes(type);
43955
44097
  }
43956
44098
 
@@ -45431,8 +45573,12 @@
45431
45573
  // If there is no metadata for this query or it somehow lacks a timestamp
45432
45574
  // skip setting the root timestamp
45433
45575
  if (queryMetadata !== undefined && queryMetadata.ingestionTimestamp !== undefined) {
45434
- // subtract 10ms from timestamp to account for ingestion processing time
45435
- input.rootTimestamp = queryMetadata.ingestionTimestamp - 10;
45576
+ const timestamp = Number(queryMetadata.ingestionTimestamp);
45577
+ if (!isNaN(timestamp)) {
45578
+ // adjust the timestamp to account for ingestion processing time
45579
+ // 30s is used because this is the default record TTL
45580
+ input.rootTimestamp = timestamp - 30000;
45581
+ }
45436
45582
  }
45437
45583
  }
45438
45584
  return recordQuery(selection, alias, apiName, [], input);
@@ -45890,7 +46036,11 @@
45890
46036
  try {
45891
46037
  const { data, seenRecords } = await queryEvaluator(rootQuery, context, eventEmitter);
45892
46038
  const rebuildWithStoreEval = ((originalSnapshot) => {
45893
- return storeEval(config, originalSnapshot, observers, connectionKeyBuilder);
46039
+ return storeEval(config, originalSnapshot, observers, connectionKeyBuilder).then((rebuiltSnapshot) => {
46040
+ return objectsDeepEqual(originalSnapshot.data, rebuiltSnapshot.data)
46041
+ ? originalSnapshot
46042
+ : rebuiltSnapshot;
46043
+ });
45894
46044
  });
45895
46045
  const recordId = generateUniqueRecordId$1();
45896
46046
  // if the non-eval'ed snapshot was an error then we return a synthetic
@@ -46241,8 +46391,12 @@
46241
46391
 
46242
46392
  const HTTP_HEADER_RETRY_AFTER = 'Retry-After';
46243
46393
  const HTTP_HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';
46394
+ const ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED = 'IDEMPOTENCY_FEATURE_NOT_ENABLED';
46395
+ const ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED = 'IDEMPOTENCY_NOT_SUPPORTED';
46244
46396
  const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = 'IDEMPOTENCY_KEY_USED_DIFFERENT_USER';
46245
46397
  const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = 'IDEMPOTENCY_CONCURRENT_REQUEST';
46398
+ const ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED = 'IDEMPOTENCY_KEY_ALREADY_USED';
46399
+ const ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR = 'IDEMPOTENCY_BACKEND_OPERATION_ERROR';
46246
46400
  /**
46247
46401
  * Get the retry after in milliseconds from the response headers, undefined if not specified.
46248
46402
  * The header could have two different format.
@@ -46272,7 +46426,9 @@
46272
46426
  const dispatchResourceRequest = async function (resourceRequest, _context) {
46273
46427
  const resourceRequestCopy = clone$1(resourceRequest);
46274
46428
  resourceRequestCopy.headers = resourceRequestCopy.headers || {};
46275
- resourceRequestCopy.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
46429
+ if (handler.hasIdempotencySupport()) {
46430
+ resourceRequestCopy.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
46431
+ }
46276
46432
  // enable return extra fields for record creation and record update http call
46277
46433
  if (resourceRequest.basePath === '/ui-api/records' &&
46278
46434
  (resourceRequest.method === 'post' || resourceRequest.method === 'patch')) {
@@ -47094,6 +47250,12 @@
47094
47250
  // the luvio store redirect table, during which a new draft might be enqueued
47095
47251
  // which would not see a necessary mapping.
47096
47252
  this.ephemeralRedirects = {};
47253
+ // determined by Server setup.
47254
+ this.isIdempotencySupported = true;
47255
+ // idempotency write flag set by lds
47256
+ this.isLdsIdempotencyWriteDisabled = ldsIdempotencyWriteDisabled.isOpen({
47257
+ fallback: false,
47258
+ });
47097
47259
  }
47098
47260
  enqueue(data) {
47099
47261
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -47123,21 +47285,43 @@
47123
47285
  retryDelayInMs = getRetryAfterInMs(response.headers);
47124
47286
  shouldRetry = true;
47125
47287
  break;
47126
- case HttpStatusCode$1.ServerError:
47288
+ case HttpStatusCode$1.ServerError: {
47127
47289
  shouldRetry = true;
47290
+ if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR)) {
47291
+ this.isIdempotencySupported = false;
47292
+ retryDelayInMs = 0;
47293
+ actionDataChanged = true;
47294
+ }
47128
47295
  break;
47296
+ }
47129
47297
  case 409 /* IdempotentWriteSpecificHttpStatusCode.Conflict */: {
47130
- const errorCode = response.body[0].errorCode;
47131
- if (errorCode === ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER) {
47132
- updatedAction.data.headers = updatedAction.data.headers || {};
47133
- updatedAction.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
47298
+ if (this.isUiApiErrors(response.body)) {
47299
+ const errorCode = response.body[0].errorCode;
47300
+ if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER)) {
47301
+ retryDelayInMs = 0;
47302
+ actionDataChanged = true;
47303
+ }
47304
+ else if (errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST) {
47305
+ retryDelayInMs = getRetryAfterInMs(response.headers);
47306
+ }
47307
+ shouldRetry = true;
47308
+ }
47309
+ break;
47310
+ }
47311
+ case HttpStatusCode$1.BadRequest: {
47312
+ if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED, ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED)) {
47134
47313
  retryDelayInMs = 0;
47135
47314
  actionDataChanged = true;
47315
+ shouldRetry = true;
47136
47316
  }
47137
- else if (errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST) {
47138
- retryDelayInMs = getRetryAfterInMs(response.headers);
47317
+ break;
47318
+ }
47319
+ case 422 /* IdempotentWriteSpecificHttpStatusCode.UnProcessableEntity */: {
47320
+ if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED)) {
47321
+ retryDelayInMs = 0;
47322
+ actionDataChanged = true;
47323
+ shouldRetry = true;
47139
47324
  }
47140
- shouldRetry = true;
47141
47325
  break;
47142
47326
  }
47143
47327
  }
@@ -47156,6 +47340,27 @@
47156
47340
  return ProcessActionResult.NETWORK_ERROR;
47157
47341
  }
47158
47342
  }
47343
+ // 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.
47344
+ handleIdempotencyServerError(responseBody, action, updateIdempotencyKey, ...targetErrorCodes) {
47345
+ if (this.isUiApiErrors(responseBody)) {
47346
+ const errorCode = responseBody[0].errorCode;
47347
+ if (targetErrorCodes.includes(errorCode)) {
47348
+ action.data.headers = action.data.headers || {};
47349
+ if (updateIdempotencyKey) {
47350
+ action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
47351
+ }
47352
+ else {
47353
+ delete action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY];
47354
+ }
47355
+ return true;
47356
+ }
47357
+ }
47358
+ return false;
47359
+ }
47360
+ // 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.
47361
+ isUiApiErrors(body) {
47362
+ return body !== undefined && Array.isArray(body) && body.length > 0 && body[0].errorCode;
47363
+ }
47159
47364
  async buildPendingAction(request, queue) {
47160
47365
  const targetId = await this.getIdFromRequest(request);
47161
47366
  if (targetId === undefined) {
@@ -47369,6 +47574,10 @@
47369
47574
  ...targetData,
47370
47575
  body: this.mergeRequestBody(targetBody, sourceBody),
47371
47576
  };
47577
+ // Updates Idempotency key if target has one
47578
+ if (targetData.headers && targetData.headers[HTTP_HEADER_IDEMPOTENCY_KEY]) {
47579
+ merged.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
47580
+ }
47372
47581
  // overlay metadata
47373
47582
  merged.metadata = { ...targetMetadata, ...sourceMetadata };
47374
47583
  // put status back to pending to auto upload if queue is active and targed is at the head.
@@ -47405,6 +47614,9 @@
47405
47614
  getDraftIdsFromAction(action) {
47406
47615
  return [action.targetId];
47407
47616
  }
47617
+ hasIdempotencySupport() {
47618
+ return this.isIdempotencySupported && !this.isLdsIdempotencyWriteDisabled;
47619
+ }
47408
47620
  async ingestResponses(responses, action) {
47409
47621
  const luvio = this.getLuvio();
47410
47622
  await luvio.handleSuccessResponse(() => {
@@ -47836,49 +48048,6 @@
47836
48048
  });
47837
48049
  }
47838
48050
 
47839
- /**
47840
- * Copyright (c) 2022, Salesforce, Inc.,
47841
- * All rights reserved.
47842
- * For full license text, see the LICENSE.txt file
47843
- */
47844
-
47845
- const API_NAMESPACE$1 = 'UiApi';
47846
- const RECORD_REPRESENTATION_NAME$2 = 'RecordRepresentation';
47847
- const RECORD_VIEW_ENTITY_REPRESENTATION_NAME$1 = 'RecordViewEntityRepresentation';
47848
- const RECORD_ID_PREFIX$1 = `${API_NAMESPACE$1}::${RECORD_REPRESENTATION_NAME$2}:`;
47849
- const RECORD_VIEW_ENTITY_ID_PREFIX$1 = `${API_NAMESPACE$1}::${RECORD_VIEW_ENTITY_REPRESENTATION_NAME$1}:Name:`;
47850
- const RECORD_FIELDS_KEY_JUNCTION$1 = '__fields__';
47851
- function isStoreKeyRecordId(key) {
47852
- return key.indexOf(RECORD_ID_PREFIX$1) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) === -1;
47853
- }
47854
- function isStoreKeyRecordViewEntity$1(key) {
47855
- return (key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX$1) > -1 &&
47856
- key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) === -1);
47857
- }
47858
- function isStoreKeyRecordField(key) {
47859
- return key.indexOf(RECORD_ID_PREFIX$1) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION$1) > -1;
47860
- }
47861
- function extractRecordIdFromStoreKey$1(key) {
47862
- if (key === undefined ||
47863
- (key.indexOf(RECORD_ID_PREFIX$1) === -1 && key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX$1) === -1)) {
47864
- return undefined;
47865
- }
47866
- const parts = key.split(':');
47867
- return parts[parts.length - 1].split('_')[0];
47868
- }
47869
- function buildRecordFieldStoreKey(recordKey, fieldName) {
47870
- return `${recordKey}${RECORD_FIELDS_KEY_JUNCTION$1}${fieldName}`;
47871
- }
47872
-
47873
- function isStoreRecordError(storeRecord) {
47874
- return storeRecord.__type === 'error';
47875
- }
47876
- function isEntryDurableRecordRepresentation(entry, key) {
47877
- // Either a DurableRecordRepresentation or StoreRecordError can live at a record key
47878
- return ((isStoreKeyRecordId(key) || isStoreKeyRecordViewEntity$1(key)) &&
47879
- entry.data.__type === undefined);
47880
- }
47881
-
47882
48051
  function serializeFieldArguments$1(argumentNodes, variables) {
47883
48052
  const mutableArgumentNodes = Object.assign([], argumentNodes);
47884
48053
  return `args__(${mutableArgumentNodes
@@ -49253,6 +49422,10 @@
49253
49422
  return node.kind === 'OperationDefinition';
49254
49423
  }
49255
49424
 
49425
+ const POLYMORPHIC_PARENT_RELATIONSHIP = 'polymorphicParentRelationship';
49426
+ const PARENT_RELATIONSHIP = 'parentRelationship';
49427
+ const CHILD_RELATIONSHIP = 'childRelationship';
49428
+ const RECORD_QUERY = 'recordQuery';
49256
49429
  function requestsDraftsField(recordFieldNode) {
49257
49430
  if (!recordFieldNode.selectionSet)
49258
49431
  return false;
@@ -49268,18 +49441,41 @@
49268
49441
  directive.arguments
49269
49442
  .map((argument) => argument.value)
49270
49443
  .filter(isStringValueNode)
49271
- .some((categoryName) => categoryName.value === 'recordQuery'));
49444
+ .some((categoryName) => categoryName.value === RECORD_QUERY));
49272
49445
  });
49273
49446
  }
49274
49447
  return false;
49275
49448
  }
49276
- // finds field with 'recordQuery' and 'childRelationship' directive
49277
- function findNearestRecordQuery(ancestors) {
49278
- const recordQueryAncester = findNearestAncesterPath(ancestors, true).node;
49279
- return recordQueryAncester === undefined ? undefined : recordQueryAncester;
49449
+ // finds connection field with 'recordQuery' and 'childRelationship' directive.
49450
+ function findNearestConnection(ancestors) {
49451
+ const connectionAncestor = findNearestAncesterPath(ancestors, true).node;
49452
+ return connectionAncestor === undefined ? undefined : connectionAncestor;
49453
+ }
49454
+ // convinient method to find nearest connection with its path
49455
+ function findNearestConnectionWithPath(ancestors) {
49456
+ const closestAncestorPath = findNearestAncesterPath(ancestors, true);
49457
+ let connection = undefined;
49458
+ let connectionPath = undefined;
49459
+ if (closestAncestorPath.parentIndex > 0) {
49460
+ const connectionAncestor = closestAncestorPath.node;
49461
+ const connectionAncestors = ancestors.slice(0, closestAncestorPath.parentIndex);
49462
+ connection =
49463
+ connectionAncestor === undefined ? undefined : connectionAncestor;
49464
+ if (connection !== undefined) {
49465
+ const ancesterPath = findAncesterPath(connectionAncestors);
49466
+ connectionPath =
49467
+ ancesterPath === ''
49468
+ ? connection.name.value
49469
+ : `${ancesterPath}#${connection.name.value}`;
49470
+ }
49471
+ }
49472
+ return {
49473
+ connection,
49474
+ path: connectionPath,
49475
+ };
49280
49476
  }
49281
- // 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.
49282
- function findNearestAncesterPath(ancestors, recordQueryOnly) {
49477
+ // 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.
49478
+ function findNearestAncesterPath(ancestors, connectionOnly) {
49283
49479
  let recordQueryPath = { node: undefined, parentIndex: -1 };
49284
49480
  let relationship = '';
49285
49481
  for (let i = ancestors.length - 1; i >= 0; i--) {
@@ -49293,9 +49489,11 @@
49293
49489
  continue;
49294
49490
  for (let arg of directive.arguments) {
49295
49491
  if (arg.value &&
49296
- (arg.value.value === 'recordQuery' ||
49297
- arg.value.value === 'childRelationship' ||
49298
- (!recordQueryOnly && arg.value.value === 'parentRelationship'))) {
49492
+ (arg.value.value === RECORD_QUERY ||
49493
+ arg.value.value === CHILD_RELATIONSHIP ||
49494
+ (!connectionOnly &&
49495
+ (arg.value.value === PARENT_RELATIONSHIP ||
49496
+ arg.value.value === POLYMORPHIC_PARENT_RELATIONSHIP)))) {
49299
49497
  recordQueryPath = { node: node, parentIndex: i };
49300
49498
  relationship = arg.value.value;
49301
49499
  break;
@@ -49310,17 +49508,19 @@
49310
49508
  //checks if nearest ancester could be an inline fragment
49311
49509
  if (recordQueryPath.node !== undefined &&
49312
49510
  recordQueryPath.node.selectionSet &&
49313
- relationship === 'parentRelationship') {
49314
- //
49315
- if (recordQueryPath.node.selectionSet.selections.every(isInlineFragmentNode)) {
49316
- //
49317
- const inlineFragmentLoc = recordQueryPath.parentIndex + 2;
49318
- if (inlineFragmentLoc < ancestors.length && ancestors[inlineFragmentLoc]) {
49511
+ (relationship === PARENT_RELATIONSHIP || relationship === POLYMORPHIC_PARENT_RELATIONSHIP)) {
49512
+ // InlineFragment is usually 3 steps aways from its FieldNode parent within ancester hierarchy if it exists. The below search
49513
+ // is applied to adapt to future AST structure change
49514
+ let parentIndex = recordQueryPath.parentIndex + 1;
49515
+ while (parentIndex < ancestors.length) {
49516
+ if (isInlineFragmentNode(ancestors[parentIndex])) {
49319
49517
  recordQueryPath = {
49320
- node: ancestors[inlineFragmentLoc],
49321
- parentIndex: inlineFragmentLoc,
49518
+ node: ancestors[parentIndex],
49519
+ parentIndex,
49322
49520
  };
49521
+ break;
49323
49522
  }
49523
+ parentIndex++;
49324
49524
  }
49325
49525
  }
49326
49526
  return recordQueryPath;
@@ -49344,7 +49544,7 @@
49344
49544
  ? sectionPath
49345
49545
  : sectionPath === ''
49346
49546
  ? path
49347
- : `${sectionPath}_${path}`;
49547
+ : `${sectionPath}#${path}`;
49348
49548
  }
49349
49549
  }
49350
49550
  boundaryIndex = parentIndex;
@@ -49402,9 +49602,9 @@
49402
49602
  const relationships = args
49403
49603
  .map((arg) => arg.value)
49404
49604
  .filter(isStringValueNode)
49405
- .filter((valueNode) => valueNode.value === 'childRelationship' ||
49406
- valueNode.value === 'parentRelationship' ||
49407
- valueNode.value === 'polymorphicParentRelationship')
49605
+ .filter((valueNode) => valueNode.value === CHILD_RELATIONSHIP ||
49606
+ valueNode.value === PARENT_RELATIONSHIP ||
49607
+ valueNode.value === POLYMORPHIC_PARENT_RELATIONSHIP)
49408
49608
  .map((relationshipNode) => relationshipNode.value);
49409
49609
  if (relationships.length > 0) {
49410
49610
  return relationships[0];
@@ -49461,8 +49661,8 @@
49461
49661
  */
49462
49662
  function isParentRelationship(node) {
49463
49663
  return (node &&
49464
- (isRelationship(node, 'parentRelationship') ||
49465
- isRelationship(node, 'polymorphicParentRelationship')));
49664
+ (isRelationship(node, PARENT_RELATIONSHIP) ||
49665
+ isRelationship(node, POLYMORPHIC_PARENT_RELATIONSHIP)));
49466
49666
  }
49467
49667
  /*
49468
49668
  checks if the InlineFragment spans
@@ -49768,6 +49968,26 @@
49768
49968
  return values$1(objectInfo.fields).find((field) => field.apiName === fieldName ||
49769
49969
  (field.dataType === 'Reference' && field.relationshipName === fieldName));
49770
49970
  }
49971
+ async function readIngestionTimestampForKey(key, query) {
49972
+ let ingestionTimestamp = 0;
49973
+ const sql = `
49974
+ SELECT json_extract(metadata, '${JSON_EXTRACT_PATH_INGESTION_TIMESTAMP}')
49975
+ FROM lds_data
49976
+ WHERE key IS ?
49977
+ `;
49978
+ const results = await query(sql, [key]);
49979
+ const [timestamp] = results.rows.map((row) => row[0]);
49980
+ if (timestamp !== null) {
49981
+ const numericalTimestamp = Number(timestamp);
49982
+ if (isNaN(numericalTimestamp)) {
49983
+ return ingestionTimestamp;
49984
+ }
49985
+ // adjust the timestamp to account for ingestion processing time
49986
+ // 30s is used because this is the default record TTL
49987
+ ingestionTimestamp = numericalTimestamp - 30000;
49988
+ }
49989
+ return ingestionTimestamp;
49990
+ }
49771
49991
 
49772
49992
  function findSpanningField(name) {
49773
49993
  return (field) => {
@@ -50287,17 +50507,7 @@
50287
50507
  const key = buildKeyStringForRecordQuery(operation,
50288
50508
  // join varables passed from query to the argument variables given from the AST
50289
50509
  { ...variableValues, ...args }, info.fieldNodes[0].arguments, apiName);
50290
- const sql = `
50291
- SELECT json_extract(metadata, '${JSON_EXTRACT_PATH_INGESTION_TIMESTAMP}')
50292
- FROM lds_data
50293
- WHERE key IS ?
50294
- `;
50295
- const results = await query(sql, [key]);
50296
- const [timestamp] = results.rows.map((row) => row[0]);
50297
- if (timestamp !== null && typeof timestamp === 'number') {
50298
- //go back 10 ms to adjust for margin of error when top level query is stored and when raml objects are stored
50299
- ingestionTimestamp = timestamp - 10;
50300
- }
50510
+ return readIngestionTimestampForKey(key, query);
50301
50511
  }
50302
50512
  return ingestionTimestamp;
50303
50513
  }
@@ -50345,26 +50555,20 @@
50345
50555
  let recordConnections = ``;
50346
50556
  const polymorphicFieldTypeNames = new Set();
50347
50557
  let typedScalars = new Set();
50558
+ let parentRelationshipFields = new Set();
50348
50559
  for (const objectInfo of values$1(objectInfos)) {
50349
50560
  const { apiName, childRelationships } = objectInfo;
50350
50561
  let fields = ``;
50351
50562
  typedScalars.add(`${apiName}_Filter`);
50352
50563
  typedScalars.add(`${apiName}_OrderBy`);
50353
- for (const childRelationship of childRelationships) {
50354
- const { childObjectApiName } = childRelationship;
50355
- // Only add the relationship if there is relevant objectinfos for it,
50356
- // otherwise we'd be defining types we cannot satisfy and aren't referenced in
50357
- // the query.
50358
- if (objectInfos[childObjectApiName] !== undefined) {
50359
- fields += `${childRelationship.relationshipName}(first: Int, where: ${childObjectApiName}_Filter, orderBy: ${childObjectApiName}_OrderBy, scope: SupportedScopes): ${childObjectApiName}Connection \n`;
50360
- typedScalars.add(`${childObjectApiName}_Filter`);
50361
- typedScalars.add(`${childObjectApiName}_OrderBy`);
50362
- }
50363
- }
50364
50564
  for (const field of values$1(objectInfo.fields)) {
50365
50565
  if (!fieldsStaticallyAdded.includes(field.apiName)) {
50366
50566
  fields += `${field.apiName}: ${dataTypeToType(field.dataType, field.apiName)}\n`;
50367
50567
  }
50568
+ //handles parent relationship
50569
+ if (field.relationshipName === null) {
50570
+ continue;
50571
+ }
50368
50572
  // For spanning parent relationships with no union types
50369
50573
  if (field.referenceToInfos.length === 1) {
50370
50574
  const [relation] = field.referenceToInfos;
@@ -50372,11 +50576,13 @@
50372
50576
  // otherwise we'd be defining types we cannot satisfy and aren't referenced in
50373
50577
  // the query.
50374
50578
  if (objectInfos[relation.apiName] !== undefined) {
50579
+ parentRelationshipFields.add(field.relationshipName);
50375
50580
  fields += `${field.relationshipName}: ${relation.apiName}\n`;
50376
50581
  }
50377
50582
  // For polymorphic field, its type is 'Record' inteface. The concrete entity type name is saved for field resolving of next phase
50378
50583
  }
50379
50584
  else if (field.referenceToInfos.length > 1) {
50585
+ parentRelationshipFields.add(field.relationshipName);
50380
50586
  fields += `${field.relationshipName}: Record\n`;
50381
50587
  for (const relation of field.referenceToInfos) {
50382
50588
  if (objectInfos[relation.apiName] !== undefined) {
@@ -50385,6 +50591,20 @@
50385
50591
  }
50386
50592
  }
50387
50593
  }
50594
+ // handles child relationship
50595
+ for (const childRelationship of childRelationships) {
50596
+ const { childObjectApiName } = childRelationship;
50597
+ // Only add the relationship if there is relevant objectinfos for it,
50598
+ // otherwise we'd be defining types we cannot satisfy and aren't referenced in
50599
+ // the query.
50600
+ // 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
50601
+ if (objectInfos[childObjectApiName] !== undefined &&
50602
+ !parentRelationshipFields.has(childRelationship.relationshipName)) {
50603
+ fields += `${childRelationship.relationshipName}(first: Int, where: ${childObjectApiName}_Filter, orderBy: ${childObjectApiName}_OrderBy, scope: SupportedScopes): ${childObjectApiName}Connection \n`;
50604
+ typedScalars.add(`${childObjectApiName}_Filter`);
50605
+ typedScalars.add(`${childObjectApiName}_OrderBy`);
50606
+ }
50607
+ }
50388
50608
  recordQueries += `${apiName}(first: Int, where: ${apiName}_Filter, orderBy: ${apiName}_OrderBy, scope: SupportedScopes): ${apiName}Connection\n`;
50389
50609
  const isServiceAppointment = apiName === 'ServiceAppointment';
50390
50610
  recordConnections += /* GraphQL */ `
@@ -50459,6 +50679,8 @@
50459
50679
  return 'PercentValue';
50460
50680
  case 'Int':
50461
50681
  return 'IntValue';
50682
+ case 'EncryptedString':
50683
+ return 'EncryptedStringValue';
50462
50684
  // ! do the rest of the custom types
50463
50685
  default:
50464
50686
  return 'String';
@@ -50544,7 +50766,7 @@
50544
50766
  },
50545
50767
  value: {
50546
50768
  kind: Kind.STRING,
50547
- value: 'parentRelationship',
50769
+ value: PARENT_RELATIONSHIP,
50548
50770
  block: false,
50549
50771
  },
50550
50772
  },
@@ -50558,8 +50780,8 @@
50558
50780
  // example 2 'ServiceAppointment' -> ['Owner']; 'Owner' -> ['User', 'Group']
50559
50781
  const objectNodeInfoTree = {};
50560
50782
  // save the field path to apiName map
50561
- // example 1: 'ServiceAppointment' -> ['ServiceAppointment']; 'ServiceAppointment_Account' -> ['Account']; 'ServiceAppointment_Account_Owner' -> ['User']
50562
- const objectInfoApiMap = {};
50783
+ // example 1: 'ServiceAppointment' -> ['ServiceAppointment']; 'ServiceAppointment#ccount' -> ['Account']; 'ServiceAppointment#Account#Owner' -> ['User']
50784
+ const pathToObjectApiNamesMap = {};
50563
50785
  let startNodes = new Set();
50564
50786
  let totalNodes = new Set();
50565
50787
  let objectInfos = {};
@@ -50573,11 +50795,11 @@
50573
50795
  visit(originalAST, {
50574
50796
  Argument: {
50575
50797
  enter(node, key, parent, path, ancestors) {
50576
- const recordQueryNode = findNearestRecordQuery(ancestors);
50577
- if (!recordQueryNode)
50798
+ const { connection: recordConnectionNode, path: ancesterPath } = findNearestConnectionWithPath(ancestors);
50799
+ if (!recordConnectionNode || !ancesterPath)
50578
50800
  return;
50579
- if (!objectNodeInfoTree[recordQueryNode.name.value]) {
50580
- objectNodeInfoTree[recordQueryNode.name.value] = [];
50801
+ if (!objectNodeInfoTree[ancesterPath]) {
50802
+ objectNodeInfoTree[ancesterPath] = [];
50581
50803
  }
50582
50804
  switch (node.name.value) {
50583
50805
  case 'orderBy':
@@ -50585,12 +50807,12 @@
50585
50807
  if (node.value.kind !== 'ObjectValue') {
50586
50808
  return;
50587
50809
  }
50588
- totalNodes.add(recordQueryNode.name.value);
50810
+ totalNodes.add(ancesterPath);
50589
50811
  // 'childRelationship' node is not taken as the startNode of the 'NodeInfoTree' graph. The field scanning will construct the graph which lead here.
50590
- if (isRecordQuery(recordQueryNode)) {
50591
- startNodes.add(recordQueryNode.name.value);
50812
+ if (isRecordQuery(recordConnectionNode)) {
50813
+ startNodes.add(recordConnectionNode.name.value);
50592
50814
  }
50593
- growObjectFieldTree(objectNodeInfoTree, recordQueryNode.name.value, node.value, totalNodes, startNodes);
50815
+ growObjectFieldTree(objectNodeInfoTree, ancesterPath, node.value, totalNodes, startNodes);
50594
50816
  break;
50595
50817
  case 'scope':
50596
50818
  if (!isScopeArgumentNodeWithType(node, 'ASSIGNEDTOME', variables)) {
@@ -50605,17 +50827,16 @@
50605
50827
  name: 'ServiceResources',
50606
50828
  });
50607
50829
  }
50608
- if (objectNodeInfoTree['ServiceResources'] === undefined) {
50609
- objectNodeInfoTree['ServiceResources'] = [];
50610
- }
50611
- if (!objectNodeInfoTree['ServiceResources'].some((child) => child.name === 'ServiceResource')) {
50612
- objectNodeInfoTree['ServiceResources'].push({
50613
- relation: 'parent',
50614
- name: 'ServiceResource',
50615
- });
50830
+ if (objectNodeInfoTree['ServiceAppointment#ServiceResources'] === undefined) {
50831
+ objectNodeInfoTree['ServiceAppointment#ServiceResources'] = [
50832
+ {
50833
+ relation: 'parent',
50834
+ name: 'ServiceResource',
50835
+ },
50836
+ ];
50616
50837
  }
50617
- if (objectNodeInfoTree['ServiceResource'] === undefined) {
50618
- objectNodeInfoTree['ServiceResource'] = [];
50838
+ if (objectNodeInfoTree['ServiceAppointment#ServiceResources#ServiceResource'] === undefined) {
50839
+ objectNodeInfoTree['ServiceAppointment#ServiceResources#ServiceResource'] = [];
50619
50840
  }
50620
50841
  break;
50621
50842
  default:
@@ -50629,7 +50850,7 @@
50629
50850
  return;
50630
50851
  if (!node.selectionSet)
50631
50852
  return;
50632
- const recordQueryField = findNearestRecordQuery(ancestors);
50853
+ const recordQueryField = findNearestConnection(ancestors);
50633
50854
  //only injects fields for 'recordQuery' field. ignores the 'childRelationship' field since it will be traversed as the child of the 'recordQuery'
50634
50855
  if (isRecordQuery(recordQueryField) && recordQueryField) {
50635
50856
  totalNodes.add(recordQueryField.name.value);
@@ -50640,21 +50861,21 @@
50640
50861
  },
50641
50862
  });
50642
50863
  if (objectInfoService && startNodes.size > 0) {
50643
- objectInfos = await resolveObjectInfos(objectNodeInfoTree, objectInfoApiMap, startNodes, objectInfoService);
50864
+ objectInfos = await resolveObjectInfos(objectNodeInfoTree, pathToObjectApiNamesMap, startNodes, objectInfoService);
50644
50865
  }
50645
50866
  // read pass; gather whats needed
50646
50867
  visit(originalAST, {
50647
50868
  Argument: {
50648
50869
  leave(node, key, parent, path, ancestors) {
50649
- const recordQueryField = findNearestRecordQuery(ancestors);
50870
+ const recordQueryField = findNearestConnection(ancestors);
50650
50871
  if (!recordQueryField)
50651
50872
  return;
50652
50873
  const ancestorPath = findAncesterPath(ancestors);
50653
50874
  if (!inlineFragmentSelections[ancestorPath]) {
50654
50875
  inlineFragmentSelections[ancestorPath] = [];
50655
50876
  }
50656
- const recordQueryApiName = objectInfoApiMap[ancestorPath]
50657
- ? objectInfoApiMap[ancestorPath][0]
50877
+ const recordQueryApiName = pathToObjectApiNamesMap[ancestorPath]
50878
+ ? pathToObjectApiNamesMap[ancestorPath][0]
50658
50879
  : recordQueryField.name.value;
50659
50880
  // The record node acts as the reference. The duplicated field in the record node is not injected
50660
50881
  const recordReferenceNode = [recordQueryField]
@@ -50668,7 +50889,7 @@
50668
50889
  case 'scope':
50669
50890
  // Hanle 'MINE' field
50670
50891
  if (isScopeArgumentNodeWithType(node, 'MINE', variables)) {
50671
- if (isMineScopeAvailable(ancestorPath, objectInfoApiMap, objectInfos)) {
50892
+ if (isMineScopeAvailable(ancestorPath, pathToObjectApiNamesMap, objectInfos)) {
50672
50893
  // 'typeConditon' is added when the 'InlineFragmentNode' is appended at the write pass
50673
50894
  inlineFragmentSelections[ancestorPath].push(...mineFragmentSelections);
50674
50895
  }
@@ -50694,7 +50915,7 @@
50694
50915
  case 'where': {
50695
50916
  inlineFragmentSelections[ancestorPath] = [
50696
50917
  ...inlineFragmentSelections[ancestorPath],
50697
- ...injectFilter(node, idState, ancestorPath, objectInfos, objectInfoApiMap, draftFunctions, recordReferenceNode),
50918
+ ...injectFilter(node, idState, ancestorPath, false, objectInfos, pathToObjectApiNamesMap, draftFunctions, recordReferenceNode),
50698
50919
  ];
50699
50920
  break;
50700
50921
  }
@@ -50710,7 +50931,7 @@
50710
50931
  if (!node.selectionSet)
50711
50932
  return;
50712
50933
  // it could be 'recordQuery' or 'childRelationship'
50713
- const recordQueryField = findNearestRecordQuery(ancestors);
50934
+ const recordQueryField = findNearestConnection(ancestors);
50714
50935
  if (!recordQueryField)
50715
50936
  return;
50716
50937
  const ancestorPath = findAncesterPath(ancestors);
@@ -50722,7 +50943,7 @@
50722
50943
  spanningSelections.push(selection);
50723
50944
  }
50724
50945
  }
50725
- const injectedFields = injectFields(spanningSelections, node, ancestors, objectInfos, objectInfoApiMap);
50946
+ const injectedFields = injectFields(spanningSelections, node, ancestorPath, ancestors, objectInfos, pathToObjectApiNamesMap);
50726
50947
  const mergedInjectedFields = mergeSelectionNodes$1(inlineFragmentSelections[ancestorPath], injectedFields);
50727
50948
  inlineFragmentSelections[ancestorPath] = mergedInjectedFields;
50728
50949
  },
@@ -50735,7 +50956,7 @@
50735
50956
  // removes 'ServicesResources' query field node if 'assignedtome' scope shows up
50736
50957
  if (assignedtomeQueryFieldNode !== undefined &&
50737
50958
  node.name.value === 'ServiceResources') {
50738
- const serviceResourcesAncestor = findNearestRecordQuery(ancestors);
50959
+ const serviceResourcesAncestor = findNearestConnection(ancestors);
50739
50960
  if (serviceResourcesAncestor === assignedtomeQueryFieldNode) {
50740
50961
  return null;
50741
50962
  }
@@ -50744,7 +50965,7 @@
50744
50965
  return;
50745
50966
  if (!node.selectionSet)
50746
50967
  return;
50747
- const recordQueryField = findNearestRecordQuery(ancestors);
50968
+ const recordQueryField = findNearestConnection(ancestors);
50748
50969
  if (!recordQueryField)
50749
50970
  return;
50750
50971
  const ancestorPath = findAncesterPath(ancestors);
@@ -50753,8 +50974,8 @@
50753
50974
  return;
50754
50975
  //const recordQueryPath = findAncesterPath(ancestors);
50755
50976
  // '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.
50756
- const recordQueryApiName = objectInfoApiMap[ancestorPath]
50757
- ? objectInfoApiMap[ancestorPath][0]
50977
+ const recordQueryApiName = pathToObjectApiNamesMap[ancestorPath]
50978
+ ? pathToObjectApiNamesMap[ancestorPath][0]
50758
50979
  : recordQueryField.name.value;
50759
50980
  const nodeWithFragments = {
50760
50981
  ...node,
@@ -50791,7 +51012,7 @@
50791
51012
  if (node.name.value === 'where') {
50792
51013
  const ancestorPath = findAncesterPath(ancestors);
50793
51014
  if (idState.paths.includes(ancestorPath)) {
50794
- const apiName = objectInfoApiMap[ancestorPath][0];
51015
+ const apiName = pathToObjectApiNamesMap[ancestorPath][0];
50795
51016
  const objectInfo = objectInfos[apiName];
50796
51017
  const swappedIdFilter = swapIdField(node.value, objectInfo, false, idState, draftFunctions);
50797
51018
  return {
@@ -50857,8 +51078,8 @@
50857
51078
  };
50858
51079
  }
50859
51080
  }
50860
- function isMineScopeAvailable(apiNamePath, objectInfoApiMap, objectInfos) {
50861
- const apiName = objectInfoApiMap[apiNamePath];
51081
+ function isMineScopeAvailable(apiNamePath, pathToObjectApiNamesMap, objectInfos) {
51082
+ const apiName = pathToObjectApiNamesMap[apiNamePath];
50862
51083
  if (!apiName)
50863
51084
  return false;
50864
51085
  const objectInfo = objectInfos[apiName[0]];
@@ -50947,15 +51168,16 @@
50947
51168
  }
50948
51169
  // example: 'Account'
50949
51170
  const childNode = objectFieldNode.name.value;
51171
+ const childNodepath = `${parentNode}#${childNode}`;
50950
51172
  if (!tree[parentNode].some((child) => child.name === childNode)) {
50951
51173
  tree[parentNode].push({
50952
51174
  relation: 'parent',
50953
51175
  name: childNode,
50954
51176
  });
50955
- totalNodes.add(childNode);
51177
+ totalNodes.add(childNodepath);
50956
51178
  }
50957
51179
  // recursively go to deeper level of filter.
50958
- growObjectFieldTree(tree, childNode, objectFieldNode.value, totalNodes, startNodes);
51180
+ growObjectFieldTree(tree, childNodepath, objectFieldNode.value, totalNodes, startNodes);
50959
51181
  }
50960
51182
  }
50961
51183
  }
@@ -50990,19 +51212,20 @@
50990
51212
  }
50991
51213
  if (!tree[parentSectionPath].some((field) => field.name === fieldName)) {
50992
51214
  tree[parentSectionPath].push({
50993
- relation: relationType === 'parentRelationship' ||
50994
- relationType === 'polymorphicParentRelationship'
51215
+ relation: relationType === PARENT_RELATIONSHIP ||
51216
+ relationType === POLYMORPHIC_PARENT_RELATIONSHIP
50995
51217
  ? 'parent'
50996
51218
  : 'child',
50997
51219
  name: fieldName,
50998
51220
  });
50999
- totalNodes.add(fieldName);
51221
+ totalNodes.add(`${parentSectionPath}#${fieldName}`);
51000
51222
  }
51001
51223
  if (entryNode.selectionSet && entryNode.selectionSet.selections) {
51002
51224
  const childNodes = entryNode.selectionSet.selections.filter(isFieldOrInlineFragmentNode);
51003
51225
  // recursively build the traversal tree
51004
51226
  for (const child of childNodes) {
51005
- growFieldTree(tree, fieldName, child, entryNode, totalNodes, startNodes);
51227
+ const path = `${parentSectionPath}#${fieldName}`;
51228
+ growFieldTree(tree, path, child, entryNode, totalNodes, startNodes);
51006
51229
  }
51007
51230
  }
51008
51231
  }
@@ -51032,23 +51255,23 @@
51032
51255
  }
51033
51256
  if (!tree[parentSectionPath].some((field) => field.name === conditionName)) {
51034
51257
  tree[parentSectionPath].push({
51035
- relation: relationType === 'parentRelationship' ||
51036
- relationType === 'polymorphicParentRelationship'
51258
+ relation: relationType === PARENT_RELATIONSHIP ||
51259
+ relationType === POLYMORPHIC_PARENT_RELATIONSHIP
51037
51260
  ? 'parent'
51038
51261
  : 'child',
51039
51262
  name: conditionName,
51040
51263
  });
51041
- totalNodes.add(conditionName);
51264
+ const path = `${parentSectionPath}#${conditionName}`;
51265
+ totalNodes.add(path);
51042
51266
  }
51043
51267
  }
51044
51268
  }
51045
51269
  // dive deep immediately for 'InlineFragment'
51046
51270
  const childNodes = entryNode.selectionSet.selections.filter(isFieldOrInlineFragmentNode);
51271
+ const path = `${parentSectionPath}${entryNode.typeCondition ? '#' + entryNode.typeCondition.name.value : ''}`;
51047
51272
  // Navigates into InLineFragment
51048
51273
  for (const child of childNodes) {
51049
- growFieldTree(tree, entryNode.typeCondition
51050
- ? entryNode.typeCondition.name.value
51051
- : parentSectionPath, child, entryNode, totalNodes, startNodes);
51274
+ growFieldTree(tree, path, child, entryNode, totalNodes, startNodes);
51052
51275
  }
51053
51276
  }
51054
51277
  }
@@ -51060,7 +51283,7 @@
51060
51283
  * @param startNodes start nodes of the tree. It can be used to fetch ObjectInfo immediately
51061
51284
  * @param path
51062
51285
  */
51063
- async function resolveObjectInfos(objectInfotree, objectInfoApiMap, startNodes, objectInfoService) {
51286
+ async function resolveObjectInfos(objectInfotree, pathToObjectApiNamesMap, startNodes, objectInfoService) {
51064
51287
  let objectInfos;
51065
51288
  try {
51066
51289
  objectInfos = await objectInfoService.getObjectInfos(Array.from(startNodes));
@@ -51074,9 +51297,9 @@
51074
51297
  throw new Error(`Unable to resolve ObjectInfo(s) for ${Array.from(startNodes)}`);
51075
51298
  }
51076
51299
  for (const startNode of startNodes) {
51077
- objectInfoApiMap[startNode] = [startNode];
51300
+ pathToObjectApiNamesMap[startNode] = [startNode];
51078
51301
  const children = objectInfotree[startNode];
51079
- const subObjectInfoMap = await fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfos, children, startNode, objectInfoService);
51302
+ const subObjectInfoMap = await fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, objectInfos, children, startNode, objectInfoService);
51080
51303
  objectInfos = { ...objectInfos, ...subObjectInfoMap };
51081
51304
  }
51082
51305
  return objectInfos;
@@ -51084,15 +51307,15 @@
51084
51307
  // example 1: 'parentPath': 'ServiceAppointment', 'nodesAtSameLevel': ['Account']
51085
51308
  // example 2: 'parentPath': 'ServiceAppointment', 'nodesAtSameLevel': ['Owner'], this example has 2 apiName for the node 'Owner'
51086
51309
  // example 3: 'parentPath': 'ServiceAppointment_Owner', 'nodesAtSameLevel': ['User', 'Group']
51087
- async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap, nodesAtSameLevel, parentPath, objectInfoService) {
51088
- const objectInfoApiNames = objectInfoApiMap[parentPath];
51310
+ async function fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, objectInfoMap, nodesAtSameLevel, parentPath, objectInfoService) {
51311
+ const objectInfoApiNames = pathToObjectApiNamesMap[parentPath];
51089
51312
  if (!objectInfoApiNames) {
51090
51313
  // eslint-disable-next-line
51091
51314
  throw new Error(`Object Info does not exist for ${parentPath}`);
51092
51315
  }
51093
51316
  const validObjectInfoNodes = [];
51094
51317
  let updatedObjectInfoMap = {};
51095
- // InlineFragment and polymorphic field support fits into this scenario ObjectInfoApiMap Entry: 'ServiceAppointment_Owner' -> ['User', 'Group']; ServiceAppointment_Owner_User' -> ['User']
51318
+ // InlineFragment and polymorphic field support fits into this scenario pathToObjectApiNamesMap Entry: 'ServiceAppointment#Owner' -> ['User', 'Group']; ServiceAppointment#Owner#User' -> ['User']
51096
51319
  if (objectInfoApiNames.length > 0 &&
51097
51320
  nodesAtSameLevel.length > 0 &&
51098
51321
  objectInfoApiNames.includes(nodesAtSameLevel[0].name)) {
@@ -51104,8 +51327,8 @@
51104
51327
  // eslint-disable-next-line
51105
51328
  throw new Error(`Condition ${field.name} does not exists for ${parentPath}`);
51106
51329
  }
51107
- const path = `${parentPath}_${field.name}`;
51108
- objectInfoApiMap[path] = [field.name];
51330
+ const path = `${parentPath}#${field.name}`;
51331
+ pathToObjectApiNamesMap[path] = [field.name];
51109
51332
  }
51110
51333
  validObjectInfoNodes.push(...nodesAtSameLevel);
51111
51334
  updatedObjectInfoMap = { ...objectInfoMap };
@@ -51120,7 +51343,7 @@
51120
51343
  let apiNames = [];
51121
51344
  for (const nodeInfo of nodesAtSameLevel) {
51122
51345
  const field = nodeInfo.name;
51123
- const path = `${parentPath}_${field}`;
51346
+ const path = `${parentPath}#${field}`;
51124
51347
  // Handle 'parentRelationship'
51125
51348
  if (nodeInfo.relation === 'parent') {
51126
51349
  const relationshipId = referenceIdFieldForRelationship(field);
@@ -51138,21 +51361,21 @@
51138
51361
  }
51139
51362
  }
51140
51363
  // This is a polymorphic field
51141
- if (fieldDefinition.referenceToInfos.length > 1 && objectInfotree[field]) {
51364
+ if (fieldDefinition.referenceToInfos.length > 1 && objectInfotree[path]) {
51142
51365
  // Fields needs to expand and heterogenous entity ObjectInfo needs to be fetched
51143
- const referencedNodeInfos = objectInfotree[field];
51366
+ const referencedNodeInfos = objectInfotree[path];
51144
51367
  const requestedApiNames = referencedNodeInfos.map((referenceNodeInfo) => referenceNodeInfo.name);
51145
51368
  // 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.
51146
- if (requestedApiNames.length > 0 && objectInfotree[field]) {
51369
+ if (requestedApiNames.length > 0 && objectInfotree[path]) {
51147
51370
  fieldDefinition.referenceToInfos
51148
51371
  .filter((referenceToInfo) => requestedApiNames.includes(referenceToInfo.apiName))
51149
51372
  .forEach((ref) => {
51150
- if (!objectInfoApiMap[path]) {
51151
- objectInfoApiMap[path] = [];
51373
+ if (!pathToObjectApiNamesMap[path]) {
51374
+ pathToObjectApiNamesMap[path] = [];
51152
51375
  }
51153
51376
  // 'ServiceAppointment_Owner' ->['User', 'Group']
51154
- if (!objectInfoApiMap[path].includes(ref.apiName)) {
51155
- objectInfoApiMap[path].push(ref.apiName);
51377
+ if (!pathToObjectApiNamesMap[path].includes(ref.apiName)) {
51378
+ pathToObjectApiNamesMap[path].push(ref.apiName);
51156
51379
  }
51157
51380
  if (!apiNames.includes(ref.apiName)) {
51158
51381
  apiNames.push(ref.apiName);
@@ -51162,11 +51385,11 @@
51162
51385
  }
51163
51386
  else if (fieldDefinition.referenceToInfos.length === 1) {
51164
51387
  const ref = fieldDefinition.referenceToInfos[0];
51165
- if (!objectInfoApiMap[path]) {
51166
- objectInfoApiMap[path] = [];
51388
+ if (!pathToObjectApiNamesMap[path]) {
51389
+ pathToObjectApiNamesMap[path] = [];
51167
51390
  }
51168
- if (!objectInfoApiMap[path].includes(ref.apiName)) {
51169
- objectInfoApiMap[path].push(ref.apiName);
51391
+ if (!pathToObjectApiNamesMap[path].includes(ref.apiName)) {
51392
+ pathToObjectApiNamesMap[path].push(ref.apiName);
51170
51393
  }
51171
51394
  if (!apiNames.includes(ref.apiName)) {
51172
51395
  apiNames.push(ref.apiName);
@@ -51177,11 +51400,11 @@
51177
51400
  // handles 'childRelationship'
51178
51401
  const childRelationship = parentObjectInfo.childRelationships.find((childRelationship) => childRelationship.relationshipName === field);
51179
51402
  if (childRelationship) {
51180
- if (!objectInfoApiMap[path]) {
51181
- objectInfoApiMap[path] = [];
51403
+ if (!pathToObjectApiNamesMap[path]) {
51404
+ pathToObjectApiNamesMap[path] = [];
51182
51405
  }
51183
- if (!objectInfoApiMap[path].includes(childRelationship.childObjectApiName)) {
51184
- objectInfoApiMap[path].push(childRelationship.childObjectApiName);
51406
+ if (!pathToObjectApiNamesMap[path].includes(childRelationship.childObjectApiName)) {
51407
+ pathToObjectApiNamesMap[path].push(childRelationship.childObjectApiName);
51185
51408
  }
51186
51409
  if (!apiNames.includes(childRelationship.childObjectApiName)) {
51187
51410
  apiNames.push(childRelationship.childObjectApiName);
@@ -51203,10 +51426,10 @@
51203
51426
  }
51204
51427
  for (const nodeInfo of validObjectInfoNodes) {
51205
51428
  const field = nodeInfo.name;
51206
- const subLevelFields = objectInfotree[field];
51207
- const path = `${parentPath}_${field}`;
51429
+ const path = `${parentPath}#${field}`;
51430
+ const subLevelFields = objectInfotree[path];
51208
51431
  if (subLevelFields && subLevelFields.length > 0) {
51209
- const subObjectInfos = await fetchObjectInfos(objectInfotree, objectInfoApiMap, updatedObjectInfoMap, subLevelFields, path, objectInfoService);
51432
+ const subObjectInfos = await fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, updatedObjectInfoMap, subLevelFields, path, objectInfoService);
51210
51433
  updatedObjectInfoMap = { ...updatedObjectInfoMap, ...subObjectInfos };
51211
51434
  }
51212
51435
  }
@@ -51221,27 +51444,29 @@
51221
51444
  * 'path' and 'queryNode' is 1 level above the 'filterNode'
51222
51445
  * @param filterNode filter node which needs to be injected. For example, 'State' ObjectFieldNode within filter 'where: { State: { eq: "Nova Scotia" }}'
51223
51446
  * @param idState ID state will be updated to determine if the ID fields in AST need to be swapped. The swapping happens later.
51224
- * @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
51447
+ * @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
51448
+ * @param isParentPolymorphic true if parent points to a polymorphic field.
51225
51449
  * @param queryNode referece FieldNode which provides the information if 'filterNode' exist in it nor not.
51226
51450
  * @param objectInfos ObjectInfo map used in injection. If ObjectInfo misses or field does not exist in ObjectInfo, the error will be thrown
51227
- * @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'].
51451
+ * @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'].
51228
51452
  * @param draftFunctions functions for working with record ids that may be draft-created ids
51229
51453
  * @returns an array of nodes with injected fields
51230
51454
  */
51231
- function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode) {
51455
+ function injectFilter(filterNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode) {
51232
51456
  const injectedSelections = [];
51457
+ let isPolymorphicField = false;
51233
51458
  switch (filterNode.kind) {
51234
51459
  case Kind.ARGUMENT:
51235
51460
  if (filterNode.value.kind !== 'ObjectValue')
51236
51461
  return [];
51237
51462
  filterNode.value.fields.forEach((objectFieldNode) => {
51238
- let subResults = injectFilter(objectFieldNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode);
51463
+ let subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
51239
51464
  for (const subResult of subResults) {
51240
51465
  mergeOrAddToGroup(injectedSelections, subResult);
51241
51466
  }
51242
51467
  // multiple Ids might need to be swapped. remember their paths for faster write.
51243
51468
  if (idState.swapNeeded) {
51244
- idState.paths.push(path);
51469
+ idState.paths.push(parentPath);
51245
51470
  }
51246
51471
  });
51247
51472
  return injectedSelections;
@@ -51250,7 +51475,7 @@
51250
51475
  case Kind.LIST: {
51251
51476
  filterNode.value.values.filter(isObjectValueNode).forEach((objectValueNode) => {
51252
51477
  objectValueNode.fields.forEach((objectFieldNode) => {
51253
- const subResults = injectFilter(objectFieldNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode);
51478
+ const subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
51254
51479
  for (const subResult of subResults) {
51255
51480
  mergeOrAddToGroup(injectedSelections, subResult);
51256
51481
  }
@@ -51261,7 +51486,7 @@
51261
51486
  case Kind.OBJECT: {
51262
51487
  if (filterNode.name.value === 'not') {
51263
51488
  filterNode.value.fields.forEach((objectFieldNode) => {
51264
- const subResults = injectFilter(objectFieldNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode);
51489
+ const subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
51265
51490
  for (const subResult of subResults) {
51266
51491
  mergeOrAddToGroup(injectedSelections, subResult);
51267
51492
  }
@@ -51271,15 +51496,15 @@
51271
51496
  let apiNames = [];
51272
51497
  let isScalarField = false;
51273
51498
  //It is possible that this is a polymorphic field
51274
- apiNames = objectInfoApiMap[path];
51275
- // example: path: 'ServiceAppointment_LastModifiedDate'; filterNode: '{eq: {literal: LAST_WEEK}}'. queryNode: 'LastModifedDate { value}' FilterNode's parent has been verifed as a valid node
51499
+ apiNames = pathToObjectApiNamesMap[parentPath];
51500
+ // example: path: 'ServiceAppointment#LastModifiedDate'; filterNode: '{eq: {literal: LAST_WEEK}}'. queryNode: 'LastModifedDate { value}' FilterNode's parent has been verifed as a valid node
51276
51501
  if (apiNames === undefined) {
51277
51502
  isScalarField = true;
51278
51503
  }
51279
51504
  else {
51280
51505
  if (apiNames.some((apiName) => objectInfos[apiName] === undefined)) {
51281
51506
  // eslint-disable-next-line
51282
- throw new Error(`ObjectInfo is missing for ${path}`);
51507
+ throw new Error(`ObjectInfo is missing for ${parentPath}`);
51283
51508
  }
51284
51509
  }
51285
51510
  if (isScalarField) {
@@ -51301,29 +51526,19 @@
51301
51526
  }
51302
51527
  });
51303
51528
  let isSpanning = false;
51529
+ // if true, current node is a polymorphic concrete type node. For example, field node `User` under `Owner`
51304
51530
  let isInlineFragment = false;
51305
- let isPolymorphicField = false;
51306
51531
  let isTypeNameExisting = false;
51307
51532
  let curPath;
51308
51533
  let fieldName = filterNode.name.value;
51309
- curPath = `${path}_${fieldName}`;
51310
- if (objectInfoApiMap[curPath] && objectInfoApiMap[curPath].length > 0) {
51534
+ curPath = `${parentPath}#${fieldName}`;
51535
+ if (pathToObjectApiNamesMap[curPath] &&
51536
+ pathToObjectApiNamesMap[curPath].length > 0) {
51311
51537
  isSpanning = true;
51312
- if (objectInfoApiMap[curPath].length === 1) {
51313
- if (objectInfoApiMap[path] &&
51314
- objectInfoApiMap[path].length >= 1 &&
51315
- objectInfoApiMap[path].includes(objectInfoApiMap[curPath][0])) {
51316
- isInlineFragment = true;
51317
- }
51318
- }
51319
- // Checks if the current filter node is a polymorphic field. 'ServiceAppointment_Owner' --> ['User']; 'ServiceAppointment_Owner_User' --> ['User']
51320
- const childApiName = objectInfoApiMap[curPath][0];
51321
- const trialApiNames = objectInfoApiMap[`${curPath}_${childApiName}`];
51322
- if (trialApiNames !== undefined &&
51323
- trialApiNames.length === 1 &&
51324
- trialApiNames[0] === childApiName) {
51325
- isPolymorphicField = true;
51326
- }
51538
+ isInlineFragment =
51539
+ isParentPolymorphic &&
51540
+ pathToObjectApiNamesMap[curPath].length === 1;
51541
+ isPolymorphicField = isPolymorphicFieldPath(curPath, pathToObjectApiNamesMap, objectInfos);
51327
51542
  }
51328
51543
  // 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]
51329
51544
  if (isInlineFragment) {
@@ -51367,23 +51582,25 @@
51367
51582
  throw new Error(`Field ${fieldName} does not exist in ${apiNames[0]}`);
51368
51583
  }
51369
51584
  }
51370
- const objectInfoName = objectInfoApiMap[curPath] !== undefined
51371
- ? objectInfoApiMap[curPath][0]
51372
- : objectInfoApiMap[path][0];
51585
+ const objectInfoName = pathToObjectApiNamesMap[curPath] !== undefined
51586
+ ? pathToObjectApiNamesMap[curPath][0]
51587
+ : pathToObjectApiNamesMap[parentPath][0];
51373
51588
  const isIdField = isFieldAnIdField(filterNode.name.value, objectInfos[objectInfoName]);
51374
51589
  if (!isIdField) {
51375
51590
  let subSelectionNodes = [];
51376
51591
  let subFieldsHasId = false;
51377
- filterNode.value.fields.forEach((subFieldNode) => {
51378
- // Check if the filter field has the 'Id'
51379
- if (isFieldAnIdField(subFieldNode.name.value, objectInfos[objectInfoName])) {
51380
- subFieldsHasId = true;
51381
- updateIDInfo(subFieldNode, idState, draftFunctions);
51382
- }
51383
- // try injecting the fields within predicate no matter it has relation or not.
51384
- let subResults = injectFilter(subFieldNode, idState, curPath, objectInfos, objectInfoApiMap, draftFunctions, existingFields ? existingFields[0] : undefined);
51385
- subSelectionNodes = subSelectionNodes.concat(subResults);
51386
- });
51592
+ if (isSpanning) {
51593
+ filterNode.value.fields.forEach((subFieldNode) => {
51594
+ // Check if the filter field has the 'Id'
51595
+ if (isFieldAnIdField(subFieldNode.name.value, objectInfos[objectInfoName])) {
51596
+ subFieldsHasId = true;
51597
+ updateIDInfo(subFieldNode, idState, draftFunctions);
51598
+ }
51599
+ // try injecting the fields within predicate no matter it has relation or not.
51600
+ let subResults = injectFilter(subFieldNode, idState, curPath, isPolymorphicField, objectInfos, pathToObjectApiNamesMap, draftFunctions, existingFields ? existingFields[0] : undefined);
51601
+ subSelectionNodes = subSelectionNodes.concat(subResults);
51602
+ });
51603
+ }
51387
51604
  if (!subFieldsHasId) {
51388
51605
  // Check if the query field has the 'Id'
51389
51606
  const existingIdsInQuery = existingFields &&
@@ -51412,6 +51629,7 @@
51412
51629
  }
51413
51630
  //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
51414
51631
  if (!existingFields ||
51632
+ existingFields.length === 0 ||
51415
51633
  subSelectionNodes.length > 0 ||
51416
51634
  (isSpanning && !subFieldsHasId) ||
51417
51635
  (isInlineFragment && !isTypeNameExisting)) {
@@ -51542,6 +51760,44 @@
51542
51760
  }
51543
51761
  group.push(element);
51544
51762
  }
51763
+ // checks if the path points to a polymorphic field. For example, for the below `pathToObjectApiNamesMap`
51764
+ // {
51765
+ // 'ServiceAppointment' -> ['ServiceAppointment']
51766
+ // 'ServiceAppointment#Owner' --> ['User'],
51767
+ // 'ServiceAppointment#Owner#User' --> ['User']
51768
+ // }
51769
+ // path `ServiceAppointment#Owner` points to a polymorphic field, but path `ServiceAppointment#Owner#User` does not.
51770
+ function isPolymorphicFieldPath(path, pathToObjectApiNamesMap, objectInfos) {
51771
+ const lastSegmentIndex = path.lastIndexOf('#');
51772
+ if (lastSegmentIndex < 0) {
51773
+ return false;
51774
+ }
51775
+ const lastSegment = path.slice(lastSegmentIndex + 1);
51776
+ const parentApiPath = path.slice(0, lastSegmentIndex);
51777
+ if (pathToObjectApiNamesMap[parentApiPath] === undefined) {
51778
+ return false;
51779
+ }
51780
+ const parentObjectApiNames = pathToObjectApiNamesMap[parentApiPath];
51781
+ // 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`.
51782
+ // 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.
51783
+ // Below are the entries in `pathToObjectApiNamesMap`
51784
+ // {
51785
+ // `ServiceAppointmen`t: [`ServiceAppointment`],
51786
+ // `ServiceAppointment#Owner`: [`User`, `Group`],
51787
+ // `ServiceAppointment#Owner#User`: [`User`],
51788
+ // `ServiceAppointment#Owner#Group`: [`Group`],
51789
+ // }
51790
+ if (parentObjectApiNames.length !== 1) {
51791
+ return false;
51792
+ }
51793
+ const parentObjectInfo = objectInfos[parentObjectApiNames[0]];
51794
+ const relationshipField = referenceIdFieldForRelationship(lastSegment);
51795
+ let fieldDefinition = parentObjectInfo.fields[relationshipField];
51796
+ if (fieldDefinition === undefined) {
51797
+ return false;
51798
+ }
51799
+ return fieldDefinition.polymorphicForeignKey;
51800
+ }
51545
51801
  function isFieldAnIdField(fieldName, objectInfo) {
51546
51802
  if (fieldName === 'Id')
51547
51803
  return true;
@@ -51594,10 +51850,10 @@
51594
51850
  * @param parentNode parent node of param 1
51595
51851
  * @param ancestors ancester of param 1
51596
51852
  * @param objectInfos ObjectInfo map used in injection. If ObjectInfo misses or field does not exist in ObjectInfo, the error will be thrown
51597
- * @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'].
51853
+ * @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'].
51598
51854
  * @return injected SelectionNodes used to construct the InlineFragment.
51599
51855
  */
51600
- function injectFields(selections, parentNode, ancestors, objectInfos, objectInfoApiMap) {
51856
+ function injectFields(selections, parentNode, parentPath, ancestors, objectInfos, pathToObjectApiNamesMap) {
51601
51857
  /**
51602
51858
  * 1 parentship can return 2 FieldNode which need to be flattened
51603
51859
  * Concact: { ** Contact { ** ContactId {
@@ -51615,6 +51871,10 @@
51615
51871
  if (!selection.selectionSet) {
51616
51872
  return selection;
51617
51873
  }
51874
+ const segment = isFieldNode(selection)
51875
+ ? selection.name.value
51876
+ : selection.typeCondition.name.value;
51877
+ const curPath = `${parentPath}#${segment}`;
51618
51878
  const spanningSubSelections = [];
51619
51879
  for (const subSelection of selection.selectionSet.selections) {
51620
51880
  if (isFieldSpanning(subSelection, selection)) {
@@ -51622,7 +51882,7 @@
51622
51882
  }
51623
51883
  }
51624
51884
  // Handles multiple level field injection like 'ServiceAppointment' --> 'Account' --> 'Owner'
51625
- const subInjectedSelections = injectFields(spanningSubSelections, selection, ancestors, objectInfos, objectInfoApiMap);
51885
+ const subInjectedSelections = injectFields(spanningSubSelections, selection, curPath, ancestors, objectInfos, pathToObjectApiNamesMap);
51626
51886
  if (!selection.selectionSet) {
51627
51887
  return selection;
51628
51888
  }
@@ -51665,7 +51925,7 @@
51665
51925
  }
51666
51926
  }
51667
51927
  // For polymorphic fields, the Id field is excluded.
51668
- const excludeId = selection.selectionSet.selections.every(isInlineFragmentNode);
51928
+ const excludeId = isPolymorphicFieldPath(curPath, pathToObjectApiNamesMap, objectInfos);
51669
51929
  const idSelection = [];
51670
51930
  if (!excludeId && !hasIdAlready) {
51671
51931
  idSelection.push({
@@ -51676,8 +51936,8 @@
51676
51936
  },
51677
51937
  });
51678
51938
  }
51679
- // 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,
51680
- // please reference 'removeSyntheticFields'.
51939
+ // 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
51940
+ // `typedCondition` of the InlineFragment in the query AST. It is used to match JSON response with AST node. For more detail, please reference 'removeSyntheticFields'.
51681
51941
  if (isInlineFragmentNode(selection) &&
51682
51942
  !selection.selectionSet.selections.find((selection) => isFieldNode(selection) && selection.name.value === '__typename')) {
51683
51943
  idSelection.push({
@@ -51711,7 +51971,7 @@
51711
51971
  if (isFieldNode(parentNode) && parentNode.selectionSet && parentNode.name.value === 'node') {
51712
51972
  if (parentNode.selectionSet.selections
51713
51973
  .filter(isFieldOrInlineFragmentNode)
51714
- .some((selectionNode) => isRelationship(selectionNode, 'childRelationship'))) {
51974
+ .some((selectionNode) => isRelationship(selectionNode, CHILD_RELATIONSHIP))) {
51715
51975
  if (!parentNode.selectionSet.selections
51716
51976
  .filter(isFieldNode)
51717
51977
  .some((sibling) => sibling.name.value === 'Id')) {
@@ -51730,15 +51990,15 @@
51730
51990
  if (parentInfo.parentIndex >= 0) {
51731
51991
  // example node { TimeSheetEntries { edges { node { Id }}}}
51732
51992
  const parent = parentInfo.node;
51733
- if (isRelationship(parent, 'childRelationship')) {
51993
+ if (isRelationship(parent, CHILD_RELATIONSHIP)) {
51734
51994
  const unVisitedAncestors = ancestors.slice(0, parentInfo.parentIndex);
51735
51995
  // path : "TimeSheet"
51736
51996
  const grandParentPath = findAncesterPath(unVisitedAncestors);
51737
- if (objectInfoApiMap &&
51738
- objectInfoApiMap[grandParentPath] &&
51997
+ if (pathToObjectApiNamesMap &&
51998
+ pathToObjectApiNamesMap[grandParentPath] &&
51739
51999
  objectInfos &&
51740
- objectInfos[objectInfoApiMap[grandParentPath][0]]) {
51741
- const grandParentObjectInfo = objectInfos[objectInfoApiMap[grandParentPath][0]];
52000
+ objectInfos[pathToObjectApiNamesMap[grandParentPath][0]]) {
52001
+ const grandParentObjectInfo = objectInfos[pathToObjectApiNamesMap[grandParentPath][0]];
51742
52002
  // exmaple "TimeSheetEntries"
51743
52003
  const parentFieldName = parent.name.value;
51744
52004
  const targetRelationship = grandParentObjectInfo.childRelationships.find((childRelationship) => {
@@ -51859,7 +52119,7 @@
51859
52119
  },
51860
52120
  value: {
51861
52121
  kind: 'StringValue',
51862
- value: 'childRelationship',
52122
+ value: CHILD_RELATIONSHIP,
51863
52123
  block: false,
51864
52124
  },
51865
52125
  },
@@ -51951,7 +52211,7 @@
51951
52211
  },
51952
52212
  value: {
51953
52213
  kind: 'StringValue',
51954
- value: 'parentRelationship',
52214
+ value: PARENT_RELATIONSHIP,
51955
52215
  block: false,
51956
52216
  },
51957
52217
  },
@@ -52088,7 +52348,9 @@
52088
52348
  jsonOutput[fieldName] = null;
52089
52349
  return;
52090
52350
  }
52091
- jsonOutput[fieldName] = {};
52351
+ if (jsonOutput[fieldName] === undefined) {
52352
+ jsonOutput[fieldName] = {};
52353
+ }
52092
52354
  createUserJsonOutput(selection, jsonInput[fieldName], jsonOutput[fieldName]);
52093
52355
  }
52094
52356
  else {
@@ -52820,34 +53082,42 @@
52820
53082
  }
52821
53083
  const { dataType, relationshipName, referenceToInfos } = fieldInfo;
52822
53084
  const draftFieldValue = record.fields[draftField].value;
52823
- if (dataType === 'Reference' && relationshipName !== null && draftFieldValue !== null) {
52824
- if (typeof draftFieldValue !== 'string') {
52825
- throw Error('reference field value is not a string');
53085
+ if (dataType === 'Reference' && relationshipName !== null) {
53086
+ if (draftFieldValue === null) {
53087
+ recordFields[relationshipName] = {
53088
+ displayValue: null,
53089
+ value: null,
53090
+ };
52826
53091
  }
52827
- const key = getRecordKeyForId(luvio, draftFieldValue);
52828
- const referencedRecord = referencedRecords.get(key);
52829
- recordFields[relationshipName] = {
52830
- displayValue: null,
52831
- value: createLink$2(key),
52832
- };
52833
- // for custom objects, we select the 'Name' field
52834
- // otherwise we check the object info for name fields.
52835
- //if there are multiple we select 'Name' if it exists, otherwise the first one
52836
- if (referencedRecord !== undefined && referenceToInfos.length > 0) {
52837
- let nameField;
52838
- const referenceToInfo = referenceToInfos[0];
52839
- const nameFields = referenceToInfo.nameFields;
52840
- if (nameFields.length !== 0) {
52841
- nameField = nameFields.find((x) => x === 'Name');
52842
- if (nameField === undefined) {
52843
- nameField = nameFields[0];
52844
- }
53092
+ else {
53093
+ if (typeof draftFieldValue !== 'string') {
53094
+ throw Error('reference field value is not a string');
52845
53095
  }
52846
- if (nameField !== undefined) {
52847
- const nameFieldRef = referencedRecord.fields[nameField];
52848
- if (nameFieldRef) {
52849
- recordFields[relationshipName].displayValue =
52850
- (_a = nameFieldRef.displayValue) !== null && _a !== void 0 ? _a : nameFieldRef.value;
53096
+ const key = getRecordKeyForId(luvio, draftFieldValue);
53097
+ const referencedRecord = referencedRecords.get(key);
53098
+ recordFields[relationshipName] = {
53099
+ displayValue: null,
53100
+ value: createLink$2(key),
53101
+ };
53102
+ // for custom objects, we select the 'Name' field
53103
+ // otherwise we check the object info for name fields.
53104
+ //if there are multiple we select 'Name' if it exists, otherwise the first one
53105
+ if (referencedRecord !== undefined && referenceToInfos.length > 0) {
53106
+ let nameField;
53107
+ const referenceToInfo = referenceToInfos[0];
53108
+ const nameFields = referenceToInfo.nameFields;
53109
+ if (nameFields.length !== 0) {
53110
+ nameField = nameFields.find((x) => x === 'Name');
53111
+ if (nameField === undefined) {
53112
+ nameField = nameFields[0];
53113
+ }
53114
+ }
53115
+ if (nameField !== undefined) {
53116
+ const nameFieldRef = referencedRecord.fields[nameField];
53117
+ if (nameFieldRef) {
53118
+ recordFields[relationshipName].displayValue =
53119
+ (_a = nameFieldRef.displayValue) !== null && _a !== void 0 ? _a : nameFieldRef.value;
53120
+ }
52851
53121
  }
52852
53122
  }
52853
53123
  }
@@ -53140,17 +53410,8 @@
53140
53410
  };
53141
53411
  for (const fieldName of keys$3(recordWithSpanningRefLinks.fields)) {
53142
53412
  const fieldKey = buildRecordFieldStoreKey(key, fieldName);
53143
- if (this.collectedFields[fieldKey] !== undefined) {
53144
- const fieldData = recordWithSpanningRefLinks.fields[fieldName];
53145
- normalizedRecord.fields[fieldName] = { __ref: fieldKey };
53146
- publishData(fieldKey, fieldData);
53147
- }
53148
- else if (recordWithSpanningRefLinks.fields[fieldName] &&
53149
- recordWithSpanningRefLinks.fields[fieldName].value &&
53150
- recordWithSpanningRefLinks.fields[fieldName].value.__ref !== undefined) {
53151
- normalizedRecord.fields[fieldName] = { __ref: fieldKey };
53152
- publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
53153
- }
53413
+ normalizedRecord.fields[fieldName] = { __ref: fieldKey };
53414
+ publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
53154
53415
  }
53155
53416
  // publish the normalized record
53156
53417
  publishData(key, normalizedRecord);
@@ -54049,6 +54310,9 @@
54049
54310
  if (!rebuildResult.errors) {
54050
54311
  rebuildResult = removeSyntheticFields(rebuildResult, config.query);
54051
54312
  }
54313
+ if (objectsDeepEqual(rebuildResult, originalSnapshot.data)) {
54314
+ return originalSnapshot;
54315
+ }
54052
54316
  // '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.
54053
54317
  return {
54054
54318
  ...originalSnapshot,
@@ -54067,7 +54331,7 @@
54067
54331
  // Fulfilled snapshot (this only happens in this code path if
54068
54332
  // the error is network error or 504), otherwise we spread over
54069
54333
  // the non-eval'ed snapshot (which will be either Fulfilled or Stale)
54070
- return nonEvaluatedSnapshot.state === 'Error'
54334
+ const resultSnapshot = nonEvaluatedSnapshot.state === 'Error'
54071
54335
  ? createLocalEvalSnapshot(gqlResult, seenRecords, recordId, rebuildWithLocalEval)
54072
54336
  : {
54073
54337
  ...nonEvaluatedSnapshot,
@@ -54076,6 +54340,22 @@
54076
54340
  seenRecords,
54077
54341
  rebuildWithLocalEval,
54078
54342
  };
54343
+ const { refresh, state } = resultSnapshot;
54344
+ if (state !== 'Error' && refresh) {
54345
+ // after refreshing a graphql snapshot, we want to force a rebuild regardless
54346
+ // of if the call failed or not or if the data changed or not because we want
54347
+ // to make sure any potential new drafts are picked up
54348
+ resultSnapshot.refresh = {
54349
+ ...refresh,
54350
+ resolve: (config) => {
54351
+ return refresh.resolve(config).finally(() => {
54352
+ luvio.storePublish(resultSnapshot.recordId, undefined);
54353
+ luvio.storeBroadcast();
54354
+ });
54355
+ },
54356
+ };
54357
+ }
54358
+ return resultSnapshot;
54079
54359
  };
54080
54360
  }
54081
54361
 
@@ -55129,7 +55409,10 @@
55129
55409
  * @returns the merged record
55130
55410
  */
55131
55411
  function mergeAggregateUiResponse(response, mergeFunc) {
55132
- const { body } = response;
55412
+ if (response.ok === false) {
55413
+ return response;
55414
+ }
55415
+ const body = response.body;
55133
55416
  try {
55134
55417
  if (body === null ||
55135
55418
  body === undefined ||
@@ -56960,6 +57243,9 @@
56960
57243
  this.ldsRecordRefresher = config.ldsRecordRefresher;
56961
57244
  this.networkWorkerPool = new AsyncWorkerPool(this.concurrency);
56962
57245
  this.useBatchGQL = ldsPrimingGraphqlBatch.isOpen({ fallback: false });
57246
+ if (this.useBatchGQL) {
57247
+ this.batchSize = this.batchSize / DEFAULT_GQL_QUERY_BATCH_SIZE;
57248
+ }
56963
57249
  this.conflictPool = new ConflictPool(config.store, this.objectInfoLoader);
56964
57250
  }
56965
57251
  // function that enqueues priming work
@@ -57816,7 +58102,7 @@
57816
58102
  id: '@salesforce/lds-network-adapter',
57817
58103
  instrument: instrument$1,
57818
58104
  });
57819
- // version: 1.228.1-4e6356f71
58105
+ // version: 1.229.0-dev10-bc9ef2513
57820
58106
 
57821
58107
  const { create: create$2, keys: keys$2 } = Object;
57822
58108
  const { stringify: stringify$1, parse: parse$1 } = JSON;
@@ -71140,7 +71426,7 @@
71140
71426
  }
71141
71427
  }
71142
71428
 
71143
- const { assign, create: create$1, freeze: freeze$1, keys: keys$1 } = Object;
71429
+ const { assign, create: create$1, freeze: freeze$1, isFrozen, keys: keys$1 } = Object;
71144
71430
  const { isArray: isArray$1 } = Array;
71145
71431
  const { concat, filter, includes, push, reduce } = Array.prototype;
71146
71432
 
@@ -72579,6 +72865,7 @@
72579
72865
  }
72580
72866
  if (fieldData === null) {
72581
72867
  reader.assignScalar(requestedFieldName, sink, fieldData);
72868
+ reader.exitPath();
72582
72869
  return sink;
72583
72870
  }
72584
72871
  const fieldType = getFieldType(sel);
@@ -72604,17 +72891,8 @@
72604
72891
  return sink;
72605
72892
  }
72606
72893
  function selectTypeLink(sel, fieldData, reader, key, sink, variables, fragments, version, selectFn, isCursorConnection) {
72607
- const resolvedLink = reader.read({
72608
- recordId: fieldData.__ref,
72609
- node: {
72610
- kind: 'Fragment',
72611
- private: [],
72612
- opaque: true,
72613
- version,
72614
- },
72615
- variables: {}
72616
- });
72617
- if (resolvedLink.data !== undefined) {
72894
+ const resolvedLink = resolveLink$1(reader, fieldData, version);
72895
+ if (resolvedLink && resolvedLink.data !== undefined) {
72618
72896
  if (isCursorConnection) {
72619
72897
  selectTypeLinkWithPagination(resolvedLink, sel, fieldData, reader, key, sink, variables, fragments, selectFn);
72620
72898
  }
@@ -73190,19 +73468,11 @@
73190
73468
  }
73191
73469
  case 'PolymorphicParentRelationship':
73192
73470
  case 'RecordRepresentation': {
73193
- const spanningFieldLink = reader.read({
73194
- recordId: fieldData.__ref,
73195
- node: {
73196
- kind: 'Fragment',
73197
- private: [],
73198
- opaque: true,
73199
- version: VERSION$f,
73200
- },
73201
- variables: {},
73202
- });
73203
- reader.markSeenId(fieldData.__ref);
73204
- const resolvedSpanningFieldValue = spanningFieldLink.data;
73471
+ const spanningFieldLink = resolveLink$1(reader, fieldData, VERSION$f);
73472
+ const resolvedSpanningFieldValue = spanningFieldLink && spanningFieldLink.data;
73473
+ const fieldDataRef = fieldData.__ref;
73205
73474
  if (resolvedSpanningFieldValue !== undefined) {
73475
+ reader.markSeenId(fieldDataRef);
73206
73476
  const { value: spanningFieldResult } = resolvedSpanningFieldValue;
73207
73477
  // Handle null values - graphql will return it at the field level, not return nested { value: null }
73208
73478
  if (spanningFieldResult === null || typeof spanningFieldResult !== 'object') {
@@ -73226,7 +73496,9 @@
73226
73496
  }
73227
73497
  }
73228
73498
  else {
73229
- reader.markMissingLink(fieldData.__ref);
73499
+ if (fieldDataRef !== undefined) {
73500
+ reader.markMissingLink(fieldDataRef);
73501
+ }
73230
73502
  reader.markMissing();
73231
73503
  }
73232
73504
  break;
@@ -76262,7 +76534,7 @@
76262
76534
  configuration: { ...configurationForGraphQLAdapters },
76263
76535
  instrument,
76264
76536
  });
76265
- // version: 1.228.1-0cb6f94f1
76537
+ // version: 1.229.0-dev10-abb060196
76266
76538
 
76267
76539
  // On core the unstable adapters are re-exported with different names,
76268
76540
 
@@ -78509,7 +78781,7 @@
78509
78781
  unstable_graphQL_imperative = createImperativeAdapter(luvio, createInstrumentedAdapter(ldsAdapter, adapterMetadata), adapterMetadata);
78510
78782
  graphQLImperative = ldsAdapter;
78511
78783
  });
78512
- // version: 1.228.1-0cb6f94f1
78784
+ // version: 1.229.0-dev10-abb060196
78513
78785
 
78514
78786
  var gqlApi = /*#__PURE__*/Object.freeze({
78515
78787
  __proto__: null,
@@ -79240,4 +79512,4 @@
79240
79512
  Object.defineProperty(exports, '__esModule', { value: true });
79241
79513
 
79242
79514
  }));
79243
- // version: 1.228.1-4e6356f71
79515
+ // version: 1.229.0-dev10-bc9ef2513