@salesforce/lds-runtime-mobile 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.
- package/dist/main.js +490 -275
- package/package.json +18 -18
- package/sfdc/main.js +490 -275
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();
|
|
@@ -1455,6 +1459,72 @@ function makeDurable(environment, { durableStore, instrumentation }) {
|
|
|
1455
1459
|
});
|
|
1456
1460
|
}
|
|
1457
1461
|
|
|
1462
|
+
/**
|
|
1463
|
+
* Copyright (c) 2022, Salesforce, Inc.,
|
|
1464
|
+
* All rights reserved.
|
|
1465
|
+
* For full license text, see the LICENSE.txt file
|
|
1466
|
+
*/
|
|
1467
|
+
|
|
1468
|
+
const API_NAMESPACE = 'UiApi';
|
|
1469
|
+
const RECORD_REPRESENTATION_NAME = 'RecordRepresentation';
|
|
1470
|
+
const RECORD_VIEW_ENTITY_REPRESENTATION_NAME = 'RecordViewEntityRepresentation';
|
|
1471
|
+
const RECORD_ID_PREFIX = `${API_NAMESPACE}::${RECORD_REPRESENTATION_NAME}:`;
|
|
1472
|
+
const RECORD_VIEW_ENTITY_ID_PREFIX = `${API_NAMESPACE}::${RECORD_VIEW_ENTITY_REPRESENTATION_NAME}:Name:`;
|
|
1473
|
+
const RECORD_FIELDS_KEY_JUNCTION = '__fields__';
|
|
1474
|
+
function isStoreKeyRecordId(key) {
|
|
1475
|
+
return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) === -1;
|
|
1476
|
+
}
|
|
1477
|
+
function isStoreKeyRecordViewEntity(key) {
|
|
1478
|
+
return (key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX) > -1 &&
|
|
1479
|
+
key.indexOf(RECORD_FIELDS_KEY_JUNCTION) === -1);
|
|
1480
|
+
}
|
|
1481
|
+
function isStoreKeyRecordField(key) {
|
|
1482
|
+
return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) > -1;
|
|
1483
|
+
}
|
|
1484
|
+
function extractRecordIdFromStoreKey(key) {
|
|
1485
|
+
if (key === undefined ||
|
|
1486
|
+
(key.indexOf(RECORD_ID_PREFIX) === -1 && key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX) === -1)) {
|
|
1487
|
+
return undefined;
|
|
1488
|
+
}
|
|
1489
|
+
const parts = key.split(':');
|
|
1490
|
+
return parts[parts.length - 1].split('_')[0];
|
|
1491
|
+
}
|
|
1492
|
+
function buildRecordFieldStoreKey(recordKey, fieldName) {
|
|
1493
|
+
return `${recordKey}${RECORD_FIELDS_KEY_JUNCTION}${fieldName}`;
|
|
1494
|
+
}
|
|
1495
|
+
function objectsDeepEqual(lhs, rhs) {
|
|
1496
|
+
if (lhs === rhs)
|
|
1497
|
+
return true;
|
|
1498
|
+
if (typeof lhs !== 'object' || typeof rhs !== 'object' || lhs === null || rhs === null)
|
|
1499
|
+
return false;
|
|
1500
|
+
const lhsKeys = Object.keys(lhs);
|
|
1501
|
+
const rhsKeys = Object.keys(rhs);
|
|
1502
|
+
if (lhsKeys.length !== rhsKeys.length)
|
|
1503
|
+
return false;
|
|
1504
|
+
for (let key of lhsKeys) {
|
|
1505
|
+
if (!rhsKeys.includes(key))
|
|
1506
|
+
return false;
|
|
1507
|
+
if (typeof lhs[key] === 'function' || typeof rhs[key] === 'function') {
|
|
1508
|
+
if (lhs[key].toString() !== rhs[key].toString())
|
|
1509
|
+
return false;
|
|
1510
|
+
}
|
|
1511
|
+
else {
|
|
1512
|
+
if (!objectsDeepEqual(lhs[key], rhs[key]))
|
|
1513
|
+
return false;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
return true;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
function isStoreRecordError(storeRecord) {
|
|
1520
|
+
return storeRecord.__type === 'error';
|
|
1521
|
+
}
|
|
1522
|
+
function isEntryDurableRecordRepresentation(entry, key) {
|
|
1523
|
+
// Either a DurableRecordRepresentation or StoreRecordError can live at a record key
|
|
1524
|
+
return ((isStoreKeyRecordId(key) || isStoreKeyRecordViewEntity(key)) &&
|
|
1525
|
+
entry.data.__type === undefined);
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1458
1528
|
/**
|
|
1459
1529
|
* Copyright (c) 2022, Salesforce, Inc.,
|
|
1460
1530
|
* All rights reserved.
|
|
@@ -2714,6 +2784,7 @@ function isScalarDataType(type) {
|
|
|
2714
2784
|
'Email',
|
|
2715
2785
|
'TextArea',
|
|
2716
2786
|
'Percent',
|
|
2787
|
+
'EncryptedString',
|
|
2717
2788
|
].includes(type);
|
|
2718
2789
|
}
|
|
2719
2790
|
|
|
@@ -4194,8 +4265,12 @@ function rootRecordQuery(selection, input) {
|
|
|
4194
4265
|
// If there is no metadata for this query or it somehow lacks a timestamp
|
|
4195
4266
|
// skip setting the root timestamp
|
|
4196
4267
|
if (queryMetadata !== undefined && queryMetadata.ingestionTimestamp !== undefined) {
|
|
4197
|
-
|
|
4198
|
-
|
|
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
|
+
}
|
|
4199
4274
|
}
|
|
4200
4275
|
}
|
|
4201
4276
|
return recordQuery(selection, alias, apiName, [], input);
|
|
@@ -4653,7 +4728,11 @@ function makeStoreEval(preconditioner, objectInfoService, userId, contextProvide
|
|
|
4653
4728
|
try {
|
|
4654
4729
|
const { data, seenRecords } = await queryEvaluator(rootQuery, context, eventEmitter);
|
|
4655
4730
|
const rebuildWithStoreEval = ((originalSnapshot) => {
|
|
4656
|
-
return storeEval(config, originalSnapshot, observers, connectionKeyBuilder)
|
|
4731
|
+
return storeEval(config, originalSnapshot, observers, connectionKeyBuilder).then((rebuiltSnapshot) => {
|
|
4732
|
+
return objectsDeepEqual(originalSnapshot.data, rebuiltSnapshot.data)
|
|
4733
|
+
? originalSnapshot
|
|
4734
|
+
: rebuiltSnapshot;
|
|
4735
|
+
});
|
|
4657
4736
|
});
|
|
4658
4737
|
const recordId = generateUniqueRecordId$1();
|
|
4659
4738
|
// if the non-eval'ed snapshot was an error then we return a synthetic
|
|
@@ -5004,8 +5083,12 @@ function uuidv4() {
|
|
|
5004
5083
|
|
|
5005
5084
|
const HTTP_HEADER_RETRY_AFTER = 'Retry-After';
|
|
5006
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';
|
|
5007
5088
|
const ERROR_CODE_IDEMPOTENCY_KEY_USED_DIFFERENT_USER = 'IDEMPOTENCY_KEY_USED_DIFFERENT_USER';
|
|
5008
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';
|
|
5009
5092
|
/**
|
|
5010
5093
|
* Get the retry after in milliseconds from the response headers, undefined if not specified.
|
|
5011
5094
|
* The header could have two different format.
|
|
@@ -5035,7 +5118,9 @@ function buildLuvioOverrideForDraftAdapters(luvio, handler, extractTargetIdFromC
|
|
|
5035
5118
|
const dispatchResourceRequest = async function (resourceRequest, _context) {
|
|
5036
5119
|
const resourceRequestCopy = clone$1(resourceRequest);
|
|
5037
5120
|
resourceRequestCopy.headers = resourceRequestCopy.headers || {};
|
|
5038
|
-
|
|
5121
|
+
if (handler.hasIdempotencySupport()) {
|
|
5122
|
+
resourceRequestCopy.headers[HTTP_HEADER_IDEMPOTENCY_KEY] = uuidv4();
|
|
5123
|
+
}
|
|
5039
5124
|
// enable return extra fields for record creation and record update http call
|
|
5040
5125
|
if (resourceRequest.basePath === '/ui-api/records' &&
|
|
5041
5126
|
(resourceRequest.method === 'post' || resourceRequest.method === 'patch')) {
|
|
@@ -5871,6 +5956,12 @@ class AbstractResourceRequestActionHandler {
|
|
|
5871
5956
|
// the luvio store redirect table, during which a new draft might be enqueued
|
|
5872
5957
|
// which would not see a necessary mapping.
|
|
5873
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
|
+
});
|
|
5874
5965
|
}
|
|
5875
5966
|
enqueue(data) {
|
|
5876
5967
|
return this.draftQueue.enqueue(this.handlerId, data);
|
|
@@ -5900,21 +5991,43 @@ class AbstractResourceRequestActionHandler {
|
|
|
5900
5991
|
retryDelayInMs = getRetryAfterInMs(response.headers);
|
|
5901
5992
|
shouldRetry = true;
|
|
5902
5993
|
break;
|
|
5903
|
-
case HttpStatusCode.ServerError:
|
|
5994
|
+
case HttpStatusCode.ServerError: {
|
|
5904
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
|
+
}
|
|
5905
6001
|
break;
|
|
6002
|
+
}
|
|
5906
6003
|
case 409 /* IdempotentWriteSpecificHttpStatusCode.Conflict */: {
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
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)) {
|
|
5911
6019
|
retryDelayInMs = 0;
|
|
5912
6020
|
actionDataChanged = true;
|
|
6021
|
+
shouldRetry = true;
|
|
5913
6022
|
}
|
|
5914
|
-
|
|
5915
|
-
|
|
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;
|
|
5916
6030
|
}
|
|
5917
|
-
shouldRetry = true;
|
|
5918
6031
|
break;
|
|
5919
6032
|
}
|
|
5920
6033
|
}
|
|
@@ -5933,6 +6046,27 @@ class AbstractResourceRequestActionHandler {
|
|
|
5933
6046
|
return ProcessActionResult.NETWORK_ERROR;
|
|
5934
6047
|
}
|
|
5935
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
|
+
}
|
|
5936
6070
|
async buildPendingAction(request, queue) {
|
|
5937
6071
|
const targetId = await this.getIdFromRequest(request);
|
|
5938
6072
|
if (targetId === undefined) {
|
|
@@ -6146,6 +6280,10 @@ class AbstractResourceRequestActionHandler {
|
|
|
6146
6280
|
...targetData,
|
|
6147
6281
|
body: this.mergeRequestBody(targetBody, sourceBody),
|
|
6148
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
|
+
}
|
|
6149
6287
|
// overlay metadata
|
|
6150
6288
|
merged.metadata = { ...targetMetadata, ...sourceMetadata };
|
|
6151
6289
|
// put status back to pending to auto upload if queue is active and targed is at the head.
|
|
@@ -6182,6 +6320,9 @@ class AbstractResourceRequestActionHandler {
|
|
|
6182
6320
|
getDraftIdsFromAction(action) {
|
|
6183
6321
|
return [action.targetId];
|
|
6184
6322
|
}
|
|
6323
|
+
hasIdempotencySupport() {
|
|
6324
|
+
return this.isIdempotencySupported && !this.isLdsIdempotencyWriteDisabled;
|
|
6325
|
+
}
|
|
6185
6326
|
async ingestResponses(responses, action) {
|
|
6186
6327
|
const luvio = this.getLuvio();
|
|
6187
6328
|
await luvio.handleSuccessResponse(() => {
|
|
@@ -6613,49 +6754,6 @@ function makeEnvironmentDraftAware(luvio, env, durableStore, handlers, draftQueu
|
|
|
6613
6754
|
});
|
|
6614
6755
|
}
|
|
6615
6756
|
|
|
6616
|
-
/**
|
|
6617
|
-
* Copyright (c) 2022, Salesforce, Inc.,
|
|
6618
|
-
* All rights reserved.
|
|
6619
|
-
* For full license text, see the LICENSE.txt file
|
|
6620
|
-
*/
|
|
6621
|
-
|
|
6622
|
-
const API_NAMESPACE = 'UiApi';
|
|
6623
|
-
const RECORD_REPRESENTATION_NAME = 'RecordRepresentation';
|
|
6624
|
-
const RECORD_VIEW_ENTITY_REPRESENTATION_NAME = 'RecordViewEntityRepresentation';
|
|
6625
|
-
const RECORD_ID_PREFIX = `${API_NAMESPACE}::${RECORD_REPRESENTATION_NAME}:`;
|
|
6626
|
-
const RECORD_VIEW_ENTITY_ID_PREFIX = `${API_NAMESPACE}::${RECORD_VIEW_ENTITY_REPRESENTATION_NAME}:Name:`;
|
|
6627
|
-
const RECORD_FIELDS_KEY_JUNCTION = '__fields__';
|
|
6628
|
-
function isStoreKeyRecordId(key) {
|
|
6629
|
-
return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) === -1;
|
|
6630
|
-
}
|
|
6631
|
-
function isStoreKeyRecordViewEntity(key) {
|
|
6632
|
-
return (key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX) > -1 &&
|
|
6633
|
-
key.indexOf(RECORD_FIELDS_KEY_JUNCTION) === -1);
|
|
6634
|
-
}
|
|
6635
|
-
function isStoreKeyRecordField(key) {
|
|
6636
|
-
return key.indexOf(RECORD_ID_PREFIX) > -1 && key.indexOf(RECORD_FIELDS_KEY_JUNCTION) > -1;
|
|
6637
|
-
}
|
|
6638
|
-
function extractRecordIdFromStoreKey(key) {
|
|
6639
|
-
if (key === undefined ||
|
|
6640
|
-
(key.indexOf(RECORD_ID_PREFIX) === -1 && key.indexOf(RECORD_VIEW_ENTITY_ID_PREFIX) === -1)) {
|
|
6641
|
-
return undefined;
|
|
6642
|
-
}
|
|
6643
|
-
const parts = key.split(':');
|
|
6644
|
-
return parts[parts.length - 1].split('_')[0];
|
|
6645
|
-
}
|
|
6646
|
-
function buildRecordFieldStoreKey(recordKey, fieldName) {
|
|
6647
|
-
return `${recordKey}${RECORD_FIELDS_KEY_JUNCTION}${fieldName}`;
|
|
6648
|
-
}
|
|
6649
|
-
|
|
6650
|
-
function isStoreRecordError(storeRecord) {
|
|
6651
|
-
return storeRecord.__type === 'error';
|
|
6652
|
-
}
|
|
6653
|
-
function isEntryDurableRecordRepresentation(entry, key) {
|
|
6654
|
-
// Either a DurableRecordRepresentation or StoreRecordError can live at a record key
|
|
6655
|
-
return ((isStoreKeyRecordId(key) || isStoreKeyRecordViewEntity(key)) &&
|
|
6656
|
-
entry.data.__type === undefined);
|
|
6657
|
-
}
|
|
6658
|
-
|
|
6659
6757
|
function serializeFieldArguments(argumentNodes, variables) {
|
|
6660
6758
|
const mutableArgumentNodes = Object.assign([], argumentNodes);
|
|
6661
6759
|
return `args__(${mutableArgumentNodes
|
|
@@ -8030,6 +8128,10 @@ function isOperationDefinitionNode(node) {
|
|
|
8030
8128
|
return node.kind === 'OperationDefinition';
|
|
8031
8129
|
}
|
|
8032
8130
|
|
|
8131
|
+
const POLYMORPHIC_PARENT_RELATIONSHIP = 'polymorphicParentRelationship';
|
|
8132
|
+
const PARENT_RELATIONSHIP = 'parentRelationship';
|
|
8133
|
+
const CHILD_RELATIONSHIP = 'childRelationship';
|
|
8134
|
+
const RECORD_QUERY = 'recordQuery';
|
|
8033
8135
|
function requestsDraftsField(recordFieldNode) {
|
|
8034
8136
|
if (!recordFieldNode.selectionSet)
|
|
8035
8137
|
return false;
|
|
@@ -8045,18 +8147,41 @@ function isRecordQuery(recordQueryField) {
|
|
|
8045
8147
|
directive.arguments
|
|
8046
8148
|
.map((argument) => argument.value)
|
|
8047
8149
|
.filter(isStringValueNode)
|
|
8048
|
-
.some((categoryName) => categoryName.value ===
|
|
8150
|
+
.some((categoryName) => categoryName.value === RECORD_QUERY));
|
|
8049
8151
|
});
|
|
8050
8152
|
}
|
|
8051
8153
|
return false;
|
|
8052
8154
|
}
|
|
8053
|
-
// finds field with 'recordQuery' and 'childRelationship' directive
|
|
8054
|
-
function
|
|
8055
|
-
const
|
|
8056
|
-
return
|
|
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
|
+
};
|
|
8057
8182
|
}
|
|
8058
|
-
// finds
|
|
8059
|
-
function findNearestAncesterPath(ancestors,
|
|
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) {
|
|
8060
8185
|
let recordQueryPath = { node: undefined, parentIndex: -1 };
|
|
8061
8186
|
let relationship = '';
|
|
8062
8187
|
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
@@ -8070,9 +8195,11 @@ function findNearestAncesterPath(ancestors, recordQueryOnly) {
|
|
|
8070
8195
|
continue;
|
|
8071
8196
|
for (let arg of directive.arguments) {
|
|
8072
8197
|
if (arg.value &&
|
|
8073
|
-
(arg.value.value ===
|
|
8074
|
-
arg.value.value ===
|
|
8075
|
-
(!
|
|
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)))) {
|
|
8076
8203
|
recordQueryPath = { node: node, parentIndex: i };
|
|
8077
8204
|
relationship = arg.value.value;
|
|
8078
8205
|
break;
|
|
@@ -8087,17 +8214,19 @@ function findNearestAncesterPath(ancestors, recordQueryOnly) {
|
|
|
8087
8214
|
//checks if nearest ancester could be an inline fragment
|
|
8088
8215
|
if (recordQueryPath.node !== undefined &&
|
|
8089
8216
|
recordQueryPath.node.selectionSet &&
|
|
8090
|
-
relationship ===
|
|
8091
|
-
//
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
if (
|
|
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])) {
|
|
8096
8223
|
recordQueryPath = {
|
|
8097
|
-
node: ancestors[
|
|
8098
|
-
parentIndex
|
|
8224
|
+
node: ancestors[parentIndex],
|
|
8225
|
+
parentIndex,
|
|
8099
8226
|
};
|
|
8227
|
+
break;
|
|
8100
8228
|
}
|
|
8229
|
+
parentIndex++;
|
|
8101
8230
|
}
|
|
8102
8231
|
}
|
|
8103
8232
|
return recordQueryPath;
|
|
@@ -8121,7 +8250,7 @@ function findAncesterPath(ancesters) {
|
|
|
8121
8250
|
? sectionPath
|
|
8122
8251
|
: sectionPath === ''
|
|
8123
8252
|
? path
|
|
8124
|
-
: `${sectionPath}
|
|
8253
|
+
: `${sectionPath}#${path}`;
|
|
8125
8254
|
}
|
|
8126
8255
|
}
|
|
8127
8256
|
boundaryIndex = parentIndex;
|
|
@@ -8179,9 +8308,9 @@ function getRelation(node) {
|
|
|
8179
8308
|
const relationships = args
|
|
8180
8309
|
.map((arg) => arg.value)
|
|
8181
8310
|
.filter(isStringValueNode)
|
|
8182
|
-
.filter((valueNode) => valueNode.value ===
|
|
8183
|
-
valueNode.value ===
|
|
8184
|
-
valueNode.value ===
|
|
8311
|
+
.filter((valueNode) => valueNode.value === CHILD_RELATIONSHIP ||
|
|
8312
|
+
valueNode.value === PARENT_RELATIONSHIP ||
|
|
8313
|
+
valueNode.value === POLYMORPHIC_PARENT_RELATIONSHIP)
|
|
8185
8314
|
.map((relationshipNode) => relationshipNode.value);
|
|
8186
8315
|
if (relationships.length > 0) {
|
|
8187
8316
|
return relationships[0];
|
|
@@ -8238,8 +8367,8 @@ function isFieldSpanning(node, parentNode) {
|
|
|
8238
8367
|
*/
|
|
8239
8368
|
function isParentRelationship(node) {
|
|
8240
8369
|
return (node &&
|
|
8241
|
-
(isRelationship(node,
|
|
8242
|
-
isRelationship(node,
|
|
8370
|
+
(isRelationship(node, PARENT_RELATIONSHIP) ||
|
|
8371
|
+
isRelationship(node, POLYMORPHIC_PARENT_RELATIONSHIP)));
|
|
8243
8372
|
}
|
|
8244
8373
|
/*
|
|
8245
8374
|
checks if the InlineFragment spans
|
|
@@ -8545,6 +8674,26 @@ function findFieldInfo(objectInfo, fieldName) {
|
|
|
8545
8674
|
return values$1(objectInfo.fields).find((field) => field.apiName === fieldName ||
|
|
8546
8675
|
(field.dataType === 'Reference' && field.relationshipName === fieldName));
|
|
8547
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
|
+
}
|
|
8548
8697
|
|
|
8549
8698
|
function findSpanningField(name) {
|
|
8550
8699
|
return (field) => {
|
|
@@ -9064,17 +9213,7 @@ async function fetchIngestionTimeStampFromDatabase(apiName, info, args, query) {
|
|
|
9064
9213
|
const key = buildKeyStringForRecordQuery(operation,
|
|
9065
9214
|
// join varables passed from query to the argument variables given from the AST
|
|
9066
9215
|
{ ...variableValues, ...args }, info.fieldNodes[0].arguments, apiName);
|
|
9067
|
-
|
|
9068
|
-
SELECT json_extract(metadata, '${JSON_EXTRACT_PATH_INGESTION_TIMESTAMP}')
|
|
9069
|
-
FROM lds_data
|
|
9070
|
-
WHERE key IS ?
|
|
9071
|
-
`;
|
|
9072
|
-
const results = await query(sql, [key]);
|
|
9073
|
-
const [timestamp] = results.rows.map((row) => row[0]);
|
|
9074
|
-
if (timestamp !== null && typeof timestamp === 'number') {
|
|
9075
|
-
//go back 10 ms to adjust for margin of error when top level query is stored and when raml objects are stored
|
|
9076
|
-
ingestionTimestamp = timestamp - 10;
|
|
9077
|
-
}
|
|
9216
|
+
return readIngestionTimestampForKey(key, query);
|
|
9078
9217
|
}
|
|
9079
9218
|
return ingestionTimestamp;
|
|
9080
9219
|
}
|
|
@@ -9122,26 +9261,20 @@ function generateRecordQueries(objectInfos) {
|
|
|
9122
9261
|
let recordConnections = ``;
|
|
9123
9262
|
const polymorphicFieldTypeNames = new Set();
|
|
9124
9263
|
let typedScalars = new Set();
|
|
9264
|
+
let parentRelationshipFields = new Set();
|
|
9125
9265
|
for (const objectInfo of values$1(objectInfos)) {
|
|
9126
9266
|
const { apiName, childRelationships } = objectInfo;
|
|
9127
9267
|
let fields = ``;
|
|
9128
9268
|
typedScalars.add(`${apiName}_Filter`);
|
|
9129
9269
|
typedScalars.add(`${apiName}_OrderBy`);
|
|
9130
|
-
for (const childRelationship of childRelationships) {
|
|
9131
|
-
const { childObjectApiName } = childRelationship;
|
|
9132
|
-
// Only add the relationship if there is relevant objectinfos for it,
|
|
9133
|
-
// otherwise we'd be defining types we cannot satisfy and aren't referenced in
|
|
9134
|
-
// the query.
|
|
9135
|
-
if (objectInfos[childObjectApiName] !== undefined) {
|
|
9136
|
-
fields += `${childRelationship.relationshipName}(first: Int, where: ${childObjectApiName}_Filter, orderBy: ${childObjectApiName}_OrderBy, scope: SupportedScopes): ${childObjectApiName}Connection \n`;
|
|
9137
|
-
typedScalars.add(`${childObjectApiName}_Filter`);
|
|
9138
|
-
typedScalars.add(`${childObjectApiName}_OrderBy`);
|
|
9139
|
-
}
|
|
9140
|
-
}
|
|
9141
9270
|
for (const field of values$1(objectInfo.fields)) {
|
|
9142
9271
|
if (!fieldsStaticallyAdded.includes(field.apiName)) {
|
|
9143
9272
|
fields += `${field.apiName}: ${dataTypeToType(field.dataType, field.apiName)}\n`;
|
|
9144
9273
|
}
|
|
9274
|
+
//handles parent relationship
|
|
9275
|
+
if (field.relationshipName === null) {
|
|
9276
|
+
continue;
|
|
9277
|
+
}
|
|
9145
9278
|
// For spanning parent relationships with no union types
|
|
9146
9279
|
if (field.referenceToInfos.length === 1) {
|
|
9147
9280
|
const [relation] = field.referenceToInfos;
|
|
@@ -9149,11 +9282,13 @@ function generateRecordQueries(objectInfos) {
|
|
|
9149
9282
|
// otherwise we'd be defining types we cannot satisfy and aren't referenced in
|
|
9150
9283
|
// the query.
|
|
9151
9284
|
if (objectInfos[relation.apiName] !== undefined) {
|
|
9285
|
+
parentRelationshipFields.add(field.relationshipName);
|
|
9152
9286
|
fields += `${field.relationshipName}: ${relation.apiName}\n`;
|
|
9153
9287
|
}
|
|
9154
9288
|
// For polymorphic field, its type is 'Record' inteface. The concrete entity type name is saved for field resolving of next phase
|
|
9155
9289
|
}
|
|
9156
9290
|
else if (field.referenceToInfos.length > 1) {
|
|
9291
|
+
parentRelationshipFields.add(field.relationshipName);
|
|
9157
9292
|
fields += `${field.relationshipName}: Record\n`;
|
|
9158
9293
|
for (const relation of field.referenceToInfos) {
|
|
9159
9294
|
if (objectInfos[relation.apiName] !== undefined) {
|
|
@@ -9162,6 +9297,20 @@ function generateRecordQueries(objectInfos) {
|
|
|
9162
9297
|
}
|
|
9163
9298
|
}
|
|
9164
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
|
+
}
|
|
9165
9314
|
recordQueries += `${apiName}(first: Int, where: ${apiName}_Filter, orderBy: ${apiName}_OrderBy, scope: SupportedScopes): ${apiName}Connection\n`;
|
|
9166
9315
|
const isServiceAppointment = apiName === 'ServiceAppointment';
|
|
9167
9316
|
recordConnections += /* GraphQL */ `
|
|
@@ -9236,6 +9385,8 @@ function dataTypeToType(objectInfoDataType, apiName) {
|
|
|
9236
9385
|
return 'PercentValue';
|
|
9237
9386
|
case 'Int':
|
|
9238
9387
|
return 'IntValue';
|
|
9388
|
+
case 'EncryptedString':
|
|
9389
|
+
return 'EncryptedStringValue';
|
|
9239
9390
|
// ! do the rest of the custom types
|
|
9240
9391
|
default:
|
|
9241
9392
|
return 'String';
|
|
@@ -9321,7 +9472,7 @@ const parentRelationshipDirective = {
|
|
|
9321
9472
|
},
|
|
9322
9473
|
value: {
|
|
9323
9474
|
kind: Kind.STRING,
|
|
9324
|
-
value:
|
|
9475
|
+
value: PARENT_RELATIONSHIP,
|
|
9325
9476
|
block: false,
|
|
9326
9477
|
},
|
|
9327
9478
|
},
|
|
@@ -9335,8 +9486,8 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9335
9486
|
// example 2 'ServiceAppointment' -> ['Owner']; 'Owner' -> ['User', 'Group']
|
|
9336
9487
|
const objectNodeInfoTree = {};
|
|
9337
9488
|
// save the field path to apiName map
|
|
9338
|
-
// example 1: 'ServiceAppointment' -> ['ServiceAppointment']; '
|
|
9339
|
-
const
|
|
9489
|
+
// example 1: 'ServiceAppointment' -> ['ServiceAppointment']; 'ServiceAppointment#ccount' -> ['Account']; 'ServiceAppointment#Account#Owner' -> ['User']
|
|
9490
|
+
const pathToObjectApiNamesMap = {};
|
|
9340
9491
|
let startNodes = new Set();
|
|
9341
9492
|
let totalNodes = new Set();
|
|
9342
9493
|
let objectInfos = {};
|
|
@@ -9350,11 +9501,11 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9350
9501
|
visit(originalAST, {
|
|
9351
9502
|
Argument: {
|
|
9352
9503
|
enter(node, key, parent, path, ancestors) {
|
|
9353
|
-
const
|
|
9354
|
-
if (!
|
|
9504
|
+
const { connection: recordConnectionNode, path: ancesterPath } = findNearestConnectionWithPath(ancestors);
|
|
9505
|
+
if (!recordConnectionNode || !ancesterPath)
|
|
9355
9506
|
return;
|
|
9356
|
-
if (!objectNodeInfoTree[
|
|
9357
|
-
objectNodeInfoTree[
|
|
9507
|
+
if (!objectNodeInfoTree[ancesterPath]) {
|
|
9508
|
+
objectNodeInfoTree[ancesterPath] = [];
|
|
9358
9509
|
}
|
|
9359
9510
|
switch (node.name.value) {
|
|
9360
9511
|
case 'orderBy':
|
|
@@ -9362,12 +9513,12 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9362
9513
|
if (node.value.kind !== 'ObjectValue') {
|
|
9363
9514
|
return;
|
|
9364
9515
|
}
|
|
9365
|
-
totalNodes.add(
|
|
9516
|
+
totalNodes.add(ancesterPath);
|
|
9366
9517
|
// 'childRelationship' node is not taken as the startNode of the 'NodeInfoTree' graph. The field scanning will construct the graph which lead here.
|
|
9367
|
-
if (isRecordQuery(
|
|
9368
|
-
startNodes.add(
|
|
9518
|
+
if (isRecordQuery(recordConnectionNode)) {
|
|
9519
|
+
startNodes.add(recordConnectionNode.name.value);
|
|
9369
9520
|
}
|
|
9370
|
-
growObjectFieldTree(objectNodeInfoTree,
|
|
9521
|
+
growObjectFieldTree(objectNodeInfoTree, ancesterPath, node.value, totalNodes, startNodes);
|
|
9371
9522
|
break;
|
|
9372
9523
|
case 'scope':
|
|
9373
9524
|
if (!isScopeArgumentNodeWithType(node, 'ASSIGNEDTOME', variables)) {
|
|
@@ -9382,17 +9533,16 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9382
9533
|
name: 'ServiceResources',
|
|
9383
9534
|
});
|
|
9384
9535
|
}
|
|
9385
|
-
if (objectNodeInfoTree['ServiceResources'] === undefined) {
|
|
9386
|
-
objectNodeInfoTree['ServiceResources'] = [
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
});
|
|
9536
|
+
if (objectNodeInfoTree['ServiceAppointment#ServiceResources'] === undefined) {
|
|
9537
|
+
objectNodeInfoTree['ServiceAppointment#ServiceResources'] = [
|
|
9538
|
+
{
|
|
9539
|
+
relation: 'parent',
|
|
9540
|
+
name: 'ServiceResource',
|
|
9541
|
+
},
|
|
9542
|
+
];
|
|
9393
9543
|
}
|
|
9394
|
-
if (objectNodeInfoTree['ServiceResource'] === undefined) {
|
|
9395
|
-
objectNodeInfoTree['ServiceResource'] = [];
|
|
9544
|
+
if (objectNodeInfoTree['ServiceAppointment#ServiceResources#ServiceResource'] === undefined) {
|
|
9545
|
+
objectNodeInfoTree['ServiceAppointment#ServiceResources#ServiceResource'] = [];
|
|
9396
9546
|
}
|
|
9397
9547
|
break;
|
|
9398
9548
|
default:
|
|
@@ -9406,7 +9556,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9406
9556
|
return;
|
|
9407
9557
|
if (!node.selectionSet)
|
|
9408
9558
|
return;
|
|
9409
|
-
const recordQueryField =
|
|
9559
|
+
const recordQueryField = findNearestConnection(ancestors);
|
|
9410
9560
|
//only injects fields for 'recordQuery' field. ignores the 'childRelationship' field since it will be traversed as the child of the 'recordQuery'
|
|
9411
9561
|
if (isRecordQuery(recordQueryField) && recordQueryField) {
|
|
9412
9562
|
totalNodes.add(recordQueryField.name.value);
|
|
@@ -9417,21 +9567,21 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9417
9567
|
},
|
|
9418
9568
|
});
|
|
9419
9569
|
if (objectInfoService && startNodes.size > 0) {
|
|
9420
|
-
objectInfos = await resolveObjectInfos(objectNodeInfoTree,
|
|
9570
|
+
objectInfos = await resolveObjectInfos(objectNodeInfoTree, pathToObjectApiNamesMap, startNodes, objectInfoService);
|
|
9421
9571
|
}
|
|
9422
9572
|
// read pass; gather whats needed
|
|
9423
9573
|
visit(originalAST, {
|
|
9424
9574
|
Argument: {
|
|
9425
9575
|
leave(node, key, parent, path, ancestors) {
|
|
9426
|
-
const recordQueryField =
|
|
9576
|
+
const recordQueryField = findNearestConnection(ancestors);
|
|
9427
9577
|
if (!recordQueryField)
|
|
9428
9578
|
return;
|
|
9429
9579
|
const ancestorPath = findAncesterPath(ancestors);
|
|
9430
9580
|
if (!inlineFragmentSelections[ancestorPath]) {
|
|
9431
9581
|
inlineFragmentSelections[ancestorPath] = [];
|
|
9432
9582
|
}
|
|
9433
|
-
const recordQueryApiName =
|
|
9434
|
-
?
|
|
9583
|
+
const recordQueryApiName = pathToObjectApiNamesMap[ancestorPath]
|
|
9584
|
+
? pathToObjectApiNamesMap[ancestorPath][0]
|
|
9435
9585
|
: recordQueryField.name.value;
|
|
9436
9586
|
// The record node acts as the reference. The duplicated field in the record node is not injected
|
|
9437
9587
|
const recordReferenceNode = [recordQueryField]
|
|
@@ -9445,7 +9595,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9445
9595
|
case 'scope':
|
|
9446
9596
|
// Hanle 'MINE' field
|
|
9447
9597
|
if (isScopeArgumentNodeWithType(node, 'MINE', variables)) {
|
|
9448
|
-
if (isMineScopeAvailable(ancestorPath,
|
|
9598
|
+
if (isMineScopeAvailable(ancestorPath, pathToObjectApiNamesMap, objectInfos)) {
|
|
9449
9599
|
// 'typeConditon' is added when the 'InlineFragmentNode' is appended at the write pass
|
|
9450
9600
|
inlineFragmentSelections[ancestorPath].push(...mineFragmentSelections);
|
|
9451
9601
|
}
|
|
@@ -9471,7 +9621,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9471
9621
|
case 'where': {
|
|
9472
9622
|
inlineFragmentSelections[ancestorPath] = [
|
|
9473
9623
|
...inlineFragmentSelections[ancestorPath],
|
|
9474
|
-
...injectFilter(node, idState, ancestorPath, objectInfos,
|
|
9624
|
+
...injectFilter(node, idState, ancestorPath, false, objectInfos, pathToObjectApiNamesMap, draftFunctions, recordReferenceNode),
|
|
9475
9625
|
];
|
|
9476
9626
|
break;
|
|
9477
9627
|
}
|
|
@@ -9487,7 +9637,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9487
9637
|
if (!node.selectionSet)
|
|
9488
9638
|
return;
|
|
9489
9639
|
// it could be 'recordQuery' or 'childRelationship'
|
|
9490
|
-
const recordQueryField =
|
|
9640
|
+
const recordQueryField = findNearestConnection(ancestors);
|
|
9491
9641
|
if (!recordQueryField)
|
|
9492
9642
|
return;
|
|
9493
9643
|
const ancestorPath = findAncesterPath(ancestors);
|
|
@@ -9499,7 +9649,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9499
9649
|
spanningSelections.push(selection);
|
|
9500
9650
|
}
|
|
9501
9651
|
}
|
|
9502
|
-
const injectedFields = injectFields(spanningSelections, node, ancestors, objectInfos,
|
|
9652
|
+
const injectedFields = injectFields(spanningSelections, node, ancestorPath, ancestors, objectInfos, pathToObjectApiNamesMap);
|
|
9503
9653
|
const mergedInjectedFields = mergeSelectionNodes(inlineFragmentSelections[ancestorPath], injectedFields);
|
|
9504
9654
|
inlineFragmentSelections[ancestorPath] = mergedInjectedFields;
|
|
9505
9655
|
},
|
|
@@ -9512,7 +9662,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9512
9662
|
// removes 'ServicesResources' query field node if 'assignedtome' scope shows up
|
|
9513
9663
|
if (assignedtomeQueryFieldNode !== undefined &&
|
|
9514
9664
|
node.name.value === 'ServiceResources') {
|
|
9515
|
-
const serviceResourcesAncestor =
|
|
9665
|
+
const serviceResourcesAncestor = findNearestConnection(ancestors);
|
|
9516
9666
|
if (serviceResourcesAncestor === assignedtomeQueryFieldNode) {
|
|
9517
9667
|
return null;
|
|
9518
9668
|
}
|
|
@@ -9521,7 +9671,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9521
9671
|
return;
|
|
9522
9672
|
if (!node.selectionSet)
|
|
9523
9673
|
return;
|
|
9524
|
-
const recordQueryField =
|
|
9674
|
+
const recordQueryField = findNearestConnection(ancestors);
|
|
9525
9675
|
if (!recordQueryField)
|
|
9526
9676
|
return;
|
|
9527
9677
|
const ancestorPath = findAncesterPath(ancestors);
|
|
@@ -9530,8 +9680,8 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9530
9680
|
return;
|
|
9531
9681
|
//const recordQueryPath = findAncesterPath(ancestors);
|
|
9532
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.
|
|
9533
|
-
const recordQueryApiName =
|
|
9534
|
-
?
|
|
9683
|
+
const recordQueryApiName = pathToObjectApiNamesMap[ancestorPath]
|
|
9684
|
+
? pathToObjectApiNamesMap[ancestorPath][0]
|
|
9535
9685
|
: recordQueryField.name.value;
|
|
9536
9686
|
const nodeWithFragments = {
|
|
9537
9687
|
...node,
|
|
@@ -9568,7 +9718,7 @@ async function injectSyntheticFields(originalAST, objectInfoService, draftFuncti
|
|
|
9568
9718
|
if (node.name.value === 'where') {
|
|
9569
9719
|
const ancestorPath = findAncesterPath(ancestors);
|
|
9570
9720
|
if (idState.paths.includes(ancestorPath)) {
|
|
9571
|
-
const apiName =
|
|
9721
|
+
const apiName = pathToObjectApiNamesMap[ancestorPath][0];
|
|
9572
9722
|
const objectInfo = objectInfos[apiName];
|
|
9573
9723
|
const swappedIdFilter = swapIdField(node.value, objectInfo, false, idState, draftFunctions);
|
|
9574
9724
|
return {
|
|
@@ -9634,8 +9784,8 @@ function swapIdField(filterFields, objectInfo, swapped, idState, draftFunctions)
|
|
|
9634
9784
|
};
|
|
9635
9785
|
}
|
|
9636
9786
|
}
|
|
9637
|
-
function isMineScopeAvailable(apiNamePath,
|
|
9638
|
-
const apiName =
|
|
9787
|
+
function isMineScopeAvailable(apiNamePath, pathToObjectApiNamesMap, objectInfos) {
|
|
9788
|
+
const apiName = pathToObjectApiNamesMap[apiNamePath];
|
|
9639
9789
|
if (!apiName)
|
|
9640
9790
|
return false;
|
|
9641
9791
|
const objectInfo = objectInfos[apiName[0]];
|
|
@@ -9724,15 +9874,16 @@ function growObjectFieldTree(tree, parentNode, entryNode, totalNodes, startNodes
|
|
|
9724
9874
|
}
|
|
9725
9875
|
// example: 'Account'
|
|
9726
9876
|
const childNode = objectFieldNode.name.value;
|
|
9877
|
+
const childNodepath = `${parentNode}#${childNode}`;
|
|
9727
9878
|
if (!tree[parentNode].some((child) => child.name === childNode)) {
|
|
9728
9879
|
tree[parentNode].push({
|
|
9729
9880
|
relation: 'parent',
|
|
9730
9881
|
name: childNode,
|
|
9731
9882
|
});
|
|
9732
|
-
totalNodes.add(
|
|
9883
|
+
totalNodes.add(childNodepath);
|
|
9733
9884
|
}
|
|
9734
9885
|
// recursively go to deeper level of filter.
|
|
9735
|
-
growObjectFieldTree(tree,
|
|
9886
|
+
growObjectFieldTree(tree, childNodepath, objectFieldNode.value, totalNodes, startNodes);
|
|
9736
9887
|
}
|
|
9737
9888
|
}
|
|
9738
9889
|
}
|
|
@@ -9767,19 +9918,20 @@ function growFieldTree(tree, parentSectionPath, entryNode, parentNode, totalNode
|
|
|
9767
9918
|
}
|
|
9768
9919
|
if (!tree[parentSectionPath].some((field) => field.name === fieldName)) {
|
|
9769
9920
|
tree[parentSectionPath].push({
|
|
9770
|
-
relation: relationType ===
|
|
9771
|
-
relationType ===
|
|
9921
|
+
relation: relationType === PARENT_RELATIONSHIP ||
|
|
9922
|
+
relationType === POLYMORPHIC_PARENT_RELATIONSHIP
|
|
9772
9923
|
? 'parent'
|
|
9773
9924
|
: 'child',
|
|
9774
9925
|
name: fieldName,
|
|
9775
9926
|
});
|
|
9776
|
-
totalNodes.add(fieldName);
|
|
9927
|
+
totalNodes.add(`${parentSectionPath}#${fieldName}`);
|
|
9777
9928
|
}
|
|
9778
9929
|
if (entryNode.selectionSet && entryNode.selectionSet.selections) {
|
|
9779
9930
|
const childNodes = entryNode.selectionSet.selections.filter(isFieldOrInlineFragmentNode);
|
|
9780
9931
|
// recursively build the traversal tree
|
|
9781
9932
|
for (const child of childNodes) {
|
|
9782
|
-
|
|
9933
|
+
const path = `${parentSectionPath}#${fieldName}`;
|
|
9934
|
+
growFieldTree(tree, path, child, entryNode, totalNodes, startNodes);
|
|
9783
9935
|
}
|
|
9784
9936
|
}
|
|
9785
9937
|
}
|
|
@@ -9809,23 +9961,23 @@ function growFieldTree(tree, parentSectionPath, entryNode, parentNode, totalNode
|
|
|
9809
9961
|
}
|
|
9810
9962
|
if (!tree[parentSectionPath].some((field) => field.name === conditionName)) {
|
|
9811
9963
|
tree[parentSectionPath].push({
|
|
9812
|
-
relation: relationType ===
|
|
9813
|
-
relationType ===
|
|
9964
|
+
relation: relationType === PARENT_RELATIONSHIP ||
|
|
9965
|
+
relationType === POLYMORPHIC_PARENT_RELATIONSHIP
|
|
9814
9966
|
? 'parent'
|
|
9815
9967
|
: 'child',
|
|
9816
9968
|
name: conditionName,
|
|
9817
9969
|
});
|
|
9818
|
-
|
|
9970
|
+
const path = `${parentSectionPath}#${conditionName}`;
|
|
9971
|
+
totalNodes.add(path);
|
|
9819
9972
|
}
|
|
9820
9973
|
}
|
|
9821
9974
|
}
|
|
9822
9975
|
// dive deep immediately for 'InlineFragment'
|
|
9823
9976
|
const childNodes = entryNode.selectionSet.selections.filter(isFieldOrInlineFragmentNode);
|
|
9977
|
+
const path = `${parentSectionPath}${entryNode.typeCondition ? '#' + entryNode.typeCondition.name.value : ''}`;
|
|
9824
9978
|
// Navigates into InLineFragment
|
|
9825
9979
|
for (const child of childNodes) {
|
|
9826
|
-
growFieldTree(tree, entryNode
|
|
9827
|
-
? entryNode.typeCondition.name.value
|
|
9828
|
-
: parentSectionPath, child, entryNode, totalNodes, startNodes);
|
|
9980
|
+
growFieldTree(tree, path, child, entryNode, totalNodes, startNodes);
|
|
9829
9981
|
}
|
|
9830
9982
|
}
|
|
9831
9983
|
}
|
|
@@ -9837,7 +9989,7 @@ function growFieldTree(tree, parentSectionPath, entryNode, parentNode, totalNode
|
|
|
9837
9989
|
* @param startNodes start nodes of the tree. It can be used to fetch ObjectInfo immediately
|
|
9838
9990
|
* @param path
|
|
9839
9991
|
*/
|
|
9840
|
-
async function resolveObjectInfos(objectInfotree,
|
|
9992
|
+
async function resolveObjectInfos(objectInfotree, pathToObjectApiNamesMap, startNodes, objectInfoService) {
|
|
9841
9993
|
let objectInfos;
|
|
9842
9994
|
try {
|
|
9843
9995
|
objectInfos = await objectInfoService.getObjectInfos(Array.from(startNodes));
|
|
@@ -9851,9 +10003,9 @@ async function resolveObjectInfos(objectInfotree, objectInfoApiMap, startNodes,
|
|
|
9851
10003
|
throw new Error(`Unable to resolve ObjectInfo(s) for ${Array.from(startNodes)}`);
|
|
9852
10004
|
}
|
|
9853
10005
|
for (const startNode of startNodes) {
|
|
9854
|
-
|
|
10006
|
+
pathToObjectApiNamesMap[startNode] = [startNode];
|
|
9855
10007
|
const children = objectInfotree[startNode];
|
|
9856
|
-
const subObjectInfoMap = await fetchObjectInfos(objectInfotree,
|
|
10008
|
+
const subObjectInfoMap = await fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, objectInfos, children, startNode, objectInfoService);
|
|
9857
10009
|
objectInfos = { ...objectInfos, ...subObjectInfoMap };
|
|
9858
10010
|
}
|
|
9859
10011
|
return objectInfos;
|
|
@@ -9861,15 +10013,15 @@ async function resolveObjectInfos(objectInfotree, objectInfoApiMap, startNodes,
|
|
|
9861
10013
|
// example 1: 'parentPath': 'ServiceAppointment', 'nodesAtSameLevel': ['Account']
|
|
9862
10014
|
// example 2: 'parentPath': 'ServiceAppointment', 'nodesAtSameLevel': ['Owner'], this example has 2 apiName for the node 'Owner'
|
|
9863
10015
|
// example 3: 'parentPath': 'ServiceAppointment_Owner', 'nodesAtSameLevel': ['User', 'Group']
|
|
9864
|
-
async function fetchObjectInfos(objectInfotree,
|
|
9865
|
-
const objectInfoApiNames =
|
|
10016
|
+
async function fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, objectInfoMap, nodesAtSameLevel, parentPath, objectInfoService) {
|
|
10017
|
+
const objectInfoApiNames = pathToObjectApiNamesMap[parentPath];
|
|
9866
10018
|
if (!objectInfoApiNames) {
|
|
9867
10019
|
// eslint-disable-next-line
|
|
9868
10020
|
throw new Error(`Object Info does not exist for ${parentPath}`);
|
|
9869
10021
|
}
|
|
9870
10022
|
const validObjectInfoNodes = [];
|
|
9871
10023
|
let updatedObjectInfoMap = {};
|
|
9872
|
-
// InlineFragment and polymorphic field support fits into this scenario
|
|
10024
|
+
// InlineFragment and polymorphic field support fits into this scenario pathToObjectApiNamesMap Entry: 'ServiceAppointment#Owner' -> ['User', 'Group']; ServiceAppointment#Owner#User' -> ['User']
|
|
9873
10025
|
if (objectInfoApiNames.length > 0 &&
|
|
9874
10026
|
nodesAtSameLevel.length > 0 &&
|
|
9875
10027
|
objectInfoApiNames.includes(nodesAtSameLevel[0].name)) {
|
|
@@ -9881,8 +10033,8 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
|
|
|
9881
10033
|
// eslint-disable-next-line
|
|
9882
10034
|
throw new Error(`Condition ${field.name} does not exists for ${parentPath}`);
|
|
9883
10035
|
}
|
|
9884
|
-
const path = `${parentPath}
|
|
9885
|
-
|
|
10036
|
+
const path = `${parentPath}#${field.name}`;
|
|
10037
|
+
pathToObjectApiNamesMap[path] = [field.name];
|
|
9886
10038
|
}
|
|
9887
10039
|
validObjectInfoNodes.push(...nodesAtSameLevel);
|
|
9888
10040
|
updatedObjectInfoMap = { ...objectInfoMap };
|
|
@@ -9897,7 +10049,7 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
|
|
|
9897
10049
|
let apiNames = [];
|
|
9898
10050
|
for (const nodeInfo of nodesAtSameLevel) {
|
|
9899
10051
|
const field = nodeInfo.name;
|
|
9900
|
-
const path = `${parentPath}
|
|
10052
|
+
const path = `${parentPath}#${field}`;
|
|
9901
10053
|
// Handle 'parentRelationship'
|
|
9902
10054
|
if (nodeInfo.relation === 'parent') {
|
|
9903
10055
|
const relationshipId = referenceIdFieldForRelationship(field);
|
|
@@ -9915,21 +10067,21 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
|
|
|
9915
10067
|
}
|
|
9916
10068
|
}
|
|
9917
10069
|
// This is a polymorphic field
|
|
9918
|
-
if (fieldDefinition.referenceToInfos.length > 1 && objectInfotree[
|
|
10070
|
+
if (fieldDefinition.referenceToInfos.length > 1 && objectInfotree[path]) {
|
|
9919
10071
|
// Fields needs to expand and heterogenous entity ObjectInfo needs to be fetched
|
|
9920
|
-
const referencedNodeInfos = objectInfotree[
|
|
10072
|
+
const referencedNodeInfos = objectInfotree[path];
|
|
9921
10073
|
const requestedApiNames = referencedNodeInfos.map((referenceNodeInfo) => referenceNodeInfo.name);
|
|
9922
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.
|
|
9923
|
-
if (requestedApiNames.length > 0 && objectInfotree[
|
|
10075
|
+
if (requestedApiNames.length > 0 && objectInfotree[path]) {
|
|
9924
10076
|
fieldDefinition.referenceToInfos
|
|
9925
10077
|
.filter((referenceToInfo) => requestedApiNames.includes(referenceToInfo.apiName))
|
|
9926
10078
|
.forEach((ref) => {
|
|
9927
|
-
if (!
|
|
9928
|
-
|
|
10079
|
+
if (!pathToObjectApiNamesMap[path]) {
|
|
10080
|
+
pathToObjectApiNamesMap[path] = [];
|
|
9929
10081
|
}
|
|
9930
10082
|
// 'ServiceAppointment_Owner' ->['User', 'Group']
|
|
9931
|
-
if (!
|
|
9932
|
-
|
|
10083
|
+
if (!pathToObjectApiNamesMap[path].includes(ref.apiName)) {
|
|
10084
|
+
pathToObjectApiNamesMap[path].push(ref.apiName);
|
|
9933
10085
|
}
|
|
9934
10086
|
if (!apiNames.includes(ref.apiName)) {
|
|
9935
10087
|
apiNames.push(ref.apiName);
|
|
@@ -9939,11 +10091,11 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
|
|
|
9939
10091
|
}
|
|
9940
10092
|
else if (fieldDefinition.referenceToInfos.length === 1) {
|
|
9941
10093
|
const ref = fieldDefinition.referenceToInfos[0];
|
|
9942
|
-
if (!
|
|
9943
|
-
|
|
10094
|
+
if (!pathToObjectApiNamesMap[path]) {
|
|
10095
|
+
pathToObjectApiNamesMap[path] = [];
|
|
9944
10096
|
}
|
|
9945
|
-
if (!
|
|
9946
|
-
|
|
10097
|
+
if (!pathToObjectApiNamesMap[path].includes(ref.apiName)) {
|
|
10098
|
+
pathToObjectApiNamesMap[path].push(ref.apiName);
|
|
9947
10099
|
}
|
|
9948
10100
|
if (!apiNames.includes(ref.apiName)) {
|
|
9949
10101
|
apiNames.push(ref.apiName);
|
|
@@ -9954,11 +10106,11 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
|
|
|
9954
10106
|
// handles 'childRelationship'
|
|
9955
10107
|
const childRelationship = parentObjectInfo.childRelationships.find((childRelationship) => childRelationship.relationshipName === field);
|
|
9956
10108
|
if (childRelationship) {
|
|
9957
|
-
if (!
|
|
9958
|
-
|
|
10109
|
+
if (!pathToObjectApiNamesMap[path]) {
|
|
10110
|
+
pathToObjectApiNamesMap[path] = [];
|
|
9959
10111
|
}
|
|
9960
|
-
if (!
|
|
9961
|
-
|
|
10112
|
+
if (!pathToObjectApiNamesMap[path].includes(childRelationship.childObjectApiName)) {
|
|
10113
|
+
pathToObjectApiNamesMap[path].push(childRelationship.childObjectApiName);
|
|
9962
10114
|
}
|
|
9963
10115
|
if (!apiNames.includes(childRelationship.childObjectApiName)) {
|
|
9964
10116
|
apiNames.push(childRelationship.childObjectApiName);
|
|
@@ -9980,10 +10132,10 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
|
|
|
9980
10132
|
}
|
|
9981
10133
|
for (const nodeInfo of validObjectInfoNodes) {
|
|
9982
10134
|
const field = nodeInfo.name;
|
|
9983
|
-
const
|
|
9984
|
-
const
|
|
10135
|
+
const path = `${parentPath}#${field}`;
|
|
10136
|
+
const subLevelFields = objectInfotree[path];
|
|
9985
10137
|
if (subLevelFields && subLevelFields.length > 0) {
|
|
9986
|
-
const subObjectInfos = await fetchObjectInfos(objectInfotree,
|
|
10138
|
+
const subObjectInfos = await fetchObjectInfos(objectInfotree, pathToObjectApiNamesMap, updatedObjectInfoMap, subLevelFields, path, objectInfoService);
|
|
9987
10139
|
updatedObjectInfoMap = { ...updatedObjectInfoMap, ...subObjectInfos };
|
|
9988
10140
|
}
|
|
9989
10141
|
}
|
|
@@ -9998,27 +10150,29 @@ async function fetchObjectInfos(objectInfotree, objectInfoApiMap, objectInfoMap,
|
|
|
9998
10150
|
* 'path' and 'queryNode' is 1 level above the 'filterNode'
|
|
9999
10151
|
* @param filterNode filter node which needs to be injected. For example, 'State' ObjectFieldNode within filter 'where: { State: { eq: "Nova Scotia" }}'
|
|
10000
10152
|
* @param idState ID state will be updated to determine if the ID fields in AST need to be swapped. The swapping happens later.
|
|
10001
|
-
* @param
|
|
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.
|
|
10002
10155
|
* @param queryNode referece FieldNode which provides the information if 'filterNode' exist in it nor not.
|
|
10003
10156
|
* @param objectInfos ObjectInfo map used in injection. If ObjectInfo misses or field does not exist in ObjectInfo, the error will be thrown
|
|
10004
|
-
* @param
|
|
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'].
|
|
10005
10158
|
* @param draftFunctions functions for working with record ids that may be draft-created ids
|
|
10006
10159
|
* @returns an array of nodes with injected fields
|
|
10007
10160
|
*/
|
|
10008
|
-
function injectFilter(filterNode, idState,
|
|
10161
|
+
function injectFilter(filterNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode) {
|
|
10009
10162
|
const injectedSelections = [];
|
|
10163
|
+
let isPolymorphicField = false;
|
|
10010
10164
|
switch (filterNode.kind) {
|
|
10011
10165
|
case Kind.ARGUMENT:
|
|
10012
10166
|
if (filterNode.value.kind !== 'ObjectValue')
|
|
10013
10167
|
return [];
|
|
10014
10168
|
filterNode.value.fields.forEach((objectFieldNode) => {
|
|
10015
|
-
let subResults = injectFilter(objectFieldNode, idState,
|
|
10169
|
+
let subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
|
|
10016
10170
|
for (const subResult of subResults) {
|
|
10017
10171
|
mergeOrAddToGroup(injectedSelections, subResult);
|
|
10018
10172
|
}
|
|
10019
10173
|
// multiple Ids might need to be swapped. remember their paths for faster write.
|
|
10020
10174
|
if (idState.swapNeeded) {
|
|
10021
|
-
idState.paths.push(
|
|
10175
|
+
idState.paths.push(parentPath);
|
|
10022
10176
|
}
|
|
10023
10177
|
});
|
|
10024
10178
|
return injectedSelections;
|
|
@@ -10027,7 +10181,7 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
|
|
|
10027
10181
|
case Kind.LIST: {
|
|
10028
10182
|
filterNode.value.values.filter(isObjectValueNode).forEach((objectValueNode) => {
|
|
10029
10183
|
objectValueNode.fields.forEach((objectFieldNode) => {
|
|
10030
|
-
const subResults = injectFilter(objectFieldNode, idState,
|
|
10184
|
+
const subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
|
|
10031
10185
|
for (const subResult of subResults) {
|
|
10032
10186
|
mergeOrAddToGroup(injectedSelections, subResult);
|
|
10033
10187
|
}
|
|
@@ -10038,7 +10192,7 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
|
|
|
10038
10192
|
case Kind.OBJECT: {
|
|
10039
10193
|
if (filterNode.name.value === 'not') {
|
|
10040
10194
|
filterNode.value.fields.forEach((objectFieldNode) => {
|
|
10041
|
-
const subResults = injectFilter(objectFieldNode, idState,
|
|
10195
|
+
const subResults = injectFilter(objectFieldNode, idState, parentPath, isParentPolymorphic, objectInfos, pathToObjectApiNamesMap, draftFunctions, queryNode);
|
|
10042
10196
|
for (const subResult of subResults) {
|
|
10043
10197
|
mergeOrAddToGroup(injectedSelections, subResult);
|
|
10044
10198
|
}
|
|
@@ -10048,15 +10202,15 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
|
|
|
10048
10202
|
let apiNames = [];
|
|
10049
10203
|
let isScalarField = false;
|
|
10050
10204
|
//It is possible that this is a polymorphic field
|
|
10051
|
-
apiNames =
|
|
10052
|
-
// example: path: '
|
|
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
|
|
10053
10207
|
if (apiNames === undefined) {
|
|
10054
10208
|
isScalarField = true;
|
|
10055
10209
|
}
|
|
10056
10210
|
else {
|
|
10057
10211
|
if (apiNames.some((apiName) => objectInfos[apiName] === undefined)) {
|
|
10058
10212
|
// eslint-disable-next-line
|
|
10059
|
-
throw new Error(`ObjectInfo is missing for ${
|
|
10213
|
+
throw new Error(`ObjectInfo is missing for ${parentPath}`);
|
|
10060
10214
|
}
|
|
10061
10215
|
}
|
|
10062
10216
|
if (isScalarField) {
|
|
@@ -10078,29 +10232,19 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
|
|
|
10078
10232
|
}
|
|
10079
10233
|
});
|
|
10080
10234
|
let isSpanning = false;
|
|
10235
|
+
// if true, current node is a polymorphic concrete type node. For example, field node `User` under `Owner`
|
|
10081
10236
|
let isInlineFragment = false;
|
|
10082
|
-
let isPolymorphicField = false;
|
|
10083
10237
|
let isTypeNameExisting = false;
|
|
10084
10238
|
let curPath;
|
|
10085
10239
|
let fieldName = filterNode.name.value;
|
|
10086
|
-
curPath = `${
|
|
10087
|
-
if (
|
|
10240
|
+
curPath = `${parentPath}#${fieldName}`;
|
|
10241
|
+
if (pathToObjectApiNamesMap[curPath] &&
|
|
10242
|
+
pathToObjectApiNamesMap[curPath].length > 0) {
|
|
10088
10243
|
isSpanning = true;
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
isInlineFragment = true;
|
|
10094
|
-
}
|
|
10095
|
-
}
|
|
10096
|
-
// Checks if the current filter node is a polymorphic field. 'ServiceAppointment_Owner' --> ['User']; 'ServiceAppointment_Owner_User' --> ['User']
|
|
10097
|
-
const childApiName = objectInfoApiMap[curPath][0];
|
|
10098
|
-
const trialApiNames = objectInfoApiMap[`${curPath}_${childApiName}`];
|
|
10099
|
-
if (trialApiNames !== undefined &&
|
|
10100
|
-
trialApiNames.length === 1 &&
|
|
10101
|
-
trialApiNames[0] === childApiName) {
|
|
10102
|
-
isPolymorphicField = true;
|
|
10103
|
-
}
|
|
10244
|
+
isInlineFragment =
|
|
10245
|
+
isParentPolymorphic &&
|
|
10246
|
+
pathToObjectApiNamesMap[curPath].length === 1;
|
|
10247
|
+
isPolymorphicField = isPolymorphicFieldPath(curPath, pathToObjectApiNamesMap, objectInfos);
|
|
10104
10248
|
}
|
|
10105
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]
|
|
10106
10250
|
if (isInlineFragment) {
|
|
@@ -10144,23 +10288,25 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
|
|
|
10144
10288
|
throw new Error(`Field ${fieldName} does not exist in ${apiNames[0]}`);
|
|
10145
10289
|
}
|
|
10146
10290
|
}
|
|
10147
|
-
const objectInfoName =
|
|
10148
|
-
?
|
|
10149
|
-
:
|
|
10291
|
+
const objectInfoName = pathToObjectApiNamesMap[curPath] !== undefined
|
|
10292
|
+
? pathToObjectApiNamesMap[curPath][0]
|
|
10293
|
+
: pathToObjectApiNamesMap[parentPath][0];
|
|
10150
10294
|
const isIdField = isFieldAnIdField(filterNode.name.value, objectInfos[objectInfoName]);
|
|
10151
10295
|
if (!isIdField) {
|
|
10152
10296
|
let subSelectionNodes = [];
|
|
10153
10297
|
let subFieldsHasId = false;
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
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
|
+
}
|
|
10164
10310
|
if (!subFieldsHasId) {
|
|
10165
10311
|
// Check if the query field has the 'Id'
|
|
10166
10312
|
const existingIdsInQuery = existingFields &&
|
|
@@ -10189,6 +10335,7 @@ function injectFilter(filterNode, idState, path, objectInfos, objectInfoApiMap,
|
|
|
10189
10335
|
}
|
|
10190
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
|
|
10191
10337
|
if (!existingFields ||
|
|
10338
|
+
existingFields.length === 0 ||
|
|
10192
10339
|
subSelectionNodes.length > 0 ||
|
|
10193
10340
|
(isSpanning && !subFieldsHasId) ||
|
|
10194
10341
|
(isInlineFragment && !isTypeNameExisting)) {
|
|
@@ -10319,6 +10466,44 @@ function mergeOrAddToGroup(group, element) {
|
|
|
10319
10466
|
}
|
|
10320
10467
|
group.push(element);
|
|
10321
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
|
+
}
|
|
10322
10507
|
function isFieldAnIdField(fieldName, objectInfo) {
|
|
10323
10508
|
if (fieldName === 'Id')
|
|
10324
10509
|
return true;
|
|
@@ -10371,10 +10556,10 @@ function updateIDInfo(fieldNode, idState, draftFunctions) {
|
|
|
10371
10556
|
* @param parentNode parent node of param 1
|
|
10372
10557
|
* @param ancestors ancester of param 1
|
|
10373
10558
|
* @param objectInfos ObjectInfo map used in injection. If ObjectInfo misses or field does not exist in ObjectInfo, the error will be thrown
|
|
10374
|
-
* @param
|
|
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'].
|
|
10375
10560
|
* @return injected SelectionNodes used to construct the InlineFragment.
|
|
10376
10561
|
*/
|
|
10377
|
-
function injectFields(selections, parentNode, ancestors, objectInfos,
|
|
10562
|
+
function injectFields(selections, parentNode, parentPath, ancestors, objectInfos, pathToObjectApiNamesMap) {
|
|
10378
10563
|
/**
|
|
10379
10564
|
* 1 parentship can return 2 FieldNode which need to be flattened
|
|
10380
10565
|
* Concact: { ** Contact { ** ContactId {
|
|
@@ -10392,6 +10577,10 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
|
|
|
10392
10577
|
if (!selection.selectionSet) {
|
|
10393
10578
|
return selection;
|
|
10394
10579
|
}
|
|
10580
|
+
const segment = isFieldNode(selection)
|
|
10581
|
+
? selection.name.value
|
|
10582
|
+
: selection.typeCondition.name.value;
|
|
10583
|
+
const curPath = `${parentPath}#${segment}`;
|
|
10395
10584
|
const spanningSubSelections = [];
|
|
10396
10585
|
for (const subSelection of selection.selectionSet.selections) {
|
|
10397
10586
|
if (isFieldSpanning(subSelection, selection)) {
|
|
@@ -10399,7 +10588,7 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
|
|
|
10399
10588
|
}
|
|
10400
10589
|
}
|
|
10401
10590
|
// Handles multiple level field injection like 'ServiceAppointment' --> 'Account' --> 'Owner'
|
|
10402
|
-
const subInjectedSelections = injectFields(spanningSubSelections, selection, ancestors, objectInfos,
|
|
10591
|
+
const subInjectedSelections = injectFields(spanningSubSelections, selection, curPath, ancestors, objectInfos, pathToObjectApiNamesMap);
|
|
10403
10592
|
if (!selection.selectionSet) {
|
|
10404
10593
|
return selection;
|
|
10405
10594
|
}
|
|
@@ -10442,7 +10631,7 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
|
|
|
10442
10631
|
}
|
|
10443
10632
|
}
|
|
10444
10633
|
// For polymorphic fields, the Id field is excluded.
|
|
10445
|
-
const excludeId =
|
|
10634
|
+
const excludeId = isPolymorphicFieldPath(curPath, pathToObjectApiNamesMap, objectInfos);
|
|
10446
10635
|
const idSelection = [];
|
|
10447
10636
|
if (!excludeId && !hasIdAlready) {
|
|
10448
10637
|
idSelection.push({
|
|
@@ -10453,8 +10642,8 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
|
|
|
10453
10642
|
},
|
|
10454
10643
|
});
|
|
10455
10644
|
}
|
|
10456
|
-
// Inject '__typename' for
|
|
10457
|
-
// 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'.
|
|
10458
10647
|
if (isInlineFragmentNode(selection) &&
|
|
10459
10648
|
!selection.selectionSet.selections.find((selection) => isFieldNode(selection) && selection.name.value === '__typename')) {
|
|
10460
10649
|
idSelection.push({
|
|
@@ -10488,7 +10677,7 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
|
|
|
10488
10677
|
if (isFieldNode(parentNode) && parentNode.selectionSet && parentNode.name.value === 'node') {
|
|
10489
10678
|
if (parentNode.selectionSet.selections
|
|
10490
10679
|
.filter(isFieldOrInlineFragmentNode)
|
|
10491
|
-
.some((selectionNode) => isRelationship(selectionNode,
|
|
10680
|
+
.some((selectionNode) => isRelationship(selectionNode, CHILD_RELATIONSHIP))) {
|
|
10492
10681
|
if (!parentNode.selectionSet.selections
|
|
10493
10682
|
.filter(isFieldNode)
|
|
10494
10683
|
.some((sibling) => sibling.name.value === 'Id')) {
|
|
@@ -10507,15 +10696,15 @@ function injectFields(selections, parentNode, ancestors, objectInfos, objectInfo
|
|
|
10507
10696
|
if (parentInfo.parentIndex >= 0) {
|
|
10508
10697
|
// example node { TimeSheetEntries { edges { node { Id }}}}
|
|
10509
10698
|
const parent = parentInfo.node;
|
|
10510
|
-
if (isRelationship(parent,
|
|
10699
|
+
if (isRelationship(parent, CHILD_RELATIONSHIP)) {
|
|
10511
10700
|
const unVisitedAncestors = ancestors.slice(0, parentInfo.parentIndex);
|
|
10512
10701
|
// path : "TimeSheet"
|
|
10513
10702
|
const grandParentPath = findAncesterPath(unVisitedAncestors);
|
|
10514
|
-
if (
|
|
10515
|
-
|
|
10703
|
+
if (pathToObjectApiNamesMap &&
|
|
10704
|
+
pathToObjectApiNamesMap[grandParentPath] &&
|
|
10516
10705
|
objectInfos &&
|
|
10517
|
-
objectInfos[
|
|
10518
|
-
const grandParentObjectInfo = objectInfos[
|
|
10706
|
+
objectInfos[pathToObjectApiNamesMap[grandParentPath][0]]) {
|
|
10707
|
+
const grandParentObjectInfo = objectInfos[pathToObjectApiNamesMap[grandParentPath][0]];
|
|
10519
10708
|
// exmaple "TimeSheetEntries"
|
|
10520
10709
|
const parentFieldName = parent.name.value;
|
|
10521
10710
|
const targetRelationship = grandParentObjectInfo.childRelationships.find((childRelationship) => {
|
|
@@ -10636,7 +10825,7 @@ const assignedToMeFragmentSelections = [
|
|
|
10636
10825
|
},
|
|
10637
10826
|
value: {
|
|
10638
10827
|
kind: 'StringValue',
|
|
10639
|
-
value:
|
|
10828
|
+
value: CHILD_RELATIONSHIP,
|
|
10640
10829
|
block: false,
|
|
10641
10830
|
},
|
|
10642
10831
|
},
|
|
@@ -10728,7 +10917,7 @@ const assignedToMeFragmentSelections = [
|
|
|
10728
10917
|
},
|
|
10729
10918
|
value: {
|
|
10730
10919
|
kind: 'StringValue',
|
|
10731
|
-
value:
|
|
10920
|
+
value: PARENT_RELATIONSHIP,
|
|
10732
10921
|
block: false,
|
|
10733
10922
|
},
|
|
10734
10923
|
},
|
|
@@ -10865,7 +11054,9 @@ function handleNonArrayJsonProperty(selection, fieldName, jsonInput, jsonOutput)
|
|
|
10865
11054
|
jsonOutput[fieldName] = null;
|
|
10866
11055
|
return;
|
|
10867
11056
|
}
|
|
10868
|
-
jsonOutput[fieldName]
|
|
11057
|
+
if (jsonOutput[fieldName] === undefined) {
|
|
11058
|
+
jsonOutput[fieldName] = {};
|
|
11059
|
+
}
|
|
10869
11060
|
createUserJsonOutput(selection, jsonInput[fieldName], jsonOutput[fieldName]);
|
|
10870
11061
|
}
|
|
10871
11062
|
else {
|
|
@@ -11597,34 +11788,42 @@ function applyReferenceLinksToDraft(record, draftMetadata) {
|
|
|
11597
11788
|
}
|
|
11598
11789
|
const { dataType, relationshipName, referenceToInfos } = fieldInfo;
|
|
11599
11790
|
const draftFieldValue = record.fields[draftField].value;
|
|
11600
|
-
if (dataType === 'Reference' && relationshipName !== null
|
|
11601
|
-
if (
|
|
11602
|
-
|
|
11791
|
+
if (dataType === 'Reference' && relationshipName !== null) {
|
|
11792
|
+
if (draftFieldValue === null) {
|
|
11793
|
+
recordFields[relationshipName] = {
|
|
11794
|
+
displayValue: null,
|
|
11795
|
+
value: null,
|
|
11796
|
+
};
|
|
11603
11797
|
}
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
displayValue: null,
|
|
11608
|
-
value: createLink(key),
|
|
11609
|
-
};
|
|
11610
|
-
// for custom objects, we select the 'Name' field
|
|
11611
|
-
// otherwise we check the object info for name fields.
|
|
11612
|
-
//if there are multiple we select 'Name' if it exists, otherwise the first one
|
|
11613
|
-
if (referencedRecord !== undefined && referenceToInfos.length > 0) {
|
|
11614
|
-
let nameField;
|
|
11615
|
-
const referenceToInfo = referenceToInfos[0];
|
|
11616
|
-
const nameFields = referenceToInfo.nameFields;
|
|
11617
|
-
if (nameFields.length !== 0) {
|
|
11618
|
-
nameField = nameFields.find((x) => x === 'Name');
|
|
11619
|
-
if (nameField === undefined) {
|
|
11620
|
-
nameField = nameFields[0];
|
|
11621
|
-
}
|
|
11798
|
+
else {
|
|
11799
|
+
if (typeof draftFieldValue !== 'string') {
|
|
11800
|
+
throw Error('reference field value is not a string');
|
|
11622
11801
|
}
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
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
|
+
}
|
|
11628
11827
|
}
|
|
11629
11828
|
}
|
|
11630
11829
|
}
|
|
@@ -11917,17 +12116,8 @@ class UiApiActionHandler extends AbstractResourceRequestActionHandler {
|
|
|
11917
12116
|
};
|
|
11918
12117
|
for (const fieldName of keys$3(recordWithSpanningRefLinks.fields)) {
|
|
11919
12118
|
const fieldKey = buildRecordFieldStoreKey(key, fieldName);
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
normalizedRecord.fields[fieldName] = { __ref: fieldKey };
|
|
11923
|
-
publishData(fieldKey, fieldData);
|
|
11924
|
-
}
|
|
11925
|
-
else if (recordWithSpanningRefLinks.fields[fieldName] &&
|
|
11926
|
-
recordWithSpanningRefLinks.fields[fieldName].value &&
|
|
11927
|
-
recordWithSpanningRefLinks.fields[fieldName].value.__ref !== undefined) {
|
|
11928
|
-
normalizedRecord.fields[fieldName] = { __ref: fieldKey };
|
|
11929
|
-
publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
|
|
11930
|
-
}
|
|
12119
|
+
normalizedRecord.fields[fieldName] = { __ref: fieldKey };
|
|
12120
|
+
publishData(fieldKey, recordWithSpanningRefLinks.fields[fieldName]);
|
|
11931
12121
|
}
|
|
11932
12122
|
// publish the normalized record
|
|
11933
12123
|
publishData(key, normalizedRecord);
|
|
@@ -12826,6 +13016,9 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
|
|
|
12826
13016
|
if (!rebuildResult.errors) {
|
|
12827
13017
|
rebuildResult = removeSyntheticFields(rebuildResult, config.query);
|
|
12828
13018
|
}
|
|
13019
|
+
if (objectsDeepEqual(rebuildResult, originalSnapshot.data)) {
|
|
13020
|
+
return originalSnapshot;
|
|
13021
|
+
}
|
|
12829
13022
|
// '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.
|
|
12830
13023
|
return {
|
|
12831
13024
|
...originalSnapshot,
|
|
@@ -12844,7 +13037,7 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
|
|
|
12844
13037
|
// Fulfilled snapshot (this only happens in this code path if
|
|
12845
13038
|
// the error is network error or 504), otherwise we spread over
|
|
12846
13039
|
// the non-eval'ed snapshot (which will be either Fulfilled or Stale)
|
|
12847
|
-
|
|
13040
|
+
const resultSnapshot = nonEvaluatedSnapshot.state === 'Error'
|
|
12848
13041
|
? createLocalEvalSnapshot(gqlResult, seenRecords, recordId, rebuildWithLocalEval)
|
|
12849
13042
|
: {
|
|
12850
13043
|
...nonEvaluatedSnapshot,
|
|
@@ -12853,6 +13046,22 @@ function draftAwareGraphQLAdapterFactory(userId, objectInfoService, store, luvio
|
|
|
12853
13046
|
seenRecords,
|
|
12854
13047
|
rebuildWithLocalEval,
|
|
12855
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;
|
|
12856
13065
|
};
|
|
12857
13066
|
}
|
|
12858
13067
|
|
|
@@ -13907,7 +14116,10 @@ function isErrorResponse(response) {
|
|
|
13907
14116
|
* @returns the merged record
|
|
13908
14117
|
*/
|
|
13909
14118
|
function mergeAggregateUiResponse(response, mergeFunc) {
|
|
13910
|
-
|
|
14119
|
+
if (response.ok === false) {
|
|
14120
|
+
return response;
|
|
14121
|
+
}
|
|
14122
|
+
const body = response.body;
|
|
13911
14123
|
try {
|
|
13912
14124
|
if (body === null ||
|
|
13913
14125
|
body === undefined ||
|
|
@@ -15870,6 +16082,9 @@ class PrimingSession extends EventEmitter {
|
|
|
15870
16082
|
this.ldsRecordRefresher = config.ldsRecordRefresher;
|
|
15871
16083
|
this.networkWorkerPool = new AsyncWorkerPool(this.concurrency);
|
|
15872
16084
|
this.useBatchGQL = ldsPrimingGraphqlBatch.isOpen({ fallback: false });
|
|
16085
|
+
if (this.useBatchGQL) {
|
|
16086
|
+
this.batchSize = this.batchSize / DEFAULT_GQL_QUERY_BATCH_SIZE;
|
|
16087
|
+
}
|
|
15873
16088
|
this.conflictPool = new ConflictPool(config.store, this.objectInfoLoader);
|
|
15874
16089
|
}
|
|
15875
16090
|
// function that enqueues priming work
|
|
@@ -16731,4 +16946,4 @@ register({
|
|
|
16731
16946
|
});
|
|
16732
16947
|
|
|
16733
16948
|
export { getRuntime, registerReportObserver, reportGraphqlQueryParseError };
|
|
16734
|
-
// version: 1.
|
|
16949
|
+
// version: 1.229.0-dev10-bc9ef2513
|