Files
klz-cables.com/.pnpm-store/v10/files/7b/1b46efb372b9e09285983345ff0e34d7428e39a966d950398be3ea51875d66dec4974b77477f052373783c8388c2f55ef0db33d3c74681524a6dae5ead1de6
Marc Mintel 5397309103
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
fix(products): fix breadcrumbs and product filtering (backport from main)
2026-02-24 16:04:21 +01:00

270 lines
13 KiB
Plaintext

/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */ /**
* Created by Ivo Meißner on 28.07.17.
*/ import { getNamedType, GraphQLError, GraphQLInterfaceType, GraphQLObjectType, isAbstractType, isCompositeType, Kind, TypeInfo, ValidationContext, visit, visitWithTypeInfo } from 'graphql';
import { getArgumentValues, getDirectiveValues, getVariableValues } from 'graphql/execution/values.js';
function queryComplexityMessage(max, actual) {
return `The query exceeds the maximum complexity of ${max}. ` + `Actual complexity is ${actual}`;
}
export function getComplexity(options) {
const typeInfo = new TypeInfo(options.schema);
const errors = [];
const context = new ValidationContext(options.schema, options.query, typeInfo, (error)=>errors.push(error));
const visitor = new QueryComplexity(context, {
// Maximum complexity does not matter since we're only interested in the calculated complexity.
context: options.context,
estimators: options.estimators,
maximumComplexity: Infinity,
operationName: options.operationName,
variables: options.variables
});
visit(options.query, visitWithTypeInfo(typeInfo, visitor));
// Throw first error if any
if (errors.length) {
throw errors.pop();
}
return visitor.complexity;
}
export class QueryComplexity {
complexity;
context;
estimators;
includeDirectiveDef;
OperationDefinition;
options;
requestContext;
skipDirectiveDef;
variableValues;
constructor(context, options){
if (!(typeof options.maximumComplexity === 'number' && options.maximumComplexity > 0)) {
throw new Error('Maximum query complexity must be a positive number');
}
this.context = context;
this.complexity = 0;
this.options = options;
this.includeDirectiveDef = this.context.getSchema().getDirective('include');
this.skipDirectiveDef = this.context.getSchema().getDirective('skip');
this.estimators = options.estimators;
this.variableValues = {};
this.requestContext = options.context;
this.OperationDefinition = {
enter: this.onOperationDefinitionEnter,
leave: this.onOperationDefinitionLeave
};
}
createError() {
if (typeof this.options.createError === 'function') {
return this.options.createError(this.options.maximumComplexity, this.complexity);
}
return new GraphQLError(queryComplexityMessage(this.options.maximumComplexity, this.complexity));
}
nodeComplexity(node, typeDef) {
if (node.selectionSet) {
let fields = {};
if (typeDef instanceof GraphQLObjectType || typeDef instanceof GraphQLInterfaceType) {
fields = typeDef.getFields();
}
// Determine all possible types of the current node
let possibleTypeNames;
if (isAbstractType(typeDef)) {
possibleTypeNames = this.context.getSchema().getPossibleTypes(typeDef).map((t)=>t.name);
} else {
possibleTypeNames = [
typeDef.name
];
}
// Collect complexities for all possible types individually
const selectionSetComplexities = node.selectionSet.selections.reduce((complexities, childNode)=>{
// let nodeComplexity = 0;
let innerComplexities = complexities;
let includeNode = true;
let skipNode = false;
for (const directive of childNode.directives ?? []){
const directiveName = directive.name.value;
switch(directiveName){
case 'include':
{
const values = getDirectiveValues(this.includeDirectiveDef, childNode, this.variableValues || {});
if (typeof values.if === 'boolean') {
includeNode = values.if;
}
break;
}
case 'skip':
{
const values = getDirectiveValues(this.skipDirectiveDef, childNode, this.variableValues || {});
if (typeof values.if === 'boolean') {
skipNode = values.if;
}
break;
}
}
}
if (!includeNode || skipNode) {
return complexities;
}
switch(childNode.kind){
case Kind.FIELD:
{
const field = fields[childNode.name.value];
// Invalid field, should be caught by other validation rules
if (!field) {
break;
}
const fieldType = getNamedType(field.type);
// Get arguments
let args;
try {
args = getArgumentValues(field, childNode, this.variableValues || {});
} catch (e) {
this.context.reportError(e);
return complexities;
}
// Check if we have child complexity
let childComplexity = 0;
if (isCompositeType(fieldType)) {
childComplexity = this.nodeComplexity(childNode, fieldType);
}
// Run estimators one after another and return first valid complexity
// score
const estimatorArgs = {
type: typeDef,
args,
childComplexity,
context: this.requestContext,
field,
node: childNode
};
const validScore = this.estimators.find((estimator)=>{
const tmpComplexity = estimator(estimatorArgs);
if (typeof tmpComplexity === 'number' && !isNaN(tmpComplexity)) {
innerComplexities = addComplexities(tmpComplexity, complexities, possibleTypeNames);
return true;
}
return false;
});
if (!validScore) {
this.context.reportError(new GraphQLError(`No complexity could be calculated for field ${typeDef.name}.${field.name}. ` + 'At least one complexity estimator has to return a complexity score.'));
return complexities;
}
break;
}
case Kind.FRAGMENT_SPREAD:
{
const fragment = this.context.getFragment(childNode.name.value);
// Unknown fragment, should be caught by other validation rules
if (!fragment) {
break;
}
const fragmentType = this.context.getSchema().getType(fragment.typeCondition.name.value);
// Invalid fragment type, ignore. Should be caught by other validation rules
if (!isCompositeType(fragmentType)) {
break;
}
const nodeComplexity = this.nodeComplexity(fragment, fragmentType);
if (isAbstractType(fragmentType)) {
// Add fragment complexity for all possible types
innerComplexities = addComplexities(nodeComplexity, complexities, this.context.getSchema().getPossibleTypes(fragmentType).map((t)=>t.name));
} else {
// Add complexity for object type
innerComplexities = addComplexities(nodeComplexity, complexities, [
fragmentType.name
]);
}
break;
}
case Kind.INLINE_FRAGMENT:
{
let inlineFragmentType = typeDef;
if (childNode.typeCondition && childNode.typeCondition.name) {
inlineFragmentType = this.context.getSchema().getType(childNode.typeCondition.name.value);
if (!isCompositeType(inlineFragmentType)) {
break;
}
}
const nodeComplexity = this.nodeComplexity(childNode, inlineFragmentType);
if (isAbstractType(inlineFragmentType)) {
// Add fragment complexity for all possible types
innerComplexities = addComplexities(nodeComplexity, complexities, this.context.getSchema().getPossibleTypes(inlineFragmentType).map((t)=>t.name));
} else {
// Add complexity for object type
innerComplexities = addComplexities(nodeComplexity, complexities, [
inlineFragmentType.name
]);
}
break;
}
default:
{
innerComplexities = addComplexities(this.nodeComplexity(childNode, typeDef), complexities, possibleTypeNames);
break;
}
}
return innerComplexities;
}, {});
// Only return max complexity of all possible types
if (!selectionSetComplexities) {
return NaN;
}
return Math.max(...Object.values(selectionSetComplexities), 0);
}
return 0;
}
onOperationDefinitionEnter(operation) {
if (typeof this.options.operationName === 'string' && this.options.operationName !== operation.name.value) {
return;
}
// Get variable values from variables that are passed from options, merged
// with default values defined in the operation
const { coerced, errors } = getVariableValues(this.context.getSchema(), // We have to create a new array here because input argument is not readonly in graphql ~14.6.0
operation.variableDefinitions ? [
...operation.variableDefinitions
] : [], this.options.variables ?? {});
if (errors && errors.length) {
// We have input validation errors, report errors and abort
errors.forEach((error)=>this.context.reportError(error));
return;
}
this.variableValues = coerced;
switch(operation.operation){
case 'mutation':
this.complexity += this.nodeComplexity(operation, this.context.getSchema().getMutationType());
break;
case 'query':
this.complexity += this.nodeComplexity(operation, this.context.getSchema().getQueryType());
break;
case 'subscription':
this.complexity += this.nodeComplexity(operation, this.context.getSchema().getSubscriptionType());
break;
default:
throw new Error(`Query complexity could not be calculated for operation of type ${operation.operation}`);
}
}
onOperationDefinitionLeave(operation) {
if (typeof this.options.operationName === 'string' && this.options.operationName !== operation.name.value) {
return;
}
if (this.options.onComplete) {
this.options.onComplete(this.complexity);
}
if (this.complexity > this.options.maximumComplexity) {
return this.context.reportError(this.createError());
}
}
}
/**
* Adds a complexity to the complexity map for all possible types
* @param complexity
* @param complexityMap
* @param possibleTypes
*/ function addComplexities(complexity, complexityMap, possibleTypes) {
for (const type of possibleTypes){
if (Object.prototype.hasOwnProperty.call(complexityMap, type)) {
complexityMap[type] += complexity;
} else {
complexityMap[type] = complexity;
}
}
return complexityMap;
}
//# sourceMappingURL=QueryComplexity.js.map