Implement independent presentation readiness boundaries
This commit is contained in:
+28
-3
@@ -4,6 +4,8 @@ import {
|
||||
validate,
|
||||
specifiedRules,
|
||||
PossibleFragmentSpreadsRule,
|
||||
NoUnusedVariablesRule,
|
||||
GraphQLError,
|
||||
print,
|
||||
printSchema,
|
||||
visit,
|
||||
@@ -201,10 +203,29 @@ export async function compileQuery(
|
||||
},
|
||||
}),
|
||||
);
|
||||
const bindingVariables = new Set(
|
||||
[...fragmentContracts.values()]
|
||||
.filter((entry) => entry.mode === "reference")
|
||||
.flatMap((entry) => Object.values(entry.variables)),
|
||||
);
|
||||
const errors = validate(
|
||||
generated.schema,
|
||||
document,
|
||||
specifiedRules.filter((rule) => rule !== PossibleFragmentSpreadsRule),
|
||||
[
|
||||
...specifiedRules.filter((rule) => rule !== PossibleFragmentSpreadsRule && rule !== NoUnusedVariablesRule),
|
||||
(context) => ({
|
||||
OperationDefinition: {
|
||||
leave(operation) {
|
||||
const used = new Set(context.getRecursiveVariableUsages(operation).map(({ node }) => node.name.value));
|
||||
for (const variable of operation.variableDefinitions ?? []) {
|
||||
const name = variable.variable.name.value;
|
||||
if (!used.has(name) && !bindingVariables.has(name))
|
||||
context.reportError(new GraphQLError(`Variable "$${name}" is never used.`, { nodes: variable }));
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
{ maxErrors: 20 },
|
||||
);
|
||||
if (errors.length) {
|
||||
@@ -399,6 +420,7 @@ export async function compileQuery(
|
||||
for (const selection of set.selections) {
|
||||
if (++expandedFields > 10000) fail("QUERY_WORK_LIMIT", "Expanded query exceeds 10000 fields", selection);
|
||||
if (selection.kind === "FragmentSpread") {
|
||||
if (fragmentContracts.get(selection.name.value)?.mode === "reference") continue;
|
||||
add(
|
||||
fragments.get(selection.name.value)!.selectionSet,
|
||||
conditional || !!selection.directives?.length,
|
||||
@@ -530,7 +552,9 @@ export async function compileQuery(
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...selection(fragments.get(node.name.value)!.selectionSet, parent, inheritedConditions),
|
||||
...(contract?.mode === "reference"
|
||||
? []
|
||||
: selection(fragments.get(node.name.value)!.selectionSet, parent, inheritedConditions)),
|
||||
];
|
||||
}
|
||||
if (node.kind !== "Field") return [];
|
||||
@@ -610,9 +634,10 @@ export async function compileQuery(
|
||||
const definitionDigest = createHash("sha256")
|
||||
.update(
|
||||
canonicalJson({
|
||||
semantics: 4,
|
||||
semantics: 5,
|
||||
declaration,
|
||||
normalized,
|
||||
fragmentContracts: Object.fromEntries(fragmentContracts),
|
||||
// Source provenance belongs to the checked installation. Identical public
|
||||
// contracts keep their query identity when retained in another repository.
|
||||
// Closed generic identity remains explicit in revisionId/application.
|
||||
|
||||
Reference in New Issue
Block a user