@wundergraph/composition 0.58.3 → 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 +26 -12
- package/dist/errors/errors.js +96 -29
- package/dist/errors/errors.js.map +1 -1
- package/dist/errors/types/params.d.ts +4 -0
- package/dist/federation/types/results.d.ts +6 -0
- package/dist/federation/types/results.js +3 -0
- package/dist/federation/types/results.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- 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 +9 -4
- package/dist/v1/federation/federation-factory.js +220 -98
- package/dist/v1/federation/federation-factory.js.map +1 -1
- package/dist/v1/federation/types/params.d.ts +15 -1
- package/dist/v1/normalization/normalization-factory.d.ts +7 -2
- package/dist/v1/normalization/normalization-factory.js +293 -15
- 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
|
}
|
|
@@ -1801,6 +1808,12 @@ class NormalizationFactory {
|
|
|
1801
1808
|
sizedFields: [],
|
|
1802
1809
|
requireOneSlicingArgument: true, // per IBM cost spec
|
|
1803
1810
|
};
|
|
1811
|
+
/**
|
|
1812
|
+
* For each accepted slicing argument, capture the full resolved chain:
|
|
1813
|
+
* (argument + each intermediate input field + leaf).
|
|
1814
|
+
* Later we check the assumedSize against the default value of every element of the chain.
|
|
1815
|
+
*/
|
|
1816
|
+
const slicingArgChainByPath = new Map();
|
|
1804
1817
|
for (const argumentNode of args) {
|
|
1805
1818
|
const argumentName = argumentNode.name.value;
|
|
1806
1819
|
// type validation handled upstream
|
|
@@ -1830,18 +1843,61 @@ class NormalizationFactory {
|
|
|
1830
1843
|
if (valueNode.kind !== graphql_1.Kind.STRING) {
|
|
1831
1844
|
continue;
|
|
1832
1845
|
}
|
|
1833
|
-
|
|
1834
|
-
const
|
|
1846
|
+
// slicingArgPath could be just an argument "inputA" or a dot-path "inputA.page.first".
|
|
1847
|
+
const slicingArgPath = valueNode.value;
|
|
1848
|
+
const segments = slicingArgPath.split(string_constants_1.LITERAL_PERIOD);
|
|
1849
|
+
// Reject empty segments (e.g. "", "a.", ".b", "a..b").
|
|
1850
|
+
if (segments.length === 0 || segments.some((s) => s.length === 0)) {
|
|
1851
|
+
errorMessages.push((0, errors_1.listSizeSlicingArgumentMalformedPathErrorMessage)(directiveCoords, slicingArgPath));
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1854
|
+
// First segment should be valid argument name:
|
|
1855
|
+
const firstSegment = segments[0];
|
|
1856
|
+
const argData = data.argumentDataByName.get(firstSegment);
|
|
1835
1857
|
if (!argData) {
|
|
1836
|
-
errorMessages.push((0, errors_1.listSizeInvalidSlicingArgumentErrorMessage)(directiveCoords,
|
|
1858
|
+
errorMessages.push((0, errors_1.listSizeInvalidSlicingArgumentErrorMessage)(directiveCoords, slicingArgPath));
|
|
1859
|
+
continue;
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Walk the rest of the path in the slicingArgument.
|
|
1863
|
+
* `current` tracks the input value reached so far:
|
|
1864
|
+
* the argument itself for a flat path, or the nested input field for each step of a
|
|
1865
|
+
* dot-path. After the loop it is the leaf, which must be Int/Int!.
|
|
1866
|
+
*/
|
|
1867
|
+
let current = argData;
|
|
1868
|
+
const chain = [argData];
|
|
1869
|
+
let isPathInvalid = false;
|
|
1870
|
+
for (let i = 1; i < segments.length; i++) {
|
|
1871
|
+
// Non-leaf step: `current` must unwrap to an Input Object. Lists are rejected here.
|
|
1872
|
+
const unwrapped = current.type.kind === graphql_1.Kind.NON_NULL_TYPE ? current.type.type : current.type;
|
|
1873
|
+
const parentTypeData = this.parentDefinitionDataByTypeName.get(current.namedTypeName);
|
|
1874
|
+
if (unwrapped.kind === graphql_1.Kind.LIST_TYPE ||
|
|
1875
|
+
!parentTypeData ||
|
|
1876
|
+
parentTypeData.kind !== graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION) {
|
|
1877
|
+
errorMessages.push((0, errors_1.listSizeSlicingArgumentSegmentNotInputObjectErrorMessage)(directiveCoords, slicingArgPath, current.name, (0, merge_1.printTypeNode)(current.type)));
|
|
1878
|
+
isPathInvalid = true;
|
|
1879
|
+
break;
|
|
1880
|
+
}
|
|
1881
|
+
const inputValue = parentTypeData.inputValueDataByName.get(segments[i]);
|
|
1882
|
+
if (!inputValue) {
|
|
1883
|
+
errorMessages.push((0, errors_1.listSizeSlicingArgumentSegmentNotFoundErrorMessage)(directiveCoords, slicingArgPath, segments[i], current.namedTypeName));
|
|
1884
|
+
isPathInvalid = true;
|
|
1885
|
+
break;
|
|
1886
|
+
}
|
|
1887
|
+
current = inputValue;
|
|
1888
|
+
chain.push(inputValue);
|
|
1889
|
+
}
|
|
1890
|
+
if (isPathInvalid) {
|
|
1837
1891
|
continue;
|
|
1838
1892
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1893
|
+
// Leaf check: the final value must be Int or Int!
|
|
1894
|
+
const unwrappedType = current.type.kind === graphql_1.Kind.NON_NULL_TYPE ? current.type.type : current.type;
|
|
1895
|
+
if (unwrappedType.kind === graphql_1.Kind.LIST_TYPE || current.namedTypeName !== string_constants_1.INT_SCALAR) {
|
|
1896
|
+
errorMessages.push((0, errors_1.listSizeSlicingArgumentNotIntErrorMessage)(directiveCoords, slicingArgPath, (0, merge_1.printTypeNode)(current.type)));
|
|
1842
1897
|
continue;
|
|
1843
1898
|
}
|
|
1844
|
-
listSizeConfig.slicingArguments.push(
|
|
1899
|
+
listSizeConfig.slicingArguments.push(slicingArgPath);
|
|
1900
|
+
slicingArgChainByPath.set(slicingArgPath, chain);
|
|
1845
1901
|
}
|
|
1846
1902
|
break;
|
|
1847
1903
|
}
|
|
@@ -1897,10 +1953,44 @@ class NormalizationFactory {
|
|
|
1897
1953
|
errorMessages.push((0, errors_1.listSizeAssumedSizeWithRequiredSlicingArgumentErrorMessage)(directiveCoords));
|
|
1898
1954
|
}
|
|
1899
1955
|
else {
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1956
|
+
/**
|
|
1957
|
+
* When assumedSize is set together with slicingArguments, the slicing argument must not have
|
|
1958
|
+
* a default leaf value in any element of the slicingArgument's chain.
|
|
1959
|
+
* That will be checked for all slicing Arguments.
|
|
1960
|
+
* For example, if the query is defined like this:
|
|
1961
|
+
* search(a: SearchInput): [Book]
|
|
1962
|
+
* @listSize(assumedSize: 50, slicingArguments: ["a.b.c"], requireOneSlicingArgument: false)
|
|
1963
|
+
* any of those defaults are forbidden:
|
|
1964
|
+
* input SearchInput { b: PaginationInput = { c: 10 } }
|
|
1965
|
+
* input PaginationInput { c: Int = 10 }
|
|
1966
|
+
* Notably, this query definition is also forbidden with the @listSize above:
|
|
1967
|
+
* search(a: SearchInput = { b: { c: 10 } }): [Book]
|
|
1968
|
+
* A default along the chain only matters if it actually supplies a value for the leaf:
|
|
1969
|
+
* walk the remaining tail through the default's AST and reject only when the walk resolves.
|
|
1970
|
+
* E.g. `a: SearchInput = { b: {} }` is allowed, and the leaf stays undefined.
|
|
1971
|
+
*/
|
|
1972
|
+
for (const slicingArgPath of listSizeConfig.slicingArguments) {
|
|
1973
|
+
const chain = slicingArgChainByPath.get(slicingArgPath);
|
|
1974
|
+
if (!chain) {
|
|
1975
|
+
continue;
|
|
1976
|
+
}
|
|
1977
|
+
const segments = slicingArgPath.split(string_constants_1.LITERAL_PERIOD);
|
|
1978
|
+
// Outer loop: which default are we starting from?
|
|
1979
|
+
for (let i = 0; i < chain.length; i++) {
|
|
1980
|
+
let value = chain[i].defaultValue;
|
|
1981
|
+
// Inner loop: try to find a defined leaf in the chain:
|
|
1982
|
+
for (let j = i + 1; j < segments.length; j++) {
|
|
1983
|
+
if (!value || value.kind !== graphql_1.Kind.OBJECT) {
|
|
1984
|
+
value = undefined;
|
|
1985
|
+
break;
|
|
1986
|
+
}
|
|
1987
|
+
const field = value.fields.find((f) => f.name.value === segments[j]);
|
|
1988
|
+
value = field?.value;
|
|
1989
|
+
}
|
|
1990
|
+
if (value !== undefined) {
|
|
1991
|
+
errorMessages.push((0, errors_1.listSizeAssumedSizeSlicingArgDefaultErrorMessage)(directiveCoords, slicingArgPath));
|
|
1992
|
+
break;
|
|
1993
|
+
}
|
|
1904
1994
|
}
|
|
1905
1995
|
}
|
|
1906
1996
|
}
|
|
@@ -2873,6 +2963,179 @@ class NormalizationFactory {
|
|
|
2873
2963
|
}
|
|
2874
2964
|
}
|
|
2875
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
|
+
}
|
|
2876
3139
|
addFieldNamesToConfigurationData(fieldDataByFieldName, configurationData) {
|
|
2877
3140
|
const externalFieldNames = new Set();
|
|
2878
3141
|
for (const [fieldName, fieldData] of fieldDataByFieldName) {
|
|
@@ -2930,6 +3193,18 @@ class NormalizationFactory {
|
|
|
2930
3193
|
}
|
|
2931
3194
|
definitions.push(...dependencies);
|
|
2932
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
|
+
}
|
|
2933
3208
|
normalize(document) {
|
|
2934
3209
|
// Collect any renamed root types
|
|
2935
3210
|
(0, walkers_1.upsertDirectiveSchemaAndEntityDefinitions)(this, document);
|
|
@@ -3036,7 +3311,10 @@ class NormalizationFactory {
|
|
|
3036
3311
|
}
|
|
3037
3312
|
const externalInterfaceFieldNames = [];
|
|
3038
3313
|
for (const [fieldName, fieldData] of parentData.fieldDataByName) {
|
|
3039
|
-
if (
|
|
3314
|
+
if (isObject) {
|
|
3315
|
+
this.handleFieldCacheDirectives(fieldData);
|
|
3316
|
+
}
|
|
3317
|
+
else if (fieldData.externalFieldDataBySubgraphName.get(this.subgraphName)?.isDefinedExternal) {
|
|
3040
3318
|
externalInterfaceFieldNames.push(fieldName);
|
|
3041
3319
|
}
|
|
3042
3320
|
// Arguments can only be fully validated once all parents types are known
|