@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.
- package/dist/main.js +416 -231
- package/package.json +18 -18
- 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
|
-
|
|
4264
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
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
|
-
|
|
5985
|
-
|
|
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 ===
|
|
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
|
|
8082
|
-
const
|
|
8083
|
-
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
|
+
};
|
|
8084
8182
|
}
|
|
8085
|
-
// finds
|
|
8086
|
-
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) {
|
|
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 ===
|
|
8101
|
-
arg.value.value ===
|
|
8102
|
-
(!
|
|
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 ===
|
|
8118
|
-
//
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
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])) {
|
|
8123
8223
|
recordQueryPath = {
|
|
8124
|
-
node: ancestors[
|
|
8125
|
-
parentIndex
|
|
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}
|
|
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 ===
|
|
8210
|
-
valueNode.value ===
|
|
8211
|
-
valueNode.value ===
|
|
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,
|
|
8269
|
-
isRelationship(node,
|
|
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
|
-
|
|
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:
|
|
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']; '
|
|
9366
|
-
const
|
|
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
|
|
9381
|
-
if (!
|
|
9504
|
+
const { connection: recordConnectionNode, path: ancesterPath } = findNearestConnectionWithPath(ancestors);
|
|
9505
|
+
if (!recordConnectionNode || !ancesterPath)
|
|
9382
9506
|
return;
|
|
9383
|
-
if (!objectNodeInfoTree[
|
|
9384
|
-
objectNodeInfoTree[
|
|
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(
|
|
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(
|
|
9395
|
-
startNodes.add(
|
|
9518
|
+
if (isRecordQuery(recordConnectionNode)) {
|
|
9519
|
+
startNodes.add(recordConnectionNode.name.value);
|
|
9396
9520
|
}
|
|
9397
|
-
growObjectFieldTree(objectNodeInfoTree,
|
|
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
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
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 =
|
|
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,
|
|
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 =
|
|
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 =
|
|
9461
|
-
?
|
|
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,
|
|
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,
|
|
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 =
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
9561
|
-
?
|
|
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 =
|
|
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,
|
|
9665
|
-
const apiName =
|
|
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(
|
|
9883
|
+
totalNodes.add(childNodepath);
|
|
9760
9884
|
}
|
|
9761
9885
|
// recursively go to deeper level of filter.
|
|
9762
|
-
growObjectFieldTree(tree,
|
|
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 ===
|
|
9798
|
-
relationType ===
|
|
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
|
-
|
|
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 ===
|
|
9840
|
-
relationType ===
|
|
9964
|
+
relation: relationType === PARENT_RELATIONSHIP ||
|
|
9965
|
+
relationType === POLYMORPHIC_PARENT_RELATIONSHIP
|
|
9841
9966
|
? 'parent'
|
|
9842
9967
|
: 'child',
|
|
9843
9968
|
name: conditionName,
|
|
9844
9969
|
});
|
|
9845
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
10006
|
+
pathToObjectApiNamesMap[startNode] = [startNode];
|
|
9882
10007
|
const children = objectInfotree[startNode];
|
|
9883
|
-
const subObjectInfoMap = await fetchObjectInfos(objectInfotree,
|
|
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,
|
|
9892
|
-
const objectInfoApiNames =
|
|
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
|
|
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}
|
|
9912
|
-
|
|
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}
|
|
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[
|
|
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[
|
|
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[
|
|
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 (!
|
|
9955
|
-
|
|
10079
|
+
if (!pathToObjectApiNamesMap[path]) {
|
|
10080
|
+
pathToObjectApiNamesMap[path] = [];
|
|
9956
10081
|
}
|
|
9957
10082
|
// 'ServiceAppointment_Owner' ->['User', 'Group']
|
|
9958
|
-
if (!
|
|
9959
|
-
|
|
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 (!
|
|
9970
|
-
|
|
10094
|
+
if (!pathToObjectApiNamesMap[path]) {
|
|
10095
|
+
pathToObjectApiNamesMap[path] = [];
|
|
9971
10096
|
}
|
|
9972
|
-
if (!
|
|
9973
|
-
|
|
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 (!
|
|
9985
|
-
|
|
10109
|
+
if (!pathToObjectApiNamesMap[path]) {
|
|
10110
|
+
pathToObjectApiNamesMap[path] = [];
|
|
9986
10111
|
}
|
|
9987
|
-
if (!
|
|
9988
|
-
|
|
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
|
|
10011
|
-
const
|
|
10135
|
+
const path = `${parentPath}#${field}`;
|
|
10136
|
+
const subLevelFields = objectInfotree[path];
|
|
10012
10137
|
if (subLevelFields && subLevelFields.length > 0) {
|
|
10013
|
-
const subObjectInfos = await fetchObjectInfos(objectInfotree,
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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(
|
|
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,
|
|
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,
|
|
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 =
|
|
10079
|
-
// 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
|
|
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 ${
|
|
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 = `${
|
|
10114
|
-
if (
|
|
10240
|
+
curPath = `${parentPath}#${fieldName}`;
|
|
10241
|
+
if (pathToObjectApiNamesMap[curPath] &&
|
|
10242
|
+
pathToObjectApiNamesMap[curPath].length > 0) {
|
|
10115
10243
|
isSpanning = true;
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
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 =
|
|
10175
|
-
?
|
|
10176
|
-
:
|
|
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
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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 =
|
|
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
|
|
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,
|
|
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,
|
|
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 (
|
|
10542
|
-
|
|
10703
|
+
if (pathToObjectApiNamesMap &&
|
|
10704
|
+
pathToObjectApiNamesMap[grandParentPath] &&
|
|
10543
10705
|
objectInfos &&
|
|
10544
|
-
objectInfos[
|
|
10545
|
-
const grandParentObjectInfo = objectInfos[
|
|
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:
|
|
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:
|
|
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
|
|
11628
|
-
if (
|
|
11629
|
-
|
|
11791
|
+
if (dataType === 'Reference' && relationshipName !== null) {
|
|
11792
|
+
if (draftFieldValue === null) {
|
|
11793
|
+
recordFields[relationshipName] = {
|
|
11794
|
+
displayValue: null,
|
|
11795
|
+
value: null,
|
|
11796
|
+
};
|
|
11630
11797
|
}
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
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
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11653
|
-
|
|
11654
|
-
|
|
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
|
-
|
|
11948
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
16949
|
+
// version: 1.229.0-dev10-bc9ef2513
|