@wundergraph/composition 0.59.0 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/ast/utils.d.ts +2 -0
  2. package/dist/ast/utils.js +4 -0
  3. package/dist/ast/utils.js.map +1 -1
  4. package/dist/directive-definition-data/directive-definition-data.d.ts +3 -0
  5. package/dist/directive-definition-data/directive-definition-data.js +82 -1
  6. package/dist/directive-definition-data/directive-definition-data.js.map +1 -1
  7. package/dist/errors/errors.d.ts +10 -3
  8. package/dist/errors/errors.js +82 -10
  9. package/dist/errors/errors.js.map +1 -1
  10. package/dist/errors/types/params.d.ts +4 -0
  11. package/dist/router-configuration/types.d.ts +26 -0
  12. package/dist/router-configuration/utils.d.ts +2 -1
  13. package/dist/router-configuration/utils.js +11 -0
  14. package/dist/router-configuration/utils.js.map +1 -1
  15. package/dist/schema-building/types/types.d.ts +1 -0
  16. package/dist/schema-building/utils.js +9 -10
  17. package/dist/schema-building/utils.js.map +1 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/dist/utils/composition-version.js +1 -1
  20. package/dist/utils/string-constants.d.ts +9 -0
  21. package/dist/utils/string-constants.js +17 -4
  22. package/dist/utils/string-constants.js.map +1 -1
  23. package/dist/v1/constants/constants.js +3 -0
  24. package/dist/v1/constants/constants.js.map +1 -1
  25. package/dist/v1/constants/directive-definitions.d.ts +3 -0
  26. package/dist/v1/constants/directive-definitions.js +60 -1
  27. package/dist/v1/constants/directive-definitions.js.map +1 -1
  28. package/dist/v1/constants/type-nodes.d.ts +3 -1
  29. package/dist/v1/constants/type-nodes.js +6 -1
  30. package/dist/v1/constants/type-nodes.js.map +1 -1
  31. package/dist/v1/federation/federation-factory.d.ts +2 -2
  32. package/dist/v1/federation/federation-factory.js +62 -55
  33. package/dist/v1/federation/federation-factory.js.map +1 -1
  34. package/dist/v1/normalization/normalization-factory.d.ts +11 -6
  35. package/dist/v1/normalization/normalization-factory.js +275 -28
  36. package/dist/v1/normalization/normalization-factory.js.map +1 -1
  37. package/dist/v1/normalization/types/params.d.ts +8 -1
  38. package/dist/v1/normalization/types/results.d.ts +5 -0
  39. package/dist/v1/normalization/types/types.d.ts +74 -6
  40. package/dist/v1/normalization/utils.js +9 -1
  41. package/dist/v1/normalization/utils.js.map +1 -1
  42. package/dist/v1/utils/utils.d.ts +2 -1
  43. package/dist/v1/utils/utils.js +7 -0
  44. package/dist/v1/utils/utils.js.map +1 -1
  45. package/dist/v1/warnings/params.d.ts +6 -0
  46. package/dist/v1/warnings/warnings.d.ts +2 -1
  47. package/dist/v1/warnings/warnings.js +12 -0
  48. package/dist/v1/warnings/warnings.js.map +1 -1
  49. package/package.json +2 -2
@@ -60,6 +60,11 @@ class NormalizationFactory {
60
60
  directiveDefinitionDataByName = (0, utils_2.initializeDirectiveDefinitionDatas)();
61
61
  doesParentRequireFetchReasons = false;
62
62
  edfsDirectiveReferences = new Set();
63
+ /* Cached entity configs keyed by type name, populated by extractEntityCacheDirective() from
64
+ * @openfed__entityCache. Future caching directives (@openfed__queryCache etc.) use this as a lookup
65
+ * to verify a field's return type is a cached entity.
66
+ */
67
+ entityCacheConfigByTypeName = new Map();
63
68
  errors = new Array();
64
69
  entityDataByTypeName = new Map();
65
70
  entityInterfaceDataByTypeName = new Map();
@@ -888,13 +893,11 @@ class NormalizationFactory {
888
893
  this.updateCompositeOutputDataByNode(node, parentData, extensionType);
889
894
  if (!directivesByName.has(string_constants_1.INTERFACE_OBJECT)) {
890
895
  this.addConcreteTypeNamesForImplementedInterfaces(parentData.implementedInterfaceTypeNames, typeName);
896
+ this.extractEntityCacheDirective(parentData);
891
897
  }
892
898
  return;
893
899
  }
894
900
  const implementedInterfaceTypeNames = this.extractImplementedInterfaceTypeNames(node, new Set());
895
- if (!directivesByName.has(string_constants_1.INTERFACE_OBJECT)) {
896
- this.addConcreteTypeNamesForImplementedInterfaces(implementedInterfaceTypeNames, typeName);
897
- }
898
901
  const newParentData = {
899
902
  configureDescriptionDataBySubgraphName: new Map(),
900
903
  directivesByName: directivesByName,
@@ -913,6 +916,10 @@ class NormalizationFactory {
913
916
  subgraphNames: new Set([this.subgraphName]),
914
917
  description: (0, utils_1.formatDescription)('description' in node ? node.description : undefined),
915
918
  };
919
+ if (!directivesByName.has(string_constants_1.INTERFACE_OBJECT)) {
920
+ this.addConcreteTypeNamesForImplementedInterfaces(implementedInterfaceTypeNames, typeName);
921
+ this.extractEntityCacheDirective(newParentData);
922
+ }
916
923
  this.extractConfigureDescriptionsData(newParentData);
917
924
  this.parentDefinitionDataByTypeName.set(typeName, newParentData);
918
925
  }
@@ -1146,40 +1153,57 @@ class NormalizationFactory {
1146
1153
  }
1147
1154
  }
1148
1155
  }
1149
- getFieldSetParent(isProvides, parentData, fieldName, parentTypeName) {
1156
+ getFieldSetParent({ isProvides, parentData, fieldName, fieldSet, parentTypeName, }) {
1150
1157
  if (!isProvides) {
1151
- return { fieldSetParentData: parentData };
1158
+ return {
1159
+ data: parentData,
1160
+ success: true,
1161
+ };
1152
1162
  }
1153
1163
  const fieldData = (0, utils_5.getOrThrowError)(parentData.fieldDataByName, fieldName, `${parentTypeName}.fieldDataByFieldName`);
1154
1164
  const fieldNamedTypeName = (0, ast_1.getTypeNodeNamedTypeName)(fieldData.node.type);
1155
1165
  const fieldCoords = `${parentTypeName}.${fieldName}`;
1156
1166
  if (constants_1.BASE_SCALARS.has(fieldNamedTypeName)) {
1157
1167
  return {
1158
- errorString: (0, errors_1.incompatibleTypeWithProvidesErrorMessage)({
1168
+ error: (0, errors_1.incompatibleTypeWithProvidesError)({
1159
1169
  fieldCoords,
1160
1170
  responseType: fieldNamedTypeName,
1161
1171
  subgraphName: this.subgraphName,
1162
1172
  }),
1173
+ success: false,
1163
1174
  };
1164
1175
  }
1165
1176
  const namedTypeData = this.parentDefinitionDataByTypeName.get(fieldNamedTypeName);
1166
1177
  // This error should never happen
1167
1178
  if (!namedTypeData) {
1168
1179
  return {
1169
- errorString: (0, errors_1.unknownNamedTypeErrorMessage)(fieldCoords, fieldNamedTypeName),
1180
+ error: (0, errors_1.unknownNamedTypeError)(fieldCoords, fieldNamedTypeName),
1181
+ success: false,
1170
1182
  };
1171
1183
  }
1172
1184
  // @TODO handle abstract types and fragments
1173
- if (namedTypeData.kind !== graphql_1.Kind.INTERFACE_TYPE_DEFINITION && namedTypeData.kind !== graphql_1.Kind.OBJECT_TYPE_DEFINITION) {
1174
- return {
1175
- errorString: (0, errors_1.incompatibleTypeWithProvidesErrorMessage)({
1185
+ if ((0, utils_1.isValidProvidesParentData)(namedTypeData)) {
1186
+ if ((0, utils_3.isUnionDefinitionData)(namedTypeData)) {
1187
+ this.warnings.push((0, warnings_1.providesOnUnionWarning)({
1176
1188
  fieldCoords,
1177
- responseType: fieldNamedTypeName,
1189
+ fieldSet,
1190
+ namedTypeName: fieldNamedTypeName,
1178
1191
  subgraphName: this.subgraphName,
1179
- }),
1192
+ }));
1193
+ }
1194
+ return {
1195
+ data: namedTypeData,
1196
+ success: true,
1180
1197
  };
1181
1198
  }
1182
- return { fieldSetParentData: namedTypeData };
1199
+ return {
1200
+ error: (0, errors_1.incompatibleTypeWithProvidesError)({
1201
+ fieldCoords,
1202
+ responseType: fieldNamedTypeName,
1203
+ subgraphName: this.subgraphName,
1204
+ }),
1205
+ success: false,
1206
+ };
1183
1207
  }
1184
1208
  #handleNonExternalConditionalField({ currentFieldCoords, directiveCoords, directiveName, fieldSet, }) {
1185
1209
  if (this.isSubgraphVersionTwo) {
@@ -1357,10 +1381,6 @@ class NormalizationFactory {
1357
1381
  shouldDefineSelectionSet = true;
1358
1382
  return;
1359
1383
  }
1360
- if (!(0, utils_1.isKindAbstract)(parentData.kind)) {
1361
- errorMessages.push((0, errors_1.invalidInlineFragmentTypeErrorMessage)(fieldSet, fieldCoordsPath, typeConditionName, parentTypeName));
1362
- return graphql_1.BREAK;
1363
- }
1364
1384
  const fragmentNamedTypeData = nf.parentDefinitionDataByTypeName.get(typeConditionName);
1365
1385
  if (!fragmentNamedTypeData) {
1366
1386
  errorMessages.push((0, errors_1.unknownInlineFragmentTypeConditionErrorMessage)(fieldSet, fieldCoordsPath, parentTypeName, typeConditionName));
@@ -1369,13 +1389,31 @@ class NormalizationFactory {
1369
1389
  shouldDefineSelectionSet = true;
1370
1390
  switch (fragmentNamedTypeData.kind) {
1371
1391
  case graphql_1.Kind.INTERFACE_TYPE_DEFINITION: {
1372
- if (!fragmentNamedTypeData.implementedInterfaceTypeNames.has(parentTypeName)) {
1392
+ // The enclosing type is an Object
1393
+ if (!(0, utils_1.isKindAbstract)(parentData.kind)) {
1394
+ if (!(0, utils_3.isObjectDefinitionData)(parentData) ||
1395
+ !parentData.implementedInterfaceTypeNames.has(typeConditionName)) {
1396
+ break;
1397
+ }
1398
+ parentDatas.push(fragmentNamedTypeData);
1399
+ return;
1400
+ }
1401
+ // The enclosing type is an Interface or Union
1402
+ const concreteTypeNames = nf.concreteTypeNamesByAbstractTypeName.get(typeConditionName);
1403
+ const parentConcreteTypeNames = nf.concreteTypeNamesByAbstractTypeName.get(parentData.name);
1404
+ if (!concreteTypeNames ||
1405
+ !parentConcreteTypeNames ||
1406
+ parentConcreteTypeNames.isDisjointFrom(concreteTypeNames)) {
1373
1407
  break;
1374
1408
  }
1375
1409
  parentDatas.push(fragmentNamedTypeData);
1376
1410
  return;
1377
1411
  }
1378
1412
  case graphql_1.Kind.OBJECT_TYPE_DEFINITION: {
1413
+ if (!(0, utils_1.isKindAbstract)(parentData.kind)) {
1414
+ errorMessages.push((0, errors_1.invalidInlineFragmentTypeErrorMessage)(fieldSet, fieldCoordsPath, typeConditionName, parentTypeName));
1415
+ return graphql_1.BREAK;
1416
+ }
1379
1417
  const concreteTypeNames = nf.concreteTypeNamesByAbstractTypeName.get(parentTypeName);
1380
1418
  if (!concreteTypeNames || !concreteTypeNames.has(typeConditionName)) {
1381
1419
  break;
@@ -1384,6 +1422,24 @@ class NormalizationFactory {
1384
1422
  return;
1385
1423
  }
1386
1424
  case graphql_1.Kind.UNION_TYPE_DEFINITION: {
1425
+ const concreteTypeNames = nf.concreteTypeNamesByAbstractTypeName.get(typeConditionName);
1426
+ if (!concreteTypeNames) {
1427
+ break;
1428
+ }
1429
+ // The enclosing type is an Object
1430
+ if (!(0, utils_1.isKindAbstract)(parentData.kind)) {
1431
+ // The enclosing Object is a valid part of the Union
1432
+ if (concreteTypeNames.has(parentTypeName)) {
1433
+ parentDatas.push(fragmentNamedTypeData);
1434
+ return;
1435
+ }
1436
+ break;
1437
+ }
1438
+ // The enclosing type is an Interface or Union; fetch its implementations/members respectively.
1439
+ const parentConcreteTypeNames = nf.concreteTypeNamesByAbstractTypeName.get(parentTypeName);
1440
+ if (!parentConcreteTypeNames || parentConcreteTypeNames.isDisjointFrom(concreteTypeNames)) {
1441
+ break;
1442
+ }
1387
1443
  parentDatas.push(fragmentNamedTypeData);
1388
1444
  return;
1389
1445
  }
@@ -1392,7 +1448,7 @@ class NormalizationFactory {
1392
1448
  return graphql_1.BREAK;
1393
1449
  }
1394
1450
  }
1395
- errorMessages.push((0, errors_1.invalidInlineFragmentTypeConditionErrorMessage)(fieldSet, fieldCoordsPath, typeConditionName, (0, utils_5.kindToNodeType)(parentData.kind), parentTypeName));
1451
+ errorMessages.push((0, errors_1.invalidInlineFragmentTypeConditionErrorMessage)(fieldSet, fieldCoordsPath, typeConditionName, (0, utils_5.kindToNodeType)(parentData.kind), parentTypeName, (0, utils_5.kindToNodeType)(fragmentNamedTypeData.kind)));
1396
1452
  return graphql_1.BREAK;
1397
1453
  },
1398
1454
  },
@@ -1459,16 +1515,19 @@ class NormalizationFactory {
1459
1515
  Consequently, at that time, it is unknown whether the named type is an entity.
1460
1516
  If it isn't, the @provides directive does not make sense and can be ignored.
1461
1517
  */
1462
- const { fieldSetParentData, errorString } = this.getFieldSetParent(isProvides, parentData, fieldName, parentTypeName);
1463
- const fieldCoords = `${parentTypeName}.${fieldName}`;
1464
- if (errorString) {
1465
- allErrorMessages.push(errorString);
1466
- continue;
1467
- }
1468
- if (!fieldSetParentData) {
1518
+ const result = this.getFieldSetParent({
1519
+ fieldName,
1520
+ fieldSet,
1521
+ isProvides,
1522
+ parentData,
1523
+ parentTypeName,
1524
+ });
1525
+ if (!result.success) {
1526
+ allErrorMessages.push(result.error.message);
1469
1527
  continue;
1470
1528
  }
1471
- const { errorMessages, configuration } = this.validateConditionalFieldSet(fieldSetParentData, fieldSet, fieldName, isProvides, parentTypeName);
1529
+ const fieldCoords = `${parentTypeName}.${fieldName}`;
1530
+ const { errorMessages, configuration } = this.validateConditionalFieldSet(result.data, fieldSet, fieldName, isProvides, parentTypeName);
1472
1531
  /*
1473
1532
  * It is possible to return no error messages nor configuration if the @provides or @requires directive is
1474
1533
  * considered completely redundant, i.e.,:
@@ -2956,6 +3015,179 @@ class NormalizationFactory {
2956
3015
  }
2957
3016
  }
2958
3017
  }
3018
+ extractEntityCacheDirective({ directivesByName, name: typeName }) {
3019
+ const entityCacheDirectives = directivesByName.get(string_constants_1.OPENFED_ENTITY_CACHE);
3020
+ if (!entityCacheDirectives || entityCacheDirectives.length === 0) {
3021
+ return;
3022
+ }
3023
+ if (!this.keyFieldSetDatasByTypeName.has(typeName)) {
3024
+ this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_ENTITY_CACHE, typeName, string_constants_1.FIRST_ORDINAL, [
3025
+ (0, errors_1.entityCacheWithoutKeyErrorMessage)(typeName),
3026
+ ]));
3027
+ return;
3028
+ }
3029
+ /* validateDirectives() (run earlier in normalize()) has already guaranteed each argument's type —
3030
+ * Int for maxAge/negativeCacheTTL, Boolean for the flags — so the generic ConstDirectiveNode is
3031
+ * narrowed once to the precise typed node, mirroring RequestScopedDirectiveNode/ComposeDirectiveNode.
3032
+ * Optional arguments may be absent (definition defaults are not materialized onto the usage AST),
3033
+ * so the config starts at the directive's documented defaults and each present argument overrides it.
3034
+ */
3035
+ const directive = entityCacheDirectives[0];
3036
+ if (this.entityCacheConfigByTypeName.has(typeName)) {
3037
+ // The error would be caught in directive validation as an invalid repeated directive use.
3038
+ return;
3039
+ }
3040
+ const config = {
3041
+ typeName,
3042
+ maxAgeSeconds: 0,
3043
+ notFoundCacheTtlSeconds: 0,
3044
+ includeHeaders: false,
3045
+ partialCacheLoad: false,
3046
+ shadowMode: false,
3047
+ };
3048
+ for (const arg of directive.arguments) {
3049
+ if (!arg) {
3050
+ continue;
3051
+ }
3052
+ const { name, value } = arg;
3053
+ switch (value.kind) {
3054
+ case graphql_1.Kind.INT: {
3055
+ switch (name.value) {
3056
+ case string_constants_1.MAX_AGE: {
3057
+ config.maxAgeSeconds = parseInt(value.value, 10);
3058
+ break;
3059
+ }
3060
+ case string_constants_1.NEGATIVE_CACHE_TTL: {
3061
+ config.notFoundCacheTtlSeconds = parseInt(value.value, 10);
3062
+ break;
3063
+ }
3064
+ }
3065
+ break;
3066
+ }
3067
+ default: {
3068
+ switch (name.value) {
3069
+ case string_constants_1.INCLUDE_HEADERS: {
3070
+ config.includeHeaders = value.value;
3071
+ break;
3072
+ }
3073
+ case string_constants_1.PARTIAL_CACHE_LOAD: {
3074
+ config.partialCacheLoad = value.value;
3075
+ break;
3076
+ }
3077
+ case string_constants_1.SHADOW_MODE: {
3078
+ config.shadowMode = value.value;
3079
+ break;
3080
+ }
3081
+ }
3082
+ break;
3083
+ }
3084
+ }
3085
+ }
3086
+ const entityCacheErrors = [];
3087
+ if (config.maxAgeSeconds <= 0) {
3088
+ entityCacheErrors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_ENTITY_CACHE, typeName, string_constants_1.FIRST_ORDINAL, [
3089
+ (0, errors_1.maxAgeNotPositiveIntegerErrorMessage)(config.maxAgeSeconds),
3090
+ ]));
3091
+ }
3092
+ if (config.notFoundCacheTtlSeconds < 0) {
3093
+ entityCacheErrors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_ENTITY_CACHE, typeName, string_constants_1.FIRST_ORDINAL, [
3094
+ (0, errors_1.negativeCacheTTLNotNonNegativeIntegerErrorMessage)(config.notFoundCacheTtlSeconds),
3095
+ ]));
3096
+ }
3097
+ if (entityCacheErrors.length > 0) {
3098
+ this.errors.push(...entityCacheErrors);
3099
+ return;
3100
+ }
3101
+ this.entityCacheConfigByTypeName.set(typeName, config);
3102
+ const configurationData = (0, utils_5.getValueOrDefault)(this.configurationDataByTypeName, typeName, () => (0, utils_6.newConfigurationData)(true, typeName));
3103
+ (0, utils_6.getOrInitializeEntityCaching)(configurationData).entityCacheConfigurations.push(config);
3104
+ }
3105
+ // Returns true if the directive is defined and false otherwise
3106
+ extractCacheInvalidateConfig(fieldData) {
3107
+ if (!fieldData.directivesByName.has(string_constants_1.OPENFED_CACHE_INVALIDATE)) {
3108
+ return false;
3109
+ }
3110
+ const operationType = this.getOperationTypeNodeForRootTypeName(fieldData.originalParentTypeName);
3111
+ const fieldCoords = `${fieldData.originalParentTypeName}.${fieldData.name}`;
3112
+ if (!operationType || operationType === graphql_1.OperationTypeNode.QUERY) {
3113
+ this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_CACHE_INVALIDATE, fieldCoords, string_constants_1.FIRST_ORDINAL, [
3114
+ (0, errors_1.invalidMutationOrSubscriptionFieldCoordsErrorMessage)(fieldCoords),
3115
+ ]));
3116
+ return true;
3117
+ }
3118
+ const returnTypeName = (0, ast_1.getTypeNodeNamedTypeName)(fieldData.node.type);
3119
+ if (!this.entityCacheConfigByTypeName.has(returnTypeName)) {
3120
+ this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_CACHE_INVALIDATE, fieldCoords, string_constants_1.FIRST_ORDINAL, [
3121
+ (0, errors_1.invalidEntityReturnTypeErrorMessage)({ fieldCoords, returnTypeName: returnTypeName }),
3122
+ ]));
3123
+ return true;
3124
+ }
3125
+ const configurationData = (0, utils_5.getValueOrDefault)(this.configurationDataByTypeName, fieldData.renamedParentTypeName, () => (0, utils_6.newConfigurationData)(false, fieldData.renamedParentTypeName));
3126
+ const config = {
3127
+ entityTypeName: returnTypeName,
3128
+ fieldName: fieldData.name,
3129
+ operationType: operationType,
3130
+ };
3131
+ (0, utils_6.getOrInitializeEntityCaching)(configurationData).cacheInvalidateConfigurations.push(config);
3132
+ return true;
3133
+ }
3134
+ /* Extracts @openfed__cachePopulate from Mutation/Subscription fields.
3135
+ * The return type must be a cached entity (@key & @openfed__entityCache).
3136
+ * Argument `maxAge` is optional; if absent, the router falls back to the entity's @openfed__entityCache TTL;
3137
+ * if provided, must be a positive integer.
3138
+ *
3139
+ * Returns true if the directive is defined and false otherwise
3140
+ */
3141
+ extractCachePopulateConfig(fieldData) {
3142
+ if (!fieldData.directivesByName.has(string_constants_1.OPENFED_CACHE_POPULATE)) {
3143
+ return false;
3144
+ }
3145
+ const fieldCoords = `${fieldData.originalParentTypeName}.${fieldData.name}`;
3146
+ const operationType = this.getOperationTypeNodeForRootTypeName(fieldData.originalParentTypeName);
3147
+ if (!operationType || operationType === graphql_1.OperationTypeNode.QUERY) {
3148
+ this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_CACHE_POPULATE, fieldCoords, string_constants_1.FIRST_ORDINAL, [
3149
+ (0, errors_1.invalidMutationOrSubscriptionFieldCoordsErrorMessage)(fieldCoords),
3150
+ ]));
3151
+ return true;
3152
+ }
3153
+ const returnTypeName = (0, ast_1.getTypeNodeNamedTypeName)(fieldData.node.type);
3154
+ const entityCacheConfig = this.entityCacheConfigByTypeName.get(returnTypeName);
3155
+ if (!entityCacheConfig) {
3156
+ this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_CACHE_POPULATE, fieldCoords, string_constants_1.FIRST_ORDINAL, [
3157
+ (0, errors_1.invalidEntityReturnTypeErrorMessage)({ fieldCoords, returnTypeName }),
3158
+ ]));
3159
+ return true;
3160
+ }
3161
+ /* validateDirectives() has already guaranteed maxAge is an Int when present, so the generic
3162
+ * ConstDirectiveNode is narrowed once to the precise typed node — mirroring the other caching
3163
+ * directives. maxAge is the only argument and is optional.
3164
+ */
3165
+ const cachePopulateDirective = fieldData.directivesByName.get(string_constants_1.OPENFED_CACHE_POPULATE)[0];
3166
+ const config = {
3167
+ entityTypeName: returnTypeName,
3168
+ fieldName: fieldData.name,
3169
+ operationType,
3170
+ maxAgeSeconds: entityCacheConfig.maxAgeSeconds, // Set fallback immediately; overridden where appropriate.
3171
+ };
3172
+ const maxAgeArgument = cachePopulateDirective.arguments[0];
3173
+ if (!maxAgeArgument) {
3174
+ const configurationData = (0, utils_5.getValueOrDefault)(this.configurationDataByTypeName, fieldData.renamedParentTypeName, () => (0, utils_6.newConfigurationData)(false, fieldData.renamedParentTypeName));
3175
+ (0, utils_6.getOrInitializeEntityCaching)(configurationData).cachePopulateConfigurations.push(config);
3176
+ return true;
3177
+ }
3178
+ config.maxAgeSeconds = parseInt(maxAgeArgument.value.value, 10);
3179
+ // This syntax handles possible NaN.
3180
+ if (isNaN(config.maxAgeSeconds) || config.maxAgeSeconds <= 0) {
3181
+ this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_CACHE_POPULATE, fieldCoords, string_constants_1.FIRST_ORDINAL, [
3182
+ // If null is explicitly provided in GraphQL the value in JS is undefined.
3183
+ (0, errors_1.maxAgeNotPositiveIntegerErrorMessage)(maxAgeArgument.value.value ?? null),
3184
+ ]));
3185
+ return true;
3186
+ }
3187
+ const configurationData = (0, utils_5.getValueOrDefault)(this.configurationDataByTypeName, fieldData.renamedParentTypeName, () => (0, utils_6.newConfigurationData)(false, fieldData.renamedParentTypeName));
3188
+ (0, utils_6.getOrInitializeEntityCaching)(configurationData).cachePopulateConfigurations.push(config);
3189
+ return true;
3190
+ }
2959
3191
  addFieldNamesToConfigurationData(fieldDataByFieldName, configurationData) {
2960
3192
  const externalFieldNames = new Set();
2961
3193
  for (const [fieldName, fieldData] of fieldDataByFieldName) {
@@ -3013,6 +3245,18 @@ class NormalizationFactory {
3013
3245
  }
3014
3246
  definitions.push(...dependencies);
3015
3247
  }
3248
+ // Only Object fields should be passed to this method
3249
+ handleFieldCacheDirectives(data) {
3250
+ /* A field can't both evict (@openfed__cacheInvalidate) and write (@openfed__cachePopulate)
3251
+ * the cache for the same entity, so the two directives are mutually exclusive.
3252
+ */
3253
+ const definesInvalidate = this.extractCacheInvalidateConfig(data);
3254
+ const definesPopulate = this.extractCachePopulateConfig(data);
3255
+ if (definesInvalidate && definesPopulate) {
3256
+ const fieldCoords = `${data.originalParentTypeName}.${data.name}`;
3257
+ this.errors.push((0, errors_1.invalidMutuallyExclusiveCacheDirectivesError)(fieldCoords));
3258
+ }
3259
+ }
3016
3260
  normalize(document) {
3017
3261
  // Collect any renamed root types
3018
3262
  (0, walkers_1.upsertDirectiveSchemaAndEntityDefinitions)(this, document);
@@ -3119,7 +3363,10 @@ class NormalizationFactory {
3119
3363
  }
3120
3364
  const externalInterfaceFieldNames = [];
3121
3365
  for (const [fieldName, fieldData] of parentData.fieldDataByName) {
3122
- if (!isObject && fieldData.externalFieldDataBySubgraphName.get(this.subgraphName)?.isDefinedExternal) {
3366
+ if (isObject) {
3367
+ this.handleFieldCacheDirectives(fieldData);
3368
+ }
3369
+ else if (fieldData.externalFieldDataBySubgraphName.get(this.subgraphName)?.isDefinedExternal) {
3123
3370
  externalInterfaceFieldNames.push(fieldName);
3124
3371
  }
3125
3372
  // Arguments can only be fully validated once all parents types are known