├── .babelrc ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .mocharc.json ├── .prettierrc ├── LICENSE ├── README.md ├── fix-hybrid-module.sh ├── fix-hybrid-module.test.cjs.sh ├── fix-hybrid-module.test.esm.sh ├── package.json ├── src ├── QueryComplexity.ts ├── __tests__ │ ├── .eslintrc.js │ ├── QueryComplexity-test.ts │ ├── fixtures │ │ ├── CompatibleValidationContext.ts │ │ └── schema.ts │ └── utils │ │ └── compatResolveType.ts ├── createComplexityRule.ts ├── estimators │ ├── directive │ │ ├── README.md │ │ ├── __tests__ │ │ │ ├── directiveEstimator-test.ts │ │ │ └── fixtures │ │ │ │ └── schema.ts │ │ └── index.ts │ ├── fieldExtensions │ │ ├── README.md │ │ ├── __tests__ │ │ │ ├── fieldExtensionsEstimator-test.ts │ │ │ └── fixtures │ │ │ │ └── schema.ts │ │ └── index.ts │ ├── index.ts │ └── simple │ │ ├── README.md │ │ ├── __tests__ │ │ ├── .eslintrc.js │ │ ├── fixtures │ │ │ └── schema.ts │ │ └── simpleEstimator-test.ts │ │ └── index.ts └── index.ts ├── tsconfig.cjs.json ├── tsconfig.esm.json ├── tsconfig.json ├── tsconfig.test.cjs.json ├── tsconfig.test.esm.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceRoot": "./packages/core/src", 3 | "parserOpts": { 4 | "allowReturnOutsideFunction": true 5 | }, 6 | "presets": ["es2015", "stage-0"], 7 | "plugins": [ 8 | "syntax-async-functions", 9 | "transform-class-properties", 10 | "transform-flow-strip-types", 11 | "transform-object-rest-spread", 12 | "transform-regenerator", 13 | "transform-runtime", 14 | ["babel-plugin-transform-builtin-extend", { 15 | "globals": ["Error"] 16 | }] 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "env": { 11 | "browser": true, 12 | "es6": true, 13 | "node": true, 14 | "mocha": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test-and-build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | graphql-version: ['~15.0', '~16.0'] 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | 21 | - name: Set up Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 'latest' 25 | 26 | - name: Restore cache 27 | uses: actions/cache@v3 28 | with: 29 | path: node_modules 30 | key: v1-dependencies-${{ hashFiles('package.json') }}-${{ matrix.graphql-version }} 31 | restore-keys: | 32 | v1-dependencies- 33 | 34 | - name: Install dependencies 35 | if: matrix.graphql-version != '' 36 | run: yarn install --ignore-scripts 37 | 38 | - name: Add specific graphql version 39 | if: matrix.graphql-version != '' 40 | run: yarn --ignore-scripts add --dev graphql@${{ matrix.graphql-version }} 41 | 42 | - name: Install dependencies with frozen lockfile 43 | if: matrix.graphql-version == '' 44 | run: yarn install --frozen-lockfile 45 | 46 | - name: Save cache 47 | uses: actions/cache@v3 48 | with: 49 | path: node_modules 50 | key: v1-dependencies-${{ hashFiles('package.json') }}-${{ matrix.graphql-version }} 51 | 52 | - name: Run tests 53 | run: yarn test 54 | 55 | test-and-build-with-typecheck: 56 | runs-on: ubuntu-latest 57 | steps: 58 | - name: Checkout repository 59 | uses: actions/checkout@v2 60 | 61 | - name: Set up Node.js 62 | uses: actions/setup-node@v4 63 | with: 64 | node-version: 'latest' 65 | 66 | - name: Restore cache 67 | uses: actions/cache@v3 68 | with: 69 | path: node_modules 70 | key: v1-dependencies-${{ hashFiles('package.json') }} 71 | restore-keys: | 72 | v1-dependencies- 73 | 74 | - name: Install dependencies 75 | run: yarn install --frozen-lockfile 76 | 77 | - name: Save cache 78 | uses: actions/cache@v3 79 | with: 80 | path: node_modules 81 | key: v1-dependencies-${{ hashFiles('package.json') }} 82 | 83 | - name: Run tests 84 | run: yarn test 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | node_modules 3 | .DS_Store 4 | .idea 5 | .vscode 6 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "node-option": ["experimental-specifier-resolution=node"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "printWidth": 80 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ivo Meißner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Query Complexity Analysis for graphql-js 2 | 3 | [![npm](https://img.shields.io/npm/dm/graphql-query-complexity)](https://www.npmjs.com/package/graphql-query-complexity) 4 | [![npm version](https://badge.fury.io/js/graphql-query-complexity.svg)](https://badge.fury.io/js/graphql-query-complexity) 5 | [![Twitter Follow](https://img.shields.io/twitter/follow/slicknode?style=social)](https://twitter.com/slicknode) 6 | 7 | This library provides GraphQL query analysis to reject complex queries to your GraphQL server. 8 | It can be used to protect your GraphQL servers against resource exhaustion and DoS attacks. 9 | 10 | This library was originally developed as part of the [Slicknode GraphQL Framework + Headless CMS](https://slicknode.com). 11 | 12 | Works with [graphql-js](https://github.com/graphql/graphql-js) reference implementation. 13 | 14 | ## Installation 15 | 16 | Install the package via npm 17 | 18 | ```bash 19 | npm install -S graphql-query-complexity 20 | ``` 21 | 22 | ## Usage 23 | 24 | Create the rule with a maximum query complexity: 25 | 26 | ```javascript 27 | import { 28 | createComplexityRule, 29 | simpleEstimator 30 | } from 'graphql-query-complexity'; 31 | 32 | const rule = createComplexityRule({ 33 | // The maximum allowed query complexity, queries above this threshold will be rejected 34 | maximumComplexity: 1_000, 35 | 36 | // The query variables. This is needed because the variables are not available 37 | // in the visitor of the graphql-js library 38 | variables: {}, 39 | 40 | // The context object for the request (optional) 41 | context: {} 42 | 43 | // Specify operation name when evaluating multi-operation documents 44 | operationName?: string, 45 | 46 | // The maximum number of query nodes to evaluate (fields, fragments, composite types). 47 | // If a query contains more than the specified number of nodes, the complexity rule will 48 | // throw an error, regardless of the complexity of the query. 49 | // 50 | // Default: 10_000 51 | maxQueryNodes?: 10_000, 52 | 53 | // Optional callback function to retrieve the determined query complexity 54 | // Will be invoked whether the query is rejected or not 55 | // This can be used for logging or to implement rate limiting 56 | onComplete: (complexity: number) => {console.log('Determined query complexity: ', complexity)}, 57 | 58 | // Optional function to create a custom error 59 | createError: (max: number, actual: number) => { 60 | return new GraphQLError(`Query is too complex: ${actual}. Maximum allowed complexity: ${max}`); 61 | }, 62 | 63 | // Add any number of estimators. The estimators are invoked in order, the first 64 | // numeric value that is being returned by an estimator is used as the field complexity. 65 | // If no estimator returns a value, an exception is raised. 66 | estimators: [ 67 | // Add more estimators here... 68 | 69 | // This will assign each field a complexity of 1 if no other estimator 70 | // returned a value. 71 | simpleEstimator({ 72 | defaultComplexity: 1 73 | }) 74 | ] 75 | }); 76 | ``` 77 | 78 | ## Configuration / Complexity Estimators 79 | 80 | The complexity calculation of a GraphQL query can be customized with so called complexity estimators. 81 | A complexity estimator is a simple function that calculates the complexity for a field. You can add 82 | any number of complexity estimators to the rule, which are then executed one after another. 83 | The first estimator that returns a numeric complexity value determines the complexity for that field. 84 | 85 | At least one estimator has to return a complexity value, otherwise an exception is raised. You can 86 | for example use the [simpleEstimator](./src/estimators/simple/README.md) as the last estimator 87 | in your chain to define a default value. 88 | 89 | You can use any of the available estimators to calculate the complexity of a field 90 | or write your own: 91 | 92 | - **[`simpleEstimator`](src/estimators/simple/README.md):** The simple estimator returns a fixed complexity for each field. Can be used as 93 | last estimator in the chain for a default value. 94 | - **[`directiveEstimator`](src/estimators/directive/README.md):** Set the complexity via a directive in your 95 | schema definition (for example via GraphQL SDL) 96 | - **[`fieldExtensionsEstimator`](src/estimators/fieldExtensions/README.md):** The field extensions estimator lets you set a numeric value or a custom estimator 97 | function in the field config extensions of your schema. 98 | - PRs welcome... 99 | 100 | Consult the documentation of each estimator for information about how to use them. 101 | 102 | ## Creating Custom Estimators 103 | 104 | An estimator has the following function signature: 105 | 106 | ```typescript 107 | type ComplexityEstimatorArgs = { 108 | // The composite type (interface, object, union) that the evaluated field belongs to 109 | type: GraphQLCompositeType; 110 | 111 | // The GraphQLField that is being evaluated 112 | field: GraphQLField; 113 | 114 | // The GraphQL node that is being evaluated 115 | node: FieldNode; 116 | 117 | // The input arguments of the field 118 | args: { [key: string]: any }; 119 | 120 | // The complexity of all child selections for that field 121 | childComplexity: number; 122 | 123 | // The context object for the request if it was provided 124 | context?: Record; 125 | }; 126 | 127 | type ComplexityEstimator = (options: ComplexityEstimatorArgs) => number | void; 128 | ``` 129 | 130 | ## Usage with express-graphql 131 | 132 | To use the query complexity analysis validation rule with express-graphql, use something like the 133 | following: 134 | 135 | ```javascript 136 | import { 137 | simpleEstimator, 138 | createComplexityRule, 139 | } from 'graphql-query-complexity'; 140 | import express from 'express'; 141 | import graphqlHTTP from 'express-graphql'; 142 | import schema from './schema'; 143 | 144 | const app = express(); 145 | app.use( 146 | '/api', 147 | graphqlHTTP(async (request, response, { variables }) => ({ 148 | schema, 149 | validationRules: [ 150 | createComplexityRule({ 151 | estimators: [ 152 | // Configure your estimators 153 | simpleEstimator({ defaultComplexity: 1 }), 154 | ], 155 | maximumComplexity: 1000, 156 | variables, 157 | onComplete: (complexity: number) => { 158 | console.log('Query Complexity:', complexity); 159 | }, 160 | }), 161 | ], 162 | })) 163 | ); 164 | ``` 165 | 166 | ## Calculate query complexity 167 | 168 | If you want to calculate the complexity of a GraphQL query outside of the validation phase, for example to 169 | return the complexity value in a resolver, you can calculate the complexity via `getComplexity`: 170 | 171 | ```javascript 172 | import { getComplexity, simpleEstimator } from 'graphql-query-complexity'; 173 | import { parse } from 'graphql'; 174 | 175 | // Import your schema or get it form the info object in your resolver 176 | import schema from './schema'; 177 | 178 | // You can also use gql template tag to get the parsed query 179 | const query = parse(` 180 | query Q($count: Int) { 181 | some_value 182 | some_list(count: $count) { 183 | some_child_value 184 | } 185 | } 186 | `); 187 | 188 | try { 189 | const complexity = getComplexity({ 190 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 191 | schema, 192 | query, 193 | variables: { 194 | count: 10, 195 | }, 196 | }); 197 | 198 | console.log(complexity); // Output: 3 199 | } catch (e) { 200 | // Log error in case complexity cannot be calculated (invalid query, misconfiguration, etc.) 201 | console.error('Could not calculate complexity', e.message); 202 | } 203 | ``` 204 | 205 | ## Prior Art 206 | 207 | This project is inspired by the following prior projects: 208 | 209 | - Query complexity analysis in the [Sangria GraphQL](http://sangria-graphql.org/) implementation. 210 | - [graphql-cost-analysis](https://github.com/pa-bru/graphql-cost-analysis) - Multipliers and directiveEstimator 211 | -------------------------------------------------------------------------------- /fix-hybrid-module.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Create package.json for CommonJS 4 | cat >dist/cjs/package.json <dist/esm/package.json <dist/test/cjs/package.json <dist/test/esm/package.json <; 46 | node: FieldNode; 47 | args: { [key: string]: any }; 48 | childComplexity: number; 49 | context?: Record; 50 | }; 51 | 52 | export type ComplexityEstimator = ( 53 | options: ComplexityEstimatorArgs 54 | ) => number | void; 55 | 56 | // Complexity can be anything that is supported by the configured estimators 57 | export type Complexity = any; 58 | 59 | // Map of complexities for possible types (of Union, Interface types) 60 | type ComplexityMap = { 61 | [typeName: string]: number; 62 | }; 63 | 64 | export interface QueryComplexityOptions { 65 | // The maximum allowed query complexity, queries above this threshold will be rejected 66 | maximumComplexity: number; 67 | 68 | // The query variables. This is needed because the variables are not available 69 | // in the visitor of the graphql-js library 70 | variables?: Record; 71 | 72 | // specify operation name only when pass multi-operation documents 73 | operationName?: string; 74 | 75 | // Optional callback function to retrieve the determined query complexity 76 | // Will be invoked whether the query is rejected or not 77 | // This can be used for logging or to implement rate limiting 78 | onComplete?: (complexity: number) => void; 79 | 80 | // Optional function to create a custom error 81 | createError?: (max: number, actual: number) => GraphQLError; 82 | 83 | // An array of complexity estimators to use for estimating the complexity 84 | estimators: Array; 85 | 86 | // Pass request context to the estimators via estimationContext 87 | context?: Record; 88 | 89 | // The maximum number of nodes to evaluate. If this is set, the query will be 90 | // rejected if it exceeds this number. (Includes fields, fragments, inline fragments, etc.) 91 | // Defaults to 10_000. 92 | maxQueryNodes?: number; 93 | } 94 | 95 | function queryComplexityMessage(max: number, actual: number): string { 96 | return ( 97 | `The query exceeds the maximum complexity of ${max}. ` + 98 | `Actual complexity is ${actual}` 99 | ); 100 | } 101 | 102 | export function getComplexity(options: { 103 | estimators: ComplexityEstimator[]; 104 | schema: GraphQLSchema; 105 | query: DocumentNode; 106 | variables?: Record; 107 | operationName?: string; 108 | context?: Record; 109 | maxQueryNodes?: number; 110 | }): number { 111 | const typeInfo = new TypeInfo(options.schema); 112 | 113 | const errors: GraphQLError[] = []; 114 | const context = new ValidationContext( 115 | options.schema, 116 | options.query, 117 | typeInfo, 118 | (error) => errors.push(error) 119 | ); 120 | const visitor = new QueryComplexity(context, { 121 | // Maximum complexity does not matter since we're only interested in the calculated complexity. 122 | maximumComplexity: Infinity, 123 | estimators: options.estimators, 124 | variables: options.variables, 125 | operationName: options.operationName, 126 | context: options.context, 127 | maxQueryNodes: options.maxQueryNodes, 128 | }); 129 | 130 | visit(options.query, visitWithTypeInfo(typeInfo, visitor)); 131 | 132 | // Throw first error if any 133 | if (errors.length) { 134 | throw errors.pop(); 135 | } 136 | 137 | return visitor.complexity; 138 | } 139 | 140 | export default class QueryComplexity { 141 | context: ValidationContext; 142 | complexity: number; 143 | options: QueryComplexityOptions; 144 | OperationDefinition: Record; 145 | estimators: Array; 146 | includeDirectiveDef: GraphQLDirective; 147 | skipDirectiveDef: GraphQLDirective; 148 | variableValues: Record; 149 | requestContext?: Record; 150 | evaluatedNodes: number; 151 | maxQueryNodes: number; 152 | 153 | constructor(context: ValidationContext, options: QueryComplexityOptions) { 154 | if ( 155 | !( 156 | typeof options.maximumComplexity === 'number' && 157 | options.maximumComplexity > 0 158 | ) 159 | ) { 160 | throw new Error('Maximum query complexity must be a positive number'); 161 | } 162 | 163 | this.context = context; 164 | this.complexity = 0; 165 | this.options = options; 166 | this.evaluatedNodes = 0; 167 | this.maxQueryNodes = options.maxQueryNodes ?? 10_000; 168 | this.includeDirectiveDef = this.context.getSchema().getDirective('include'); 169 | this.skipDirectiveDef = this.context.getSchema().getDirective('skip'); 170 | this.estimators = options.estimators; 171 | this.variableValues = {}; 172 | this.requestContext = options.context; 173 | 174 | this.OperationDefinition = { 175 | enter: this.onOperationDefinitionEnter, 176 | leave: this.onOperationDefinitionLeave, 177 | }; 178 | } 179 | 180 | onOperationDefinitionEnter(operation: OperationDefinitionNode): void { 181 | if ( 182 | typeof this.options.operationName === 'string' && 183 | this.options.operationName !== operation.name.value 184 | ) { 185 | return; 186 | } 187 | 188 | // Get variable values from variables that are passed from options, merged 189 | // with default values defined in the operation 190 | const { coerced, errors } = getVariableValues( 191 | this.context.getSchema(), 192 | // We have to create a new array here because input argument is not readonly in graphql ~14.6.0 193 | operation.variableDefinitions ? [...operation.variableDefinitions] : [], 194 | this.options.variables ?? {} 195 | ); 196 | if (errors && errors.length) { 197 | // We have input validation errors, report errors and abort 198 | errors.forEach((error) => this.context.reportError(error)); 199 | return; 200 | } 201 | this.variableValues = coerced; 202 | 203 | switch (operation.operation) { 204 | case 'query': 205 | this.complexity += this.nodeComplexity( 206 | operation, 207 | this.context.getSchema().getQueryType() 208 | ); 209 | break; 210 | case 'mutation': 211 | this.complexity += this.nodeComplexity( 212 | operation, 213 | this.context.getSchema().getMutationType() 214 | ); 215 | break; 216 | case 'subscription': 217 | this.complexity += this.nodeComplexity( 218 | operation, 219 | this.context.getSchema().getSubscriptionType() 220 | ); 221 | break; 222 | default: 223 | throw new Error( 224 | `Query complexity could not be calculated for operation of type ${operation.operation}` 225 | ); 226 | } 227 | } 228 | 229 | onOperationDefinitionLeave( 230 | operation: OperationDefinitionNode 231 | ): GraphQLError | void { 232 | if ( 233 | typeof this.options.operationName === 'string' && 234 | this.options.operationName !== operation.name.value 235 | ) { 236 | return; 237 | } 238 | 239 | if (this.options.onComplete) { 240 | this.options.onComplete(this.complexity); 241 | } 242 | 243 | if (this.complexity > this.options.maximumComplexity) { 244 | return this.context.reportError(this.createError()); 245 | } 246 | } 247 | 248 | nodeComplexity( 249 | node: 250 | | FieldNode 251 | | FragmentDefinitionNode 252 | | InlineFragmentNode 253 | | OperationDefinitionNode, 254 | typeDef: 255 | | GraphQLObjectType 256 | | GraphQLInterfaceType 257 | | GraphQLUnionType 258 | | undefined 259 | ): number { 260 | if (node.selectionSet && typeDef) { 261 | let fields: GraphQLFieldMap = {}; 262 | if ( 263 | typeDef instanceof GraphQLObjectType || 264 | typeDef instanceof GraphQLInterfaceType 265 | ) { 266 | fields = typeDef.getFields(); 267 | } 268 | 269 | // Determine all possible types of the current node 270 | let possibleTypeNames: string[]; 271 | if (isAbstractType(typeDef)) { 272 | possibleTypeNames = this.context 273 | .getSchema() 274 | .getPossibleTypes(typeDef) 275 | .map((t) => t.name); 276 | } else { 277 | possibleTypeNames = [typeDef.name]; 278 | } 279 | 280 | // Collect complexities for all possible types individually 281 | const selectionSetComplexities: ComplexityMap = 282 | node.selectionSet.selections.reduce( 283 | ( 284 | complexities: ComplexityMap, 285 | childNode: FieldNode | FragmentSpreadNode | InlineFragmentNode 286 | ): ComplexityMap => { 287 | this.evaluatedNodes++; 288 | if (this.evaluatedNodes >= this.maxQueryNodes) { 289 | throw new GraphQLError( 290 | 'Query exceeds the maximum allowed number of nodes.' 291 | ); 292 | } 293 | let innerComplexities = complexities; 294 | 295 | let includeNode = true; 296 | let skipNode = false; 297 | 298 | for (const directive of childNode.directives ?? []) { 299 | const directiveName = directive.name.value; 300 | switch (directiveName) { 301 | case 'include': { 302 | const values = getDirectiveValues( 303 | this.includeDirectiveDef, 304 | childNode, 305 | this.variableValues || {} 306 | ); 307 | if (typeof values.if === 'boolean') { 308 | includeNode = values.if; 309 | } 310 | break; 311 | } 312 | case 'skip': { 313 | const values = getDirectiveValues( 314 | this.skipDirectiveDef, 315 | childNode, 316 | this.variableValues || {} 317 | ); 318 | if (typeof values.if === 'boolean') { 319 | skipNode = values.if; 320 | } 321 | break; 322 | } 323 | } 324 | } 325 | 326 | if (!includeNode || skipNode) { 327 | return complexities; 328 | } 329 | 330 | switch (childNode.kind) { 331 | case Kind.FIELD: { 332 | let field = null; 333 | 334 | switch (childNode.name.value) { 335 | case SchemaMetaFieldDef.name: 336 | field = SchemaMetaFieldDef; 337 | break; 338 | case TypeMetaFieldDef.name: 339 | field = TypeMetaFieldDef; 340 | break; 341 | case TypeNameMetaFieldDef.name: 342 | field = TypeNameMetaFieldDef; 343 | break; 344 | default: 345 | field = fields[childNode.name.value]; 346 | break; 347 | } 348 | 349 | // Invalid field, should be caught by other validation rules 350 | if (!field) { 351 | break; 352 | } 353 | const fieldType = getNamedType(field.type); 354 | 355 | // Get arguments 356 | let args: { [key: string]: any }; 357 | try { 358 | args = getArgumentValues( 359 | field, 360 | childNode, 361 | this.variableValues || {} 362 | ); 363 | } catch (e) { 364 | this.context.reportError(e); 365 | return complexities; 366 | } 367 | 368 | // Check if we have child complexity 369 | let childComplexity = 0; 370 | if (isCompositeType(fieldType)) { 371 | childComplexity = this.nodeComplexity(childNode, fieldType); 372 | } 373 | 374 | // Run estimators one after another and return first valid complexity 375 | // score 376 | const estimatorArgs: ComplexityEstimatorArgs = { 377 | childComplexity, 378 | args, 379 | field, 380 | node: childNode, 381 | type: typeDef, 382 | context: this.requestContext, 383 | }; 384 | const validScore = this.estimators.find((estimator) => { 385 | const tmpComplexity = estimator(estimatorArgs); 386 | 387 | if ( 388 | typeof tmpComplexity === 'number' && 389 | !isNaN(tmpComplexity) 390 | ) { 391 | innerComplexities = addComplexities( 392 | tmpComplexity, 393 | complexities, 394 | possibleTypeNames 395 | ); 396 | return true; 397 | } 398 | 399 | return false; 400 | }); 401 | if (!validScore) { 402 | this.context.reportError( 403 | new GraphQLError( 404 | `No complexity could be calculated for field ${typeDef.name}.${field.name}. ` + 405 | 'At least one complexity estimator has to return a complexity score.' 406 | ) 407 | ); 408 | return complexities; 409 | } 410 | break; 411 | } 412 | case Kind.FRAGMENT_SPREAD: { 413 | const fragment = this.context.getFragment(childNode.name.value); 414 | // Unknown fragment, should be caught by other validation rules 415 | if (!fragment) { 416 | break; 417 | } 418 | const fragmentType = this.context 419 | .getSchema() 420 | .getType(fragment.typeCondition.name.value); 421 | // Invalid fragment type, ignore. Should be caught by other validation rules 422 | if (!isCompositeType(fragmentType)) { 423 | break; 424 | } 425 | const nodeComplexity = this.nodeComplexity( 426 | fragment, 427 | fragmentType 428 | ); 429 | if (isAbstractType(fragmentType)) { 430 | // Add fragment complexity for all possible types 431 | innerComplexities = addComplexities( 432 | nodeComplexity, 433 | complexities, 434 | this.context 435 | .getSchema() 436 | .getPossibleTypes(fragmentType) 437 | .map((t) => t.name) 438 | ); 439 | } else { 440 | // Add complexity for object type 441 | innerComplexities = addComplexities( 442 | nodeComplexity, 443 | complexities, 444 | [fragmentType.name] 445 | ); 446 | } 447 | break; 448 | } 449 | case Kind.INLINE_FRAGMENT: { 450 | let inlineFragmentType: GraphQLNamedType = typeDef; 451 | if (childNode.typeCondition && childNode.typeCondition.name) { 452 | inlineFragmentType = this.context 453 | .getSchema() 454 | .getType(childNode.typeCondition.name.value); 455 | if (!isCompositeType(inlineFragmentType)) { 456 | break; 457 | } 458 | } 459 | 460 | const nodeComplexity = this.nodeComplexity( 461 | childNode, 462 | inlineFragmentType 463 | ); 464 | if (isAbstractType(inlineFragmentType)) { 465 | // Add fragment complexity for all possible types 466 | innerComplexities = addComplexities( 467 | nodeComplexity, 468 | complexities, 469 | this.context 470 | .getSchema() 471 | .getPossibleTypes(inlineFragmentType) 472 | .map((t) => t.name) 473 | ); 474 | } else { 475 | // Add complexity for object type 476 | innerComplexities = addComplexities( 477 | nodeComplexity, 478 | complexities, 479 | [inlineFragmentType.name] 480 | ); 481 | } 482 | break; 483 | } 484 | default: { 485 | innerComplexities = addComplexities( 486 | this.nodeComplexity(childNode, typeDef), 487 | complexities, 488 | possibleTypeNames 489 | ); 490 | break; 491 | } 492 | } 493 | 494 | return innerComplexities; 495 | }, 496 | {} 497 | ); 498 | // Only return max complexity of all possible types 499 | if (!selectionSetComplexities) { 500 | return NaN; 501 | } 502 | return Math.max(...Object.values(selectionSetComplexities), 0); 503 | } 504 | return 0; 505 | } 506 | 507 | createError(): GraphQLError { 508 | if (typeof this.options.createError === 'function') { 509 | return this.options.createError( 510 | this.options.maximumComplexity, 511 | this.complexity 512 | ); 513 | } 514 | return new GraphQLError( 515 | queryComplexityMessage(this.options.maximumComplexity, this.complexity) 516 | ); 517 | } 518 | } 519 | 520 | /** 521 | * Adds a complexity to the complexity map for all possible types 522 | * @param complexity 523 | * @param complexityMap 524 | * @param possibleTypes 525 | */ 526 | function addComplexities( 527 | complexity: number, 528 | complexityMap: ComplexityMap, 529 | possibleTypes: string[] 530 | ): ComplexityMap { 531 | for (const type of possibleTypes) { 532 | if (Object.prototype.hasOwnProperty.call(complexityMap, type)) { 533 | complexityMap[type] += complexity; 534 | } else { 535 | complexityMap[type] = complexity; 536 | } 537 | } 538 | return complexityMap; 539 | } 540 | -------------------------------------------------------------------------------- /src/__tests__/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | '@typescript-eslint/explicit-function-return-type': "off" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/__tests__/QueryComplexity-test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Ivo Meißner on 28.07.17. 3 | */ 4 | 5 | import { 6 | parse, 7 | TypeInfo, 8 | visit, 9 | visitWithTypeInfo, 10 | validate, 11 | specifiedRules, 12 | } from 'graphql'; 13 | 14 | import { expect } from 'chai'; 15 | 16 | import schema from './fixtures/schema.js'; 17 | 18 | import ComplexityVisitor, { 19 | getComplexity, 20 | ComplexityEstimator, 21 | } from '../QueryComplexity.js'; 22 | import defaultExport, { 23 | createComplexityRule, 24 | simpleEstimator, 25 | directiveEstimator, 26 | fieldExtensionsEstimator, 27 | } from '../index.js'; 28 | import { CompatibleValidationContext } from './fixtures/CompatibleValidationContext.js'; 29 | 30 | describe('QueryComplexity analysis', () => { 31 | const typeInfo = new TypeInfo(schema); 32 | 33 | it('exports createComplexityRule as default and named export in index', () => { 34 | expect(createComplexityRule).to.equal(defaultExport); 35 | expect(typeof createComplexityRule).to.equal('function'); 36 | }); 37 | 38 | it('should calculate complexity', () => { 39 | const ast = parse(` 40 | query { 41 | variableScalar(count: 10) 42 | } 43 | `); 44 | 45 | const complexity = getComplexity({ 46 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 47 | schema, 48 | query: ast, 49 | }); 50 | expect(complexity).to.equal(1); 51 | }); 52 | 53 | it('should respect @include(if: false) via default variable value', () => { 54 | const ast = parse(` 55 | query Foo ($shouldSkip: Boolean = false) { 56 | variableScalar(count: 10) @include(if: $shouldSkip) 57 | } 58 | `); 59 | 60 | const complexity = getComplexity({ 61 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 62 | schema, 63 | query: ast, 64 | }); 65 | expect(complexity).to.equal(0); 66 | }); 67 | 68 | it('should respect @include(if: false)', () => { 69 | const ast = parse(` 70 | query { 71 | variableScalar(count: 10) @include(if: false) 72 | } 73 | `); 74 | 75 | const complexity = getComplexity({ 76 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 77 | schema, 78 | query: ast, 79 | }); 80 | expect(complexity).to.equal(0); 81 | }); 82 | 83 | it('should respect @include(if: true)', () => { 84 | const ast = parse(` 85 | query { 86 | variableScalar(count: 10) @include(if: true) 87 | } 88 | `); 89 | 90 | const complexity = getComplexity({ 91 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 92 | schema, 93 | query: ast, 94 | }); 95 | expect(complexity).to.equal(1); 96 | }); 97 | 98 | it('should respect @skip(if: true)', () => { 99 | const ast = parse(` 100 | query { 101 | variableScalar(count: 10) @skip(if: true) 102 | } 103 | `); 104 | 105 | const complexity = getComplexity({ 106 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 107 | schema, 108 | query: ast, 109 | }); 110 | expect(complexity).to.equal(0); 111 | }); 112 | 113 | it('should respect @skip(if: false)', () => { 114 | const ast = parse(` 115 | query { 116 | variableScalar(count: 10) @skip(if: false) 117 | } 118 | `); 119 | 120 | const complexity = getComplexity({ 121 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 122 | schema, 123 | query: ast, 124 | }); 125 | expect(complexity).to.equal(1); 126 | }); 127 | 128 | it('should respect @skip(if: false) @include(if: true)', () => { 129 | const ast = parse(` 130 | query { 131 | variableScalar(count: 10) @skip(if: false) @include(if: true) 132 | } 133 | `); 134 | 135 | const complexity = getComplexity({ 136 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 137 | schema, 138 | query: ast, 139 | }); 140 | expect(complexity).to.equal(1); 141 | }); 142 | 143 | it('should calculate complexity with variables', () => { 144 | const ast = parse(` 145 | query Q($count: Int) { 146 | variableScalar(count: $count) 147 | } 148 | `); 149 | 150 | const complexity = getComplexity({ 151 | estimators: [ 152 | fieldExtensionsEstimator(), 153 | simpleEstimator({ defaultComplexity: 1 }), 154 | ], 155 | schema, 156 | query: ast, 157 | variables: { 158 | count: 5, 159 | }, 160 | }); 161 | expect(complexity).to.equal(50); 162 | }); 163 | 164 | it('should calculate complexity with variables and default value', () => { 165 | const ast = parse(` 166 | query Q($count: Int = 5) { 167 | variableScalar(count: $count) 168 | } 169 | `); 170 | 171 | const complexity = getComplexity({ 172 | estimators: [ 173 | fieldExtensionsEstimator(), 174 | simpleEstimator({ defaultComplexity: 1 }), 175 | ], 176 | schema, 177 | query: ast, 178 | variables: {}, 179 | }); 180 | expect(complexity).to.equal(50); 181 | }); 182 | 183 | it('should not allow negative cost', () => { 184 | const ast = parse(` 185 | query { 186 | variableScalar(count: -100) 187 | } 188 | `); 189 | 190 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 191 | const visitor = new ComplexityVisitor(context, { 192 | maximumComplexity: 100, 193 | estimators: [simpleEstimator({ defaultComplexity: -100 })], 194 | }); 195 | 196 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 197 | expect(visitor.complexity).to.equal(0); 198 | }); 199 | 200 | it('should ignore unknown fragments', () => { 201 | const ast = parse(` 202 | query { 203 | ...UnknownFragment 204 | variableScalar(count: 100) 205 | } 206 | `); 207 | 208 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 209 | const visitor = new ComplexityVisitor(context, { 210 | maximumComplexity: 100, 211 | estimators: [simpleEstimator({ defaultComplexity: 10 })], 212 | }); 213 | 214 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 215 | expect(visitor.complexity).to.equal(10); 216 | }); 217 | 218 | it('should ignore inline fragment on unknown type', () => { 219 | const ast = parse(` 220 | query { 221 | ...on UnknownType { 222 | variableScalar(count: 100) 223 | } 224 | } 225 | `); 226 | 227 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 228 | const visitor = new ComplexityVisitor(context, { 229 | maximumComplexity: 100, 230 | estimators: [simpleEstimator({ defaultComplexity: 10 })], 231 | }); 232 | 233 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 234 | expect(visitor.complexity).to.equal(0); 235 | }); 236 | 237 | it('should ignore fragment on unknown type', () => { 238 | const ast = parse(` 239 | query { 240 | ...F 241 | } 242 | fragment F on UnknownType { 243 | variableScalar(count: 100) 244 | } 245 | `); 246 | 247 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 248 | const visitor = new ComplexityVisitor(context, { 249 | maximumComplexity: 100, 250 | estimators: [simpleEstimator({ defaultComplexity: 10 })], 251 | }); 252 | 253 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 254 | expect(visitor.complexity).to.equal(0); 255 | }); 256 | 257 | it('should report errors for unused variables', () => { 258 | const ast = parse(` 259 | query ($unusedVar: ID!) { 260 | variableScalar(count: 100) 261 | } 262 | `); 263 | 264 | const errors = validate(schema, ast, [ 265 | createComplexityRule({ 266 | maximumComplexity: 1000, 267 | estimators: [ 268 | simpleEstimator({ 269 | defaultComplexity: 1, 270 | }), 271 | ], 272 | variables: { 273 | unusedVar: 'someID', 274 | }, 275 | }), 276 | ]); 277 | expect(errors).to.have.length(1); 278 | expect(errors[0].message).to.contain('$unusedVar'); 279 | }); 280 | 281 | it('should ignore unknown field', () => { 282 | const ast = parse(` 283 | query { 284 | unknownField 285 | } 286 | `); 287 | 288 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 289 | const visitor = new ComplexityVisitor(context, { 290 | maximumComplexity: 100, 291 | estimators: [simpleEstimator({ defaultComplexity: 10 })], 292 | }); 293 | 294 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 295 | expect(visitor.complexity).to.equal(0); 296 | }); 297 | 298 | it('should report error above threshold', () => { 299 | const ast = parse(` 300 | query { 301 | variableScalar(count: 100) 302 | } 303 | `); 304 | 305 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 306 | const visitor = new ComplexityVisitor(context, { 307 | maximumComplexity: 100, 308 | estimators: [ 309 | fieldExtensionsEstimator(), 310 | simpleEstimator({ 311 | defaultComplexity: 1, 312 | }), 313 | ], 314 | }); 315 | 316 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 317 | expect(visitor.complexity).to.equal(1000); 318 | expect(context.getErrors().length).to.equal(1); 319 | expect(context.getErrors()[0].message).to.equal( 320 | 'The query exceeds the maximum complexity of 100. Actual complexity is 1000' 321 | ); 322 | }); 323 | 324 | it('should add inline fragments', () => { 325 | const ast = parse(` 326 | query { 327 | variableScalar(count: 5) 328 | ...on Query { 329 | scalar 330 | alias: scalar 331 | } 332 | } 333 | `); 334 | 335 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 336 | const visitor = new ComplexityVisitor(context, { 337 | maximumComplexity: 100, 338 | estimators: [ 339 | fieldExtensionsEstimator(), 340 | simpleEstimator({ 341 | defaultComplexity: 1, 342 | }), 343 | ], 344 | }); 345 | 346 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 347 | expect(visitor.complexity).to.equal(52); 348 | }); 349 | 350 | it('should add fragments', () => { 351 | const ast = parse(` 352 | query { 353 | scalar 354 | ...QueryFragment 355 | } 356 | 357 | fragment QueryFragment on Query { 358 | variableScalar(count: 2) 359 | } 360 | `); 361 | 362 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 363 | const visitor = new ComplexityVisitor(context, { 364 | maximumComplexity: 100, 365 | estimators: [ 366 | fieldExtensionsEstimator(), 367 | simpleEstimator({ 368 | defaultComplexity: 1, 369 | }), 370 | ], 371 | }); 372 | 373 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 374 | expect(visitor.complexity).to.equal(21); 375 | }); 376 | 377 | it('should add complexity for union types', () => { 378 | const ast = parse(` 379 | query { 380 | union { 381 | ...on Item { 382 | scalar 383 | complexScalar 384 | } 385 | } 386 | } 387 | `); 388 | 389 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 390 | const visitor = new ComplexityVisitor(context, { 391 | maximumComplexity: 100, 392 | estimators: [ 393 | fieldExtensionsEstimator(), 394 | simpleEstimator({ 395 | defaultComplexity: 1, 396 | }), 397 | ], 398 | }); 399 | 400 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 401 | expect(visitor.complexity).to.equal(22); 402 | }); 403 | 404 | it('should add complexity for interface types', () => { 405 | const ast = parse(` 406 | query { 407 | interface { 408 | name 409 | ...on NameInterface { 410 | name 411 | } 412 | } 413 | } 414 | `); 415 | 416 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 417 | const visitor = new ComplexityVisitor(context, { 418 | maximumComplexity: 100, 419 | estimators: [ 420 | fieldExtensionsEstimator(), 421 | simpleEstimator({ 422 | defaultComplexity: 1, 423 | }), 424 | ], 425 | }); 426 | 427 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 428 | expect(visitor.complexity).to.equal(3); 429 | }); 430 | 431 | it('should add complexity for inline fragments without type condition', () => { 432 | const ast = parse(` 433 | query { 434 | interface { 435 | ... { 436 | name 437 | } 438 | } 439 | } 440 | `); 441 | 442 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 443 | const visitor = new ComplexityVisitor(context, { 444 | maximumComplexity: 100, 445 | estimators: [ 446 | fieldExtensionsEstimator(), 447 | simpleEstimator({ 448 | defaultComplexity: 1, 449 | }), 450 | ], 451 | }); 452 | 453 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 454 | expect(visitor.complexity).to.equal(2); 455 | }); 456 | 457 | it('should add complexity for enum types', () => { 458 | const ast = parse(` 459 | query { 460 | enum 461 | } 462 | `); 463 | 464 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 465 | const visitor = new ComplexityVisitor(context, { 466 | maximumComplexity: 100, 467 | estimators: [ 468 | fieldExtensionsEstimator(), 469 | simpleEstimator({ 470 | defaultComplexity: 1, 471 | }), 472 | ], 473 | }); 474 | 475 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 476 | expect(visitor.complexity).to.equal(1); 477 | }); 478 | 479 | it('should report error on a missing non-null argument', () => { 480 | const ast = parse(` 481 | query { 482 | requiredArgs 483 | } 484 | `); 485 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 486 | const visitor = new ComplexityVisitor(context, { 487 | maximumComplexity: 100, 488 | estimators: [ 489 | fieldExtensionsEstimator(), 490 | simpleEstimator({ 491 | defaultComplexity: 1, 492 | }), 493 | ], 494 | }); 495 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 496 | expect(context.getErrors().length).to.equal(1); 497 | expect(context.getErrors()[0].message).to.equal( 498 | 'Argument "count" of required type "Int!" was not provided.' 499 | ); 500 | }); 501 | 502 | it('should report error when no estimator is configured', () => { 503 | const ast = parse(` 504 | query { 505 | scalar 506 | } 507 | `); 508 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 509 | const visitor = new ComplexityVisitor(context, { 510 | maximumComplexity: 100, 511 | estimators: [], 512 | }); 513 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 514 | expect(context.getErrors().length).to.equal(1); 515 | expect(context.getErrors()[0].message).to.equal( 516 | 'No complexity could be calculated for field Query.scalar. ' + 517 | 'At least one complexity estimator has to return a complexity score.' 518 | ); 519 | }); 520 | 521 | it('should report error when no estimator returns value', () => { 522 | const ast = parse(` 523 | query { 524 | scalar 525 | } 526 | `); 527 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 528 | const visitor = new ComplexityVisitor(context, { 529 | maximumComplexity: 100, 530 | estimators: [fieldExtensionsEstimator()], 531 | }); 532 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 533 | expect(context.getErrors().length).to.equal(1); 534 | expect(context.getErrors()[0].message).to.equal( 535 | 'No complexity could be calculated for field Query.scalar. ' + 536 | 'At least one complexity estimator has to return a complexity score.' 537 | ); 538 | }); 539 | 540 | it('should throw error when no astNode available on field when using directiveEstimator', () => { 541 | const ast = parse(` 542 | query { 543 | _service { 544 | sdl 545 | } 546 | } 547 | `); 548 | 549 | expect(() => { 550 | getComplexity({ 551 | estimators: [directiveEstimator()], 552 | schema, 553 | query: ast, 554 | }); 555 | }).to.throw(/No complexity could be calculated for field Query._service/); 556 | }); 557 | 558 | it('should skip complexity calculation by directiveEstimator when no astNode available on field', () => { 559 | const ast = parse(` 560 | query { 561 | _service { 562 | sdl 563 | } 564 | } 565 | `); 566 | 567 | const complexity = getComplexity({ 568 | estimators: [ 569 | directiveEstimator(), 570 | simpleEstimator({ 571 | defaultComplexity: 1, 572 | }), 573 | ], 574 | schema, 575 | query: ast, 576 | }); 577 | expect(complexity).to.equal(2); 578 | }); 579 | 580 | it('should calculate complexity for specific operation', () => { 581 | const ast = parse(` 582 | query Primary { 583 | scalar 584 | complexScalar 585 | } 586 | 587 | query Secondary { 588 | complexScalar 589 | } 590 | `); 591 | 592 | const complexity1 = getComplexity({ 593 | estimators: [ 594 | fieldExtensionsEstimator(), 595 | simpleEstimator({ defaultComplexity: 1 }), 596 | ], 597 | schema, 598 | query: ast, 599 | }); 600 | expect(complexity1).to.equal(41); 601 | 602 | const complexity2 = getComplexity({ 603 | estimators: [ 604 | fieldExtensionsEstimator(), 605 | simpleEstimator({ defaultComplexity: 1 }), 606 | ], 607 | schema, 608 | query: ast, 609 | operationName: 'Secondary', 610 | }); 611 | expect(complexity2).to.equal(20); 612 | }); 613 | 614 | it('should calculate complexity for meta fields', () => { 615 | const query = parse(` 616 | query Primary { 617 | __typename 618 | __type(name: "Primary") { 619 | name 620 | } 621 | __schema { 622 | types { 623 | name 624 | } 625 | } 626 | } 627 | `); 628 | 629 | const complexity = getComplexity({ 630 | estimators: [ 631 | fieldExtensionsEstimator(), 632 | simpleEstimator({ defaultComplexity: 1 }), 633 | ], 634 | schema, 635 | query, 636 | }); 637 | 638 | expect(complexity).to.equal(6); 639 | }); 640 | 641 | it('should calculate max complexity for fragment on union type', () => { 642 | const query = parse(` 643 | query Primary { 644 | union { 645 | ...on Item { 646 | scalar 647 | } 648 | ...on SecondItem { 649 | scalar 650 | } 651 | ...on SecondItem { 652 | scalar 653 | } 654 | } 655 | } 656 | `); 657 | 658 | const complexity = getComplexity({ 659 | estimators: [ 660 | fieldExtensionsEstimator(), 661 | simpleEstimator({ defaultComplexity: 1 }), 662 | ], 663 | schema, 664 | query, 665 | }); 666 | expect(complexity).to.equal(3); 667 | }); 668 | 669 | it('should calculate max complexity for nested fragment on union type', () => { 670 | const query = parse(` 671 | query Primary { 672 | union { 673 | ...on Union { 674 | ...on Item { 675 | complexScalar1: complexScalar 676 | } 677 | } 678 | ...on SecondItem { 679 | scalar 680 | } 681 | ...on Item { 682 | complexScalar2: complexScalar 683 | } 684 | } 685 | } 686 | `); 687 | 688 | const complexity = getComplexity({ 689 | estimators: [ 690 | fieldExtensionsEstimator(), 691 | simpleEstimator({ defaultComplexity: 0 }), 692 | ], 693 | schema, 694 | query, 695 | }); 696 | expect(complexity).to.equal(40); 697 | }); 698 | 699 | it('should calculate max complexity for nested fragment on union type + named fragment', () => { 700 | const query = parse(` 701 | query Primary { 702 | union { 703 | ...F 704 | ...on SecondItem { 705 | scalar 706 | } 707 | ...on Item { 708 | complexScalar2: complexScalar 709 | } 710 | } 711 | } 712 | fragment F on Union { 713 | ...on Item { 714 | complexScalar1: complexScalar 715 | } 716 | } 717 | `); 718 | 719 | const complexity = getComplexity({ 720 | estimators: [ 721 | fieldExtensionsEstimator(), 722 | simpleEstimator({ defaultComplexity: 0 }), 723 | ], 724 | schema, 725 | query, 726 | }); 727 | expect(complexity).to.equal(40); 728 | }); 729 | 730 | it('should calculate max complexity for multiple interfaces', () => { 731 | const query = parse(` 732 | query Primary { 733 | interface { 734 | ...on Query { 735 | complexScalar 736 | } 737 | ...on SecondItem { 738 | name 739 | name2: name 740 | } 741 | } 742 | } 743 | `); 744 | 745 | const complexity = getComplexity({ 746 | estimators: [ 747 | fieldExtensionsEstimator(), 748 | simpleEstimator({ defaultComplexity: 1 }), 749 | ], 750 | schema, 751 | query, 752 | }); 753 | expect(complexity).to.equal(21); 754 | }); 755 | 756 | it('should calculate max complexity for multiple interfaces with nesting', () => { 757 | const query = parse(` 758 | query Primary { 759 | interface { 760 | ...on Query { 761 | complexScalar 762 | ...on Query { 763 | a: complexScalar 764 | } 765 | } 766 | ...on SecondItem { 767 | name 768 | name2: name 769 | } 770 | } 771 | } 772 | `); 773 | 774 | const complexity = getComplexity({ 775 | estimators: [ 776 | fieldExtensionsEstimator(), 777 | simpleEstimator({ defaultComplexity: 1 }), 778 | ], 779 | schema, 780 | query, 781 | }); 782 | expect(complexity).to.equal(41); // 1 for interface, 20 * 2 for complexScalar 783 | }); 784 | 785 | it('should calculate max complexity for multiple interfaces with nesting + named fragment', () => { 786 | const query = parse(` 787 | query Primary { 788 | interface { 789 | ...F 790 | ...on SecondItem { 791 | name 792 | name2: name 793 | } 794 | } 795 | } 796 | 797 | fragment F on Query { 798 | complexScalar 799 | ...on Query { 800 | a: complexScalar 801 | } 802 | } 803 | `); 804 | 805 | const complexity = getComplexity({ 806 | estimators: [ 807 | fieldExtensionsEstimator(), 808 | simpleEstimator({ defaultComplexity: 1 }), 809 | ], 810 | schema, 811 | query, 812 | }); 813 | expect(complexity).to.equal(41); // 1 for interface, 20 * 2 for complexScalar 814 | }); 815 | 816 | it('should include the current node in the estimator args', () => { 817 | const ast = parse(` 818 | query { 819 | nonNullItem { 820 | scalar 821 | complexScalar 822 | variableScalar(count: 10) 823 | } 824 | } 825 | `); 826 | 827 | const fieldCountEstimator: ComplexityEstimator = ({ node }) => { 828 | if (node.selectionSet) { 829 | return 10 * node.selectionSet.selections.length; 830 | } 831 | return 0; 832 | }; 833 | 834 | const complexity = getComplexity({ 835 | estimators: [fieldCountEstimator], 836 | schema, 837 | query: ast, 838 | }); 839 | expect(complexity).to.equal(30); // 3 fields on nonNullItem * 10 840 | }); 841 | 842 | it('should handle invalid argument values for multiple query fields', () => { 843 | const ast = parse(` 844 | query { 845 | requiredArgs(count: x) { 846 | scalar 847 | complexScalar 848 | } 849 | nonNullItem { 850 | scalar 851 | complexScalar 852 | variableScalar(count: 10) 853 | } 854 | } 855 | `); 856 | 857 | validate(schema, ast, [ 858 | ...specifiedRules, 859 | createComplexityRule({ 860 | maximumComplexity: 1000, 861 | estimators: [ 862 | simpleEstimator({ 863 | defaultComplexity: 1, 864 | }), 865 | ], 866 | }), 867 | ]); 868 | }); 869 | 870 | it('passed context to estimators', () => { 871 | const ast = parse(` 872 | query { 873 | scalar 874 | requiredArgs(count: 10) { 875 | scalar 876 | } 877 | } 878 | `); 879 | 880 | const contextEstimator: ComplexityEstimator = ({ 881 | context, 882 | childComplexity, 883 | }) => { 884 | return context['complexityMultiplier'] * (childComplexity || 1); 885 | }; 886 | 887 | const complexity = getComplexity({ 888 | estimators: [contextEstimator], 889 | schema, 890 | query: ast, 891 | context: { complexityMultiplier: 5 }, 892 | }); 893 | 894 | // query.scalar(5) + query.requiredArgs(5) * requiredArgs.scalar(5) 895 | expect(complexity).to.equal(30); 896 | }); 897 | 898 | it('reports variable coercion errors', () => { 899 | const ast = parse(` 900 | query ($input: RGB!){ 901 | enumInputArg(enum: $input) 902 | } 903 | `); 904 | 905 | const errors = validate(schema, ast, [ 906 | createComplexityRule({ 907 | maximumComplexity: 1000, 908 | estimators: [ 909 | simpleEstimator({ 910 | defaultComplexity: 1, 911 | }), 912 | ], 913 | variables: { 914 | input: 'INVALIDVALUE', 915 | }, 916 | }), 917 | ]); 918 | expect(errors).to.have.length(1); 919 | expect(errors[0].message).to.contain('INVALIDVALUE'); 920 | }); 921 | 922 | it('falls back to 0 complexity for GraphQL operations not supported by the schema', () => { 923 | const ast = parse(` 924 | subscription { 925 | foo 926 | } 927 | `); 928 | 929 | const errors = validate(schema, ast, [ 930 | createComplexityRule({ 931 | maximumComplexity: 1000, 932 | estimators: [ 933 | simpleEstimator({ 934 | defaultComplexity: 1, 935 | }), 936 | ], 937 | }), 938 | ]); 939 | 940 | expect(errors).to.have.length(0); 941 | }); 942 | 943 | it('should reject queries that exceed the maximum number of fragment nodes', () => { 944 | const query = parse(` 945 | query { 946 | ...F 947 | ...F 948 | } 949 | fragment F on Query { 950 | scalar 951 | } 952 | `); 953 | 954 | expect(() => 955 | getComplexity({ 956 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 957 | schema, 958 | query, 959 | maxQueryNodes: 1, 960 | variables: {}, 961 | }) 962 | ).to.throw('Query exceeds the maximum allowed number of nodes.'); 963 | }); 964 | 965 | it('should reject queries that exceed the maximum number of field nodes', () => { 966 | const query = parse(` 967 | query { 968 | scalar 969 | scalar1: scalar 970 | scalar2: scalar 971 | scalar3: scalar 972 | scalar4: scalar 973 | scalar5: scalar 974 | scalar6: scalar 975 | scalar7: scalar 976 | } 977 | `); 978 | 979 | expect(() => 980 | getComplexity({ 981 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 982 | schema, 983 | query, 984 | maxQueryNodes: 1, 985 | variables: {}, 986 | }) 987 | ).to.throw('Query exceeds the maximum allowed number of nodes.'); 988 | }); 989 | 990 | it('should limit the number of query nodes to 10_000 by default', () => { 991 | const failingQuery = parse(` 992 | query { 993 | ${Array.from({ length: 10_000 }, (_, i) => `scalar${i}: scalar`).join( 994 | '\n' 995 | )} 996 | } 997 | `); 998 | 999 | expect(() => 1000 | getComplexity({ 1001 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 1002 | schema, 1003 | query: failingQuery, 1004 | variables: {}, 1005 | }) 1006 | ).to.throw('Query exceeds the maximum allowed number of nodes.'); 1007 | 1008 | const passingQuery = parse(` 1009 | query { 1010 | ${Array.from({ length: 9999 }, (_, i) => `scalar${i}: scalar`).join( 1011 | '\n' 1012 | )} 1013 | } 1014 | `); 1015 | 1016 | expect(() => 1017 | getComplexity({ 1018 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 1019 | schema, 1020 | query: passingQuery, 1021 | variables: {}, 1022 | }) 1023 | ).not.to.throw(); 1024 | }); 1025 | }); 1026 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/CompatibleValidationContext.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLError, 3 | TypeInfo, 4 | ValidationContext, 5 | GraphQLSchema, 6 | DocumentNode, 7 | } from 'graphql'; 8 | 9 | /** 10 | * This class is used to test that validation errors are raised correctly 11 | * 12 | * A compatibility layer is necessary to support graphql versions since 15.0.0 13 | * *as well as* versions prior to 14.5.0 with the same test, because older 14 | * versions of `ValidationContext` only expose a `getErrors` API and newer 15 | * versions only expose the `onError` API via a fourth constructor argument. 16 | * 17 | * Once we drop support for versions older than 14.5.0 this layer will no 18 | * longer be necessary and tests may use `ValidationContext` directly using the 19 | * `onError` API. 20 | */ 21 | export class CompatibleValidationContext extends ValidationContext { 22 | private errors: GraphQLError[] = []; 23 | 24 | constructor(schema: GraphQLSchema, ast: DocumentNode, typeInfo: TypeInfo) { 25 | super(schema, ast, typeInfo, (err) => this.errors.push(err)); 26 | } 27 | 28 | getErrors(): ReadonlyArray { 29 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 30 | // @ts-ignore 31 | return super.getErrors ? super.getErrors() : this.errors; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/__tests__/fixtures/schema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Ivo Meißner on 28.07.17. 3 | */ 4 | 5 | import { 6 | GraphQLList, 7 | GraphQLObjectType, 8 | GraphQLNonNull, 9 | GraphQLSchema, 10 | GraphQLString, 11 | GraphQLInt, 12 | GraphQLEnumType, 13 | GraphQLUnionType, 14 | GraphQLInterfaceType, 15 | } from 'graphql'; 16 | 17 | import { ComplexityEstimatorArgs } from '../../QueryComplexity.js'; 18 | import { compatResolveType } from '../utils/compatResolveType.js'; 19 | 20 | const Item: GraphQLObjectType = new GraphQLObjectType({ 21 | name: 'Item', 22 | fields: () => ({ 23 | variableList: { 24 | type: Item, 25 | extensions: { 26 | complexity: (args: ComplexityEstimatorArgs) => 27 | args.childComplexity * (args.args.count || 10), 28 | }, 29 | args: { 30 | count: { 31 | type: GraphQLInt, 32 | }, 33 | }, 34 | }, 35 | scalar: { type: GraphQLString }, 36 | complexScalar: { type: GraphQLString, extensions: { complexity: 20 } }, 37 | variableScalar: { 38 | type: Item, 39 | extensions: { 40 | complexity: (args: ComplexityEstimatorArgs) => 41 | 10 * (args.args.count || 10), 42 | }, 43 | args: { 44 | count: { 45 | type: GraphQLInt, 46 | }, 47 | }, 48 | }, 49 | list: { type: new GraphQLList(Item) }, 50 | nonNullItem: { 51 | type: new GraphQLNonNull(Item), 52 | resolve: () => ({}), 53 | }, 54 | nonNullList: { 55 | type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Item))), 56 | resolve: () => [], 57 | }, 58 | }), 59 | }); 60 | 61 | const NameInterface = new GraphQLInterfaceType({ 62 | name: 'NameInterface', 63 | fields: { 64 | name: { type: GraphQLString }, 65 | }, 66 | resolveType: compatResolveType(Item), 67 | }); 68 | 69 | const SecondItem = new GraphQLObjectType({ 70 | name: 'SecondItem', 71 | fields: () => ({ 72 | name: { type: GraphQLString }, 73 | scalar: { type: GraphQLString }, 74 | }), 75 | interfaces: [NameInterface], 76 | }); 77 | 78 | const EnumType = new GraphQLEnumType({ 79 | name: 'RGB', 80 | values: { 81 | RED: { value: 0 }, 82 | GREEN: { value: 1 }, 83 | BLUE: { value: 2 }, 84 | }, 85 | }); 86 | 87 | const Union = new GraphQLUnionType({ 88 | name: 'Union', 89 | types: [Item, SecondItem], 90 | resolveType: compatResolveType(Item), 91 | }); 92 | 93 | const UnionInterface = new GraphQLInterfaceType({ 94 | name: 'UnionInterface', 95 | fields: () => ({ 96 | union: { type: Union }, 97 | }), 98 | resolveType: compatResolveType(Item), 99 | }); 100 | 101 | const SDLInterface = new GraphQLInterfaceType({ 102 | name: 'SDLInterface', 103 | fields: { 104 | sdl: { type: GraphQLString }, 105 | }, 106 | resolveType: () => 'SDL', 107 | }); 108 | 109 | const SDL = new GraphQLObjectType({ 110 | name: 'SDL', 111 | fields: { 112 | sdl: { type: GraphQLString }, 113 | }, 114 | interfaces: () => [SDLInterface], 115 | }); 116 | 117 | const Query = new GraphQLObjectType({ 118 | name: 'Query', 119 | fields: () => ({ 120 | name: { type: GraphQLString }, 121 | variableList: { 122 | type: Item, 123 | extensions: { 124 | complexity: (args: ComplexityEstimatorArgs) => 125 | args.childComplexity * (args.args.count || 10), 126 | }, 127 | args: { 128 | count: { 129 | type: GraphQLInt, 130 | }, 131 | }, 132 | }, 133 | interface: { type: NameInterface }, 134 | enum: { type: EnumType }, 135 | scalar: { type: GraphQLString }, 136 | complexScalar: { type: GraphQLString, extensions: { complexity: 20 } }, 137 | union: { type: Union }, 138 | variableScalar: { 139 | type: Item, 140 | extensions: { 141 | complexity: (args: ComplexityEstimatorArgs) => 142 | 10 * (args.args.count || 10), 143 | }, 144 | args: { 145 | count: { 146 | type: GraphQLInt, 147 | }, 148 | }, 149 | }, 150 | list: { type: new GraphQLList(Item) }, 151 | nonNullItem: { 152 | type: new GraphQLNonNull(Item), 153 | resolve: () => ({}), 154 | }, 155 | nonNullList: { 156 | type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Item))), 157 | resolve: () => [], 158 | }, 159 | requiredArgs: { 160 | type: Item, 161 | args: { 162 | count: { 163 | type: new GraphQLNonNull(GraphQLInt), 164 | }, 165 | }, 166 | }, 167 | enumInputArg: { 168 | type: GraphQLString, 169 | args: { 170 | enum: { 171 | type: EnumType, 172 | }, 173 | }, 174 | }, 175 | _service: { type: SDLInterface }, 176 | }), 177 | interfaces: () => [NameInterface, UnionInterface], 178 | }); 179 | 180 | export default new GraphQLSchema({ 181 | query: Query, 182 | types: [SDL], 183 | }); 184 | -------------------------------------------------------------------------------- /src/__tests__/utils/compatResolveType.ts: -------------------------------------------------------------------------------- 1 | import * as graphql from 'graphql'; 2 | import semver from 'semver'; 3 | 4 | /** 5 | * GraphQL v16 changed how types are resolved, so we need to return a string 6 | * for the type name for newer version, and the type itself to be compatible with older versions. 7 | * 8 | * @param type 9 | * @returns 10 | */ 11 | export function compatResolveType(type: graphql.GraphQLType): any { 12 | if (graphql.version && semver.gte(graphql.version, '16.0.0')) { 13 | return () => type.toString(); 14 | } else { 15 | return () => type; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/createComplexityRule.ts: -------------------------------------------------------------------------------- 1 | import { ValidationContext } from 'graphql'; 2 | import QueryComplexity from './QueryComplexity.js'; 3 | import { QueryComplexityOptions } from './QueryComplexity.js'; 4 | 5 | export function createComplexityRule( 6 | options: QueryComplexityOptions 7 | ): (context: ValidationContext) => QueryComplexity { 8 | return (context: ValidationContext): QueryComplexity => { 9 | return new QueryComplexity(context, options); 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/estimators/directive/README.md: -------------------------------------------------------------------------------- 1 | # Directive Estimator 2 | 3 | The `directiveEstimator` lets you define the complexity of each field via GraphQL directives. 4 | That way you can define your schema and the complexity via GraphQL SDL and you don't have to 5 | change the field config manually. 6 | 7 | ## Usage 8 | 9 | Add estimator to rule: 10 | 11 | ```typescript 12 | import { 13 | directiveEstimator, 14 | createComplexityRule, 15 | } from 'graphql-query-complexity'; 16 | 17 | const rule = createComplexityRule({ 18 | estimators: [ 19 | directiveEstimator({ 20 | // Optionally change the name of the directive here... Default value is `complexity` 21 | name: 'complexity', 22 | }), 23 | ], 24 | // ... other config 25 | }); 26 | ``` 27 | 28 | Define your schema and add the complexity directive: 29 | 30 | ```graphql 31 | directive @complexity( 32 | # The complexity value for the field 33 | value: Int! 34 | 35 | # Optional multipliers 36 | multipliers: [String!] 37 | ) on FIELD_DEFINITION 38 | 39 | type Query { 40 | # Fixed complexity of 5 41 | someField: String @complexity(value: 5) 42 | 43 | # Multiply the complexity of the field with a numeric input value 44 | # If limit=2 this would result in a complexity of 4 45 | listScalar(limit: Int): String @complexity(value: 2, multipliers: ["limit"]) 46 | 47 | # Use a multiplier that is nested in a by defining the multiplier with path notation (see library lodash.get) 48 | multiLevelMultiplier(filter: Filter): String 49 | @complexity(value: 1, multipliers: ["filter.limit"]) 50 | 51 | # If the multiplier is an array, it uses the array length as multiplier 52 | arrayLength(ids: [ID]): String @complexity(value: 1, multipliers: ["ids"]) 53 | 54 | # Using multipliers on fields with composite types calculates the complexity as follows: 55 | # (value + childComplexity) * multiplier1 [... * multiplier2] 56 | compositeTypes(ids: [ID]): ChildType 57 | @complexity(value: 2, multipliers: ["ids"]) 58 | } 59 | 60 | type ChildType { 61 | a: String @complexity(value: 1) 62 | } 63 | 64 | input Filter { 65 | limit: Int 66 | } 67 | ``` 68 | 69 | The multipliers can be combined. Configured multipliers that don't have a value or `NULL` are ignored. 70 | 71 | If you are using the code first approach you can use the `createComplexityDirective` function to create 72 | the complexity directive: 73 | 74 | ```typescript 75 | import { createComplexityDirective } from 'graphql-query-complexity'; 76 | 77 | const schema = new GraphQLSchema({ 78 | directives: [ 79 | createComplexityDirective({ 80 | // Optionally change the name of the directive here... Default value is `complexity` 81 | name: 'complexity', 82 | }), 83 | ], 84 | // ... other config 85 | }); 86 | ``` 87 | -------------------------------------------------------------------------------- /src/estimators/directive/__tests__/directiveEstimator-test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Ivo Meißner on 28.07.17. 3 | */ 4 | 5 | import { 6 | GraphQLSchema, 7 | parse, 8 | print, 9 | printSchema, 10 | TypeInfo, 11 | visit, 12 | visitWithTypeInfo, 13 | buildSchema, 14 | GraphQLString, 15 | GraphQLObjectType, 16 | } from 'graphql'; 17 | 18 | import { expect } from 'chai'; 19 | 20 | import schema from './fixtures/schema.js'; 21 | 22 | import ComplexityVisitor from '../../../QueryComplexity.js'; 23 | import directiveEstimator, { createComplexityDirective } from '../index.js'; 24 | import { CompatibleValidationContext } from '../../../__tests__/fixtures/CompatibleValidationContext.js'; 25 | 26 | describe('directiveEstimator analysis', () => { 27 | const typeInfo = new TypeInfo(schema); 28 | 29 | it('should read complexity from directive', () => { 30 | const ast = parse(` 31 | query { 32 | scalar 33 | } 34 | `); 35 | 36 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 37 | const visitor = new ComplexityVisitor(context, { 38 | maximumComplexity: 100, 39 | estimators: [directiveEstimator()], 40 | }); 41 | 42 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 43 | expect(visitor.complexity).to.equal(5); 44 | }); 45 | 46 | it('should not allow negative cost', () => { 47 | const ast = parse(` 48 | query { 49 | negativeCostScalar 50 | } 51 | `); 52 | 53 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 54 | const visitor = new ComplexityVisitor(context, { 55 | maximumComplexity: 100, 56 | estimators: [directiveEstimator()], 57 | }); 58 | 59 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 60 | expect(visitor.complexity).to.equal(0); 61 | }); 62 | 63 | it('uses default directive name', () => { 64 | const ast = parse(` 65 | query { 66 | multiDirective 67 | } 68 | `); 69 | 70 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 71 | const visitor = new ComplexityVisitor(context, { 72 | maximumComplexity: 100, 73 | estimators: [directiveEstimator()], 74 | }); 75 | 76 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 77 | expect(visitor.complexity).to.equal(2); 78 | }); 79 | 80 | it('uses configured directive name', () => { 81 | const ast = parse(` 82 | query { 83 | multiDirective 84 | } 85 | `); 86 | 87 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 88 | const visitor = new ComplexityVisitor(context, { 89 | maximumComplexity: 100, 90 | estimators: [ 91 | directiveEstimator({ 92 | name: 'cost', 93 | }), 94 | ], 95 | }); 96 | 97 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 98 | expect(visitor.complexity).to.equal(1); 99 | }); 100 | 101 | it('returns value + child complexity for configured multipliers but no values', () => { 102 | const ast = parse(` 103 | query { 104 | childList { 105 | scalar 106 | } 107 | } 108 | `); 109 | 110 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 111 | const visitor = new ComplexityVisitor(context, { 112 | maximumComplexity: 100, 113 | estimators: [directiveEstimator()], 114 | }); 115 | 116 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 117 | expect(visitor.complexity).to.equal(5); 118 | }); 119 | 120 | it('uses numeric multiplier value', () => { 121 | const ast = parse(` 122 | query { 123 | childList(limit: 2) { 124 | scalar 125 | } 126 | } 127 | `); 128 | 129 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 130 | const visitor = new ComplexityVisitor(context, { 131 | maximumComplexity: 100, 132 | estimators: [directiveEstimator()], 133 | }); 134 | 135 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 136 | expect(visitor.complexity).to.equal(10); 137 | }); 138 | 139 | it('combines multiple numeric multiplier values', () => { 140 | const ast = parse(` 141 | query { 142 | childList(limit: 2, first: 2) { 143 | scalar 144 | } 145 | } 146 | `); 147 | 148 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 149 | const visitor = new ComplexityVisitor(context, { 150 | maximumComplexity: 100, 151 | estimators: [directiveEstimator()], 152 | }); 153 | 154 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 155 | expect(visitor.complexity).to.equal(20); 156 | }); 157 | 158 | it('uses multiplier array value length', () => { 159 | const ast = parse(` 160 | query { 161 | childList(ids: ["a", "b"]) { 162 | scalar 163 | } 164 | } 165 | `); 166 | 167 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 168 | const visitor = new ComplexityVisitor(context, { 169 | maximumComplexity: 100, 170 | estimators: [directiveEstimator()], 171 | }); 172 | 173 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 174 | expect(visitor.complexity).to.equal(10); 175 | }); 176 | 177 | it('uses nested multiplier paths', () => { 178 | const ast = parse(` 179 | query { 180 | childList(filter: {limit: 3}) { 181 | scalar 182 | } 183 | } 184 | `); 185 | 186 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 187 | const visitor = new ComplexityVisitor(context, { 188 | maximumComplexity: 100, 189 | estimators: [directiveEstimator()], 190 | }); 191 | 192 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 193 | expect(visitor.complexity).to.equal(15); 194 | }); 195 | 196 | it('uses multi level nested multiplier paths with array reference', () => { 197 | const ast = parse(` 198 | query { 199 | childList(filter: {filters: [{limit: 2}]}) { 200 | scalar 201 | } 202 | } 203 | `); 204 | 205 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 206 | const visitor = new ComplexityVisitor(context, { 207 | maximumComplexity: 100, 208 | estimators: [directiveEstimator()], 209 | }); 210 | 211 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 212 | expect(visitor.complexity).to.equal(10); 213 | }); 214 | 215 | it('ignores fields without complexity directive', () => { 216 | const ast = parse(` 217 | query { 218 | noDirective 219 | } 220 | `); 221 | 222 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 223 | const visitor = new ComplexityVisitor(context, { 224 | maximumComplexity: 100, 225 | estimators: [directiveEstimator()], 226 | }); 227 | 228 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 229 | expect(context.getErrors().length).to.equal(1); 230 | expect(context.getErrors()[0].message).to.include( 231 | 'No complexity could be calculated for field Query.noDirective' 232 | ); 233 | }); 234 | 235 | it('should create complexity directive that can be used to generate directive definition', () => { 236 | const complexityDirective = createComplexityDirective(); 237 | const codeFirstSchema = new GraphQLSchema({ 238 | directives: [complexityDirective], 239 | query: new GraphQLObjectType({ 240 | name: 'Query', 241 | fields: { 242 | dummy: { 243 | type: GraphQLString, 244 | }, 245 | }, 246 | }), 247 | }); 248 | 249 | // rebuilding code first schema 250 | // graphql-js <= 14 prints descriptions in different ways printSchema(schema) vs print(astNode) 251 | // and directive from code first schema has no astNode 252 | const builtCodeFirstSchema = buildSchema(printSchema(codeFirstSchema)); 253 | 254 | const printedSchemaFirstDirective = print( 255 | schema.getDirective('complexity').astNode 256 | ); 257 | const printedCodeFirstDirective = print( 258 | builtCodeFirstSchema.getDirective('complexity').astNode 259 | ); 260 | 261 | expect(printedSchemaFirstDirective).to.equal(printedCodeFirstDirective); 262 | }); 263 | 264 | it('should create complexity directive with configured name', () => { 265 | const complexityDirective = createComplexityDirective({ name: 'cost' }); 266 | const codeFirstSchema = new GraphQLSchema({ 267 | directives: [complexityDirective], 268 | query: new GraphQLObjectType({ 269 | name: 'Query', 270 | fields: { 271 | dummy: { 272 | type: GraphQLString, 273 | }, 274 | }, 275 | }), 276 | }); 277 | 278 | // rebuilding code first schema 279 | // graphql-js <= 14 prints descriptions in different ways printSchema(schema) vs print(astNode) 280 | // and directive from code first schema has no astNode 281 | const builtCodeFirstSchema = buildSchema(printSchema(codeFirstSchema)); 282 | 283 | const printedSchemaFirstDirective = print( 284 | schema.getDirective('cost').astNode 285 | ); 286 | const printedCodeFirstDirective = print( 287 | builtCodeFirstSchema.getDirective('cost').astNode 288 | ); 289 | 290 | expect(printedSchemaFirstDirective).to.equal(printedCodeFirstDirective); 291 | }); 292 | }); 293 | -------------------------------------------------------------------------------- /src/estimators/directive/__tests__/fixtures/schema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Ivo Meißner on 28.07.17. 3 | */ 4 | 5 | import { buildSchema } from 'graphql'; 6 | 7 | export default buildSchema(` 8 | """Define a relation between the field and other nodes""" 9 | directive @cost( 10 | """The complexity value for the field""" 11 | value: Int!, 12 | multipliers: [String!] 13 | ) on FIELD_DEFINITION 14 | 15 | """Define a relation between the field and other nodes""" 16 | directive @complexity( 17 | """The complexity value for the field""" 18 | value: Int!, 19 | multipliers: [String!] 20 | ) on FIELD_DEFINITION 21 | 22 | type Query { 23 | scalar: String @complexity(value: 5) 24 | negativeCostScalar: String @complexity(value: -20) 25 | multiDirective: String @cost(value: 1) @complexity(value: 2) 26 | 27 | noDirective: Boolean 28 | 29 | childList( 30 | limit: Int, 31 | ids: [ID], 32 | first: Int, 33 | filter: Filter 34 | ): [ChildType] @complexity( 35 | value: 3, 36 | multipliers: ["first", "limit", "ids", "filter.limit", "filter.filters.0.limit"] 37 | ) 38 | } 39 | 40 | input Filter { 41 | limit: Int 42 | ids: [ID] 43 | filters: [Filter] 44 | } 45 | 46 | type ChildType { 47 | scalar: String @complexity(value: 2) 48 | } 49 | `); 50 | -------------------------------------------------------------------------------- /src/estimators/directive/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComplexityEstimator, 3 | ComplexityEstimatorArgs, 4 | } from '../../QueryComplexity.js'; 5 | import { 6 | getDirectiveValues, 7 | GraphQLInt, 8 | GraphQLList, 9 | GraphQLNonNull, 10 | GraphQLString, 11 | GraphQLDirective, 12 | DirectiveLocation, 13 | } from 'graphql'; 14 | import get from 'lodash.get'; 15 | 16 | export type ComplexityDirectiveOptions = { 17 | name?: string; 18 | }; 19 | 20 | export function createComplexityDirective( 21 | options?: ComplexityDirectiveOptions 22 | ): GraphQLDirective { 23 | const mergedOptions = { 24 | name: 'complexity', 25 | ...(options || {}), 26 | }; 27 | 28 | return new GraphQLDirective({ 29 | name: mergedOptions.name, 30 | description: 'Define a relation between the field and other nodes', 31 | locations: [DirectiveLocation.FIELD_DEFINITION], 32 | args: { 33 | value: { 34 | type: new GraphQLNonNull(GraphQLInt), 35 | description: 'The complexity value for the field', 36 | }, 37 | multipliers: { 38 | type: new GraphQLList(new GraphQLNonNull(GraphQLString)), 39 | }, 40 | }, 41 | }); 42 | } 43 | 44 | export default function ( 45 | options: ComplexityDirectiveOptions = {} 46 | ): ComplexityEstimator { 47 | const directive = createComplexityDirective(options); 48 | 49 | return (args: ComplexityEstimatorArgs): number | void => { 50 | // Ignore if astNode is undefined 51 | if (!args.field.astNode) { 52 | return; 53 | } 54 | 55 | const values = getDirectiveValues(directive, args.field.astNode); 56 | 57 | // Ignore if no directive set 58 | if (!values) { 59 | return; 60 | } 61 | 62 | // Get multipliers 63 | let totalMultiplier = 1; 64 | if (values.multipliers && Array.isArray(values.multipliers)) { 65 | totalMultiplier = values.multipliers.reduce( 66 | (aggregated: number, multiplier: string) => { 67 | const multiplierValue = get(args.args, multiplier); 68 | 69 | if (typeof multiplierValue === 'number') { 70 | return aggregated * multiplierValue; 71 | } 72 | if (Array.isArray(multiplierValue)) { 73 | return aggregated * multiplierValue.length; 74 | } 75 | return aggregated; 76 | }, 77 | totalMultiplier 78 | ); 79 | } 80 | 81 | return (Number(values.value) + args.childComplexity) * totalMultiplier; 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /src/estimators/fieldExtensions/README.md: -------------------------------------------------------------------------------- 1 | # Field Extensions Estimator 2 | 3 | The `fieldExtensionsEstimator` lets you define a numeric value or a custom estimator 4 | in the field config extensions of your GraphQL schema. If no complexity is set in the field config, 5 | the estimator does not return any value and the next estimator in the chain is executed. 6 | 7 | ## Usage 8 | 9 | ```typescript 10 | import { 11 | createComplexityRule, 12 | fieldExtensionsEstimator, 13 | simpleEstimator, 14 | } from 'graphql-query-complexity'; 15 | 16 | const rule = createComplexityRule({ 17 | estimators: [ 18 | fieldExtensionsEstimator(), 19 | 20 | // We use the simpleEstimator as fallback so we only need to 21 | // define the complexity for non 1 values (this is not required...) 22 | simpleEstimator({ defaultComplexity: 1 }), 23 | ], 24 | // ... other config 25 | }); 26 | ``` 27 | 28 | You can set a custom complexity as a numeric value in the field config: 29 | 30 | ```javascript 31 | const Post = new GraphQLObjectType({ 32 | name: 'Post', 33 | fields: () => ({ 34 | title: { type: GraphQLString }, 35 | text: { 36 | type: GraphQLString, 37 | extensions: { 38 | complexity: 5, 39 | }, 40 | }, 41 | }), 42 | }); 43 | ``` 44 | 45 | **Example Query:** 46 | 47 | ```graphql 48 | query { 49 | posts(count: 10) { 50 | title 51 | text 52 | } 53 | } 54 | ``` 55 | 56 | This query would result in a complexity of 7. 57 | 5 for the `text` field and 1 for each of the other fields. 58 | 59 | You can also pass an estimator in the field config to determine a custom complexity. 60 | This function will provide the complexity of the child nodes as well as the field input arguments. 61 | 62 | The function signature is the same as for the main estimator which lets you reuse estimators: 63 | 64 | ```typescript 65 | type ComplexityEstimatorArgs = { 66 | type: GraphQLCompositeType; 67 | field: GraphQLField; 68 | node: FieldNode; 69 | args: { [key: string]: any }; 70 | childComplexity: number; 71 | }; 72 | 73 | type ComplexityEstimator = (options: ComplexityEstimatorArgs) => number | void; 74 | ``` 75 | 76 | That way you can make a more realistic estimation of individual field complexity values: 77 | 78 | ```javascript 79 | const Query = new GraphQLObjectType({ 80 | name: 'Query', 81 | fields: () => ({ 82 | posts: { 83 | type: new GraphQLList(Post), 84 | args: { 85 | count: { 86 | type: GraphQLInt, 87 | defaultValue: 10, 88 | }, 89 | }, 90 | extensions: { 91 | complexity: ({ args, childComplexity }) => childComplexity * args.count, 92 | }, 93 | }, 94 | }), 95 | }); 96 | ``` 97 | 98 | This would result in a complexity of 60 since the `childComplexity` of posts (`text` 5, `title` 1) is multiplied by the 99 | number of posts (`args.count`). 100 | -------------------------------------------------------------------------------- /src/estimators/fieldExtensions/__tests__/fieldExtensionsEstimator-test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Ivo Meißner on 28.07.17. 3 | */ 4 | 5 | import { parse, TypeInfo, visit, visitWithTypeInfo } from 'graphql'; 6 | 7 | import { expect } from 'chai'; 8 | 9 | import schema from './fixtures/schema.js'; 10 | 11 | import ComplexityVisitor from '../../../QueryComplexity.js'; 12 | import simpleEstimator from '../../simple/index.js'; 13 | import fieldExtensionsEstimator from '../index.js'; 14 | import { CompatibleValidationContext } from '../../../__tests__/fixtures/CompatibleValidationContext.js'; 15 | 16 | describe('fieldExtensions estimator', () => { 17 | const typeInfo = new TypeInfo(schema); 18 | 19 | it('should consider default scalar cost', () => { 20 | const ast = parse(` 21 | query { 22 | scalar 23 | } 24 | `); 25 | 26 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 27 | const visitor = new ComplexityVisitor(context, { 28 | maximumComplexity: 100, 29 | estimators: [ 30 | fieldExtensionsEstimator(), 31 | simpleEstimator({ 32 | defaultComplexity: 1, 33 | }), 34 | ], 35 | }); 36 | 37 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 38 | expect(visitor.complexity).to.equal(1); 39 | }); 40 | 41 | it('should consider custom scalar cost', () => { 42 | const ast = parse(` 43 | query { 44 | complexScalar 45 | } 46 | `); 47 | 48 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 49 | const visitor = new ComplexityVisitor(context, { 50 | maximumComplexity: 100, 51 | estimators: [ 52 | fieldExtensionsEstimator(), 53 | simpleEstimator({ 54 | defaultComplexity: 1, 55 | }), 56 | ], 57 | }); 58 | 59 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 60 | expect(visitor.complexity).to.equal(20); 61 | }); 62 | 63 | it('should consider variable scalar cost', () => { 64 | const ast = parse(` 65 | query { 66 | variableScalar(count: 100) 67 | } 68 | `); 69 | 70 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 71 | const visitor = new ComplexityVisitor(context, { 72 | maximumComplexity: 100, 73 | estimators: [ 74 | fieldExtensionsEstimator(), 75 | simpleEstimator({ 76 | defaultComplexity: 1, 77 | }), 78 | ], 79 | }); 80 | 81 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 82 | expect(visitor.complexity).to.equal(1000); 83 | }); 84 | 85 | it('should not allow negative cost', () => { 86 | const ast = parse(` 87 | query { 88 | variableScalar(count: -100) 89 | } 90 | `); 91 | 92 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 93 | const visitor = new ComplexityVisitor(context, { 94 | maximumComplexity: 100, 95 | estimators: [ 96 | fieldExtensionsEstimator(), 97 | simpleEstimator({ 98 | defaultComplexity: 1, 99 | }), 100 | ], 101 | }); 102 | 103 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 104 | expect(visitor.complexity).to.equal(0); 105 | }); 106 | 107 | it('should report error above threshold', () => { 108 | const ast = parse(` 109 | query { 110 | variableScalar(count: 100) 111 | } 112 | `); 113 | 114 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 115 | const visitor = new ComplexityVisitor(context, { 116 | maximumComplexity: 100, 117 | estimators: [ 118 | fieldExtensionsEstimator(), 119 | simpleEstimator({ 120 | defaultComplexity: 1, 121 | }), 122 | ], 123 | }); 124 | 125 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 126 | expect(visitor.complexity).to.equal(1000); 127 | expect(context.getErrors().length).to.equal(1); 128 | expect(context.getErrors()[0].message).to.equal( 129 | 'The query exceeds the maximum complexity of 100. Actual complexity is 1000' 130 | ); 131 | }); 132 | 133 | it('should add inline fragments', () => { 134 | const ast = parse(` 135 | query { 136 | variableScalar(count: 5) 137 | ...on Query { 138 | scalar 139 | alias: scalar 140 | } 141 | } 142 | `); 143 | 144 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 145 | const visitor = new ComplexityVisitor(context, { 146 | maximumComplexity: 100, 147 | estimators: [ 148 | fieldExtensionsEstimator(), 149 | simpleEstimator({ 150 | defaultComplexity: 1, 151 | }), 152 | ], 153 | }); 154 | 155 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 156 | expect(visitor.complexity).to.equal(52); 157 | }); 158 | 159 | it('should add fragments', () => { 160 | const ast = parse(` 161 | query { 162 | scalar 163 | ...QueryFragment 164 | } 165 | 166 | fragment QueryFragment on Query { 167 | variableScalar(count: 2) 168 | } 169 | `); 170 | 171 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 172 | const visitor = new ComplexityVisitor(context, { 173 | maximumComplexity: 100, 174 | estimators: [ 175 | fieldExtensionsEstimator(), 176 | simpleEstimator({ 177 | defaultComplexity: 1, 178 | }), 179 | ], 180 | }); 181 | 182 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 183 | expect(visitor.complexity).to.equal(21); 184 | }); 185 | 186 | it('should add complexity for union types', () => { 187 | const ast = parse(` 188 | query { 189 | union { 190 | ...on Item { 191 | scalar 192 | complexScalar 193 | } 194 | } 195 | } 196 | `); 197 | 198 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 199 | const visitor = new ComplexityVisitor(context, { 200 | maximumComplexity: 100, 201 | estimators: [ 202 | fieldExtensionsEstimator(), 203 | simpleEstimator({ 204 | defaultComplexity: 1, 205 | }), 206 | ], 207 | }); 208 | 209 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 210 | expect(visitor.complexity).to.equal(22); 211 | }); 212 | 213 | it('should add complexity for interface types', () => { 214 | const ast = parse(` 215 | query { 216 | interface { 217 | name 218 | ...on NameInterface { 219 | name 220 | } 221 | } 222 | } 223 | `); 224 | 225 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 226 | const visitor = new ComplexityVisitor(context, { 227 | maximumComplexity: 100, 228 | estimators: [ 229 | fieldExtensionsEstimator(), 230 | simpleEstimator({ 231 | defaultComplexity: 1, 232 | }), 233 | ], 234 | }); 235 | 236 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 237 | expect(visitor.complexity).to.equal(3); 238 | }); 239 | 240 | it('should add complexity for inline fragments without type condition', () => { 241 | const ast = parse(` 242 | query { 243 | interface { 244 | ... { 245 | name 246 | } 247 | } 248 | } 249 | `); 250 | 251 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 252 | const visitor = new ComplexityVisitor(context, { 253 | maximumComplexity: 100, 254 | estimators: [ 255 | fieldExtensionsEstimator(), 256 | simpleEstimator({ 257 | defaultComplexity: 1, 258 | }), 259 | ], 260 | }); 261 | 262 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 263 | expect(visitor.complexity).to.equal(2); 264 | }); 265 | 266 | it('should add complexity for enum types', () => { 267 | const ast = parse(` 268 | query { 269 | enum 270 | } 271 | `); 272 | 273 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 274 | const visitor = new ComplexityVisitor(context, { 275 | maximumComplexity: 100, 276 | estimators: [ 277 | fieldExtensionsEstimator(), 278 | simpleEstimator({ 279 | defaultComplexity: 1, 280 | }), 281 | ], 282 | }); 283 | 284 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 285 | expect(visitor.complexity).to.equal(1); 286 | }); 287 | 288 | it('should error on a missing non-null argument', () => { 289 | const ast = parse(` 290 | query { 291 | requiredArgs 292 | } 293 | `); 294 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 295 | const visitor = new ComplexityVisitor(context, { 296 | maximumComplexity: 100, 297 | estimators: [ 298 | fieldExtensionsEstimator(), 299 | simpleEstimator({ 300 | defaultComplexity: 1, 301 | }), 302 | ], 303 | }); 304 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 305 | expect(context.getErrors().length).to.equal(1); 306 | expect(context.getErrors()[0].message).to.equal( 307 | 'Argument "count" of required type "Int!" was not provided.' 308 | ); 309 | }); 310 | }); 311 | -------------------------------------------------------------------------------- /src/estimators/fieldExtensions/__tests__/fixtures/schema.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-function-return-type */ 2 | /** 3 | * Created by Ivo Meißner on 28.07.17. 4 | */ 5 | 6 | import { 7 | GraphQLList, 8 | GraphQLObjectType, 9 | GraphQLNonNull, 10 | GraphQLSchema, 11 | GraphQLString, 12 | GraphQLInt, 13 | GraphQLEnumType, 14 | GraphQLUnionType, 15 | GraphQLInterfaceType, 16 | } from 'graphql'; 17 | import { compatResolveType } from '../../../../__tests__/utils/compatResolveType.js'; 18 | 19 | import { ComplexityEstimatorArgs } from '../../../../QueryComplexity.js'; 20 | 21 | const Item: GraphQLObjectType = new GraphQLObjectType({ 22 | name: 'Item', 23 | fields: () => ({ 24 | variableList: { 25 | type: Item, 26 | extensions: { 27 | complexity: (args: ComplexityEstimatorArgs) => 28 | args.childComplexity * (args.args.count || 10), 29 | }, 30 | args: { 31 | count: { 32 | type: GraphQLInt, 33 | }, 34 | }, 35 | }, 36 | scalar: { type: GraphQLString }, 37 | complexScalar: { 38 | type: GraphQLString, 39 | extensions: { 40 | complexity: 20, 41 | }, 42 | }, 43 | variableScalar: { 44 | type: Item, 45 | extensions: { 46 | complexity: (args: ComplexityEstimatorArgs) => 47 | 10 * (args.args.count || 10), 48 | }, 49 | args: { 50 | count: { 51 | type: GraphQLInt, 52 | }, 53 | }, 54 | }, 55 | list: { type: new GraphQLList(Item) }, 56 | nonNullItem: { 57 | type: new GraphQLNonNull(Item), 58 | resolve: () => ({}), 59 | }, 60 | nonNullList: { 61 | type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Item))), 62 | resolve: () => [], 63 | }, 64 | }), 65 | }); 66 | 67 | const NameInterface = new GraphQLInterfaceType({ 68 | name: 'NameInterface', 69 | fields: { 70 | name: { type: GraphQLString }, 71 | }, 72 | resolveType: compatResolveType(Item), 73 | }); 74 | 75 | const SecondItem = new GraphQLObjectType({ 76 | name: 'SecondItem', 77 | fields: () => ({ 78 | name: { type: GraphQLString }, 79 | scalar: { type: GraphQLString }, 80 | }), 81 | interfaces: [NameInterface], 82 | }); 83 | 84 | const EnumType = new GraphQLEnumType({ 85 | name: 'RGB', 86 | values: { 87 | RED: { value: 0 }, 88 | GREEN: { value: 1 }, 89 | BLUE: { value: 2 }, 90 | }, 91 | }); 92 | 93 | const Union = new GraphQLUnionType({ 94 | name: 'Union', 95 | types: [Item, SecondItem], 96 | resolveType: compatResolveType(Item), 97 | }); 98 | 99 | const Query = new GraphQLObjectType({ 100 | name: 'Query', 101 | fields: () => ({ 102 | name: { type: GraphQLString }, 103 | variableList: { 104 | type: Item, 105 | extensions: { 106 | complexity: (args: ComplexityEstimatorArgs) => 107 | args.childComplexity * (args.args.count || 10), 108 | }, 109 | args: { 110 | count: { 111 | type: GraphQLInt, 112 | }, 113 | }, 114 | }, 115 | interface: { type: NameInterface }, 116 | enum: { type: EnumType }, 117 | scalar: { type: GraphQLString }, 118 | complexScalar: { 119 | type: GraphQLString, 120 | extensions: { 121 | complexity: 20, 122 | }, 123 | }, 124 | union: { type: Union }, 125 | variableScalar: { 126 | type: Item, 127 | extensions: { 128 | complexity: (args: ComplexityEstimatorArgs) => 129 | 10 * (args.args.count || 10), 130 | }, 131 | args: { 132 | count: { 133 | type: GraphQLInt, 134 | }, 135 | }, 136 | }, 137 | list: { type: new GraphQLList(Item) }, 138 | nonNullItem: { 139 | type: new GraphQLNonNull(Item), 140 | resolve: () => ({}), 141 | }, 142 | nonNullList: { 143 | type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Item))), 144 | resolve: () => [], 145 | }, 146 | requiredArgs: { 147 | type: Item, 148 | args: { 149 | count: { 150 | type: new GraphQLNonNull(GraphQLInt), 151 | }, 152 | }, 153 | }, 154 | }), 155 | }); 156 | 157 | export default new GraphQLSchema({ query: Query }); 158 | -------------------------------------------------------------------------------- /src/estimators/fieldExtensions/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComplexityEstimator, 3 | ComplexityEstimatorArgs, 4 | } from '../../QueryComplexity.js'; 5 | 6 | export default function (): ComplexityEstimator { 7 | return (args: ComplexityEstimatorArgs): number | void => { 8 | if (args.field.extensions) { 9 | // Calculate complexity score 10 | if (typeof args.field.extensions.complexity === 'number') { 11 | return args.childComplexity + args.field.extensions.complexity; 12 | } else if (typeof args.field.extensions.complexity === 'function') { 13 | return args.field.extensions.complexity(args); 14 | } 15 | } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/estimators/index.ts: -------------------------------------------------------------------------------- 1 | export { default as simpleEstimator } from './simple/index.js'; 2 | export { 3 | default as directiveEstimator, 4 | ComplexityDirectiveOptions, 5 | createComplexityDirective, 6 | } from './directive/index.js'; 7 | export { default as fieldExtensionsEstimator } from './fieldExtensions/index.js'; 8 | -------------------------------------------------------------------------------- /src/estimators/simple/README.md: -------------------------------------------------------------------------------- 1 | # Simple Estimator 2 | 3 | The simple estimator just adds a fixed complexity to every field that is queried. 4 | This can be used as the last estimator in the chain to return the default value. 5 | 6 | ## Usage 7 | 8 | ```typescript 9 | import { 10 | simpleEstimator, 11 | createComplexityRule, 12 | } from 'graphql-query-complexity'; 13 | 14 | const rule = createComplexityRule({ 15 | estimators: [ 16 | simpleEstimator({ 17 | // Add a default complexity of 1 for each queried field 18 | defaultComplexity: 1, 19 | }), 20 | ], 21 | // ... other config 22 | }); 23 | ``` 24 | -------------------------------------------------------------------------------- /src/estimators/simple/__tests__/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | '@typescript-eslint/explicit-function-return-type': "off" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/estimators/simple/__tests__/fixtures/schema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Ivo Meißner on 28.07.17. 3 | */ 4 | 5 | import { 6 | GraphQLList, 7 | GraphQLObjectType, 8 | GraphQLNonNull, 9 | GraphQLSchema, 10 | GraphQLString, 11 | GraphQLInt, 12 | GraphQLEnumType, 13 | GraphQLUnionType, 14 | GraphQLInterfaceType, 15 | } from 'graphql'; 16 | import { compatResolveType } from '../../../../__tests__/utils/compatResolveType.js'; 17 | 18 | const Item: GraphQLObjectType = new GraphQLObjectType({ 19 | name: 'Item', 20 | fields: () => ({ 21 | variableList: { 22 | type: Item, 23 | args: { 24 | count: { 25 | type: GraphQLInt, 26 | }, 27 | }, 28 | }, 29 | scalar: { type: GraphQLString }, 30 | list: { type: new GraphQLList(Item) }, 31 | nonNullItem: { 32 | type: new GraphQLNonNull(Item), 33 | resolve: () => ({}), 34 | }, 35 | nonNullList: { 36 | type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Item))), 37 | resolve: () => [], 38 | }, 39 | }), 40 | }); 41 | 42 | const NameInterface = new GraphQLInterfaceType({ 43 | name: 'NameInterface', 44 | fields: { 45 | name: { type: GraphQLString }, 46 | }, 47 | resolveType: compatResolveType(Item), 48 | }); 49 | 50 | const SecondItem = new GraphQLObjectType({ 51 | name: 'SecondItem', 52 | fields: () => ({ 53 | name: { type: GraphQLString }, 54 | scalar: { type: GraphQLString }, 55 | }), 56 | interfaces: [NameInterface], 57 | }); 58 | 59 | const EnumType = new GraphQLEnumType({ 60 | name: 'RGB', 61 | values: { 62 | RED: { value: 0 }, 63 | GREEN: { value: 1 }, 64 | BLUE: { value: 2 }, 65 | }, 66 | }); 67 | 68 | const Union = new GraphQLUnionType({ 69 | name: 'Union', 70 | types: [Item, SecondItem], 71 | resolveType: compatResolveType(Item), 72 | }); 73 | 74 | const Query = new GraphQLObjectType({ 75 | name: 'Query', 76 | fields: () => ({ 77 | name: { type: GraphQLString }, 78 | variableList: { 79 | type: Item, 80 | args: { 81 | count: { 82 | type: GraphQLInt, 83 | }, 84 | }, 85 | }, 86 | interface: { type: NameInterface }, 87 | enum: { type: EnumType }, 88 | scalar: { type: GraphQLString }, 89 | union: { type: Union }, 90 | variableScalar: { 91 | type: Item, 92 | args: { 93 | count: { 94 | type: GraphQLInt, 95 | }, 96 | }, 97 | }, 98 | list: { type: new GraphQLList(Item) }, 99 | nonNullItem: { 100 | type: new GraphQLNonNull(Item), 101 | resolve: () => ({}), 102 | }, 103 | nonNullList: { 104 | type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Item))), 105 | resolve: () => [], 106 | }, 107 | requiredArgs: { 108 | type: Item, 109 | args: { 110 | count: { 111 | type: new GraphQLNonNull(GraphQLInt), 112 | }, 113 | }, 114 | }, 115 | }), 116 | }); 117 | 118 | export default new GraphQLSchema({ query: Query }); 119 | -------------------------------------------------------------------------------- /src/estimators/simple/__tests__/simpleEstimator-test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Ivo Meißner on 28.07.17. 3 | */ 4 | 5 | import { parse, TypeInfo, visit, visitWithTypeInfo } from 'graphql'; 6 | 7 | import { expect } from 'chai'; 8 | 9 | import schema from './fixtures/schema.js'; 10 | import simpleEstimator from '../index.js'; 11 | 12 | import ComplexityVisitor from '../../../QueryComplexity.js'; 13 | import { CompatibleValidationContext } from '../../../__tests__/fixtures/CompatibleValidationContext.js'; 14 | 15 | describe('simple estimator', () => { 16 | const typeInfo = new TypeInfo(schema); 17 | 18 | it('should consider default scalar cost', () => { 19 | const ast = parse(` 20 | query { 21 | scalar 22 | } 23 | `); 24 | 25 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 26 | const visitor = new ComplexityVisitor(context, { 27 | maximumComplexity: 100, 28 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 29 | }); 30 | 31 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 32 | expect(visitor.complexity).to.equal(1); 33 | }); 34 | 35 | it('should consider default scalar cost + defaultComplexity', () => { 36 | const ast = parse(` 37 | query { 38 | scalar 39 | } 40 | `); 41 | 42 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 43 | const visitor = new ComplexityVisitor(context, { 44 | maximumComplexity: 100, 45 | estimators: [simpleEstimator({ defaultComplexity: 10 })], 46 | }); 47 | 48 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 49 | expect(visitor.complexity).to.equal(10); 50 | }); 51 | 52 | it('should not allow negative cost', () => { 53 | const ast = parse(` 54 | query { 55 | scalar 56 | } 57 | `); 58 | 59 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 60 | const visitor = new ComplexityVisitor(context, { 61 | maximumComplexity: 100, 62 | estimators: [simpleEstimator({ defaultComplexity: -10 })], 63 | }); 64 | 65 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 66 | expect(visitor.complexity).to.equal(0); 67 | }); 68 | 69 | it('should report error above threshold', () => { 70 | const ast = parse(` 71 | query { 72 | scalar 73 | } 74 | `); 75 | 76 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 77 | const visitor = new ComplexityVisitor(context, { 78 | maximumComplexity: 100, 79 | estimators: [simpleEstimator({ defaultComplexity: 1000 })], 80 | }); 81 | 82 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 83 | expect(visitor.complexity).to.equal(1000); 84 | expect(context.getErrors().length).to.equal(1); 85 | expect(context.getErrors()[0].message).to.equal( 86 | 'The query exceeds the maximum complexity of 100. Actual complexity is 1000' 87 | ); 88 | }); 89 | 90 | it('should add inline fragments', () => { 91 | const ast = parse(` 92 | query { 93 | variableScalar(count: 5) 94 | ...on Query { 95 | scalar 96 | alias: scalar 97 | } 98 | } 99 | `); 100 | 101 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 102 | const visitor = new ComplexityVisitor(context, { 103 | maximumComplexity: 100, 104 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 105 | }); 106 | 107 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 108 | expect(visitor.complexity).to.equal(3); 109 | }); 110 | 111 | it('should add fragments', () => { 112 | const ast = parse(` 113 | query { 114 | scalar 115 | ...QueryFragment 116 | } 117 | 118 | fragment QueryFragment on Query { 119 | variableScalar(count: 2) 120 | } 121 | `); 122 | 123 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 124 | const visitor = new ComplexityVisitor(context, { 125 | maximumComplexity: 100, 126 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 127 | }); 128 | 129 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 130 | expect(visitor.complexity).to.equal(2); 131 | }); 132 | 133 | it('should add complexity for union types', () => { 134 | const ast = parse(` 135 | query { 136 | union { 137 | ...on Item { 138 | scalar 139 | } 140 | } 141 | } 142 | `); 143 | 144 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 145 | const visitor = new ComplexityVisitor(context, { 146 | maximumComplexity: 100, 147 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 148 | }); 149 | 150 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 151 | expect(visitor.complexity).to.equal(2); 152 | }); 153 | 154 | it('should add complexity for interface types', () => { 155 | const ast = parse(` 156 | query { 157 | interface { 158 | name 159 | ...on NameInterface { 160 | name 161 | } 162 | } 163 | } 164 | `); 165 | 166 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 167 | const visitor = new ComplexityVisitor(context, { 168 | maximumComplexity: 100, 169 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 170 | }); 171 | 172 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 173 | expect(visitor.complexity).to.equal(3); 174 | }); 175 | 176 | it('should add complexity for inline fragments without type condition', () => { 177 | const ast = parse(` 178 | query { 179 | interface { 180 | ... { 181 | name 182 | } 183 | } 184 | } 185 | `); 186 | 187 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 188 | const visitor = new ComplexityVisitor(context, { 189 | maximumComplexity: 100, 190 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 191 | }); 192 | 193 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 194 | expect(visitor.complexity).to.equal(2); 195 | }); 196 | 197 | it('should add complexity for enum types', () => { 198 | const ast = parse(` 199 | query { 200 | enum 201 | } 202 | `); 203 | 204 | const context = new CompatibleValidationContext(schema, ast, typeInfo); 205 | const visitor = new ComplexityVisitor(context, { 206 | maximumComplexity: 100, 207 | estimators: [simpleEstimator({ defaultComplexity: 1 })], 208 | }); 209 | 210 | visit(ast, visitWithTypeInfo(typeInfo, visitor)); 211 | expect(visitor.complexity).to.equal(1); 212 | }); 213 | }); 214 | -------------------------------------------------------------------------------- /src/estimators/simple/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComplexityEstimator, 3 | ComplexityEstimatorArgs, 4 | } from '../../QueryComplexity.js'; 5 | 6 | export default function (options?: { 7 | defaultComplexity?: number; 8 | }): ComplexityEstimator { 9 | const defaultComplexity = 10 | options && typeof options.defaultComplexity === 'number' 11 | ? options.defaultComplexity 12 | : 1; 13 | return (args: ComplexityEstimatorArgs): number | void => { 14 | return defaultComplexity + args.childComplexity; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Ivo Meißner on 28.07.17. 3 | */ 4 | 5 | export * from './estimators/index.js'; 6 | export * from './QueryComplexity.js'; 7 | import { createComplexityRule as createComplexityRuleFn } from './createComplexityRule.js'; 8 | 9 | export const createComplexityRule = createComplexityRuleFn; 10 | export default createComplexityRule; 11 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false 5 | }, 6 | "exclude": ["**/__tests__/**"] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "target": "ES2015", 6 | "module": "ES2015", 7 | "outDir": "dist/esm" 8 | }, 9 | "exclude": ["**/__tests__/**"] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "resolveJsonModule": true, 6 | "target": "es6", 7 | "noImplicitAny": true, 8 | "moduleResolution": "node", 9 | "sourceMap": true, 10 | "declaration": true, 11 | "baseUrl": ".", 12 | "outDir": "dist/cjs", 13 | "paths": { 14 | "*": ["node_modules/*", "src/types/*"] 15 | }, 16 | "lib": ["esnext", "esnext.asynciterable"] 17 | }, 18 | "include": ["src/**/*"] 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.test.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.cjs.json", 3 | "compilerOptions": { 4 | "outDir": "dist/test/cjs", 5 | "sourceMap": true 6 | }, 7 | "exclude": [] 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.test.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.esm.json", 3 | "compilerOptions": { 4 | "outDir": "dist/test/esm", 5 | "sourceMap": true 6 | }, 7 | "exclude": [] 8 | } 9 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@eslint/eslintrc@^1.0.4": 6 | version "1.0.4" 7 | resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.4.tgz#dfe0ff7ba270848d10c5add0715e04964c034b31" 8 | integrity sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q== 9 | dependencies: 10 | ajv "^6.12.4" 11 | debug "^4.3.2" 12 | espree "^9.0.0" 13 | globals "^13.9.0" 14 | ignore "^4.0.6" 15 | import-fresh "^3.2.1" 16 | js-yaml "^4.1.0" 17 | minimatch "^3.0.4" 18 | strip-json-comments "^3.1.1" 19 | 20 | "@humanwhocodes/config-array@^0.6.0": 21 | version "0.6.0" 22 | resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.6.0.tgz#b5621fdb3b32309d2d16575456cbc277fa8f021a" 23 | integrity sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A== 24 | dependencies: 25 | "@humanwhocodes/object-schema" "^1.2.0" 26 | debug "^4.1.1" 27 | minimatch "^3.0.4" 28 | 29 | "@humanwhocodes/object-schema@^1.2.0": 30 | version "1.2.1" 31 | resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" 32 | integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== 33 | 34 | "@nodelib/fs.scandir@2.1.5": 35 | version "2.1.5" 36 | resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" 37 | integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== 38 | dependencies: 39 | "@nodelib/fs.stat" "2.0.5" 40 | run-parallel "^1.1.9" 41 | 42 | "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": 43 | version "2.0.5" 44 | resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" 45 | integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== 46 | 47 | "@nodelib/fs.walk@^1.2.3": 48 | version "1.2.8" 49 | resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" 50 | integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== 51 | dependencies: 52 | "@nodelib/fs.scandir" "2.1.5" 53 | fastq "^1.6.0" 54 | 55 | "@types/assert@^1.5.6": 56 | version "1.5.6" 57 | resolved "https://registry.yarnpkg.com/@types/assert/-/assert-1.5.6.tgz#a8b5a94ce5fb8f4ba65fdc37fc9507609114189e" 58 | integrity sha512-Y7gDJiIqb9qKUHfBQYOWGngUpLORtirAVPuj/CWJrU2C6ZM4/y3XLwuwfGMF8s7QzW746LQZx23m0+1FSgjfug== 59 | 60 | "@types/chai@^4.2.22": 61 | version "4.2.22" 62 | resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.22.tgz#47020d7e4cf19194d43b5202f35f75bd2ad35ce7" 63 | integrity sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ== 64 | 65 | "@types/json-schema@^7.0.9": 66 | version "7.0.9" 67 | resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" 68 | integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== 69 | 70 | "@types/lodash.get@^4.4.6": 71 | version "4.4.6" 72 | resolved "https://registry.yarnpkg.com/@types/lodash.get/-/lodash.get-4.4.6.tgz#0c7ac56243dae0f9f09ab6f75b29471e2e777240" 73 | integrity sha512-E6zzjR3GtNig8UJG/yodBeJeIOtgPkMgsLjDU3CbgCAPC++vJ0eCMnJhVpRZb/ENqEFlov1+3K9TKtY4UdWKtQ== 74 | dependencies: 75 | "@types/lodash" "*" 76 | 77 | "@types/lodash@*": 78 | version "4.14.176" 79 | resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.176.tgz#641150fc1cda36fbfa329de603bbb175d7ee20c0" 80 | integrity sha512-xZmuPTa3rlZoIbtDUyJKZQimJV3bxCmzMIO2c9Pz9afyDro6kr7R79GwcB6mRhuoPmV2p1Vb66WOJH7F886WKQ== 81 | 82 | "@types/mocha@^9.0.0": 83 | version "9.0.0" 84 | resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.0.0.tgz#3205bcd15ada9bc681ac20bef64e9e6df88fd297" 85 | integrity sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA== 86 | 87 | "@types/semver@^7.3.9": 88 | version "7.3.9" 89 | resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc" 90 | integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ== 91 | 92 | "@typescript-eslint/eslint-plugin@^5.1.0": 93 | version "5.3.1" 94 | resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.1.tgz#d8ff412f10f54f6364e7fd7c1e70eb6767f434c3" 95 | integrity sha512-cFImaoIr5Ojj358xI/SDhjog57OK2NqlpxwdcgyxDA3bJlZcJq5CPzUXtpD7CxI2Hm6ATU7w5fQnnkVnmwpHqw== 96 | dependencies: 97 | "@typescript-eslint/experimental-utils" "5.3.1" 98 | "@typescript-eslint/scope-manager" "5.3.1" 99 | debug "^4.3.2" 100 | functional-red-black-tree "^1.0.1" 101 | ignore "^5.1.8" 102 | regexpp "^3.2.0" 103 | semver "^7.3.5" 104 | tsutils "^3.21.0" 105 | 106 | "@typescript-eslint/experimental-utils@5.3.1": 107 | version "5.3.1" 108 | resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.3.1.tgz#bbd8f9b67b4d5fdcb9d2f90297d8fcda22561e05" 109 | integrity sha512-RgFn5asjZ5daUhbK5Sp0peq0SSMytqcrkNfU4pnDma2D8P3ElZ6JbYjY8IMSFfZAJ0f3x3tnO3vXHweYg0g59w== 110 | dependencies: 111 | "@types/json-schema" "^7.0.9" 112 | "@typescript-eslint/scope-manager" "5.3.1" 113 | "@typescript-eslint/types" "5.3.1" 114 | "@typescript-eslint/typescript-estree" "5.3.1" 115 | eslint-scope "^5.1.1" 116 | eslint-utils "^3.0.0" 117 | 118 | "@typescript-eslint/parser@^5.1.0": 119 | version "5.3.1" 120 | resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.3.1.tgz#8ff1977c3d3200c217b3e4628d43ef92f89e5261" 121 | integrity sha512-TD+ONlx5c+Qhk21x9gsJAMRohWAUMavSOmJgv3JGy9dgPhuBd5Wok0lmMClZDyJNLLZK1JRKiATzCKZNUmoyfw== 122 | dependencies: 123 | "@typescript-eslint/scope-manager" "5.3.1" 124 | "@typescript-eslint/types" "5.3.1" 125 | "@typescript-eslint/typescript-estree" "5.3.1" 126 | debug "^4.3.2" 127 | 128 | "@typescript-eslint/scope-manager@5.3.1": 129 | version "5.3.1" 130 | resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.3.1.tgz#3cfbfbcf5488fb2a9a6fbbe97963ee1e8d419269" 131 | integrity sha512-XksFVBgAq0Y9H40BDbuPOTUIp7dn4u8oOuhcgGq7EoDP50eqcafkMVGrypyVGvDYHzjhdUCUwuwVUK4JhkMAMg== 132 | dependencies: 133 | "@typescript-eslint/types" "5.3.1" 134 | "@typescript-eslint/visitor-keys" "5.3.1" 135 | 136 | "@typescript-eslint/types@5.3.1": 137 | version "5.3.1" 138 | resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.3.1.tgz#afaa715b69ebfcfde3af8b0403bf27527912f9b7" 139 | integrity sha512-bG7HeBLolxKHtdHG54Uac750eXuQQPpdJfCYuw4ZI3bZ7+GgKClMWM8jExBtp7NSP4m8PmLRM8+lhzkYnSmSxQ== 140 | 141 | "@typescript-eslint/typescript-estree@5.3.1": 142 | version "5.3.1" 143 | resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.3.1.tgz#50cc4bfb93dc31bc75e08ae52e29fcb786d606ec" 144 | integrity sha512-PwFbh/PKDVo/Wct6N3w+E4rLZxUDgsoII/GrWM2A62ETOzJd4M6s0Mu7w4CWsZraTbaC5UQI+dLeyOIFF1PquQ== 145 | dependencies: 146 | "@typescript-eslint/types" "5.3.1" 147 | "@typescript-eslint/visitor-keys" "5.3.1" 148 | debug "^4.3.2" 149 | globby "^11.0.4" 150 | is-glob "^4.0.3" 151 | semver "^7.3.5" 152 | tsutils "^3.21.0" 153 | 154 | "@typescript-eslint/visitor-keys@5.3.1": 155 | version "5.3.1" 156 | resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.1.tgz#c2860ff22939352db4f3806f34b21d8ad00588ba" 157 | integrity sha512-3cHUzUuVTuNHx0Gjjt5pEHa87+lzyqOiHXy/Gz+SJOCW1mpw9xQHIIEwnKn+Thph1mgWyZ90nboOcSuZr/jTTQ== 158 | dependencies: 159 | "@typescript-eslint/types" "5.3.1" 160 | eslint-visitor-keys "^3.0.0" 161 | 162 | "@ungap/promise-all-settled@1.1.2": 163 | version "1.1.2" 164 | resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" 165 | integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== 166 | 167 | acorn-jsx@^5.3.1: 168 | version "5.3.2" 169 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" 170 | integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== 171 | 172 | acorn@^8.5.0: 173 | version "8.5.0" 174 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" 175 | integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== 176 | 177 | ajv@^6.10.0, ajv@^6.12.4: 178 | version "6.12.6" 179 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" 180 | integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== 181 | dependencies: 182 | fast-deep-equal "^3.1.1" 183 | fast-json-stable-stringify "^2.0.0" 184 | json-schema-traverse "^0.4.1" 185 | uri-js "^4.2.2" 186 | 187 | ansi-colors@4.1.1, ansi-colors@^4.1.1: 188 | version "4.1.1" 189 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" 190 | integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== 191 | 192 | ansi-regex@^5.0.1: 193 | version "5.0.1" 194 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 195 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 196 | 197 | ansi-styles@^4.0.0, ansi-styles@^4.1.0: 198 | version "4.3.0" 199 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 200 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 201 | dependencies: 202 | color-convert "^2.0.1" 203 | 204 | anymatch@~3.1.2: 205 | version "3.1.2" 206 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" 207 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== 208 | dependencies: 209 | normalize-path "^3.0.0" 210 | picomatch "^2.0.4" 211 | 212 | argparse@^2.0.1: 213 | version "2.0.1" 214 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" 215 | integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 216 | 217 | array-union@^2.1.0: 218 | version "2.1.0" 219 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" 220 | integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 221 | 222 | assertion-error@^1.1.0: 223 | version "1.1.0" 224 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" 225 | integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== 226 | 227 | balanced-match@^1.0.0: 228 | version "1.0.2" 229 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 230 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 231 | 232 | binary-extensions@^2.0.0: 233 | version "2.2.0" 234 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 235 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 236 | 237 | brace-expansion@^1.1.7: 238 | version "1.1.11" 239 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 240 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 241 | dependencies: 242 | balanced-match "^1.0.0" 243 | concat-map "0.0.1" 244 | 245 | braces@^3.0.1, braces@~3.0.2: 246 | version "3.0.2" 247 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 248 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 249 | dependencies: 250 | fill-range "^7.0.1" 251 | 252 | browser-stdout@1.3.1: 253 | version "1.3.1" 254 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 255 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== 256 | 257 | callsites@^3.0.0: 258 | version "3.1.0" 259 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" 260 | integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== 261 | 262 | camelcase@^6.0.0: 263 | version "6.2.0" 264 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" 265 | integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== 266 | 267 | chai@^4.3.4: 268 | version "4.3.4" 269 | resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49" 270 | integrity sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA== 271 | dependencies: 272 | assertion-error "^1.1.0" 273 | check-error "^1.0.2" 274 | deep-eql "^3.0.1" 275 | get-func-name "^2.0.0" 276 | pathval "^1.1.1" 277 | type-detect "^4.0.5" 278 | 279 | chalk@^4.0.0, chalk@^4.1.0: 280 | version "4.1.2" 281 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 282 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 283 | dependencies: 284 | ansi-styles "^4.1.0" 285 | supports-color "^7.1.0" 286 | 287 | check-error@^1.0.2: 288 | version "1.0.2" 289 | resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 290 | integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= 291 | 292 | chokidar@3.5.2: 293 | version "3.5.2" 294 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" 295 | integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== 296 | dependencies: 297 | anymatch "~3.1.2" 298 | braces "~3.0.2" 299 | glob-parent "~5.1.2" 300 | is-binary-path "~2.1.0" 301 | is-glob "~4.0.1" 302 | normalize-path "~3.0.0" 303 | readdirp "~3.6.0" 304 | optionalDependencies: 305 | fsevents "~2.3.2" 306 | 307 | cliui@^7.0.2: 308 | version "7.0.4" 309 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" 310 | integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== 311 | dependencies: 312 | string-width "^4.2.0" 313 | strip-ansi "^6.0.0" 314 | wrap-ansi "^7.0.0" 315 | 316 | color-convert@^2.0.1: 317 | version "2.0.1" 318 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 319 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 320 | dependencies: 321 | color-name "~1.1.4" 322 | 323 | color-name@~1.1.4: 324 | version "1.1.4" 325 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 326 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 327 | 328 | concat-map@0.0.1: 329 | version "0.0.1" 330 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 331 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 332 | 333 | cross-spawn@^7.0.2: 334 | version "7.0.3" 335 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 336 | integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== 337 | dependencies: 338 | path-key "^3.1.0" 339 | shebang-command "^2.0.0" 340 | which "^2.0.1" 341 | 342 | debug@4.3.2, debug@^4.1.1, debug@^4.3.2: 343 | version "4.3.2" 344 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" 345 | integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== 346 | dependencies: 347 | ms "2.1.2" 348 | 349 | decamelize@^4.0.0: 350 | version "4.0.0" 351 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" 352 | integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== 353 | 354 | deep-eql@^3.0.1: 355 | version "3.0.1" 356 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" 357 | integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== 358 | dependencies: 359 | type-detect "^4.0.0" 360 | 361 | deep-is@^0.1.3: 362 | version "0.1.4" 363 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" 364 | integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== 365 | 366 | diff@5.0.0: 367 | version "5.0.0" 368 | resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" 369 | integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== 370 | 371 | dir-glob@^3.0.1: 372 | version "3.0.1" 373 | resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" 374 | integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== 375 | dependencies: 376 | path-type "^4.0.0" 377 | 378 | doctrine@^3.0.0: 379 | version "3.0.0" 380 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" 381 | integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== 382 | dependencies: 383 | esutils "^2.0.2" 384 | 385 | emoji-regex@^8.0.0: 386 | version "8.0.0" 387 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 388 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 389 | 390 | enquirer@^2.3.5: 391 | version "2.3.6" 392 | resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" 393 | integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== 394 | dependencies: 395 | ansi-colors "^4.1.1" 396 | 397 | escalade@^3.1.1: 398 | version "3.1.1" 399 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 400 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 401 | 402 | escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: 403 | version "4.0.0" 404 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 405 | integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 406 | 407 | eslint-scope@^5.1.1: 408 | version "5.1.1" 409 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" 410 | integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== 411 | dependencies: 412 | esrecurse "^4.3.0" 413 | estraverse "^4.1.1" 414 | 415 | eslint-scope@^6.0.0: 416 | version "6.0.0" 417 | resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-6.0.0.tgz#9cf45b13c5ac8f3d4c50f46a5121f61b3e318978" 418 | integrity sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA== 419 | dependencies: 420 | esrecurse "^4.3.0" 421 | estraverse "^5.2.0" 422 | 423 | eslint-utils@^3.0.0: 424 | version "3.0.0" 425 | resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" 426 | integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== 427 | dependencies: 428 | eslint-visitor-keys "^2.0.0" 429 | 430 | eslint-visitor-keys@^2.0.0: 431 | version "2.1.0" 432 | resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" 433 | integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== 434 | 435 | eslint-visitor-keys@^3.0.0: 436 | version "3.1.0" 437 | resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" 438 | integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== 439 | 440 | eslint@^8.0.1: 441 | version "8.2.0" 442 | resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.2.0.tgz#44d3fb506d0f866a506d97a0fc0e90ee6d06a815" 443 | integrity sha512-erw7XmM+CLxTOickrimJ1SiF55jiNlVSp2qqm0NuBWPtHYQCegD5ZMaW0c3i5ytPqL+SSLaCxdvQXFPLJn+ABw== 444 | dependencies: 445 | "@eslint/eslintrc" "^1.0.4" 446 | "@humanwhocodes/config-array" "^0.6.0" 447 | ajv "^6.10.0" 448 | chalk "^4.0.0" 449 | cross-spawn "^7.0.2" 450 | debug "^4.3.2" 451 | doctrine "^3.0.0" 452 | enquirer "^2.3.5" 453 | escape-string-regexp "^4.0.0" 454 | eslint-scope "^6.0.0" 455 | eslint-utils "^3.0.0" 456 | eslint-visitor-keys "^3.0.0" 457 | espree "^9.0.0" 458 | esquery "^1.4.0" 459 | esutils "^2.0.2" 460 | fast-deep-equal "^3.1.3" 461 | file-entry-cache "^6.0.1" 462 | functional-red-black-tree "^1.0.1" 463 | glob-parent "^6.0.1" 464 | globals "^13.6.0" 465 | ignore "^4.0.6" 466 | import-fresh "^3.0.0" 467 | imurmurhash "^0.1.4" 468 | is-glob "^4.0.0" 469 | js-yaml "^4.1.0" 470 | json-stable-stringify-without-jsonify "^1.0.1" 471 | levn "^0.4.1" 472 | lodash.merge "^4.6.2" 473 | minimatch "^3.0.4" 474 | natural-compare "^1.4.0" 475 | optionator "^0.9.1" 476 | progress "^2.0.0" 477 | regexpp "^3.2.0" 478 | semver "^7.2.1" 479 | strip-ansi "^6.0.1" 480 | strip-json-comments "^3.1.0" 481 | text-table "^0.2.0" 482 | v8-compile-cache "^2.0.3" 483 | 484 | espree@^9.0.0: 485 | version "9.0.0" 486 | resolved "https://registry.yarnpkg.com/espree/-/espree-9.0.0.tgz#e90a2965698228502e771c7a58489b1a9d107090" 487 | integrity sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ== 488 | dependencies: 489 | acorn "^8.5.0" 490 | acorn-jsx "^5.3.1" 491 | eslint-visitor-keys "^3.0.0" 492 | 493 | esquery@^1.4.0: 494 | version "1.4.0" 495 | resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" 496 | integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== 497 | dependencies: 498 | estraverse "^5.1.0" 499 | 500 | esrecurse@^4.3.0: 501 | version "4.3.0" 502 | resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" 503 | integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== 504 | dependencies: 505 | estraverse "^5.2.0" 506 | 507 | estraverse@^4.1.1: 508 | version "4.3.0" 509 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" 510 | integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== 511 | 512 | estraverse@^5.1.0, estraverse@^5.2.0: 513 | version "5.3.0" 514 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" 515 | integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== 516 | 517 | esutils@^2.0.2: 518 | version "2.0.3" 519 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" 520 | integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== 521 | 522 | fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: 523 | version "3.1.3" 524 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 525 | integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 526 | 527 | fast-glob@^3.1.1: 528 | version "3.2.7" 529 | resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" 530 | integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== 531 | dependencies: 532 | "@nodelib/fs.stat" "^2.0.2" 533 | "@nodelib/fs.walk" "^1.2.3" 534 | glob-parent "^5.1.2" 535 | merge2 "^1.3.0" 536 | micromatch "^4.0.4" 537 | 538 | fast-json-stable-stringify@^2.0.0: 539 | version "2.1.0" 540 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" 541 | integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== 542 | 543 | fast-levenshtein@^2.0.6: 544 | version "2.0.6" 545 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" 546 | integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= 547 | 548 | fastq@^1.6.0: 549 | version "1.13.0" 550 | resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" 551 | integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== 552 | dependencies: 553 | reusify "^1.0.4" 554 | 555 | file-entry-cache@^6.0.1: 556 | version "6.0.1" 557 | resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" 558 | integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== 559 | dependencies: 560 | flat-cache "^3.0.4" 561 | 562 | fill-range@^7.0.1: 563 | version "7.0.1" 564 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 565 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 566 | dependencies: 567 | to-regex-range "^5.0.1" 568 | 569 | find-up@5.0.0: 570 | version "5.0.0" 571 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" 572 | integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== 573 | dependencies: 574 | locate-path "^6.0.0" 575 | path-exists "^4.0.0" 576 | 577 | flat-cache@^3.0.4: 578 | version "3.0.4" 579 | resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" 580 | integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== 581 | dependencies: 582 | flatted "^3.1.0" 583 | rimraf "^3.0.2" 584 | 585 | flat@^5.0.2: 586 | version "5.0.2" 587 | resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" 588 | integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== 589 | 590 | flatted@^3.1.0: 591 | version "3.2.2" 592 | resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" 593 | integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== 594 | 595 | fs.realpath@^1.0.0: 596 | version "1.0.0" 597 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 598 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 599 | 600 | fsevents@~2.3.2: 601 | version "2.3.2" 602 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 603 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 604 | 605 | functional-red-black-tree@^1.0.1: 606 | version "1.0.1" 607 | resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" 608 | integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= 609 | 610 | get-caller-file@^2.0.5: 611 | version "2.0.5" 612 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 613 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 614 | 615 | get-func-name@^2.0.0: 616 | version "2.0.0" 617 | resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" 618 | integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= 619 | 620 | glob-parent@^5.1.2, glob-parent@~5.1.2: 621 | version "5.1.2" 622 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 623 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 624 | dependencies: 625 | is-glob "^4.0.1" 626 | 627 | glob-parent@^6.0.1: 628 | version "6.0.2" 629 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" 630 | integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== 631 | dependencies: 632 | is-glob "^4.0.3" 633 | 634 | glob@7.1.7: 635 | version "7.1.7" 636 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" 637 | integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== 638 | dependencies: 639 | fs.realpath "^1.0.0" 640 | inflight "^1.0.4" 641 | inherits "2" 642 | minimatch "^3.0.4" 643 | once "^1.3.0" 644 | path-is-absolute "^1.0.0" 645 | 646 | glob@^7.1.3: 647 | version "7.2.0" 648 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" 649 | integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== 650 | dependencies: 651 | fs.realpath "^1.0.0" 652 | inflight "^1.0.4" 653 | inherits "2" 654 | minimatch "^3.0.4" 655 | once "^1.3.0" 656 | path-is-absolute "^1.0.0" 657 | 658 | globals@^13.6.0, globals@^13.9.0: 659 | version "13.12.0" 660 | resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" 661 | integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== 662 | dependencies: 663 | type-fest "^0.20.2" 664 | 665 | globby@^11.0.4: 666 | version "11.0.4" 667 | resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" 668 | integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== 669 | dependencies: 670 | array-union "^2.1.0" 671 | dir-glob "^3.0.1" 672 | fast-glob "^3.1.1" 673 | ignore "^5.1.4" 674 | merge2 "^1.3.0" 675 | slash "^3.0.0" 676 | 677 | "graphql@~14.6.0 || ~15.0.0 || ~16.0.0": 678 | version "16.0.1" 679 | resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.0.1.tgz#93a13cd4e0e38ca8d0832e79614c8578bfd34f10" 680 | integrity sha512-oPvCuu6dlLdiz8gZupJ47o1clgb72r1u8NDBcQYjcV6G/iEdmE11B1bBlkhXRvV0LisP/SXRFP7tT6AgaTjpzg== 681 | 682 | growl@1.10.5: 683 | version "1.10.5" 684 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" 685 | integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== 686 | 687 | has-flag@^4.0.0: 688 | version "4.0.0" 689 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 690 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 691 | 692 | he@1.2.0: 693 | version "1.2.0" 694 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" 695 | integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 696 | 697 | ignore@^4.0.6: 698 | version "4.0.6" 699 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" 700 | integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== 701 | 702 | ignore@^5.1.4, ignore@^5.1.8: 703 | version "5.1.9" 704 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" 705 | integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== 706 | 707 | import-fresh@^3.0.0, import-fresh@^3.2.1: 708 | version "3.3.0" 709 | resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" 710 | integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== 711 | dependencies: 712 | parent-module "^1.0.0" 713 | resolve-from "^4.0.0" 714 | 715 | imurmurhash@^0.1.4: 716 | version "0.1.4" 717 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 718 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= 719 | 720 | inflight@^1.0.4: 721 | version "1.0.6" 722 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 723 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 724 | dependencies: 725 | once "^1.3.0" 726 | wrappy "1" 727 | 728 | inherits@2: 729 | version "2.0.4" 730 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 731 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 732 | 733 | is-binary-path@~2.1.0: 734 | version "2.1.0" 735 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 736 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 737 | dependencies: 738 | binary-extensions "^2.0.0" 739 | 740 | is-extglob@^2.1.1: 741 | version "2.1.1" 742 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 743 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 744 | 745 | is-fullwidth-code-point@^3.0.0: 746 | version "3.0.0" 747 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 748 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 749 | 750 | is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: 751 | version "4.0.3" 752 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 753 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 754 | dependencies: 755 | is-extglob "^2.1.1" 756 | 757 | is-number@^7.0.0: 758 | version "7.0.0" 759 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 760 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 761 | 762 | is-plain-obj@^2.1.0: 763 | version "2.1.0" 764 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" 765 | integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== 766 | 767 | is-unicode-supported@^0.1.0: 768 | version "0.1.0" 769 | resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" 770 | integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== 771 | 772 | isexe@^2.0.0: 773 | version "2.0.0" 774 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 775 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 776 | 777 | js-yaml@4.1.0, js-yaml@^4.1.0: 778 | version "4.1.0" 779 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" 780 | integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== 781 | dependencies: 782 | argparse "^2.0.1" 783 | 784 | json-schema-traverse@^0.4.1: 785 | version "0.4.1" 786 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 787 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== 788 | 789 | json-stable-stringify-without-jsonify@^1.0.1: 790 | version "1.0.1" 791 | resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" 792 | integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= 793 | 794 | levn@^0.4.1: 795 | version "0.4.1" 796 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" 797 | integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== 798 | dependencies: 799 | prelude-ls "^1.2.1" 800 | type-check "~0.4.0" 801 | 802 | locate-path@^6.0.0: 803 | version "6.0.0" 804 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" 805 | integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== 806 | dependencies: 807 | p-locate "^5.0.0" 808 | 809 | lodash.get@^4.4.2: 810 | version "4.4.2" 811 | resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" 812 | integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= 813 | 814 | lodash.merge@^4.6.2: 815 | version "4.6.2" 816 | resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" 817 | integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== 818 | 819 | log-symbols@4.1.0: 820 | version "4.1.0" 821 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" 822 | integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== 823 | dependencies: 824 | chalk "^4.1.0" 825 | is-unicode-supported "^0.1.0" 826 | 827 | lru-cache@^6.0.0: 828 | version "6.0.0" 829 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 830 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 831 | dependencies: 832 | yallist "^4.0.0" 833 | 834 | merge2@^1.3.0: 835 | version "1.4.1" 836 | resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" 837 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== 838 | 839 | micromatch@^4.0.4: 840 | version "4.0.4" 841 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" 842 | integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== 843 | dependencies: 844 | braces "^3.0.1" 845 | picomatch "^2.2.3" 846 | 847 | minimatch@3.0.4, minimatch@^3.0.4: 848 | version "3.0.4" 849 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 850 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 851 | dependencies: 852 | brace-expansion "^1.1.7" 853 | 854 | mocha@^9.1.3: 855 | version "9.1.3" 856 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.1.3.tgz#8a623be6b323810493d8c8f6f7667440fa469fdb" 857 | integrity sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw== 858 | dependencies: 859 | "@ungap/promise-all-settled" "1.1.2" 860 | ansi-colors "4.1.1" 861 | browser-stdout "1.3.1" 862 | chokidar "3.5.2" 863 | debug "4.3.2" 864 | diff "5.0.0" 865 | escape-string-regexp "4.0.0" 866 | find-up "5.0.0" 867 | glob "7.1.7" 868 | growl "1.10.5" 869 | he "1.2.0" 870 | js-yaml "4.1.0" 871 | log-symbols "4.1.0" 872 | minimatch "3.0.4" 873 | ms "2.1.3" 874 | nanoid "3.1.25" 875 | serialize-javascript "6.0.0" 876 | strip-json-comments "3.1.1" 877 | supports-color "8.1.1" 878 | which "2.0.2" 879 | workerpool "6.1.5" 880 | yargs "16.2.0" 881 | yargs-parser "20.2.4" 882 | yargs-unparser "2.0.0" 883 | 884 | ms@2.1.2: 885 | version "2.1.2" 886 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 887 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 888 | 889 | ms@2.1.3: 890 | version "2.1.3" 891 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 892 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 893 | 894 | nanoid@3.1.25: 895 | version "3.1.25" 896 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" 897 | integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== 898 | 899 | natural-compare@^1.4.0: 900 | version "1.4.0" 901 | resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" 902 | integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= 903 | 904 | normalize-path@^3.0.0, normalize-path@~3.0.0: 905 | version "3.0.0" 906 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 907 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 908 | 909 | once@^1.3.0: 910 | version "1.4.0" 911 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 912 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 913 | dependencies: 914 | wrappy "1" 915 | 916 | optionator@^0.9.1: 917 | version "0.9.1" 918 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" 919 | integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== 920 | dependencies: 921 | deep-is "^0.1.3" 922 | fast-levenshtein "^2.0.6" 923 | levn "^0.4.1" 924 | prelude-ls "^1.2.1" 925 | type-check "^0.4.0" 926 | word-wrap "^1.2.3" 927 | 928 | p-limit@^3.0.2: 929 | version "3.1.0" 930 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" 931 | integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== 932 | dependencies: 933 | yocto-queue "^0.1.0" 934 | 935 | p-locate@^5.0.0: 936 | version "5.0.0" 937 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" 938 | integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== 939 | dependencies: 940 | p-limit "^3.0.2" 941 | 942 | parent-module@^1.0.0: 943 | version "1.0.1" 944 | resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" 945 | integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== 946 | dependencies: 947 | callsites "^3.0.0" 948 | 949 | path-exists@^4.0.0: 950 | version "4.0.0" 951 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 952 | integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 953 | 954 | path-is-absolute@^1.0.0: 955 | version "1.0.1" 956 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 957 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 958 | 959 | path-key@^3.1.0: 960 | version "3.1.1" 961 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" 962 | integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== 963 | 964 | path-type@^4.0.0: 965 | version "4.0.0" 966 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 967 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 968 | 969 | pathval@^1.1.1: 970 | version "1.1.1" 971 | resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" 972 | integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== 973 | 974 | picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: 975 | version "2.3.0" 976 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" 977 | integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== 978 | 979 | prelude-ls@^1.2.1: 980 | version "1.2.1" 981 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" 982 | integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== 983 | 984 | prettier@^2.4.1: 985 | version "2.4.1" 986 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c" 987 | integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA== 988 | 989 | progress@^2.0.0: 990 | version "2.0.3" 991 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" 992 | integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== 993 | 994 | punycode@^2.1.0: 995 | version "2.1.1" 996 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 997 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 998 | 999 | queue-microtask@^1.2.2: 1000 | version "1.2.3" 1001 | resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" 1002 | integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== 1003 | 1004 | randombytes@^2.1.0: 1005 | version "2.1.0" 1006 | resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" 1007 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 1008 | dependencies: 1009 | safe-buffer "^5.1.0" 1010 | 1011 | readdirp@~3.6.0: 1012 | version "3.6.0" 1013 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 1014 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 1015 | dependencies: 1016 | picomatch "^2.2.1" 1017 | 1018 | regexpp@^3.2.0: 1019 | version "3.2.0" 1020 | resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" 1021 | integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== 1022 | 1023 | require-directory@^2.1.1: 1024 | version "2.1.1" 1025 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 1026 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 1027 | 1028 | resolve-from@^4.0.0: 1029 | version "4.0.0" 1030 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" 1031 | integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== 1032 | 1033 | reusify@^1.0.4: 1034 | version "1.0.4" 1035 | resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 1036 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 1037 | 1038 | rimraf@^3.0.2: 1039 | version "3.0.2" 1040 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 1041 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 1042 | dependencies: 1043 | glob "^7.1.3" 1044 | 1045 | run-parallel@^1.1.9: 1046 | version "1.2.0" 1047 | resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" 1048 | integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== 1049 | dependencies: 1050 | queue-microtask "^1.2.2" 1051 | 1052 | safe-buffer@^5.1.0: 1053 | version "5.2.1" 1054 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 1055 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 1056 | 1057 | semver@^7.2.1, semver@^7.3.5: 1058 | version "7.3.5" 1059 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" 1060 | integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== 1061 | dependencies: 1062 | lru-cache "^6.0.0" 1063 | 1064 | serialize-javascript@6.0.0: 1065 | version "6.0.0" 1066 | resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" 1067 | integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== 1068 | dependencies: 1069 | randombytes "^2.1.0" 1070 | 1071 | shebang-command@^2.0.0: 1072 | version "2.0.0" 1073 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" 1074 | integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== 1075 | dependencies: 1076 | shebang-regex "^3.0.0" 1077 | 1078 | shebang-regex@^3.0.0: 1079 | version "3.0.0" 1080 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 1081 | integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 1082 | 1083 | slash@^3.0.0: 1084 | version "3.0.0" 1085 | resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" 1086 | integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== 1087 | 1088 | string-width@^4.1.0, string-width@^4.2.0: 1089 | version "4.2.3" 1090 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 1091 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 1092 | dependencies: 1093 | emoji-regex "^8.0.0" 1094 | is-fullwidth-code-point "^3.0.0" 1095 | strip-ansi "^6.0.1" 1096 | 1097 | strip-ansi@^6.0.0, strip-ansi@^6.0.1: 1098 | version "6.0.1" 1099 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 1100 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 1101 | dependencies: 1102 | ansi-regex "^5.0.1" 1103 | 1104 | strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: 1105 | version "3.1.1" 1106 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" 1107 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 1108 | 1109 | supports-color@8.1.1: 1110 | version "8.1.1" 1111 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" 1112 | integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== 1113 | dependencies: 1114 | has-flag "^4.0.0" 1115 | 1116 | supports-color@^7.1.0: 1117 | version "7.2.0" 1118 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 1119 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 1120 | dependencies: 1121 | has-flag "^4.0.0" 1122 | 1123 | text-table@^0.2.0: 1124 | version "0.2.0" 1125 | resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 1126 | integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= 1127 | 1128 | to-regex-range@^5.0.1: 1129 | version "5.0.1" 1130 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1131 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1132 | dependencies: 1133 | is-number "^7.0.0" 1134 | 1135 | tslib@^1.8.1: 1136 | version "1.14.1" 1137 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" 1138 | integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== 1139 | 1140 | tsutils@^3.21.0: 1141 | version "3.21.0" 1142 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" 1143 | integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== 1144 | dependencies: 1145 | tslib "^1.8.1" 1146 | 1147 | type-check@^0.4.0, type-check@~0.4.0: 1148 | version "0.4.0" 1149 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" 1150 | integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== 1151 | dependencies: 1152 | prelude-ls "^1.2.1" 1153 | 1154 | type-detect@^4.0.0, type-detect@^4.0.5: 1155 | version "4.0.8" 1156 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 1157 | integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 1158 | 1159 | type-fest@^0.20.2: 1160 | version "0.20.2" 1161 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" 1162 | integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== 1163 | 1164 | typescript@^4.4.4: 1165 | version "4.4.4" 1166 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" 1167 | integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== 1168 | 1169 | uri-js@^4.2.2: 1170 | version "4.4.1" 1171 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" 1172 | integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== 1173 | dependencies: 1174 | punycode "^2.1.0" 1175 | 1176 | v8-compile-cache@^2.0.3: 1177 | version "2.3.0" 1178 | resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" 1179 | integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== 1180 | 1181 | which@2.0.2, which@^2.0.1: 1182 | version "2.0.2" 1183 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 1184 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 1185 | dependencies: 1186 | isexe "^2.0.0" 1187 | 1188 | word-wrap@^1.2.3: 1189 | version "1.2.3" 1190 | resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" 1191 | integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== 1192 | 1193 | workerpool@6.1.5: 1194 | version "6.1.5" 1195 | resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581" 1196 | integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw== 1197 | 1198 | wrap-ansi@^7.0.0: 1199 | version "7.0.0" 1200 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 1201 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 1202 | dependencies: 1203 | ansi-styles "^4.0.0" 1204 | string-width "^4.1.0" 1205 | strip-ansi "^6.0.0" 1206 | 1207 | wrappy@1: 1208 | version "1.0.2" 1209 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1210 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 1211 | 1212 | y18n@^5.0.5: 1213 | version "5.0.8" 1214 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" 1215 | integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== 1216 | 1217 | yallist@^4.0.0: 1218 | version "4.0.0" 1219 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 1220 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 1221 | 1222 | yargs-parser@20.2.4: 1223 | version "20.2.4" 1224 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" 1225 | integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== 1226 | 1227 | yargs-parser@^20.2.2: 1228 | version "20.2.9" 1229 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" 1230 | integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== 1231 | 1232 | yargs-unparser@2.0.0: 1233 | version "2.0.0" 1234 | resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" 1235 | integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== 1236 | dependencies: 1237 | camelcase "^6.0.0" 1238 | decamelize "^4.0.0" 1239 | flat "^5.0.2" 1240 | is-plain-obj "^2.1.0" 1241 | 1242 | yargs@16.2.0: 1243 | version "16.2.0" 1244 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" 1245 | integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== 1246 | dependencies: 1247 | cliui "^7.0.2" 1248 | escalade "^3.1.1" 1249 | get-caller-file "^2.0.5" 1250 | require-directory "^2.1.1" 1251 | string-width "^4.2.0" 1252 | y18n "^5.0.5" 1253 | yargs-parser "^20.2.2" 1254 | 1255 | yocto-queue@^0.1.0: 1256 | version "0.1.0" 1257 | resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" 1258 | integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== 1259 | --------------------------------------------------------------------------------