Skip to content

Commit e96a801

Browse files
committed
Merge queryCache into requestScoped (requestScoped becomes 5/5)
Restacks @openfed__requestScoped on top of the queryCache branch. The resulting tree is identical to the previous combined stack tip; this commit re-applies the requestScoped feature (including its assertions in shared/test/entity-caching.test.ts) on top of queryCache.
2 parents 840f24d + efa4cdf commit e96a801

21 files changed

Lines changed: 4111 additions & 694 deletions

File tree

composition/src/directive-definition-data/directive-definition-data.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,11 @@ import {
8585
OPENFED_CACHE_POPULATE,
8686
OPENFED_ENTITY_CACHE,
8787
INCLUDE_HEADERS,
88+
OPENFED_IS,
8889
MAX_AGE,
8990
NEGATIVE_CACHE_TTL,
9091
PARTIAL_CACHE_LOAD,
92+
OPENFED_QUERY_CACHE,
9193
OPENFED_REQUEST_SCOPED,
9294
SHADOW_MODE,
9395
} from '../utils/string-constants';
@@ -124,6 +126,8 @@ import {
124126
OPENFED_CACHE_INVALIDATE_DEFINITION,
125127
OPENFED_CACHE_POPULATE_DEFINITION,
126128
OPENFED_ENTITY_CACHE_DEFINITION,
129+
OPENFED_IS_DEFINITION,
130+
OPENFED_QUERY_CACHE_DEFINITION,
127131
OPENFED_REQUEST_SCOPED_DEFINITION,
128132
SPECIFIED_BY_DEFINITION,
129133
SUBSCRIPTION_FILTER_DEFINITION,
@@ -1047,6 +1051,64 @@ export const CACHE_POPULATE_DEFINITION_DATA = newDirectiveDefinitionData({
10471051
node: OPENFED_CACHE_POPULATE_DEFINITION,
10481052
optionalArgumentNames: new Set<ArgumentName>([MAX_AGE]),
10491053
});
1054+
1055+
export const QUERY_CACHE_DEFINITION_DATA = newDirectiveDefinitionData({
1056+
argumentDataByName: new Map<ArgumentName, DirectiveArgumentData>([
1057+
[
1058+
MAX_AGE,
1059+
newDirectiveArgumentData({
1060+
directive: `@${OPENFED_QUERY_CACHE}`,
1061+
name: MAX_AGE,
1062+
namedTypeKind: Kind.SCALAR_TYPE_DEFINITION,
1063+
typeNode: REQUIRED_INT_TYPE_NODE,
1064+
}),
1065+
],
1066+
[
1067+
INCLUDE_HEADERS,
1068+
newDirectiveArgumentData({
1069+
directive: `@${OPENFED_QUERY_CACHE}`,
1070+
defaultValue: { kind: Kind.BOOLEAN, value: false },
1071+
name: INCLUDE_HEADERS,
1072+
namedTypeKind: Kind.SCALAR_TYPE_DEFINITION,
1073+
typeNode: stringToNamedTypeNode(BOOLEAN_SCALAR),
1074+
}),
1075+
],
1076+
[
1077+
SHADOW_MODE,
1078+
newDirectiveArgumentData({
1079+
directive: `@${OPENFED_QUERY_CACHE}`,
1080+
defaultValue: { kind: Kind.BOOLEAN, value: false },
1081+
name: SHADOW_MODE,
1082+
namedTypeKind: Kind.SCALAR_TYPE_DEFINITION,
1083+
typeNode: stringToNamedTypeNode(BOOLEAN_SCALAR),
1084+
}),
1085+
],
1086+
]),
1087+
locations: new Set<DirectiveLocation>([FIELD_DEFINITION_UPPER]),
1088+
name: OPENFED_QUERY_CACHE,
1089+
node: OPENFED_QUERY_CACHE_DEFINITION,
1090+
optionalArgumentNames: new Set<ArgumentName>([INCLUDE_HEADERS, SHADOW_MODE]),
1091+
requiredArgumentNames: new Set<ArgumentName>([MAX_AGE]),
1092+
});
1093+
1094+
export const IS_DEFINITION_DATA = newDirectiveDefinitionData({
1095+
argumentDataByName: new Map<ArgumentName, DirectiveArgumentData>([
1096+
[
1097+
FIELDS,
1098+
newDirectiveArgumentData({
1099+
directive: `@${OPENFED_IS}`,
1100+
name: FIELDS,
1101+
namedTypeKind: Kind.SCALAR_TYPE_DEFINITION,
1102+
typeNode: REQUIRED_STRING_TYPE_NODE,
1103+
}),
1104+
],
1105+
]),
1106+
locations: new Set<DirectiveLocation>([ARGUMENT_DEFINITION_UPPER]),
1107+
name: OPENFED_IS,
1108+
node: OPENFED_IS_DEFINITION,
1109+
requiredArgumentNames: new Set<ArgumentName>([FIELDS]),
1110+
});
1111+
10501112
export const REQUEST_SCOPED_DEFINITION_DATA = newDirectiveDefinitionData({
10511113
argumentDataByName: new Map<ArgumentName, DirectiveArgumentData>([
10521114
[

composition/src/errors/errors.ts

Lines changed: 247 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
type InvalidRepeatedDirectiveErrorParams,
1919
type InvalidSubValueFieldLinkDirectiveImportErrorParams,
2020
type invalidVersionLinkDirectiveUrlErrorParams,
21+
type MaxAgeNotPositiveIntegerErrorParams,
2122
type NonExternalConditionalFieldErrorParams,
2223
type OneOfRequiredFieldsErrorParams,
2324
type SemanticNonNullLevelsIndexOutOfBoundsErrorParams,
@@ -2056,14 +2057,31 @@ export function entityCacheWithoutKeyErrorMessage(typeName: TypeName): string {
20562057
return `Object "${typeName}" does not define a "@key" directive.`;
20572058
}
20582059

2059-
export function maxAgeNotPositiveIntegerErrorMessage(value: number | string | null): string {
2060-
return `The argument "maxAge" must be provided a positive integer; received "${value}".`;
2060+
export function maxAgeNotPositiveIntegerErrorMessage({
2061+
directiveName,
2062+
value,
2063+
}: MaxAgeNotPositiveIntegerErrorParams): string {
2064+
return `The directive "@${directiveName}" argument "maxAge" must be provided a positive integer; received "${value}".`;
20612065
}
20622066

20632067
export function negativeCacheTTLNotNonNegativeIntegerErrorMessage(value: number): string {
20642068
return `The argument "negativeCacheTTL" must be provided zero or a positive integer; received "${value}".`;
20652069
}
20662070

2071+
export function queryCacheOnNonQueryFieldErrorMessage(fieldCoords: string): string {
2072+
return (
2073+
`@openfed__queryCache must only be defined on fields of the root query type; found on "${fieldCoords}".` +
2074+
` Use @openfed__cachePopulate or @openfed__cacheInvalidate on mutation or subscription fields.`
2075+
);
2076+
}
2077+
2078+
export function queryCacheOnNonEntityReturnTypeErrorMessage(fieldCoords: string, returnType: string): string {
2079+
return (
2080+
`Field "${fieldCoords}" has @openfed__queryCache but returns non-entity type "${returnType}".` +
2081+
` @openfed__queryCache requires the return type to be an entity with @key.`
2082+
);
2083+
}
2084+
20672085
export function invalidMutationOrSubscriptionFieldCoordsErrorMessage(fieldCoords: string): string {
20682086
return `Field coordinates "${fieldCoords}" are not a Mutation or Subscription root field.`;
20692087
}
@@ -2081,3 +2099,230 @@ export function invalidMutuallyExclusiveCacheDirectivesError(fieldCoords: string
20812099
` and "@openfed__cachePopulate".`,
20822100
);
20832101
}
2102+
2103+
export function isWithoutQueryCacheErrorMessage(argumentName: string, fieldCoords: string): string {
2104+
return `@openfed__is on argument "${argumentName}" of field "${fieldCoords}" has no effect without @openfed__queryCache.`;
2105+
}
2106+
2107+
export function isReferencesUnknownKeyFieldErrorMessage(
2108+
isField: string,
2109+
argumentName: string,
2110+
fieldCoords: string,
2111+
entityType: string,
2112+
): string {
2113+
return (
2114+
`@openfed__is(fields: "${isField}") on argument "${argumentName}" of field "${fieldCoords}"` +
2115+
` references unknown @key field "${isField}" on type "${entityType}".`
2116+
);
2117+
}
2118+
2119+
export function duplicateKeyFieldMappingErrorMessage(fieldCoords: string, keyField: string): string {
2120+
return `Multiple arguments on field "${fieldCoords}" map to @key field "${keyField}".`;
2121+
}
2122+
2123+
export function explicitTypeMismatchErrorMessage(
2124+
argumentName: string,
2125+
fieldCoords: string,
2126+
argumentType: string,
2127+
isField: string,
2128+
entityType: string,
2129+
keyFieldType: string,
2130+
): string {
2131+
return `Argument "${argumentName}" on field "${fieldCoords}" has type "${argumentType}" but @openfed__is(fields: "${isField}") targets @key field "${isField}" of type "${keyFieldType}" on entity "${entityType}".`;
2132+
}
2133+
2134+
export function nonKeyFieldSpecErrorMessage(
2135+
argumentName: string,
2136+
fieldCoords: string,
2137+
isField: string,
2138+
entityType: string,
2139+
): string {
2140+
return `Argument "${argumentName}" on field "${fieldCoords}" uses @openfed__is(fields: "${isField}"), but "${isField}" is not a @key field on entity "${entityType}". @openfed__is can only target fields that are part of a @key.`;
2141+
}
2142+
2143+
export function listArgumentToScalarKeySpecErrorMessage(
2144+
argumentName: string,
2145+
fieldCoords: string,
2146+
argumentType: string,
2147+
isField: string,
2148+
entityType: string,
2149+
keyFieldType: string,
2150+
): string {
2151+
return (
2152+
`Argument "${argumentName}" on field "${fieldCoords}" has type "${argumentType}" but @openfed__is(fields: "${isField}") targets @key field "${isField}" of type "${keyFieldType}" on entity "${entityType}".` +
2153+
' List arguments can only map to scalar key fields when the field returns a list of entities, or to list key fields when the key field itself is a list type.'
2154+
);
2155+
}
2156+
2157+
export function scalarArgumentToListKeySpecErrorMessage(
2158+
argumentName: string,
2159+
fieldCoords: string,
2160+
argumentType: string,
2161+
isField: string,
2162+
entityType: string,
2163+
keyFieldType: string,
2164+
): string {
2165+
return (
2166+
`Argument "${argumentName}" on field "${fieldCoords}" has type "${argumentType}" but @openfed__is(fields: "${isField}") targets @key field "${isField}" of type "${keyFieldType}" on entity "${entityType}".` +
2167+
' A scalar argument cannot map to a list key field.'
2168+
);
2169+
}
2170+
2171+
export function explicitIncompleteCompositeKeyErrorMessage(
2172+
fieldCoords: string,
2173+
argumentName: string,
2174+
mappedField: string,
2175+
entityType: string,
2176+
compositeKey: string,
2177+
missingField: string,
2178+
): string {
2179+
return `Field "${fieldCoords}" has argument "${argumentName}" with @openfed__is mapping to @key field "${mappedField}" on entity "${entityType}", but composite @key "${compositeKey}" is incomplete because no argument maps to required key field "${missingField}".`;
2180+
}
2181+
2182+
export function explicitSingularAdditionalNonKeyArgumentErrorMessage(
2183+
fieldCoords: string,
2184+
argumentName: string,
2185+
keyField: string,
2186+
entityType: string,
2187+
extraArgument: string,
2188+
): string {
2189+
return `Field "${fieldCoords}" has argument "${argumentName}" with @openfed__is mapping to @key field "${keyField}" on entity "${entityType}", but also has additional argument "${extraArgument}" which is not mapped to a key field. All arguments must be key arguments — additional arguments may filter the response, making the cache key incomplete.`;
2190+
}
2191+
2192+
export function explicitCompositeAdditionalNonKeyArgumentErrorMessage(
2193+
fieldCoords: string,
2194+
firstArgument: string,
2195+
secondArgument: string,
2196+
compositeKey: string,
2197+
entityType: string,
2198+
extraArgument: string,
2199+
): string {
2200+
return `Field "${fieldCoords}" has arguments "${firstArgument}" and "${secondArgument}" with @openfed__is mappings covering composite @key "${compositeKey}" on entity "${entityType}", but also has additional argument "${extraArgument}" which is not mapped to a key field. All arguments must be key arguments — additional arguments may filter the response, making the cache key incomplete.`;
2201+
}
2202+
2203+
export function batchListValuedKeyRequiresNestedListsErrorMessage(
2204+
fieldCoords: string,
2205+
isField: string,
2206+
entityType: string,
2207+
actualType: string,
2208+
): string {
2209+
return `Field "${fieldCoords}" returns a list of entities, so cache lookup is a batch lookup and requires one key value per entity. Because @openfed__is(fields: "${isField}") targets list-valued @key field "${isField}" on entity "${entityType}", the argument must provide a list of tag lists (e.g., "[[String!]!]!"), not ${actualType}.`;
2210+
}
2211+
2212+
export function explicitBatchAdditionalNonKeyArgumentErrorMessage(
2213+
fieldCoords: string,
2214+
argumentName: string,
2215+
keyField: string,
2216+
entityType: string,
2217+
extraArgument: string,
2218+
): string {
2219+
return `Field "${fieldCoords}" returns a list of entities, so cache lookup is a batch lookup and requires a single key input that determines the returned entities. Argument "${argumentName}" uses @openfed__is to map to @key field "${keyField}" on entity "${entityType}", but additional argument "${extraArgument}" is not mapped to a key field and may filter the response, so the batch key would be incomplete.`;
2220+
}
2221+
2222+
export function explicitScalarArgumentsCannotEstablishBatchMappingErrorMessage(
2223+
fieldCoords: string,
2224+
entityType: string,
2225+
): string {
2226+
return `Field "${fieldCoords}" returns a list of entities, so cache lookup is a batch lookup and requires one key value per entity. Scalar arguments with @openfed__is mapping to @key fields on entity "${entityType}" cannot provide a batch of keys, so they cannot establish cache key mappings for this field. Use list arguments for batch cache lookups.`;
2227+
}
2228+
2229+
export function multipleListArgumentsBatchFactoryMessage(fieldCoords: string, entityType: string): string {
2230+
return (
2231+
`Field "${fieldCoords}" has multiple list arguments mapping to @key fields on entity "${entityType}".` +
2232+
' Batch cache lookups require a single list argument.' +
2233+
' For composite keys, use a single list of input objects instead.'
2234+
);
2235+
}
2236+
2237+
export function inputObjectCompositeTypeMismatchErrorMessage(
2238+
argumentName: string,
2239+
fieldCoords: string,
2240+
keyFields: string,
2241+
entityType: string,
2242+
inputType: string,
2243+
inputFieldName: string,
2244+
inputFieldType: string,
2245+
entityFieldPath: string,
2246+
entityFieldType: string,
2247+
): string {
2248+
return (
2249+
`Argument "${argumentName}" on field "${fieldCoords}" uses @openfed__is(fields: "${keyFields}") mapping to composite @key on entity "${entityType}",` +
2250+
` but input type "${inputType}" field "${inputFieldName}" has type "${inputFieldType}"` +
2251+
` which does not match key field "${entityFieldPath}" of type "${entityFieldType}".`
2252+
);
2253+
}
2254+
2255+
export function inputObjectCompositeMissingFieldErrorMessage(
2256+
argumentName: string,
2257+
fieldCoords: string,
2258+
keyFields: string,
2259+
entityType: string,
2260+
inputType: string,
2261+
missingFieldName: string,
2262+
): string {
2263+
return (
2264+
`Argument "${argumentName}" on field "${fieldCoords}" uses @openfed__is(fields: "${keyFields}") mapping to composite @key on entity "${entityType}",` +
2265+
` but input type "${inputType}" is missing required key field "${missingFieldName}".`
2266+
);
2267+
}
2268+
2269+
export function nestedKeyRequiresInputObjectErrorMessage(
2270+
argumentName: string,
2271+
fieldCoords: string,
2272+
keyFields: string,
2273+
entityType: string,
2274+
inputTypeName: string,
2275+
entityKeyPath: string,
2276+
): string {
2277+
return (
2278+
`Argument "${argumentName}" on field "${fieldCoords}" maps to nested @key "${keyFields}" on entity "${entityType}",` +
2279+
` but the input field at key path "${entityKeyPath}" has type "${inputTypeName}",` +
2280+
` which is not an input object type and therefore cannot provide the nested key selection.`
2281+
);
2282+
}
2283+
2284+
export function nestedInputObjectTypeMismatchErrorMessage(
2285+
argumentName: string,
2286+
fieldCoords: string,
2287+
keyFields: string,
2288+
entityType: string,
2289+
inputType: string,
2290+
inputFieldName: string,
2291+
inputFieldType: string,
2292+
entityFieldPath: string,
2293+
entityFieldType: string,
2294+
): string {
2295+
return (
2296+
`Argument "${argumentName}" on field "${fieldCoords}" maps to nested @key "${keyFields}" on entity "${entityType}",` +
2297+
` but input type "${inputType}" field "${inputFieldName}" has type "${inputFieldType}"` +
2298+
` which does not match key field "${entityFieldPath}" of type "${entityFieldType}".`
2299+
);
2300+
}
2301+
2302+
export function nestedInputObjectMissingFieldErrorMessage(
2303+
argumentName: string,
2304+
fieldCoords: string,
2305+
keyFields: string,
2306+
entityType: string,
2307+
inputType: string,
2308+
missingFieldName: string,
2309+
): string {
2310+
return (
2311+
`Argument "${argumentName}" on field "${fieldCoords}" maps to nested @key "${keyFields}" on entity "${entityType}",` +
2312+
` but input type "${inputType}" is missing required key field "${missingFieldName}".`
2313+
);
2314+
}
2315+
2316+
export function nonInputArgumentCannotTargetCompositeKeyErrorMessage(
2317+
argumentName: string,
2318+
fieldCoords: string,
2319+
keyFields: string,
2320+
entityType: string,
2321+
argumentType: string,
2322+
): string {
2323+
return (
2324+
`Argument "${argumentName}" on field "${fieldCoords}" uses @openfed__is(fields: "${keyFields}") targeting composite @key on entity "${entityType}",` +
2325+
` but argument type "${argumentType}" does not provide nested fields for each key field.` +
2326+
' Use separate arguments or an input object that matches the composite key shape.'
2327+
);
2328+
}

composition/src/errors/types/params.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,8 @@ export type InvalidEntityReturnTypeErrorParams = {
9999
fieldCoords: string;
100100
returnTypeName: string;
101101
};
102+
103+
export type MaxAgeNotPositiveIntegerErrorParams = {
104+
directiveName: DirectiveName;
105+
value: number | string | null;
106+
};

0 commit comments

Comments
 (0)