@salesforce/lds-runtime-mobile 1.229.0-dev1 → 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.
Files changed (3) hide show
  1. package/dist/main.js +416 -231
  2. package/package.json +18 -18
  3. package/sfdc/main.js +416 -231
package/dist/main.js CHANGED
@@ -17,6 +17,7 @@ import { HttpStatusCode, StoreKeySet, serializeStructuredKey, Reader, deepFreeze
17
17
  import excludeStaleRecordsGate from '@salesforce/gate/lds.graphqlEvalExcludeStaleRecords';
18
18
  import { parseAndVisit, Kind, visit, execute, buildSchema, isObjectType, defaultFieldResolver } from '@luvio/graphql-parser';
19
19
  import { getRecordId18, keyBuilderQuickActionExecutionRepresentation, ingestQuickActionExecutionRepresentation, keyBuilderContentDocumentCompositeRepresentation, getResponseCacheKeysContentDocumentCompositeRepresentation, keyBuilderFromTypeContentDocumentCompositeRepresentation, ingestContentDocumentCompositeRepresentation, keyBuilderRecord, getTypeCacheKeysRecord, keyBuilderFromTypeRecordRepresentation, ingestRecord, RecordRepresentationRepresentationType, ObjectInfoRepresentationType, getRecordAdapterFactory, getObjectInfoAdapterFactory, getObjectInfosAdapterFactory, UiApiNamespace, RecordRepresentationType, RecordRepresentationTTL, RecordRepresentationVersion, getRecordsAdapterFactory } from '@salesforce/lds-adapters-uiapi';
20
+ import ldsIdempotencyWriteDisabled from '@salesforce/gate/lds.idempotencyWriteDisabled';
20
21
  import caseSensitiveUserId from '@salesforce/user/Id';
21
22
  import { idleDetector, getInstrumentation } from 'o11y/client';
22
23
  import ldsUseShortUrlGate from '@salesforce/gate/lds.useShortUrl';
@@ -552,6 +553,9 @@ function handleDurableStoreRejection(instrument) {
552
553
  }
553
554
 
554
555
  function isStoreEntryError(storeRecord) {
556
+ if (!storeRecord || typeof storeRecord !== 'object') {
557
+ return false;
558
+ }
555
559
  return storeRecord.__type === 'error';
556
560
  }
557
561
 
@@ -1136,12 +1140,12 @@ function makeDurable(environment, { durableStore, instrumentation }) {
1136
1140
  }
1137
1141
  return environment.getNode(key, ingestStagingStore);
1138
1142
  };
1139
- const wrapNormalizedGraphNode = function (normalized) {
1143
+ const wrapNormalizedGraphNode = function (normalized, key) {
1140
1144
  validateNotDisposed();
1141
1145
  if (ingestStagingStore === null) {
1142
1146
  ingestStagingStore = buildIngestStagingStore(environment);
1143
1147
  }
1144
- return environment.wrapNormalizedGraphNode(normalized, ingestStagingStore);
1148
+ return environment.wrapNormalizedGraphNode(normalized, key, ingestStagingStore);
1145
1149
  };
1146
1150
  const rebuildSnapshot = function (snapshot, onRebuild) {
1147
1151
  validateNotDisposed();
@@ -2780,6 +2784,7 @@ function isScalarDataType(type) {
2780
2784
  'Email',
2781
2785
  'TextArea',
2782
2786
  'Percent',
2787
+ 'EncryptedString',
2783
2788
  ].includes(type);
2784
2789
  }
2785
2790
 
@@ -4260,8 +4265,12 @@ function rootRecordQuery(selection, input) {
4260
4265
  // If there is no metadata for this query or it somehow lacks a timestamp
4261
4266
  // skip setting the root timestamp
4262
4267
  if (queryMetadata !== undefined && queryMetadata.ingestionTimestamp !== undefined) {
4263
- // subtract 10ms from timestamp to account for ingestion processing time
4264
- input.rootTimestamp = queryMetadata.ingestionTimestamp - 10;
4268
+ const timestamp = Number(queryMetadata.ingestionTimestamp);
4269
+ if (!isNaN(timestamp)) {
4270
+ // adjust the timestamp to account for ingestion processing time
4271
+ // 30s is used because this is the default record TTL
4272
+ input.rootTimestamp = timestamp - 30000;
4273
+ }
4265
4274
  }
4266
4275
  }
4267
4276
  return recordQuery(selection, alias, apiName, [], input);
@@ -5074,8 +5083,12 @@ function uuidv4() {
5074
5083
 
5075
5084
  const HTTP_HEADER_RETRY_AFTER = 'Retry-After';
5076
5085
  const HTTP_HEADER_IDEMPOTENCY_KEY = 'Idempotency-Key';
5086
+ const ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED = 'IDEMPOTENCY_FEATURE_NOT_ENABLED';
5087
+ const ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED = 'IDEMPOTENCY_NOT_SUPPORTED';
5077
5088
  const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = 'IDEMPOTENCY_KEY_USED_DIFFERENT_USER';
5078
5089
  const ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST = 'IDEMPOTENCY_CONCURRENT_REQUEST';
5090
+ const ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED = 'IDEMPOTENCY_KEY_ALREADY_USED';
5091
+ const ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR = 'IDEMPOTENCY_BACKEND_OPERATION_ERROR';
5079
5092
  /**
5080
5093
  * Get the retry after in milliseconds from the response headers, undefined if not specified.
5081
5094
  * The header could have two different format.
@@ -5105,7 +5118,9 @@ function buildLuvioOverrideForDraftAdapters(luvio, handler, extractTargetIdFromC
5105
5118
  const dispatchResourceRequest = async function (resourceRequest, _context) {
5106
5119
  const resourceRequestCopy = clone$1(resourceRequest);
5107
5120
  resourceRequestCopy.headers = resourceRequestCopy.headers || {};
5108
- resourceRequestCopy.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
5121
+ if (handler.hasIdempotencySupport()) {
5122
+ resourceRequestCopy.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
5123
+ }
5109
5124
  // enable return extra fields for record creation and record update http call
5110
5125
  if (resourceRequest.basePath === '/ui-api/records' &&
5111
5126
  (resourceRequest.method === 'post' || resourceRequest.method === 'patch')) {
@@ -5941,6 +5956,12 @@ class AbstractResourceRequestActionHandler {
5941
5956
  // the luvio store redirect table, during which a new draft might be enqueued
5942
5957
  // which would not see a necessary mapping.
5943
5958
  this.ephemeralRedirects = {};
5959
+ // determined by Server setup.
5960
+ this.isIdempotencySupported = true;
5961
+ // idempotency write flag set by lds
5962
+ this.isLdsIdempotencyWriteDisabled = ldsIdempotencyWriteDisabled.isOpen({
5963
+ fallback: false,
5964
+ });
5944
5965
  }
5945
5966
  enqueue(data) {
5946
5967
  return this.draftQueue.enqueue(this.handlerId, data);
@@ -5970,21 +5991,43 @@ class AbstractResourceRequestActionHandler {
5970
5991
  retryDelayInMs = getRetryAfterInMs(response.headers);
5971
5992
  shouldRetry = true;
5972
5993
  break;
5973
- case HttpStatusCode.ServerError:
5994
+ case HttpStatusCode.ServerError: {
5974
5995
  shouldRetry = true;
5996
+ if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_BACKEND_OPERATION_ERROR)) {
5997
+ this.isIdempotencySupported = false;
5998
+ retryDelayInMs = 0;
5999
+ actionDataChanged = true;
6000
+ }
5975
6001
  break;
6002
+ }
5976
6003
  case 409 /* IdempotentWriteSpecificHttpStatusCode.Conflict */: {
5977
- const errorCode = response.body[0].errorCode;
5978
- if (errorCode === ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER) {
5979
- updatedAction.data.headers = updatedAction.data.headers || {};
5980
- updatedAction.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
6004
+ if (this.isUiApiErrors(response.body)) {
6005
+ const errorCode = response.body[0].errorCode;
6006
+ if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER)) {
6007
+ retryDelayInMs = 0;
6008
+ actionDataChanged = true;
6009
+ }
6010
+ else if (errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST) {
6011
+ retryDelayInMs = getRetryAfterInMs(response.headers);
6012
+ }
6013
+ shouldRetry = true;
6014
+ }
6015
+ break;
6016
+ }
6017
+ case HttpStatusCode.BadRequest: {
6018
+ if (this.handleIdempotencyServerError(response.body, updatedAction, false, ERROR_CODE_IDEMPOTENCY_FEATURE_NOT_ENABLED, ERROR_CODE_IDEMPOTENCY_NOT_SUPPORTED)) {
5981
6019
  retryDelayInMs = 0;
5982
6020
  actionDataChanged = true;
6021
+ shouldRetry = true;
5983
6022
  }
5984
- else if (errorCode === ERROR_CODE_IDEMPOTENCY_CONCURRENT_REQUEST) {
5985
- retryDelayInMs = getRetryAfterInMs(response.headers);
6023
+ break;
6024
+ }
6025
+ case 422 /* IdempotentWriteSpecificHttpStatusCode.UnProcessableEntity */: {
6026
+ if (this.handleIdempotencyServerError(response.body, updatedAction, true, ERROR_CODE_IDEMPOTENCY_KEY_ALREADY_USED)) {
6027
+ retryDelayInMs = 0;
6028
+ actionDataChanged = true;
6029
+ shouldRetry = true;
5986
6030
  }
5987
- shouldRetry = true;
5988
6031
  break;
5989
6032
  }
5990
6033
  }
@@ -6003,6 +6046,27 @@ class AbstractResourceRequestActionHandler {
6003
6046
  return ProcessActionResult.NETWORK_ERROR;
6004
6047
  }
6005
6048
  }
6049
+ // 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.
6050
+ handleIdempotencyServerError(responseBody, action, updateIdempotencyKey, ...targetErrorCodes) {
6051
+ if (this.isUiApiErrors(responseBody)) {
6052
+ const errorCode = responseBody[0].errorCode;
6053
+ if (targetErrorCodes.includes(errorCode)) {
6054
+ action.data.headers = action.data.headers || {};
6055
+ if (updateIdempotencyKey) {
6056
+ action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
6057
+ }
6058
+ else {
6059
+ delete action.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY];
6060
+ }
6061
+ return true;
6062
+ }
6063
+ }
6064
+ return false;
6065
+ }
6066
+ // 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.
6067
+ isUiApiErrors(body) {
6068
+ return body !== undefined && Array.isArray(body) && body.length > 0 && body[0].errorCode;
6069
+ }
6006
6070
  async buildPendingAction(request, queue) {
6007
6071
  const targetId = await this.getIdFromRequest(request);
6008
6072
  if (targetId === undefined) {
@@ -6216,6 +6280,10 @@ class AbstractResourceRequestActionHandler {
6216
6280
  ...targetData,
6217
6281
  body: this.mergeRequestBody(targetBody, sourceBody),
6218
6282
  };
6283
+ // Updates Idempotency key if target has one
6284
+ if (targetData.headers && targetData.headers[HTTP_HEADER_IDEMPOTENCY_KEY]) {
6285
+ merged.data.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
6286
+ }
6219
6287
  // overlay metadata
6220
6288
  merged.metadata = { ...targetMetadata, ...sourceMetadata };
6221
6289
  // put status back to pending to auto upload if queue is active and targed is at the head.
@@ -6252,6 +6320,9 @@ class AbstractResourceRequestActionHandler {
6252
6320
  getDraftIdsFromAction(action) {
6253
6321
  return [action.targetId];
6254
6322
  }
6323
+ hasIdempotencySupport() {
6324
+ return this.isIdempotencySupported && !this.isLdsIdempotencyWriteDisabled;
6325
+ }
6255
6326
  async ingestResponses(responses, action) {
6256
6327
  const luvio = this.getLuvio();
6257
6328
  await luvio.handleSuccessResponse(() => {
@@ -8057,6 +8128,10 @@ function isOperationDefinitionNode(node) {
8057
8128
  return node.kind === 'OperationDefinition';
8058
8129
  }
8059
8130
 
8131
+ const POLYMORPHIC_PARENT_RELATIONSHIP = 'polymorphicParentRelationship';
8132
+ const PARENT_RELATIONSHIP = 'parentRelationship';
8133
+ const CHILD_RELATIONSHIP = 'childRelationship';
8134
+ const RECORD_QUERY = 'recordQuery';
8060
8135
  function requestsDraftsField(recordFieldNode) {
8061
8136
  if (!recordFieldNode.selectionSet)
8062
8137
  return false;
@@ -8072,18 +8147,41 @@ function isRecordQuery(recordQueryField) {
8072
8147
  directive.arguments
8073
8148
  .map((argument) => argument.value)
8074
8149
  .filter(isStringValueNode)
8075
- .some((categoryName) => categoryName.value === 'recordQuery'));
8150
+ .some((categoryName) => categoryName.value === RECORD_QUERY));
8076
8151
  });
8077
8152
  }
8078
8153
  return false;
8079
8154
  }
8080
- // finds field with 'recordQuery' and 'childRelationship' directive
8081
- function findNearestRecordQuery(ancestors) {
8082
- const recordQueryAncester = findNearestAncesterPath(ancestors, true).node;
8083
- return recordQueryAncester === undefined ? undefined : recordQueryAncester;
8155
+ // finds connection field with 'recordQuery' and 'childRelationship' directive.
8156
+ function findNearestConnection(ancestors) {
8157
+ const connectionAncestor = findNearestAncesterPath(ancestors, true).node;
8158
+ return connectionAncestor === undefined ? undefined : connectionAncestor;
8159
+ }
8160
+ // convinient method to find nearest connection with its path
8161
+ function findNearestConnectionWithPath(ancestors) {
8162
+ const closestAncestorPath = findNearestAncesterPath(ancestors, true);
8163
+ let connection = undefined;
8164
+ let connectionPath = undefined;
8165
+ if (closestAncestorPath.parentIndex > 0) {
8166
+ const connectionAncestor = closestAncestorPath.node;
8167
+ const connectionAncestors = ancestors.slice(0, closestAncestorPath.parentIndex);
8168
+ connection =
8169
+ connectionAncestor === undefined ? undefined : connectionAncestor;
8170
+ if (connection !== undefined) {
8171
+ const ancesterPath = findAncesterPath(connectionAncestors);
8172
+ connectionPath =
8173
+ ancesterPath === ''
8174
+ ? connection.name.value
8175
+ : `${ancesterPath}#${connection.name.value}`;
8176
+ }
8177
+ }
8178
+ return {
8179
+ connection,
8180
+ path: connectionPath,
8181
+ };
8084
8182
  }
8085
- // 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.
8086
- function findNearestAncesterPath(ancestors, recordQueryOnly) {
8183
+ // 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.
8184
+ function findNearestAncesterPath(ancestors, connectionOnly) {
8087
8185
  let recordQueryPath = { node: undefined, parentIndex: -1 };
8088
8186
  let relationship = '';
8089
8187
  for (let i = ancestors.length - 1; i >= 0; i--) {
@@ -8097,9 +8195,11 @@ function findNearestAncesterPath(ancestors, recordQueryOnly) {
8097
8195
  continue;
8098
8196
  for (let arg of directive.arguments) {
8099
8197
  if (arg.value &&
8100
- (arg.value.value === 'recordQuery' ||
8101
- arg.value.value === 'childRelationship' ||
8102
- (!recordQueryOnly && arg.value.value === 'parentRelationship'))) {
8198
+ (arg.value.value === RECORD_QUERY ||
8199
+ arg.value.value === CHILD_RELATIONSHIP ||
8200
+ (!connectionOnly &&
8201
+ (arg.value.value === PARENT_RELATIONSHIP ||
8202
+ arg.value.value === POLYMORPHIC_PARENT_RELATIONSHIP)))) {
8103
8203
  recordQueryPath = { node: node, parentIndex: i };
8104
8204
  relationship = arg.value.value;
8105
8205
  break;
@@ -8114,17 +8214,19 @@ function findNearestAncesterPath(ancestors, recordQueryOnly) {
8114
8214
  //checks if nearest ancester could be an inline fragment
8115
8215
  if (recordQueryPath.node !== undefined &&
8116
8216
  recordQueryPath.node.selectionSet &&
8117
- relationship === 'parentRelationship') {
8118
- //
8119
- if (recordQueryPath.node.selectionSet.selections.every(isInlineFragmentNode)) {
8120
- //
8121
- const inlineFragmentLoc = recordQueryPath.parentIndex + 2;
8122
- if (inlineFragmentLoc < ancestors.length && ancestors[inlineFragmentLoc]) {
8217
+ (relationship === PARENT_RELATIONSHIP || relationship === POLYMORPHIC_PARENT_RELATIONSHIP)) {
8218
+ // InlineFragment is usually 3 steps aways from its FieldNode parent within ancester hierarchy if it exists. The below search
8219
+ // is applied to adapt to future AST structure change
8220
+ let parentIndex = recordQueryPath.parentIndex + 1;
8221
+ while (parentIndex < ancestors.length) {
8222
+ if (isInlineFragmentNode(ancestors[parentIndex])) {
8123
8223
  recordQueryPath = {
8124
- node: ancestors[inlineFragmentLoc],
8125
- parentIndex: inlineFragmentLoc,
8224
+ node: ancestors[parentIndex],
8225
+ parentIndex,
8126
8226
  };
8227
+ break;
8127
8228
  }
8229
+ parentIndex++;
8128
8230
  }
8129
8231
  }
8130
8232
  return recordQueryPath;
@@ -8148,7 +8250,7 @@ function findAncesterPath(ancesters) {
8148
8250
  ? sectionPath
8149
8251
  : sectionPath === ''
8150
8252
  ? path
8151
- : `${sectionPath}_${path}`;
8253
+ : `${sectionPath}#${path}`;
8152
8254
  }
8153
8255
  }
8154
8256
  boundaryIndex = parentIndex;
@@ -8206,9 +8308,9 @@ function getRelation(node) {
8206
8308
  const relationships = args
8207
8309
  .map((arg) => arg.value)
8208
8310
  .filter(isStringValueNode)
8209
- .filter((valueNode) => valueNode.value === 'childRelationship' ||
8210
- valueNode.value === 'parentRelationship' ||
8211
- valueNode.value === 'polymorphicParentRelationship')
8311
+ .filter((valueNode) => valueNode.value === CHILD_RELATIONSHIP ||
8312
+ valueNode.value === PARENT_RELATIONSHIP ||
8313
+ valueNode.value === POLYMORPHIC_PARENT_RELATIONSHIP)
8212
8314
  .map((relationshipNode) => relationshipNode.value);
8213
8315
  if (relationships.length > 0) {
8214
8316
  return relationships[0];
@@ -8265,8 +8367,8 @@ function isFieldSpanning(node, parentNode) {
8265
8367
  */
8266
8368
  function isParentRelationship(node) {
8267
8369
  return (node &&
8268
- (isRelationship(node, 'parentRelationship') ||
8269
- isRelationship(node, 'polymorphicParentRelationship')));
8370
+ (isRelationship(node, PARENT_RELATIONSHIP) ||
8371
+ isRelationship(node, POLYMORPHIC_PARENT_RELATIONSHIP)));
8270
8372
  }
8271
8373
  /*
8272
8374
  checks if the InlineFragment spans
@@ -8572,6 +8674,26 @@ function findFieldInfo(objectInfo, fieldName) {
8572
8674
  return values$1(objectInfo.fields).find((field) => field.apiName === fieldName ||
8573
8675
  (field.dataType === 'Reference' && field.relationshipName === fieldName));
8574
8676
  }
8677
+ async function readIngestionTimestampForKey(key, query) {
8678
+ let ingestionTimestamp = 0;
8679
+ const sql = `
8680
+ SELECT json_extract(metadata, '${JSON_EXTRACT_PATH_INGESTION_TIMESTAMP}')
8681
+ FROM lds_data
8682
+ WHERE key IS ?
8683
+ `;
8684
+ const results = await query(sql, [key]);
8685
+ const [timestamp] = results.rows.map((row) => row[0]);
8686
+ if (timestamp !== null) {
8687
+ const numericalTimestamp = Number(timestamp);
8688
+ if (isNaN(numericalTimestamp)) {
8689
+ return ingestionTimestamp;
8690
+ }
8691
+ // adjust the timestamp to account for ingestion processing time
8692
+ // 30s is used because this is the default record TTL
8693
+ ingestionTimestamp = numericalTimestamp - 30000;
8694
+ }
8695
+ return ingestionTimestamp;
8696
+ }
8575
8697
 
8576
8698
  function findSpanningField(name) {
8577
8699
  return (field) => {
@@ -9091,17 +9213,7 @@ async function fetchIngestionTimeStampFromDatabase(apiName, info, args, query) {
9091
9213
  const key = buildKeyStringForRecordQuery(operation,
9092
9214
  // join varables passed from query to the argument variables given from the AST
9093
9215
  { ...variableValues, ...args }, info.fieldNodes[0].arguments, apiName);
9094
- const sql = `
9095
- SELECT json_extract(metadata, '${JSON_EXTRACT_PATH_INGESTION_TIMESTAMP}')
9096
- FROM lds_data
9097
- WHERE key IS ?
9098
- `;
9099
- const results = await query(sql, [key]);
9100
- const [timestamp] = results.rows.map((row) => row[0]);
9101
- if (timestamp !== null && typeof timestamp === 'number') {
9102
- //go back 10 ms to adjust for margin of error when top level query is stored and when raml objects are stored
9103
- ingestionTimestamp = timestamp - 10;
9104
- }
9216
+ return readIngestionTimestampForKey(key, query);
9105
9217
  }
9106
9218
  return ingestionTimestamp;
9107
9219
  }
@@ -9149,26 +9261,20 @@ function generateRecordQueries(objectInfos) {
9149
9261
  let recordConnections = ``;
9150
9262
  const polymorphicFieldTypeNames = new Set();
9151
9263
  let typedScalars = new Set();
9264
+ let parentRelationshipFields = new Set();
9152
9265
  for (const objectInfo of values$1(objectInfos)) {
9153
9266
  const { apiName, childRelationships } = objectInfo;
9154
9267
  let fields = ``;
9155
9268
  typedScalars.add(`${apiName}_Filter`);
9156
9269
  typedScalars.add(`${apiName}_OrderBy`);
9157
- for (const childRelationship of childRelationships) {
9158
- const { childObjectApiName } = childRelationship;
9159
- // Only add the relationship if there is relevant objectinfos for it,
9160
- // otherwise we'd be defining types we cannot satisfy and aren't referenced in
9161
- // the query.
9162
- if (objectInfos[childObjectApiName] !== undefined) {
9163
- fields += `${childRelationship.relationshipName}(first: Int, where: ${childObjectApiName}_Filter, orderBy: ${childObjectApiName}_OrderBy, scope: SupportedScopes): ${childObjectApiName}Connection \n`;
9164
- typedScalars.add(`${childObjectApiName}_Filter`);
9165
- typedScalars.add(`${childObjectApiName}_OrderBy`);
9166
- }
9167
- }
9168
9270
  for (const field of values$1(objectInfo.fields)) {
9169
9271
  if (!fieldsStaticallyAdded.includes(field.apiName)) {
9170
9272
  fields += `${field.apiName}: ${dataTypeToType(field.dataType, field.apiName)}\n`;
9171
9273
  }
9274
+ //handles parent relationship
9275
+ if (field.relationshipName === null) {
9276
+ continue;
9277
+ }
9172
9278
  // For spanning parent relationships with no union types
9173
9279
  if (field.referenceToInfos.length === 1) {
9174
9280
  const [relation] = field.referenceToInfos;
@@ -9176,11 +9282,13 @@ function generateRecordQueries(objectInfos) {
9176
9282
  // otherwise we'd be defining types we cannot satisfy and aren't referenced in
9177
9283
  // the query.
9178
9284
  if (objectInfos[relation.apiName] !== undefined) {
9285
+ parentRelationshipFields.add(field.relationshipName);
9179
9286
  fields += `${field.relationshipName}: ${relation.apiName}\n`;
9180
9287
  }
9181
9288
  // For polymorphic field, its type is 'Record' inteface. The concrete entity type name is saved for field resolving of next phase
9182
9289
  }
9183
9290
  else if (field.referenceToInfos.length > 1) {
9291
+ parentRelationshipFields.add(field.relationshipName);
9184
9292
  fields += `${field.relationshipName}: Record\n`;
9185
9293
  for (const relation of field.referenceToInfos) {
9186
9294
  if (objectInfos[relation.apiName] !== undefined) {
@@ -9189,6 +9297,20 @@ function generateRecordQueries(objectInfos) {
9189
9297
  }
9190
9298
  }
9191
9299
  }
9300
+ // handles child relationship
9301
+ for (const childRelationship of childRelationships) {
9302
+ const { childObjectApiName } = childRelationship;
9303
+ // Only add the relationship if there is relevant objectinfos for it,
9304
+ // otherwise we'd be defining types we cannot satisfy and aren't referenced in
9305
+ // the query.
9306
+ // 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
9307
+ if (objectInfos[childObjectApiName] !== undefined &&
9308
+ !parentRelationshipFields.has(childRelationship.relationshipName)) {
9309
+ fields += `${childRelationship.relationshipName}(first: Int, where: ${childObjectApiName}_Filter, orderBy: ${childObjectApiName}_OrderBy, scope: SupportedScopes): ${childObjectApiName}Connection \n`;
9310
+ typedScalars.add(`${childObjectApiName}_Filter`);
9311
+ typedScalars.add(`${childObjectApiName}_OrderBy`);
9312
+ }
9313
+ }
9192
9314
  recordQueries += `${apiName}(first: Int, where: ${apiName}_Filter, orderBy: ${apiName}_OrderBy, scope: SupportedScopes): ${apiName}Connection\n`;
9193
9315
  const isServiceAppointment = apiName === 'ServiceAppointment';
9194
9316
  recordConnections += /* GraphQL */ `
@@ -9263,6 +9385,8 @@ function dataTypeToType(objectInfoDataType, apiName) {
9263
9385
  return 'PercentValue';
9264
9386
  case 'Int':
9265
9387
  return 'IntValue';
9388
+ case 'EncryptedString':
9389
+ return 'EncryptedStringValue';
9266
9390
  // ! do the rest of the custom types
9267
9391
  default:
9268
9392
  return 'String';
@@ -9348,7 +9472,7 @@ const parentRelationshipDirective = {
9348
9472
  },
9349
9473
  value: {
9350
9474
  kind: Kind.STRING,
9351
- value: 'parentRelationship',
9475
+ value: PARENT_RELATIONSHIP,
9352
9476
  block: false,
9353
9477
  },
9354
9478
  },
@@ -9362,8 +9486,8 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9362
9486
  // example 2 'ServiceAppointment' -> ['Owner']; 'Owner' -> ['User', 'Group']
9363
9487
  const objectNodeInfoTree = {};
9364
9488
  // save the field path to apiName map
9365
- // example 1: 'ServiceAppointment' -> ['ServiceAppointment']; 'ServiceAppointment_Account' -> ['Account']; 'ServiceAppointment_Account_Owner' -> ['User']
9366
- const objectInfoApiMap = {};
9489
+ // example 1: 'ServiceAppointment' -> ['ServiceAppointment']; 'ServiceAppointment#ccount' -> ['Account']; 'ServiceAppointment#Account#Owner' -> ['User']
9490
+ const pathToObjectApiNamesMap = {};
9367
9491
  let startNodes = new Set();
9368
9492
  let totalNodes = new Set();
9369
9493
  let objectInfos = {};
@@ -9377,11 +9501,11 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9377
9501
  visit(originalAST, {
9378
9502
  Argument: {
9379
9503
  enter(node, key, parent, path, ancestors) {
9380
- const recordQueryNode = findNearestRecordQuery(ancestors);
9381
- if (!recordQueryNode)
9504
+ const { connection: recordConnectionNode, path: ancesterPath } = findNearestConnectionWithPath(ancestors);
9505
+ if (!recordConnectionNode || !ancesterPath)
9382
9506
  return;
9383
- if (!objectNodeInfoTree[recordQueryNode.name.value]) {
9384
- objectNodeInfoTree[recordQueryNode.name.value] = [];
9507
+ if (!objectNodeInfoTree[ancesterPath]) {
9508
+ objectNodeInfoTree[ancesterPath] = [];
9385
9509
  }
9386
9510
  switch (node.name.value) {
9387
9511
  case 'orderBy':
@@ -9389,12 +9513,12 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9389
9513
  if (node.value.kind !== 'ObjectValue') {
9390
9514
  return;
9391
9515
  }
9392
- totalNodes.add(recordQueryNode.name.value);
9516
+ totalNodes.add(ancesterPath);
9393
9517
  // 'childRelationship' node is not taken as the startNode of the 'NodeInfoTree' graph. The field scanning will construct the graph which lead here.
9394
- if (isRecordQuery(recordQueryNode)) {
9395
- startNodes.add(recordQueryNode.name.value);
9518
+ if (isRecordQuery(recordConnectionNode)) {
9519
+ startNodes.add(recordConnectionNode.name.value);
9396
9520
  }
9397
- growObjectFieldTree(objectNodeInfoTree, recordQueryNode.name.value, node.value, totalNodes, startNodes);
9521
+ growObjectFieldTree(objectNodeInfoTree, ancesterPath, node.value, totalNodes, startNodes);
9398
9522
  break;
9399
9523
  case 'scope':
9400
9524
  if (!isScopeArgumentNodeWithType(node, 'ASSIGNEDTOME', variables)) {
@@ -9409,17 +9533,16 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9409
9533
  name: 'ServiceResources',
9410
9534
  });
9411
9535
  }
9412
- if (objectNodeInfoTree['ServiceResources'] === undefined) {
9413
- objectNodeInfoTree['ServiceResources'] = [];
9414
- }
9415
- if (!objectNodeInfoTree['ServiceResources'].some((child) => child.name === 'ServiceResource')) {
9416
- objectNodeInfoTree['ServiceResources'].push({
9417
- relation: 'parent',
9418
- name: 'ServiceResource',
9419
- });
9536
+ if (objectNodeInfoTree['ServiceAppointment#ServiceResources'] === undefined) {
9537
+ objectNodeInfoTree['ServiceAppointment#ServiceResources'] = [
9538
+ {
9539
+ relation: 'parent',
9540
+ name: 'ServiceResource',
9541
+ },
9542
+ ];
9420
9543
  }
9421
- if (objectNodeInfoTree['ServiceResource'] === undefined) {
9422
- objectNodeInfoTree['ServiceResource'] = [];
9544
+ if (objectNodeInfoTree['ServiceAppointment#ServiceResources#ServiceResource'] === undefined) {
9545
+ objectNodeInfoTree['ServiceAppointment#ServiceResources#ServiceResource'] = [];
9423
9546
  }
9424
9547
  break;
9425
9548
  default:
@@ -9433,7 +9556,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9433
9556
  return;
9434
9557
  if (!node.selectionSet)
9435
9558
  return;
9436
- const recordQueryField = findNearestRecordQuery(ancestors);
9559
+ const recordQueryField = findNearestConnection(ancestors);
9437
9560
  //only injects fields for 'recordQuery' field. ignores the 'childRelationship' field since it will be traversed as the child of the 'recordQuery'
9438
9561
  if (isRecordQuery(recordQueryField) && recordQueryField) {
9439
9562
  totalNodes.add(recordQueryField.name.value);
@@ -9444,21 +9567,21 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9444
9567
  },
9445
9568
  });
9446
9569
  if (objectInfoService && startNodes.size > 0) {
9447
- objectInfos = await resolveObjectInfos(objectNodeInfoTree, objectInfoApiMap, startNodes, objectInfoService);
9570
+ objectInfos = await resolveObjectInfos(objectNodeInfoTree, pathToObjectApiNamesMap, startNodes, objectInfoService);
9448
9571
  }
9449
9572
  // read pass; gather whats needed
9450
9573
  visit(originalAST, {
9451
9574
  Argument: {
9452
9575
  leave(node, key, parent, path, ancestors) {
9453
- const recordQueryField = findNearestRecordQuery(ancestors);
9576
+ const recordQueryField = findNearestConnection(ancestors);
9454
9577
  if (!recordQueryField)
9455
9578
  return;
9456
9579
  const ancestorPath = findAncesterPath(ancestors);
9457
9580
  if (!inlineFragmentSelections[ancestorPath]) {
9458
9581
  inlineFragmentSelections[ancestorPath] = [];
9459
9582
  }
9460
- const recordQueryApiName = objectInfoApiMap[ancestorPath]
9461
- ? objectInfoApiMap[ancestorPath][0]
9583
+ const recordQueryApiName = pathToObjectApiNamesMap[ancestorPath]
9584
+ ? pathToObjectApiNamesMap[ancestorPath][0]
9462
9585
  : recordQueryField.name.value;
9463
9586
  // The record node acts as the reference. The duplicated field in the record node is not injected
9464
9587
  const recordReferenceNode = [recordQueryField]
@@ -9472,7 +9595,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9472
9595
  case 'scope':
9473
9596
  // Hanle 'MINE' field
9474
9597
  if (isScopeArgumentNodeWithType(node, 'MINE', variables)) {
9475
- if (isMineScopeAvailable(ancestorPath, objectInfoApiMap, objectInfos)) {
9598
+ if (isMineScopeAvailable(ancestorPath, pathToObjectApiNamesMap, objectInfos)) {
9476
9599
  // 'typeConditon' is added when the 'InlineFragmentNode' is appended at the write pass
9477
9600
  inlineFragmentSelections[ancestorPath].push(...mineFragmentSelections);
9478
9601
  }
@@ -9498,7 +9621,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9498
9621
  case 'where': {
9499
9622
  inlineFragmentSelections[ancestorPath] = [
9500
9623
  ...inlineFragmentSelections[ancestorPath],
9501
- ...injectFilter(node, idState, ancestorPath, objectInfos, objectInfoApiMap, draftFunctions, recordReferenceNode),
9624
+ ...injectFilter(node, idState, ancestorPath, false, objectInfos, pathToObjectApiNamesMap, draftFunctions, recordReferenceNode),
9502
9625
  ];
9503
9626
  break;
9504
9627
  }
@@ -9514,7 +9637,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9514
9637
  if (!node.selectionSet)
9515
9638
  return;
9516
9639
  // it could be 'recordQuery' or 'childRelationship'
9517
- const recordQueryField = findNearestRecordQuery(ancestors);
9640
+ const recordQueryField = findNearestConnection(ancestors);
9518
9641
  if (!recordQueryField)
9519
9642
  return;
9520
9643
  const ancestorPath = findAncesterPath(ancestors);
@@ -9526,7 +9649,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9526
9649
  spanningSelections.push(selection);
9527
9650
  }
9528
9651
  }
9529
- const injectedFields = injectFields(spanningSelections, node, ancestors, objectInfos, objectInfoApiMap);
9652
+ const injectedFields = injectFields(spanningSelections, node, ancestorPath, ancestors, objectInfos, pathToObjectApiNamesMap);
9530
9653
  const mergedInjectedFields = mergeSelectionNodes(inlineFragmentSelections[ancestorPath], injectedFields);
9531
9654
  inlineFragmentSelections[ancestorPath] = mergedInjectedFields;
9532
9655
  },
@@ -9539,7 +9662,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9539
9662
  // removes 'ServicesResources' query field node if 'assignedtome' scope shows up
9540
9663
  if (assignedtomeQueryFieldNode !== undefined &&
9541
9664
  node.name.value === 'ServiceResources') {
9542
- const serviceResourcesAncestor = findNearestRecordQuery(ancestors);
9665
+ const serviceResourcesAncestor = findNearestConnection(ancestors);
9543
9666
  if (serviceResourcesAncestor === assignedtomeQueryFieldNode) {
9544
9667
  return null;
9545
9668
  }
@@ -9548,7 +9671,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9548
9671
  return;
9549
9672
  if (!node.selectionSet)
9550
9673
  return;
9551
- const recordQueryField = findNearestRecordQuery(ancestors);
9674
+ const recordQueryField = findNearestConnection(ancestors);
9552
9675
  if (!recordQueryField)
9553
9676
  return;
9554
9677
  const ancestorPath = findAncesterPath(ancestors);
@@ -9557,8 +9680,8 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9557
9680
  return;
9558
9681
  //const recordQueryPath = findAncesterPath(ancestors);
9559
9682
  // '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.
9560
- const recordQueryApiName = objectInfoApiMap[ancestorPath]
9561
- ? objectInfoApiMap[ancestorPath][0]
9683
+ const recordQueryApiName = pathToObjectApiNamesMap[ancestorPath]
9684
+ ? pathToObjectApiNamesMap[ancestorPath][0]
9562
9685
  : recordQueryField.name.value;
9563
9686
  const nodeWithFragments = {
9564
9687
  ...node,
@@ -9595,7 +9718,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
9595
9718
  if (node.name.value === 'where') {
9596
9719
  const ancestorPath = findAncesterPath(ancestors);
9597
9720
  if (idState.paths.includes(ancestorPath)) {
9598
- const apiName = objectInfoApiMap[ancestorPath][0];
9721
+ const apiName = pathToObjectApiNamesMap[ancestorPath][0];
9599
9722
  const objectInfo = objectInfos[apiName];
9600
9723
  const swappedIdFilter = swapIdField(node.value, objectInfo, false, idState, draftFunctions);
9601
9724
  return {
@@ -9661,8 +9784,8 @@ function swapIdField(filterFields, objectInfo, swapped, idState, draftFunctions)
9661
9784
  };
9662
9785
  }
9663
9786
  }
9664
- function isMineScopeAvailable(apiNamePath, objectInfoApiMap, objectInfos) {
9665
- const apiName = objectInfoApiMap[apiNamePath];
9787
+ function isMineScopeAvailable(apiNamePath, pathToObjectApiNamesMap, objectInfos) {
9788
+ const apiName = pathToObjectApiNamesMap[apiNamePath];
9666
9789
  if (!apiName)
9667
9790
  return false;
9668
9791
  const objectInfo = objectInfos[apiName[0]];
@@ -9751,15 +9874,16 @@ function growObjectFieldTree(tree, parentNode, entryNode, totalNodes, startNodes
9751
9874
  }
9752
9875
  // example: 'Account'
9753
9876
  const childNode = objectFieldNode.name.value;
9877
+ const childNodepath = `${parentNode}#${childNode}`;
9754
9878
  if (!tree[parentNode].some((child) => child.name === childNode)) {
9755
9879
  tree[parentNode].push({
9756
9880
  relation: 'parent',
9757
9881
  name: childNode,
9758
9882
  });
9759
- totalNodes.add(childNode);
9883
+ totalNodes.add(childNodepath);
9760
9884
  }
9761
9885
  // recursively go to deeper level of filter.
9762
- growObjectFieldTree(tree, childNode, objectFieldNode.value, totalNodes, startNodes);
9886
+ growObjectFieldTree(tree, childNodepath, objectFieldNode.value, totalNodes, startNodes);
9763
9887
  }
9764
9888
  }
9765
9889
  }
@@ -9794,19 +9918,20 @@ function growFieldTree(tree, parentSectionPath, entryNode, parentNode, totalNode
9794
9918
  }
9795
9919
  if (!tree[parentSectionPath].some((field) => field.name === fieldName)) {
9796
9920
  tree[parentSectionPath].push({
9797
- relation: relationType === 'parentRelationship' ||
9798
- relationType === 'polymorphicParentRelationship'
9921
+ relation: relationType === PARENT_RELATIONSHIP ||
9922
+ relationType === POLYMORPHIC_PARENT_RELATIONSHIP
9799
9923
  ? 'parent'
9800
9924
  : 'child',
9801
9925
  name: fieldName,
9802
9926
  });
9803
- totalNodes.add(fieldName);
9927
+ totalNodes.add(`${parentSectionPath}#${fieldName}`);
9804
9928
  }
9805
9929
  if (entryNode.selectionSet && entryNode.selectionSet.selections) {
9806
9930
  const childNodes = entryNode.selectionSet.selections.filter(isFieldOrInlineFragmentNode);
9807
9931
  // recursively build the traversal tree
9808
9932
  for (const child of childNodes) {
9809
- growFieldTree(tree, fieldName, child, entryNode, totalNodes, startNodes);
9933
+ const path = `${parentSectionPath}#${fieldName}`;
9934
+ growFieldTree(tree, path, child, entryNode, totalNodes, startNodes);
9810
9935
  }
9811
9936
  }
9812
9937
  }
@@ -9836,23 +9961,23 @@ function growFieldTree(tree, parentSectionPath, entryNode, parentNode, totalNode
9836
9961
  }
9837
9962
  if (!tree[parentSectionPath].some((field) => field.name === conditionName)) {
9838
9963
  tree[parentSectionPath].push({
9839
- relation: relationType === 'parentRelationship' ||
9840
- relationType === 'polymorphicParentRelationship'
9964
+ relation: relationType === PARENT_RELATIONSHIP ||
9965
+ relationType === POLYMORPHIC_PARENT_RELATIONSHIP
9841
9966
  ? 'parent'
9842
9967
  : 'child',
9843
9968
  name: conditionName,
9844
9969
  });
9845
- totalNodes.add(conditionName);
9970
+ const path = `${parentSectionPath}#${conditionName}`;
9971
+ totalNodes.add(path);
9846
9972
  }
9847
9973
  }
9848
9974
  }
9849
9975
  // dive deep immediately for 'InlineFragment'
9850
9976
  const childNodes = entryNode.selectionSet.selections.filter(isFieldOrInlineFragmentNode);
9977
+ const path = `${parentSectionPath}${entryNode.typeCondition ? '#' + entryNode.typeCondition.name.value : ''}`;
9851
9978
  // Navigates into InLineFragment
9852
9979
  for (const child of childNodes) {
9853
- growFieldTree(tree, entryNode.typeCondition
9854
- ? entryNode.typeCondition.name.value
9855
- : parentSectionPath, child, entryNode, totalNodes, startNodes);
9980
+ growFieldTree(tree, path, child, entryNode, totalNodes, startNodes);
9856
9981
  }
9857
9982
  }
9858
9983
  }
@@ -9864,7 +9989,7 @@ function growFieldTree(tree, parentSectionPath, entryNode, parentNode, totalNode
9864
9989
  * @param startNodes start nodes of the tree. It can be used to fetch ObjectInfo immediately
9865
9990
  * @param path
9866
9991
  */
9867
- async function resolveObjectInfos(objectInfotree, objectInfoApiMap, startNodes, objectInfoService) {
9992
+ async function resolveObjectInfos(objectInfotree, pathToObjectApiNamesMap, startNodes, objectInfoService) {
9868
9993
  let objectInfos;
9869
9994
  try {
9870
9995
  objectInfos = await objectInfoService.getObjectInfos(Array.from(startNodes));
@@ -9878,9 +10003,9 @@ async function resolveObjectInfos(objectInfotree, objectInfoApiMap, startNodes,
9878
10003
  throw new Error(`Unable to resolve ObjectInfo(s) for ${Array.from(startNodes)}`);
9879
10004
  }
9880
10005
  for (const startNode of startNodes) {
9881
- objectInfoApiMap[startNode] = [startNode];
10006
+ pathToObjectApiNamesMap[startNode] = [startNode];
9882
10007
  const children = objectInfotree[startNode];
9883
- const subObjectInfoMap = await fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfos, children, startNode, objectInfoService);
10008
+ const subObjectInfoMap = await fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, objectInfos, children, startNode, objectInfoService);
9884
10009
  objectInfos = { ...objectInfos, ...subObjectInfoMap };
9885
10010
  }
9886
10011
  return objectInfos;
@@ -9888,15 +10013,15 @@ async function resolveObjectInfos(objectInfotree, objectInfoApiMap, startNodes,
9888
10013
  // example 1: 'parentPath': 'ServiceAppointment', 'nodesAtSameLevel': ['Account']
9889
10014
  // example 2: 'parentPath': 'ServiceAppointment', 'nodesAtSameLevel': ['Owner'], this example has 2 apiName for the node 'Owner'
9890
10015
  // example 3: 'parentPath': 'ServiceAppointment_Owner', 'nodesAtSameLevel': ['User', 'Group']
9891
- async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap, nodesAtSameLevel, parentPath, objectInfoService) {
9892
- const objectInfoApiNames = objectInfoApiMap[parentPath];
10016
+ async function fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, objectInfoMap, nodesAtSameLevel, parentPath, objectInfoService) {
10017
+ const objectInfoApiNames = pathToObjectApiNamesMap[parentPath];
9893
10018
  if (!objectInfoApiNames) {
9894
10019
  // eslint-disable-next-line
9895
10020
  throw new Error(`Object Info does not exist for ${parentPath}`);
9896
10021
  }
9897
10022
  const validObjectInfoNodes = [];
9898
10023
  let updatedObjectInfoMap = {};
9899
- // InlineFragment and polymorphic field support fits into this scenario ObjectInfoApiMap Entry: 'ServiceAppointment_Owner' -> ['User', 'Group']; ServiceAppointment_Owner_User' -> ['User']
10024
+ // InlineFragment and polymorphic field support fits into this scenario pathToObjectApiNamesMap Entry: 'ServiceAppointment#Owner' -> ['User', 'Group']; ServiceAppointment#Owner#User' -> ['User']
9900
10025
  if (objectInfoApiNames.length > 0 &&
9901
10026
  nodesAtSameLevel.length > 0 &&
9902
10027
  objectInfoApiNames.includes(nodesAtSameLevel[0].name)) {
@@ -9908,8 +10033,8 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
9908
10033
  // eslint-disable-next-line
9909
10034
  throw new Error(`Condition ${field.name} does not exists for ${parentPath}`);
9910
10035
  }
9911
- const path = `${parentPath}_${field.name}`;
9912
- objectInfoApiMap[path] = [field.name];
10036
+ const path = `${parentPath}#${field.name}`;
10037
+ pathToObjectApiNamesMap[path] = [field.name];
9913
10038
  }
9914
10039
  validObjectInfoNodes.push(...nodesAtSameLevel);
9915
10040
  updatedObjectInfoMap = { ...objectInfoMap };
@@ -9924,7 +10049,7 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
9924
10049
  let apiNames = [];
9925
10050
  for (const nodeInfo of nodesAtSameLevel) {
9926
10051
  const field = nodeInfo.name;
9927
- const path = `${parentPath}_${field}`;
10052
+ const path = `${parentPath}#${field}`;
9928
10053
  // Handle 'parentRelationship'
9929
10054
  if (nodeInfo.relation === 'parent') {
9930
10055
  const relationshipId = referenceIdFieldForRelationship(field);
@@ -9942,21 +10067,21 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
9942
10067
  }
9943
10068
  }
9944
10069
  // This is a polymorphic field
9945
- if (fieldDefinition.referenceToInfos.length > 1 && objectInfotree[field]) {
10070
+ if (fieldDefinition.referenceToInfos.length > 1 && objectInfotree[path]) {
9946
10071
  // Fields needs to expand and heterogenous entity ObjectInfo needs to be fetched
9947
- const referencedNodeInfos = objectInfotree[field];
10072
+ const referencedNodeInfos = objectInfotree[path];
9948
10073
  const requestedApiNames = referencedNodeInfos.map((referenceNodeInfo) => referenceNodeInfo.name);
9949
10074
  // 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.
9950
- if (requestedApiNames.length > 0 && objectInfotree[field]) {
10075
+ if (requestedApiNames.length > 0 && objectInfotree[path]) {
9951
10076
  fieldDefinition.referenceToInfos
9952
10077
  .filter((referenceToInfo) => requestedApiNames.includes(referenceToInfo.apiName))
9953
10078
  .forEach((ref) => {
9954
- if (!objectInfoApiMap[path]) {
9955
- objectInfoApiMap[path] = [];
10079
+ if (!pathToObjectApiNamesMap[path]) {
10080
+ pathToObjectApiNamesMap[path] = [];
9956
10081
  }
9957
10082
  // 'ServiceAppointment_Owner' ->['User', 'Group']
9958
- if (!objectInfoApiMap[path].includes(ref.apiName)) {
9959
- objectInfoApiMap[path].push(ref.apiName);
10083
+ if (!pathToObjectApiNamesMap[path].includes(ref.apiName)) {
10084
+ pathToObjectApiNamesMap[path].push(ref.apiName);
9960
10085
  }
9961
10086
  if (!apiNames.includes(ref.apiName)) {
9962
10087
  apiNames.push(ref.apiName);
@@ -9966,11 +10091,11 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
9966
10091
  }
9967
10092
  else if (fieldDefinition.referenceToInfos.length === 1) {
9968
10093
  const ref = fieldDefinition.referenceToInfos[0];
9969
- if (!objectInfoApiMap[path]) {
9970
- objectInfoApiMap[path] = [];
10094
+ if (!pathToObjectApiNamesMap[path]) {
10095
+ pathToObjectApiNamesMap[path] = [];
9971
10096
  }
9972
- if (!objectInfoApiMap[path].includes(ref.apiName)) {
9973
- objectInfoApiMap[path].push(ref.apiName);
10097
+ if (!pathToObjectApiNamesMap[path].includes(ref.apiName)) {
10098
+ pathToObjectApiNamesMap[path].push(ref.apiName);
9974
10099
  }
9975
10100
  if (!apiNames.includes(ref.apiName)) {
9976
10101
  apiNames.push(ref.apiName);
@@ -9981,11 +10106,11 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
9981
10106
  // handles 'childRelationship'
9982
10107
  const childRelationship = parentObjectInfo.childRelationships.find((childRelationship) => childRelationship.relationshipName === field);
9983
10108
  if (childRelationship) {
9984
- if (!objectInfoApiMap[path]) {
9985
- objectInfoApiMap[path] = [];
10109
+ if (!pathToObjectApiNamesMap[path]) {
10110
+ pathToObjectApiNamesMap[path] = [];
9986
10111
  }
9987
- if (!objectInfoApiMap[path].includes(childRelationship.childObjectApiName)) {
9988
- objectInfoApiMap[path].push(childRelationship.childObjectApiName);
10112
+ if (!pathToObjectApiNamesMap[path].includes(childRelationship.childObjectApiName)) {
10113
+ pathToObjectApiNamesMap[path].push(childRelationship.childObjectApiName);
9989
10114
  }
9990
10115
  if (!apiNames.includes(childRelationship.childObjectApiName)) {
9991
10116
  apiNames.push(childRelationship.childObjectApiName);
@@ -10007,10 +10132,10 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
10007
10132
  }
10008
10133
  for (const nodeInfo of validObjectInfoNodes) {
10009
10134
  const field = nodeInfo.name;
10010
- const subLevelFields = objectInfotree[field];
10011
- const path = `${parentPath}_${field}`;
10135
+ const path = `${parentPath}#${field}`;
10136
+ const subLevelFields = objectInfotree[path];
10012
10137
  if (subLevelFields && subLevelFields.length > 0) {
10013
- const subObjectInfos = await fetchObjectInfos(objectInfotree, objectInfoApiMap, updatedObjectInfoMap, subLevelFields, path, objectInfoService);
10138
+ const subObjectInfos = await fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, updatedObjectInfoMap, subLevelFields, path, objectInfoService);
10014
10139
  updatedObjectInfoMap = { ...updatedObjectInfoMap, ...subObjectInfos };
10015
10140
  }
10016
10141
  }
@@ -10025,27 +10150,29 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
10025
10150
  * 'path' and 'queryNode' is 1 level above the 'filterNode'
10026
10151
  * @param filterNode filter node which needs to be injected. For example, 'State' ObjectFieldNode within filter 'where: { State: { eq: "Nova Scotia" }}'
10027
10152
  * @param idState ID state will be updated to determine if the ID fields in AST need to be swapped. The swapping happens later.
10028
- * @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
10153
+ * @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
10154
+ * @param isParentPolymorphic true if parent points to a polymorphic field.
10029
10155
  * @param queryNode referece FieldNode which provides the information if 'filterNode' exist in it nor not.
10030
10156
  * @param objectInfos ObjectInfo map used in injection. If ObjectInfo misses or field does not exist in ObjectInfo, the error will be thrown
10031
- * @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'].
10157
+ * @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'].
10032
10158
  * @param draftFunctions functions for working with record ids that may be draft-created ids
10033
10159
  * @returns an array of nodes with injected fields
10034
10160
  */
10035
- function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode) {
10161
+ function injectFilter(filterNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode) {
10036
10162
  const injectedSelections = [];
10163
+ let isPolymorphicField = false;
10037
10164
  switch (filterNode.kind) {
10038
10165
  case Kind.ARGUMENT:
10039
10166
  if (filterNode.value.kind !== 'ObjectValue')
10040
10167
  return [];
10041
10168
  filterNode.value.fields.forEach((objectFieldNode) => {
10042
- let subResults = injectFilter(objectFieldNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode);
10169
+ let subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
10043
10170
  for (const subResult of subResults) {
10044
10171
  mergeOrAddToGroup(injectedSelections, subResult);
10045
10172
  }
10046
10173
  // multiple Ids might need to be swapped. remember their paths for faster write.
10047
10174
  if (idState.swapNeeded) {
10048
- idState.paths.push(path);
10175
+ idState.paths.push(parentPath);
10049
10176
  }
10050
10177
  });
10051
10178
  return injectedSelections;
@@ -10054,7 +10181,7 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
10054
10181
  case Kind.LIST: {
10055
10182
  filterNode.value.values.filter(isObjectValueNode).forEach((objectValueNode) => {
10056
10183
  objectValueNode.fields.forEach((objectFieldNode) => {
10057
- const subResults = injectFilter(objectFieldNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode);
10184
+ const subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
10058
10185
  for (const subResult of subResults) {
10059
10186
  mergeOrAddToGroup(injectedSelections, subResult);
10060
10187
  }
@@ -10065,7 +10192,7 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
10065
10192
  case Kind.OBJECT: {
10066
10193
  if (filterNode.name.value === 'not') {
10067
10194
  filterNode.value.fields.forEach((objectFieldNode) => {
10068
- const subResults = injectFilter(objectFieldNode, idState, path, objectInfos, objectInfoApiMap, draftFunctions, queryNode);
10195
+ const subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
10069
10196
  for (const subResult of subResults) {
10070
10197
  mergeOrAddToGroup(injectedSelections, subResult);
10071
10198
  }
@@ -10075,15 +10202,15 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
10075
10202
  let apiNames = [];
10076
10203
  let isScalarField = false;
10077
10204
  //It is possible that this is a polymorphic field
10078
- apiNames = objectInfoApiMap[path];
10079
- // example: path: 'ServiceAppointment_LastModifiedDate'; filterNode: '{eq: {literal: LAST_WEEK}}'. queryNode: 'LastModifedDate { value}' FilterNode's parent has been verifed as a valid node
10205
+ apiNames = pathToObjectApiNamesMap[parentPath];
10206
+ // example: path: 'ServiceAppointment#LastModifiedDate'; filterNode: '{eq: {literal: LAST_WEEK}}'. queryNode: 'LastModifedDate { value}' FilterNode's parent has been verifed as a valid node
10080
10207
  if (apiNames === undefined) {
10081
10208
  isScalarField = true;
10082
10209
  }
10083
10210
  else {
10084
10211
  if (apiNames.some((apiName) => objectInfos[apiName] === undefined)) {
10085
10212
  // eslint-disable-next-line
10086
- throw new Error(`ObjectInfo is missing for ${path}`);
10213
+ throw new Error(`ObjectInfo is missing for ${parentPath}`);
10087
10214
  }
10088
10215
  }
10089
10216
  if (isScalarField) {
@@ -10105,29 +10232,19 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
10105
10232
  }
10106
10233
  });
10107
10234
  let isSpanning = false;
10235
+ // if true, current node is a polymorphic concrete type node. For example, field node `User` under `Owner`
10108
10236
  let isInlineFragment = false;
10109
- let isPolymorphicField = false;
10110
10237
  let isTypeNameExisting = false;
10111
10238
  let curPath;
10112
10239
  let fieldName = filterNode.name.value;
10113
- curPath = `${path}_${fieldName}`;
10114
- if (objectInfoApiMap[curPath] && objectInfoApiMap[curPath].length > 0) {
10240
+ curPath = `${parentPath}#${fieldName}`;
10241
+ if (pathToObjectApiNamesMap[curPath] &&
10242
+ pathToObjectApiNamesMap[curPath].length > 0) {
10115
10243
  isSpanning = true;
10116
- if (objectInfoApiMap[curPath].length === 1) {
10117
- if (objectInfoApiMap[path] &&
10118
- objectInfoApiMap[path].length >= 1 &&
10119
- objectInfoApiMap[path].includes(objectInfoApiMap[curPath][0])) {
10120
- isInlineFragment = true;
10121
- }
10122
- }
10123
- // Checks if the current filter node is a polymorphic field. 'ServiceAppointment_Owner' --> ['User']; 'ServiceAppointment_Owner_User' --> ['User']
10124
- const childApiName = objectInfoApiMap[curPath][0];
10125
- const trialApiNames = objectInfoApiMap[`${curPath}_${childApiName}`];
10126
- if (trialApiNames !== undefined &&
10127
- trialApiNames.length === 1 &&
10128
- trialApiNames[0] === childApiName) {
10129
- isPolymorphicField = true;
10130
- }
10244
+ isInlineFragment =
10245
+ isParentPolymorphic &&
10246
+ pathToObjectApiNamesMap[curPath].length === 1;
10247
+ isPolymorphicField = isPolymorphicFieldPath(curPath, pathToObjectApiNamesMap, objectInfos);
10131
10248
  }
10132
10249
  // 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]
10133
10250
  if (isInlineFragment) {
@@ -10171,23 +10288,25 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
10171
10288
  throw new Error(`Field ${fieldName} does not exist in ${apiNames[0]}`);
10172
10289
  }
10173
10290
  }
10174
- const objectInfoName = objectInfoApiMap[curPath] !== undefined
10175
- ? objectInfoApiMap[curPath][0]
10176
- : objectInfoApiMap[path][0];
10291
+ const objectInfoName = pathToObjectApiNamesMap[curPath] !== undefined
10292
+ ? pathToObjectApiNamesMap[curPath][0]
10293
+ : pathToObjectApiNamesMap[parentPath][0];
10177
10294
  const isIdField = isFieldAnIdField(filterNode.name.value, objectInfos[objectInfoName]);
10178
10295
  if (!isIdField) {
10179
10296
  let subSelectionNodes = [];
10180
10297
  let subFieldsHasId = false;
10181
- filterNode.value.fields.forEach((subFieldNode) => {
10182
- // Check if the filter field has the 'Id'
10183
- if (isFieldAnIdField(subFieldNode.name.value, objectInfos[objectInfoName])) {
10184
- subFieldsHasId = true;
10185
- updateIDInfo(subFieldNode, idState, draftFunctions);
10186
- }
10187
- // try injecting the fields within predicate no matter it has relation or not.
10188
- let subResults = injectFilter(subFieldNode, idState, curPath, objectInfos, objectInfoApiMap, draftFunctions, existingFields ? existingFields[0] : undefined);
10189
- subSelectionNodes = subSelectionNodes.concat(subResults);
10190
- });
10298
+ if (isSpanning) {
10299
+ filterNode.value.fields.forEach((subFieldNode) => {
10300
+ // Check if the filter field has the 'Id'
10301
+ if (isFieldAnIdField(subFieldNode.name.value, objectInfos[objectInfoName])) {
10302
+ subFieldsHasId = true;
10303
+ updateIDInfo(subFieldNode, idState, draftFunctions);
10304
+ }
10305
+ // try injecting the fields within predicate no matter it has relation or not.
10306
+ let subResults = injectFilter(subFieldNode, idState, curPath, isPolymorphicField, objectInfos, pathToObjectApiNamesMap, draftFunctions, existingFields ? existingFields[0] : undefined);
10307
+ subSelectionNodes = subSelectionNodes.concat(subResults);
10308
+ });
10309
+ }
10191
10310
  if (!subFieldsHasId) {
10192
10311
  // Check if the query field has the 'Id'
10193
10312
  const existingIdsInQuery = existingFields &&
@@ -10216,6 +10335,7 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
10216
10335
  }
10217
10336
  //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
10218
10337
  if (!existingFields ||
10338
+ existingFields.length === 0 ||
10219
10339
  subSelectionNodes.length > 0 ||
10220
10340
  (isSpanning && !subFieldsHasId) ||
10221
10341
  (isInlineFragment && !isTypeNameExisting)) {
@@ -10346,6 +10466,44 @@ function mergeOrAddToGroup(group, element) {
10346
10466
  }
10347
10467
  group.push(element);
10348
10468
  }
10469
+ // checks if the path points to a polymorphic field. For example, for the below `pathToObjectApiNamesMap`
10470
+ // {
10471
+ // 'ServiceAppointment' -> ['ServiceAppointment']
10472
+ // 'ServiceAppointment#Owner' --> ['User'],
10473
+ // 'ServiceAppointment#Owner#User' --> ['User']
10474
+ // }
10475
+ // path `ServiceAppointment#Owner` points to a polymorphic field, but path `ServiceAppointment#Owner#User` does not.
10476
+ function isPolymorphicFieldPath(path, pathToObjectApiNamesMap, objectInfos) {
10477
+ const lastSegmentIndex = path.lastIndexOf('#');
10478
+ if (lastSegmentIndex < 0) {
10479
+ return false;
10480
+ }
10481
+ const lastSegment = path.slice(lastSegmentIndex + 1);
10482
+ const parentApiPath = path.slice(0, lastSegmentIndex);
10483
+ if (pathToObjectApiNamesMap[parentApiPath] === undefined) {
10484
+ return false;
10485
+ }
10486
+ const parentObjectApiNames = pathToObjectApiNamesMap[parentApiPath];
10487
+ // 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`.
10488
+ // 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.
10489
+ // Below are the entries in `pathToObjectApiNamesMap`
10490
+ // {
10491
+ // `ServiceAppointmen`t: [`ServiceAppointment`],
10492
+ // `ServiceAppointment#Owner`: [`User`, `Group`],
10493
+ // `ServiceAppointment#Owner#User`: [`User`],
10494
+ // `ServiceAppointment#Owner#Group`: [`Group`],
10495
+ // }
10496
+ if (parentObjectApiNames.length !== 1) {
10497
+ return false;
10498
+ }
10499
+ const parentObjectInfo = objectInfos[parentObjectApiNames[0]];
10500
+ const relationshipField = referenceIdFieldForRelationship(lastSegment);
10501
+ let fieldDefinition = parentObjectInfo.fields[relationshipField];
10502
+ if (fieldDefinition === undefined) {
10503
+ return false;
10504
+ }
10505
+ return fieldDefinition.polymorphicForeignKey;
10506
+ }
10349
10507
  function isFieldAnIdField(fieldName, objectInfo) {
10350
10508
  if (fieldName === 'Id')
10351
10509
  return true;
@@ -10398,10 +10556,10 @@ function updateIDInfo(fieldNode, idState, draftFunctions) {
10398
10556
  * @param parentNode parent node of param 1
10399
10557
  * @param ancestors ancester of param 1
10400
10558
  * @param objectInfos ObjectInfo map used in injection. If ObjectInfo misses or field does not exist in ObjectInfo, the error will be thrown
10401
- * @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'].
10559
+ * @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'].
10402
10560
  * @return injected SelectionNodes used to construct the InlineFragment.
10403
10561
  */
10404
- function injectFields(selections, parentNode, ancestors, objectInfos, objectInfoApiMap) {
10562
+ function injectFields(selections, parentNode, parentPath, ancestors, objectInfos, pathToObjectApiNamesMap) {
10405
10563
  /**
10406
10564
  * 1 parentship can return 2 FieldNode which need to be flattened
10407
10565
  * Concact: { ** Contact { ** ContactId {
@@ -10419,6 +10577,10 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
10419
10577
  if (!selection.selectionSet) {
10420
10578
  return selection;
10421
10579
  }
10580
+ const segment = isFieldNode(selection)
10581
+ ? selection.name.value
10582
+ : selection.typeCondition.name.value;
10583
+ const curPath = `${parentPath}#${segment}`;
10422
10584
  const spanningSubSelections = [];
10423
10585
  for (const subSelection of selection.selectionSet.selections) {
10424
10586
  if (isFieldSpanning(subSelection, selection)) {
@@ -10426,7 +10588,7 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
10426
10588
  }
10427
10589
  }
10428
10590
  // Handles multiple level field injection like 'ServiceAppointment' --> 'Account' --> 'Owner'
10429
- const subInjectedSelections = injectFields(spanningSubSelections, selection, ancestors, objectInfos, objectInfoApiMap);
10591
+ const subInjectedSelections = injectFields(spanningSubSelections, selection, curPath, ancestors, objectInfos, pathToObjectApiNamesMap);
10430
10592
  if (!selection.selectionSet) {
10431
10593
  return selection;
10432
10594
  }
@@ -10469,7 +10631,7 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
10469
10631
  }
10470
10632
  }
10471
10633
  // For polymorphic fields, the Id field is excluded.
10472
- const excludeId = selection.selectionSet.selections.every(isInlineFragmentNode);
10634
+ const excludeId = isPolymorphicFieldPath(curPath, pathToObjectApiNamesMap, objectInfos);
10473
10635
  const idSelection = [];
10474
10636
  if (!excludeId && !hasIdAlready) {
10475
10637
  idSelection.push({
@@ -10480,8 +10642,8 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
10480
10642
  },
10481
10643
  });
10482
10644
  }
10483
- // 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,
10484
- // please reference 'removeSyntheticFields'.
10645
+ // 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
10646
+ // `typedCondition` of the InlineFragment in the query AST. It is used to match JSON response with AST node. For more detail, please reference 'removeSyntheticFields'.
10485
10647
  if (isInlineFragmentNode(selection) &&
10486
10648
  !selection.selectionSet.selections.find((selection) => isFieldNode(selection) && selection.name.value === '__typename')) {
10487
10649
  idSelection.push({
@@ -10515,7 +10677,7 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
10515
10677
  if (isFieldNode(parentNode) && parentNode.selectionSet && parentNode.name.value === 'node') {
10516
10678
  if (parentNode.selectionSet.selections
10517
10679
  .filter(isFieldOrInlineFragmentNode)
10518
- .some((selectionNode) => isRelationship(selectionNode, 'childRelationship'))) {
10680
+ .some((selectionNode) => isRelationship(selectionNode, CHILD_RELATIONSHIP))) {
10519
10681
  if (!parentNode.selectionSet.selections
10520
10682
  .filter(isFieldNode)
10521
10683
  .some((sibling) => sibling.name.value === 'Id')) {
@@ -10534,15 +10696,15 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
10534
10696
  if (parentInfo.parentIndex >= 0) {
10535
10697
  // example node { TimeSheetEntries { edges { node { Id }}}}
10536
10698
  const parent = parentInfo.node;
10537
- if (isRelationship(parent, 'childRelationship')) {
10699
+ if (isRelationship(parent, CHILD_RELATIONSHIP)) {
10538
10700
  const unVisitedAncestors = ancestors.slice(0, parentInfo.parentIndex);
10539
10701
  // path : "TimeSheet"
10540
10702
  const grandParentPath = findAncesterPath(unVisitedAncestors);
10541
- if (objectInfoApiMap &&
10542
- objectInfoApiMap[grandParentPath] &&
10703
+ if (pathToObjectApiNamesMap &&
10704
+ pathToObjectApiNamesMap[grandParentPath] &&
10543
10705
  objectInfos &&
10544
- objectInfos[objectInfoApiMap[grandParentPath][0]]) {
10545
- const grandParentObjectInfo = objectInfos[objectInfoApiMap[grandParentPath][0]];
10706
+ objectInfos[pathToObjectApiNamesMap[grandParentPath][0]]) {
10707
+ const grandParentObjectInfo = objectInfos[pathToObjectApiNamesMap[grandParentPath][0]];
10546
10708
  // exmaple "TimeSheetEntries"
10547
10709
  const parentFieldName = parent.name.value;
10548
10710
  const targetRelationship = grandParentObjectInfo.childRelationships.find((childRelationship) => {
@@ -10663,7 +10825,7 @@ const assignedToMeFragmentSelections = [
10663
10825
  },
10664
10826
  value: {
10665
10827
  kind: 'StringValue',
10666
- value: 'childRelationship',
10828
+ value: CHILD_RELATIONSHIP,
10667
10829
  block: false,
10668
10830
  },
10669
10831
  },
@@ -10755,7 +10917,7 @@ const assignedToMeFragmentSelections = [
10755
10917
  },
10756
10918
  value: {
10757
10919
  kind: 'StringValue',
10758
- value: 'parentRelationship',
10920
+ value: PARENT_RELATIONSHIP,
10759
10921
  block: false,
10760
10922
  },
10761
10923
  },
@@ -10892,7 +11054,9 @@ function handleNonArrayJsonProperty(selection, fieldName, jsonInput, jsonOutput)
10892
11054
  jsonOutput[fieldName] = null;
10893
11055
  return;
10894
11056
  }
10895
- jsonOutput[fieldName] = {};
11057
+ if (jsonOutput[fieldName] === undefined) {
11058
+ jsonOutput[fieldName] = {};
11059
+ }
10896
11060
  createUserJsonOutput(selection, jsonInput[fieldName], jsonOutput[fieldName]);
10897
11061
  }
10898
11062
  else {
@@ -11624,34 +11788,42 @@ function applyReferenceLinksToDraft(record, draftMetadata) {
11624
11788
  }
11625
11789
  const { dataType, relationshipName, referenceToInfos } = fieldInfo;
11626
11790
  const draftFieldValue = record.fields[draftField].value;
11627
- if (dataType === 'Reference' && relationshipName !== null && draftFieldValue !== null) {
11628
- if (typeof draftFieldValue !== 'string') {
11629
- throw Error('reference field value is not a string');
11791
+ if (dataType === 'Reference' && relationshipName !== null) {
11792
+ if (draftFieldValue === null) {
11793
+ recordFields[relationshipName] = {
11794
+ displayValue: null,
11795
+ value: null,
11796
+ };
11630
11797
  }
11631
- const key = getRecordKeyForId(luvio, draftFieldValue);
11632
- const referencedRecord = referencedRecords.get(key);
11633
- recordFields[relationshipName] = {
11634
- displayValue: null,
11635
- value: createLink(key),
11636
- };
11637
- // for custom objects, we select the 'Name' field
11638
- // otherwise we check the object info for name fields.
11639
- //if there are multiple we select 'Name' if it exists, otherwise the first one
11640
- if (referencedRecord !== undefined && referenceToInfos.length > 0) {
11641
- let nameField;
11642
- const referenceToInfo = referenceToInfos[0];
11643
- const nameFields = referenceToInfo.nameFields;
11644
- if (nameFields.length !== 0) {
11645
- nameField = nameFields.find((x) => x === 'Name');
11646
- if (nameField === undefined) {
11647
- nameField = nameFields[0];
11648
- }
11798
+ else {
11799
+ if (typeof draftFieldValue !== 'string') {
11800
+ throw Error('reference field value is not a string');
11649
11801
  }
11650
- if (nameField !== undefined) {
11651
- const nameFieldRef = referencedRecord.fields[nameField];
11652
- if (nameFieldRef) {
11653
- recordFields[relationshipName].displayValue =
11654
- (_a = nameFieldRef.displayValue) !== null && _a !== void 0 ? _a : nameFieldRef.value;
11802
+ const key = getRecordKeyForId(luvio, draftFieldValue);
11803
+ const referencedRecord = referencedRecords.get(key);
11804
+ recordFields[relationshipName] = {
11805
+ displayValue: null,
11806
+ value: createLink(key),
11807
+ };
11808
+ // for custom objects, we select the 'Name' field
11809
+ // otherwise we check the object info for name fields.
11810
+ //if there are multiple we select 'Name' if it exists, otherwise the first one
11811
+ if (referencedRecord !== undefined && referenceToInfos.length > 0) {
11812
+ let nameField;
11813
+ const referenceToInfo = referenceToInfos[0];
11814
+ const nameFields = referenceToInfo.nameFields;
11815
+ if (nameFields.length !== 0) {
11816
+ nameField = nameFields.find((x) => x === 'Name');
11817
+ if (nameField === undefined) {
11818
+ nameField = nameFields[0];
11819
+ }
11820
+ }
11821
+ if (nameField !== undefined) {
11822
+ const nameFieldRef = referencedRecord.fields[nameField];
11823
+ if (nameFieldRef) {
11824
+ recordFields[relationshipName].displayValue =
11825
+ (_a = nameFieldRef.displayValue) !== null && _a !== void 0 ? _a : nameFieldRef.value;
11826
+ }
11655
11827
  }
11656
11828
  }
11657
11829
  }
@@ -11944,17 +12116,8 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
11944
12116
  };
11945
12117
  for (const fieldName of keys$3(recordWithSpanningRefLinks.fields)) {
11946
12118
  const fieldKey = buildRecordFieldStoreKey(key, fieldName);
11947
- if (this.collectedFields[fieldKey] !== undefined) {
11948
- const fieldData = recordWithSpanningRefLinks.fields[fieldName];
11949
- normalizedRecord.fields[fieldName] = { __ref: fieldKey };
11950
- publishData(fieldKey, fieldData);
11951
- }
11952
- else if (recordWithSpanningRefLinks.fields[fieldName] &&
11953
- recordWithSpanningRefLinks.fields[fieldName].value &&
11954
- recordWithSpanningRefLinks.fields[fieldName].value.__ref !== undefined) {
11955
- normalizedRecord.fields[fieldName] = { __ref: fieldKey };
11956
- publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
11957
- }
12119
+ normalizedRecord.fields[fieldName] = { __ref: fieldKey };
12120
+ publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
11958
12121
  }
11959
12122
  // publish the normalized record
11960
12123
  publishData(key, normalizedRecord);
@@ -12874,7 +13037,7 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
12874
13037
  // Fulfilled snapshot (this only happens in this code path if
12875
13038
  // the error is network error or 504), otherwise we spread over
12876
13039
  // the non-eval'ed snapshot (which will be either Fulfilled or Stale)
12877
- return nonEvaluatedSnapshot.state === 'Error'
13040
+ const resultSnapshot = nonEvaluatedSnapshot.state === 'Error'
12878
13041
  ? createLocalEvalSnapshot(gqlResult, seenRecords, recordId, rebuildWithLocalEval)
12879
13042
  : {
12880
13043
  ...nonEvaluatedSnapshot,
@@ -12883,6 +13046,22 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
12883
13046
  seenRecords,
12884
13047
  rebuildWithLocalEval,
12885
13048
  };
13049
+ const { refresh, state } = resultSnapshot;
13050
+ if (state !== 'Error' && refresh) {
13051
+ // after refreshing a graphql snapshot, we want to force a rebuild regardless
13052
+ // of if the call failed or not or if the data changed or not because we want
13053
+ // to make sure any potential new drafts are picked up
13054
+ resultSnapshot.refresh = {
13055
+ ...refresh,
13056
+ resolve: (config) => {
13057
+ return refresh.resolve(config).finally(() => {
13058
+ luvio.storePublish(resultSnapshot.recordId, undefined);
13059
+ luvio.storeBroadcast();
13060
+ });
13061
+ },
13062
+ };
13063
+ }
13064
+ return resultSnapshot;
12886
13065
  };
12887
13066
  }
12888
13067
 
@@ -13937,7 +14116,10 @@ function isErrorResponse(response) {
13937
14116
  * @returns the merged record
13938
14117
  */
13939
14118
  function mergeAggregateUiResponse(response, mergeFunc) {
13940
- const { body } = response;
14119
+ if (response.ok === false) {
14120
+ return response;
14121
+ }
14122
+ const body = response.body;
13941
14123
  try {
13942
14124
  if (body === null ||
13943
14125
  body === undefined ||
@@ -15900,6 +16082,9 @@ class PrimingSession extends EventEmitter {
15900
16082
  this.ldsRecordRefresher = config.ldsRecordRefresher;
15901
16083
  this.networkWorkerPool = new AsyncWorkerPool(this.concurrency);
15902
16084
  this.useBatchGQL = ldsPrimingGraphqlBatch.isOpen({ fallback: false });
16085
+ if (this.useBatchGQL) {
16086
+ this.batchSize = this.batchSize / DEFAULT_GQL_QUERY_BATCH_SIZE;
16087
+ }
15903
16088
  this.conflictPool = new ConflictPool(config.store, this.objectInfoLoader);
15904
16089
  }
15905
16090
  // function that enqueues priming work
@@ -16761,4 +16946,4 @@ register({
16761
16946
  });
16762
16947
 
16763
16948
  export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
16764
- // version: 1.229.0-dev1-f69d054a9
16949
+ // version: 1.229.0-dev10-bc9ef2513