@wundergraph/composition 0.59.0 → 0.62.2
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/directive-definition-data/directive-definition-data.d.ts +3 -0
- package/dist/directive-definition-data/directive-definition-data.js +82 -1
- package/dist/directive-definition-data/directive-definition-data.js.map +1 -1
- package/dist/errors/errors.d.ts +8 -1
- package/dist/errors/errors.js +32 -0
- package/dist/errors/errors.js.map +1 -1
- package/dist/errors/types/params.d.ts +4 -0
- package/dist/router-configuration/types.d.ts +26 -0
- package/dist/router-configuration/utils.d.ts +2 -1
- package/dist/router-configuration/utils.js +11 -0
- package/dist/router-configuration/utils.js.map +1 -1
- package/dist/schema-building/utils.js +9 -10
- package/dist/schema-building/utils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/composition-version.js +1 -1
- package/dist/utils/string-constants.d.ts +8 -0
- package/dist/utils/string-constants.js +12 -4
- package/dist/utils/string-constants.js.map +1 -1
- package/dist/v1/constants/constants.js +3 -0
- package/dist/v1/constants/constants.js.map +1 -1
- package/dist/v1/constants/directive-definitions.d.ts +3 -0
- package/dist/v1/constants/directive-definitions.js +60 -1
- package/dist/v1/constants/directive-definitions.js.map +1 -1
- package/dist/v1/constants/type-nodes.d.ts +3 -1
- package/dist/v1/constants/type-nodes.js +6 -1
- package/dist/v1/constants/type-nodes.js.map +1 -1
- package/dist/v1/federation/federation-factory.d.ts +2 -2
- package/dist/v1/federation/federation-factory.js +62 -55
- package/dist/v1/federation/federation-factory.js.map +1 -1
- package/dist/v1/normalization/normalization-factory.d.ts +7 -2
- package/dist/v1/normalization/normalization-factory.js +199 -4
- package/dist/v1/normalization/normalization-factory.js.map +1 -1
- package/dist/v1/normalization/types/types.d.ts +73 -1
- package/dist/v1/normalization/utils.js +9 -1
- package/dist/v1/normalization/utils.js.map +1 -1
- 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
|
}
|
|
@@ -2956,6 +2963,179 @@ class NormalizationFactory {
|
|
|
2956
2963
|
}
|
|
2957
2964
|
}
|
|
2958
2965
|
}
|
|
2966
|
+
extractEntityCacheDirective({ directivesByName, name: typeName }) {
|
|
2967
|
+
const entityCacheDirectives = directivesByName.get(string_constants_1.OPENFED_ENTITY_CACHE);
|
|
2968
|
+
if (!entityCacheDirectives || entityCacheDirectives.length === 0) {
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2971
|
+
if (!this.keyFieldSetDatasByTypeName.has(typeName)) {
|
|
2972
|
+
this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_ENTITY_CACHE, typeName, string_constants_1.FIRST_ORDINAL, [
|
|
2973
|
+
(0, errors_1.entityCacheWithoutKeyErrorMessage)(typeName),
|
|
2974
|
+
]));
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
/* validateDirectives() (run earlier in normalize()) has already guaranteed each argument's type —
|
|
2978
|
+
* Int for maxAge/negativeCacheTTL, Boolean for the flags — so the generic ConstDirectiveNode is
|
|
2979
|
+
* narrowed once to the precise typed node, mirroring RequestScopedDirectiveNode/ComposeDirectiveNode.
|
|
2980
|
+
* Optional arguments may be absent (definition defaults are not materialized onto the usage AST),
|
|
2981
|
+
* so the config starts at the directive's documented defaults and each present argument overrides it.
|
|
2982
|
+
*/
|
|
2983
|
+
const directive = entityCacheDirectives[0];
|
|
2984
|
+
if (this.entityCacheConfigByTypeName.has(typeName)) {
|
|
2985
|
+
// The error would be caught in directive validation as an invalid repeated directive use.
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
const config = {
|
|
2989
|
+
typeName,
|
|
2990
|
+
maxAgeSeconds: 0,
|
|
2991
|
+
notFoundCacheTtlSeconds: 0,
|
|
2992
|
+
includeHeaders: false,
|
|
2993
|
+
partialCacheLoad: false,
|
|
2994
|
+
shadowMode: false,
|
|
2995
|
+
};
|
|
2996
|
+
for (const arg of directive.arguments) {
|
|
2997
|
+
if (!arg) {
|
|
2998
|
+
continue;
|
|
2999
|
+
}
|
|
3000
|
+
const { name, value } = arg;
|
|
3001
|
+
switch (value.kind) {
|
|
3002
|
+
case graphql_1.Kind.INT: {
|
|
3003
|
+
switch (name.value) {
|
|
3004
|
+
case string_constants_1.MAX_AGE: {
|
|
3005
|
+
config.maxAgeSeconds = parseInt(value.value, 10);
|
|
3006
|
+
break;
|
|
3007
|
+
}
|
|
3008
|
+
case string_constants_1.NEGATIVE_CACHE_TTL: {
|
|
3009
|
+
config.notFoundCacheTtlSeconds = parseInt(value.value, 10);
|
|
3010
|
+
break;
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
break;
|
|
3014
|
+
}
|
|
3015
|
+
default: {
|
|
3016
|
+
switch (name.value) {
|
|
3017
|
+
case string_constants_1.INCLUDE_HEADERS: {
|
|
3018
|
+
config.includeHeaders = value.value;
|
|
3019
|
+
break;
|
|
3020
|
+
}
|
|
3021
|
+
case string_constants_1.PARTIAL_CACHE_LOAD: {
|
|
3022
|
+
config.partialCacheLoad = value.value;
|
|
3023
|
+
break;
|
|
3024
|
+
}
|
|
3025
|
+
case string_constants_1.SHADOW_MODE: {
|
|
3026
|
+
config.shadowMode = value.value;
|
|
3027
|
+
break;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
break;
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
const entityCacheErrors = [];
|
|
3035
|
+
if (config.maxAgeSeconds <= 0) {
|
|
3036
|
+
entityCacheErrors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_ENTITY_CACHE, typeName, string_constants_1.FIRST_ORDINAL, [
|
|
3037
|
+
(0, errors_1.maxAgeNotPositiveIntegerErrorMessage)(config.maxAgeSeconds),
|
|
3038
|
+
]));
|
|
3039
|
+
}
|
|
3040
|
+
if (config.notFoundCacheTtlSeconds < 0) {
|
|
3041
|
+
entityCacheErrors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_ENTITY_CACHE, typeName, string_constants_1.FIRST_ORDINAL, [
|
|
3042
|
+
(0, errors_1.negativeCacheTTLNotNonNegativeIntegerErrorMessage)(config.notFoundCacheTtlSeconds),
|
|
3043
|
+
]));
|
|
3044
|
+
}
|
|
3045
|
+
if (entityCacheErrors.length > 0) {
|
|
3046
|
+
this.errors.push(...entityCacheErrors);
|
|
3047
|
+
return;
|
|
3048
|
+
}
|
|
3049
|
+
this.entityCacheConfigByTypeName.set(typeName, config);
|
|
3050
|
+
const configurationData = (0, utils_5.getValueOrDefault)(this.configurationDataByTypeName, typeName, () => (0, utils_6.newConfigurationData)(true, typeName));
|
|
3051
|
+
(0, utils_6.getOrInitializeEntityCaching)(configurationData).entityCacheConfigurations.push(config);
|
|
3052
|
+
}
|
|
3053
|
+
// Returns true if the directive is defined and false otherwise
|
|
3054
|
+
extractCacheInvalidateConfig(fieldData) {
|
|
3055
|
+
if (!fieldData.directivesByName.has(string_constants_1.OPENFED_CACHE_INVALIDATE)) {
|
|
3056
|
+
return false;
|
|
3057
|
+
}
|
|
3058
|
+
const operationType = this.getOperationTypeNodeForRootTypeName(fieldData.originalParentTypeName);
|
|
3059
|
+
const fieldCoords = `${fieldData.originalParentTypeName}.${fieldData.name}`;
|
|
3060
|
+
if (!operationType || operationType === graphql_1.OperationTypeNode.QUERY) {
|
|
3061
|
+
this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_CACHE_INVALIDATE, fieldCoords, string_constants_1.FIRST_ORDINAL, [
|
|
3062
|
+
(0, errors_1.invalidMutationOrSubscriptionFieldCoordsErrorMessage)(fieldCoords),
|
|
3063
|
+
]));
|
|
3064
|
+
return true;
|
|
3065
|
+
}
|
|
3066
|
+
const returnTypeName = (0, ast_1.getTypeNodeNamedTypeName)(fieldData.node.type);
|
|
3067
|
+
if (!this.entityCacheConfigByTypeName.has(returnTypeName)) {
|
|
3068
|
+
this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_CACHE_INVALIDATE, fieldCoords, string_constants_1.FIRST_ORDINAL, [
|
|
3069
|
+
(0, errors_1.invalidEntityReturnTypeErrorMessage)({ fieldCoords, returnTypeName: returnTypeName }),
|
|
3070
|
+
]));
|
|
3071
|
+
return true;
|
|
3072
|
+
}
|
|
3073
|
+
const configurationData = (0, utils_5.getValueOrDefault)(this.configurationDataByTypeName, fieldData.renamedParentTypeName, () => (0, utils_6.newConfigurationData)(false, fieldData.renamedParentTypeName));
|
|
3074
|
+
const config = {
|
|
3075
|
+
entityTypeName: returnTypeName,
|
|
3076
|
+
fieldName: fieldData.name,
|
|
3077
|
+
operationType: operationType,
|
|
3078
|
+
};
|
|
3079
|
+
(0, utils_6.getOrInitializeEntityCaching)(configurationData).cacheInvalidateConfigurations.push(config);
|
|
3080
|
+
return true;
|
|
3081
|
+
}
|
|
3082
|
+
/* Extracts @openfed__cachePopulate from Mutation/Subscription fields.
|
|
3083
|
+
* The return type must be a cached entity (@key & @openfed__entityCache).
|
|
3084
|
+
* Argument `maxAge` is optional; if absent, the router falls back to the entity's @openfed__entityCache TTL;
|
|
3085
|
+
* if provided, must be a positive integer.
|
|
3086
|
+
*
|
|
3087
|
+
* Returns true if the directive is defined and false otherwise
|
|
3088
|
+
*/
|
|
3089
|
+
extractCachePopulateConfig(fieldData) {
|
|
3090
|
+
if (!fieldData.directivesByName.has(string_constants_1.OPENFED_CACHE_POPULATE)) {
|
|
3091
|
+
return false;
|
|
3092
|
+
}
|
|
3093
|
+
const fieldCoords = `${fieldData.originalParentTypeName}.${fieldData.name}`;
|
|
3094
|
+
const operationType = this.getOperationTypeNodeForRootTypeName(fieldData.originalParentTypeName);
|
|
3095
|
+
if (!operationType || operationType === graphql_1.OperationTypeNode.QUERY) {
|
|
3096
|
+
this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_CACHE_POPULATE, fieldCoords, string_constants_1.FIRST_ORDINAL, [
|
|
3097
|
+
(0, errors_1.invalidMutationOrSubscriptionFieldCoordsErrorMessage)(fieldCoords),
|
|
3098
|
+
]));
|
|
3099
|
+
return true;
|
|
3100
|
+
}
|
|
3101
|
+
const returnTypeName = (0, ast_1.getTypeNodeNamedTypeName)(fieldData.node.type);
|
|
3102
|
+
const entityCacheConfig = this.entityCacheConfigByTypeName.get(returnTypeName);
|
|
3103
|
+
if (!entityCacheConfig) {
|
|
3104
|
+
this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_CACHE_POPULATE, fieldCoords, string_constants_1.FIRST_ORDINAL, [
|
|
3105
|
+
(0, errors_1.invalidEntityReturnTypeErrorMessage)({ fieldCoords, returnTypeName }),
|
|
3106
|
+
]));
|
|
3107
|
+
return true;
|
|
3108
|
+
}
|
|
3109
|
+
/* validateDirectives() has already guaranteed maxAge is an Int when present, so the generic
|
|
3110
|
+
* ConstDirectiveNode is narrowed once to the precise typed node — mirroring the other caching
|
|
3111
|
+
* directives. maxAge is the only argument and is optional.
|
|
3112
|
+
*/
|
|
3113
|
+
const cachePopulateDirective = fieldData.directivesByName.get(string_constants_1.OPENFED_CACHE_POPULATE)[0];
|
|
3114
|
+
const config = {
|
|
3115
|
+
entityTypeName: returnTypeName,
|
|
3116
|
+
fieldName: fieldData.name,
|
|
3117
|
+
operationType,
|
|
3118
|
+
maxAgeSeconds: entityCacheConfig.maxAgeSeconds, // Set fallback immediately; overridden where appropriate.
|
|
3119
|
+
};
|
|
3120
|
+
const maxAgeArgument = cachePopulateDirective.arguments[0];
|
|
3121
|
+
if (!maxAgeArgument) {
|
|
3122
|
+
const configurationData = (0, utils_5.getValueOrDefault)(this.configurationDataByTypeName, fieldData.renamedParentTypeName, () => (0, utils_6.newConfigurationData)(false, fieldData.renamedParentTypeName));
|
|
3123
|
+
(0, utils_6.getOrInitializeEntityCaching)(configurationData).cachePopulateConfigurations.push(config);
|
|
3124
|
+
return true;
|
|
3125
|
+
}
|
|
3126
|
+
config.maxAgeSeconds = parseInt(maxAgeArgument.value.value, 10);
|
|
3127
|
+
// This syntax handles possible NaN.
|
|
3128
|
+
if (isNaN(config.maxAgeSeconds) || config.maxAgeSeconds <= 0) {
|
|
3129
|
+
this.errors.push((0, errors_1.invalidDirectiveError)(string_constants_1.OPENFED_CACHE_POPULATE, fieldCoords, string_constants_1.FIRST_ORDINAL, [
|
|
3130
|
+
// If null is explicitly provided in GraphQL the value in JS is undefined.
|
|
3131
|
+
(0, errors_1.maxAgeNotPositiveIntegerErrorMessage)(maxAgeArgument.value.value ?? null),
|
|
3132
|
+
]));
|
|
3133
|
+
return true;
|
|
3134
|
+
}
|
|
3135
|
+
const configurationData = (0, utils_5.getValueOrDefault)(this.configurationDataByTypeName, fieldData.renamedParentTypeName, () => (0, utils_6.newConfigurationData)(false, fieldData.renamedParentTypeName));
|
|
3136
|
+
(0, utils_6.getOrInitializeEntityCaching)(configurationData).cachePopulateConfigurations.push(config);
|
|
3137
|
+
return true;
|
|
3138
|
+
}
|
|
2959
3139
|
addFieldNamesToConfigurationData(fieldDataByFieldName, configurationData) {
|
|
2960
3140
|
const externalFieldNames = new Set();
|
|
2961
3141
|
for (const [fieldName, fieldData] of fieldDataByFieldName) {
|
|
@@ -3013,6 +3193,18 @@ class NormalizationFactory {
|
|
|
3013
3193
|
}
|
|
3014
3194
|
definitions.push(...dependencies);
|
|
3015
3195
|
}
|
|
3196
|
+
// Only Object fields should be passed to this method
|
|
3197
|
+
handleFieldCacheDirectives(data) {
|
|
3198
|
+
/* A field can't both evict (@openfed__cacheInvalidate) and write (@openfed__cachePopulate)
|
|
3199
|
+
* the cache for the same entity, so the two directives are mutually exclusive.
|
|
3200
|
+
*/
|
|
3201
|
+
const definesInvalidate = this.extractCacheInvalidateConfig(data);
|
|
3202
|
+
const definesPopulate = this.extractCachePopulateConfig(data);
|
|
3203
|
+
if (definesInvalidate && definesPopulate) {
|
|
3204
|
+
const fieldCoords = `${data.originalParentTypeName}.${data.name}`;
|
|
3205
|
+
this.errors.push((0, errors_1.invalidMutuallyExclusiveCacheDirectivesError)(fieldCoords));
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3016
3208
|
normalize(document) {
|
|
3017
3209
|
// Collect any renamed root types
|
|
3018
3210
|
(0, walkers_1.upsertDirectiveSchemaAndEntityDefinitions)(this, document);
|
|
@@ -3119,7 +3311,10 @@ class NormalizationFactory {
|
|
|
3119
3311
|
}
|
|
3120
3312
|
const externalInterfaceFieldNames = [];
|
|
3121
3313
|
for (const [fieldName, fieldData] of parentData.fieldDataByName) {
|
|
3122
|
-
if (
|
|
3314
|
+
if (isObject) {
|
|
3315
|
+
this.handleFieldCacheDirectives(fieldData);
|
|
3316
|
+
}
|
|
3317
|
+
else if (fieldData.externalFieldDataBySubgraphName.get(this.subgraphName)?.isDefinedExternal) {
|
|
3123
3318
|
externalInterfaceFieldNames.push(fieldName);
|
|
3124
3319
|
}
|
|
3125
3320
|
// Arguments can only be fully validated once all parents types are known
|