@envelop/resource-limitations 8.0.1-alpha-20260116142832-a2277336f0a5a4d64d168c98cf97b7513f70b9f7 → 8.1.0-alpha-20260120163327-6e77ec22fc62a09ba152ed764585743e1557eaf8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Dotan Simha
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.
package/cjs/index.js ADDED
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useResourceLimitations = exports.ResourceLimitationValidationRule = exports.defaultPaginationArgumentMinimum = exports.defaultPaginationArgumentMaximum = exports.defaultNodeCostLimit = void 0;
4
+ const graphql_1 = require("graphql");
5
+ const core_1 = require("@envelop/core");
6
+ const extended_validation_1 = require("@envelop/extended-validation");
7
+ const utils_1 = require("@graphql-tools/utils");
8
+ const getWrappedType = (graphqlType) => {
9
+ if (graphqlType instanceof graphql_1.GraphQLList || graphqlType instanceof graphql_1.GraphQLNonNull) {
10
+ return getWrappedType(graphqlType.ofType);
11
+ }
12
+ return graphqlType;
13
+ };
14
+ const isValidArgType = (type, paginationArgumentTypes) => type === graphql_1.GraphQLInt ||
15
+ ((0, graphql_1.isScalarType)(type) && !!paginationArgumentTypes && paginationArgumentTypes.includes(type.name));
16
+ const hasFieldDefConnectionArgs = (field, argumentTypes) => {
17
+ let hasFirst = false;
18
+ let hasLast = false;
19
+ for (const arg of field.args) {
20
+ if (arg.name === 'first' && isValidArgType(arg.type, argumentTypes)) {
21
+ hasFirst = true;
22
+ }
23
+ else if (arg.name === 'last' && isValidArgType(arg.type, argumentTypes)) {
24
+ hasLast = true;
25
+ }
26
+ else if (hasLast && hasFirst) {
27
+ break;
28
+ }
29
+ }
30
+ return { hasFirst, hasLast };
31
+ };
32
+ const buildMissingPaginationFieldErrorMessage = (params) => `Missing pagination argument for field '${params.fieldName}'. ` +
33
+ `Please provide ` +
34
+ (params.hasFirst && params.hasLast
35
+ ? "either the 'first' or 'last'"
36
+ : params.hasFirst
37
+ ? "the 'first'"
38
+ : "the 'last'") +
39
+ ' field argument.';
40
+ const buildInvalidPaginationRangeErrorMessage = (params) => `Invalid pagination argument for field '${params.fieldName}'. ` +
41
+ `The value for the '${params.argumentName}' argument must be an integer within ${params.paginationArgumentMinimum}-${params.paginationArgumentMaximum}.`;
42
+ exports.defaultNodeCostLimit = 500000;
43
+ exports.defaultPaginationArgumentMaximum = 100;
44
+ exports.defaultPaginationArgumentMinimum = 1;
45
+ /**
46
+ * Validate whether a user is allowed to execute a certain GraphQL operation.
47
+ */
48
+ const ResourceLimitationValidationRule = (params) => (context, executionArgs) => {
49
+ const { paginationArgumentMaximum, paginationArgumentMinimum } = params;
50
+ const nodeCostStack = [];
51
+ let totalNodeCost = 0;
52
+ const connectionFieldMap = new WeakSet();
53
+ return {
54
+ Field: {
55
+ enter(fieldNode) {
56
+ const fieldDef = context.getFieldDef();
57
+ // if it is not found the query is invalid and graphql validation will complain
58
+ if (fieldDef != null) {
59
+ const argumentValues = (0, utils_1.getArgumentValues)(fieldDef, fieldNode, executionArgs.variableValues || undefined);
60
+ const type = getWrappedType(fieldDef.type);
61
+ if (type instanceof graphql_1.GraphQLObjectType && type.name.endsWith('Connection')) {
62
+ let nodeCost = 1;
63
+ connectionFieldMap.add(fieldNode);
64
+ const { hasFirst, hasLast } = hasFieldDefConnectionArgs(fieldDef, params.paginationArgumentTypes);
65
+ if (hasFirst === false && hasLast === false) {
66
+ // eslint-disable-next-line no-console
67
+ console.warn('Encountered paginated field without pagination arguments.');
68
+ }
69
+ else if (hasFirst === true || hasLast === true) {
70
+ if (('first' in argumentValues === false && 'last' in argumentValues === false) ||
71
+ (argumentValues.first === null && argumentValues.last === null)) {
72
+ context.reportError(new graphql_1.GraphQLError(buildMissingPaginationFieldErrorMessage({
73
+ fieldName: fieldDef.name,
74
+ hasFirst,
75
+ hasLast,
76
+ }), fieldNode));
77
+ }
78
+ else if ('first' in argumentValues && !argumentValues.last) {
79
+ if (argumentValues.first < paginationArgumentMinimum ||
80
+ argumentValues.first > paginationArgumentMaximum) {
81
+ context.reportError(new graphql_1.GraphQLError(buildInvalidPaginationRangeErrorMessage({
82
+ paginationArgumentMaximum,
83
+ paginationArgumentMinimum,
84
+ argumentName: 'first',
85
+ fieldName: fieldDef.name,
86
+ }), fieldNode));
87
+ }
88
+ else {
89
+ // eslint-disable-next-line dot-notation
90
+ nodeCost = argumentValues['first'];
91
+ }
92
+ }
93
+ else if (!argumentValues.first && 'last' in argumentValues) {
94
+ if (argumentValues.last < paginationArgumentMinimum ||
95
+ argumentValues.last > paginationArgumentMaximum) {
96
+ context.reportError(new graphql_1.GraphQLError(buildInvalidPaginationRangeErrorMessage({
97
+ paginationArgumentMaximum,
98
+ paginationArgumentMinimum,
99
+ argumentName: 'last',
100
+ fieldName: fieldDef.name,
101
+ }), fieldNode));
102
+ }
103
+ else {
104
+ // eslint-disable-next-line dot-notation
105
+ nodeCost = argumentValues['last'];
106
+ }
107
+ }
108
+ else {
109
+ context.reportError(new graphql_1.GraphQLError(buildMissingPaginationFieldErrorMessage({
110
+ fieldName: fieldDef.name,
111
+ hasFirst,
112
+ hasLast,
113
+ }), fieldNode));
114
+ }
115
+ }
116
+ nodeCostStack.push(nodeCost);
117
+ }
118
+ }
119
+ },
120
+ leave(node) {
121
+ if (connectionFieldMap.delete(node)) {
122
+ totalNodeCost = totalNodeCost + nodeCostStack.reduce((a, b) => a * b, 1);
123
+ nodeCostStack.pop();
124
+ }
125
+ },
126
+ },
127
+ Document: {
128
+ leave(documentNode) {
129
+ if (totalNodeCost === 0) {
130
+ totalNodeCost = 1;
131
+ }
132
+ if (totalNodeCost > params.nodeCostLimit) {
133
+ context.reportError(new graphql_1.GraphQLError(`Cannot request more than ${params.nodeCostLimit} nodes in a single document. Please split your operation into multiple sub operations or reduce the amount of requested nodes.`, documentNode));
134
+ }
135
+ params.reportNodeCost?.(totalNodeCost, executionArgs);
136
+ },
137
+ },
138
+ };
139
+ };
140
+ exports.ResourceLimitationValidationRule = ResourceLimitationValidationRule;
141
+ const useResourceLimitations = (params) => {
142
+ const paginationArgumentMaximum = params?.paginationArgumentMaximum ?? exports.defaultPaginationArgumentMaximum;
143
+ const paginationArgumentMinimum = params?.paginationArgumentMinimum ?? exports.defaultPaginationArgumentMinimum;
144
+ const nodeCostLimit = params?.nodeCostLimit ?? exports.defaultNodeCostLimit;
145
+ const extensions = params?.extensions ?? false;
146
+ const nodeCostMap = new WeakMap();
147
+ const handleResult = ({ result, args }) => {
148
+ const nodeCost = nodeCostMap.get(args);
149
+ if (nodeCost != null) {
150
+ result.extensions = {
151
+ ...result.extensions,
152
+ resourceLimitations: {
153
+ nodeCost,
154
+ },
155
+ };
156
+ }
157
+ };
158
+ return {
159
+ onPluginInit({ addPlugin }) {
160
+ addPlugin((0, extended_validation_1.useExtendedValidation)({
161
+ rules: [
162
+ (0, exports.ResourceLimitationValidationRule)({
163
+ nodeCostLimit,
164
+ paginationArgumentMaximum,
165
+ paginationArgumentMinimum,
166
+ paginationArgumentTypes: params?.paginationArgumentScalars,
167
+ reportNodeCost: extensions
168
+ ? (nodeCost, ref) => {
169
+ nodeCostMap.set(ref, nodeCost);
170
+ }
171
+ : undefined,
172
+ }),
173
+ ],
174
+ onValidationFailed: params => handleResult(params),
175
+ }));
176
+ },
177
+ onExecute({ args }) {
178
+ return {
179
+ onExecuteDone(payload) {
180
+ return (0, core_1.handleStreamOrSingleExecutionResult)(payload, ({ result }) => handleResult({ result, args }));
181
+ },
182
+ };
183
+ },
184
+ };
185
+ };
186
+ exports.useResourceLimitations = useResourceLimitations;
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
package/esm/index.js ADDED
@@ -0,0 +1,181 @@
1
+ import { GraphQLError, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, isScalarType, } from 'graphql';
2
+ import { handleStreamOrSingleExecutionResult } from '@envelop/core';
3
+ import { useExtendedValidation } from '@envelop/extended-validation';
4
+ import { getArgumentValues } from '@graphql-tools/utils';
5
+ const getWrappedType = (graphqlType) => {
6
+ if (graphqlType instanceof GraphQLList || graphqlType instanceof GraphQLNonNull) {
7
+ return getWrappedType(graphqlType.ofType);
8
+ }
9
+ return graphqlType;
10
+ };
11
+ const isValidArgType = (type, paginationArgumentTypes) => type === GraphQLInt ||
12
+ (isScalarType(type) && !!paginationArgumentTypes && paginationArgumentTypes.includes(type.name));
13
+ const hasFieldDefConnectionArgs = (field, argumentTypes) => {
14
+ let hasFirst = false;
15
+ let hasLast = false;
16
+ for (const arg of field.args) {
17
+ if (arg.name === 'first' && isValidArgType(arg.type, argumentTypes)) {
18
+ hasFirst = true;
19
+ }
20
+ else if (arg.name === 'last' && isValidArgType(arg.type, argumentTypes)) {
21
+ hasLast = true;
22
+ }
23
+ else if (hasLast && hasFirst) {
24
+ break;
25
+ }
26
+ }
27
+ return { hasFirst, hasLast };
28
+ };
29
+ const buildMissingPaginationFieldErrorMessage = (params) => `Missing pagination argument for field '${params.fieldName}'. ` +
30
+ `Please provide ` +
31
+ (params.hasFirst && params.hasLast
32
+ ? "either the 'first' or 'last'"
33
+ : params.hasFirst
34
+ ? "the 'first'"
35
+ : "the 'last'") +
36
+ ' field argument.';
37
+ const buildInvalidPaginationRangeErrorMessage = (params) => `Invalid pagination argument for field '${params.fieldName}'. ` +
38
+ `The value for the '${params.argumentName}' argument must be an integer within ${params.paginationArgumentMinimum}-${params.paginationArgumentMaximum}.`;
39
+ export const defaultNodeCostLimit = 500000;
40
+ export const defaultPaginationArgumentMaximum = 100;
41
+ export const defaultPaginationArgumentMinimum = 1;
42
+ /**
43
+ * Validate whether a user is allowed to execute a certain GraphQL operation.
44
+ */
45
+ export const ResourceLimitationValidationRule = (params) => (context, executionArgs) => {
46
+ const { paginationArgumentMaximum, paginationArgumentMinimum } = params;
47
+ const nodeCostStack = [];
48
+ let totalNodeCost = 0;
49
+ const connectionFieldMap = new WeakSet();
50
+ return {
51
+ Field: {
52
+ enter(fieldNode) {
53
+ const fieldDef = context.getFieldDef();
54
+ // if it is not found the query is invalid and graphql validation will complain
55
+ if (fieldDef != null) {
56
+ const argumentValues = getArgumentValues(fieldDef, fieldNode, executionArgs.variableValues || undefined);
57
+ const type = getWrappedType(fieldDef.type);
58
+ if (type instanceof GraphQLObjectType && type.name.endsWith('Connection')) {
59
+ let nodeCost = 1;
60
+ connectionFieldMap.add(fieldNode);
61
+ const { hasFirst, hasLast } = hasFieldDefConnectionArgs(fieldDef, params.paginationArgumentTypes);
62
+ if (hasFirst === false && hasLast === false) {
63
+ // eslint-disable-next-line no-console
64
+ console.warn('Encountered paginated field without pagination arguments.');
65
+ }
66
+ else if (hasFirst === true || hasLast === true) {
67
+ if (('first' in argumentValues === false && 'last' in argumentValues === false) ||
68
+ (argumentValues.first === null && argumentValues.last === null)) {
69
+ context.reportError(new GraphQLError(buildMissingPaginationFieldErrorMessage({
70
+ fieldName: fieldDef.name,
71
+ hasFirst,
72
+ hasLast,
73
+ }), fieldNode));
74
+ }
75
+ else if ('first' in argumentValues && !argumentValues.last) {
76
+ if (argumentValues.first < paginationArgumentMinimum ||
77
+ argumentValues.first > paginationArgumentMaximum) {
78
+ context.reportError(new GraphQLError(buildInvalidPaginationRangeErrorMessage({
79
+ paginationArgumentMaximum,
80
+ paginationArgumentMinimum,
81
+ argumentName: 'first',
82
+ fieldName: fieldDef.name,
83
+ }), fieldNode));
84
+ }
85
+ else {
86
+ // eslint-disable-next-line dot-notation
87
+ nodeCost = argumentValues['first'];
88
+ }
89
+ }
90
+ else if (!argumentValues.first && 'last' in argumentValues) {
91
+ if (argumentValues.last < paginationArgumentMinimum ||
92
+ argumentValues.last > paginationArgumentMaximum) {
93
+ context.reportError(new GraphQLError(buildInvalidPaginationRangeErrorMessage({
94
+ paginationArgumentMaximum,
95
+ paginationArgumentMinimum,
96
+ argumentName: 'last',
97
+ fieldName: fieldDef.name,
98
+ }), fieldNode));
99
+ }
100
+ else {
101
+ // eslint-disable-next-line dot-notation
102
+ nodeCost = argumentValues['last'];
103
+ }
104
+ }
105
+ else {
106
+ context.reportError(new GraphQLError(buildMissingPaginationFieldErrorMessage({
107
+ fieldName: fieldDef.name,
108
+ hasFirst,
109
+ hasLast,
110
+ }), fieldNode));
111
+ }
112
+ }
113
+ nodeCostStack.push(nodeCost);
114
+ }
115
+ }
116
+ },
117
+ leave(node) {
118
+ if (connectionFieldMap.delete(node)) {
119
+ totalNodeCost = totalNodeCost + nodeCostStack.reduce((a, b) => a * b, 1);
120
+ nodeCostStack.pop();
121
+ }
122
+ },
123
+ },
124
+ Document: {
125
+ leave(documentNode) {
126
+ if (totalNodeCost === 0) {
127
+ totalNodeCost = 1;
128
+ }
129
+ if (totalNodeCost > params.nodeCostLimit) {
130
+ context.reportError(new GraphQLError(`Cannot request more than ${params.nodeCostLimit} nodes in a single document. Please split your operation into multiple sub operations or reduce the amount of requested nodes.`, documentNode));
131
+ }
132
+ params.reportNodeCost?.(totalNodeCost, executionArgs);
133
+ },
134
+ },
135
+ };
136
+ };
137
+ export const useResourceLimitations = (params) => {
138
+ const paginationArgumentMaximum = params?.paginationArgumentMaximum ?? defaultPaginationArgumentMaximum;
139
+ const paginationArgumentMinimum = params?.paginationArgumentMinimum ?? defaultPaginationArgumentMinimum;
140
+ const nodeCostLimit = params?.nodeCostLimit ?? defaultNodeCostLimit;
141
+ const extensions = params?.extensions ?? false;
142
+ const nodeCostMap = new WeakMap();
143
+ const handleResult = ({ result, args }) => {
144
+ const nodeCost = nodeCostMap.get(args);
145
+ if (nodeCost != null) {
146
+ result.extensions = {
147
+ ...result.extensions,
148
+ resourceLimitations: {
149
+ nodeCost,
150
+ },
151
+ };
152
+ }
153
+ };
154
+ return {
155
+ onPluginInit({ addPlugin }) {
156
+ addPlugin(useExtendedValidation({
157
+ rules: [
158
+ ResourceLimitationValidationRule({
159
+ nodeCostLimit,
160
+ paginationArgumentMaximum,
161
+ paginationArgumentMinimum,
162
+ paginationArgumentTypes: params?.paginationArgumentScalars,
163
+ reportNodeCost: extensions
164
+ ? (nodeCost, ref) => {
165
+ nodeCostMap.set(ref, nodeCost);
166
+ }
167
+ : undefined,
168
+ }),
169
+ ],
170
+ onValidationFailed: params => handleResult(params),
171
+ }));
172
+ },
173
+ onExecute({ args }) {
174
+ return {
175
+ onExecuteDone(payload) {
176
+ return handleStreamOrSingleExecutionResult(payload, ({ result }) => handleResult({ result, args }));
177
+ },
178
+ };
179
+ },
180
+ };
181
+ };
package/package.json CHANGED
@@ -1,11 +1,20 @@
1
1
  {
2
2
  "name": "@envelop/resource-limitations",
3
- "version": "8.0.1-alpha-20260116142832-a2277336f0a5a4d64d168c98cf97b7513f70b9f7",
4
- "type": "module",
3
+ "version": "8.1.0-alpha-20260120163327-6e77ec22fc62a09ba152ed764585743e1557eaf8",
5
4
  "description": "A rate-limit implementation based on resource limitations and static calculation of the score (similar to GitHub GraphQL API)",
5
+ "sideEffects": false,
6
+ "peerDependencies": {
7
+ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0",
8
+ "@envelop/core": "^5.5.0-alpha-20260120163327-6e77ec22fc62a09ba152ed764585743e1557eaf8"
9
+ },
10
+ "dependencies": {
11
+ "@graphql-tools/utils": "^11.0.0",
12
+ "tslib": "^2.5.0",
13
+ "@envelop/extended-validation": "^7.1.0-alpha-20260120163327-6e77ec22fc62a09ba152ed764585743e1557eaf8"
14
+ },
6
15
  "repository": {
7
16
  "type": "git",
8
- "url": "https://github.com/graphql-hive/graphql-yoga",
17
+ "url": "https://github.com/graphql-hive/envelop.git",
9
18
  "directory": "packages/plugins/resource-limitations"
10
19
  },
11
20
  "author": "Laurin Quast <laurinquast@googlemail.com>",
@@ -13,71 +22,42 @@
13
22
  "engines": {
14
23
  "node": ">=18.0.0"
15
24
  },
16
- "main": "dist/index.cjs",
17
- "typings": "dist/index.d.mts",
18
- "module": "dist/index.mjs",
25
+ "main": "cjs/index.js",
26
+ "module": "esm/index.js",
27
+ "typings": "typings/index.d.ts",
28
+ "typescript": {
29
+ "definition": "typings/index.d.ts"
30
+ },
31
+ "type": "module",
19
32
  "exports": {
20
33
  ".": {
21
34
  "require": {
22
- "types": "./dist/index.d.cts",
23
- "default": "./dist/index.cjs"
35
+ "types": "./typings/index.d.cts",
36
+ "default": "./cjs/index.js"
24
37
  },
25
38
  "import": {
26
- "types": "./dist/index.d.mts",
27
- "default": "./dist/index.mjs"
39
+ "types": "./typings/index.d.ts",
40
+ "default": "./esm/index.js"
28
41
  },
29
42
  "default": {
30
- "types": "./dist/index.d.mts",
31
- "default": "./dist/index.mjs"
43
+ "types": "./typings/index.d.ts",
44
+ "default": "./esm/index.js"
32
45
  }
33
46
  },
34
47
  "./*": {
35
48
  "require": {
36
- "types": "./dist/*.d.cts",
37
- "default": "./dist/*.cjs"
49
+ "types": "./typings/*.d.cts",
50
+ "default": "./cjs/*.js"
38
51
  },
39
52
  "import": {
40
- "types": "./dist/*.d.mts",
41
- "default": "./dist/*.mjs"
53
+ "types": "./typings/*.d.ts",
54
+ "default": "./esm/*.js"
42
55
  },
43
56
  "default": {
44
- "types": "./dist/*.d.mts",
45
- "default": "./dist/*.mjs"
57
+ "types": "./typings/*.d.ts",
58
+ "default": "./esm/*.js"
46
59
  }
47
60
  },
48
61
  "./package.json": "./package.json"
49
- },
50
- "files": [
51
- "dist"
52
- ],
53
- "scripts": {
54
- "build": "tsdown",
55
- "check": "tsc --pretty --noEmit"
56
- },
57
- "peerDependencies": {
58
- "@envelop/core": "workspace:^",
59
- "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
60
- },
61
- "dependencies": {
62
- "@envelop/extended-validation": "workspace:^",
63
- "@graphql-tools/utils": "^10.0.0",
64
- "tslib": "^2.5.0"
65
- },
66
- "devDependencies": {
67
- "@envelop/core": "workspace:^",
68
- "@graphql-tools/schema": "10.0.30",
69
- "graphql": "16.12.0",
70
- "tsdown": "^0.20.0-beta.1",
71
- "typescript": "5.9.3"
72
- },
73
- "publishConfig": {
74
- "access": "public"
75
- },
76
- "sideEffects": false,
77
- "buildOptions": {
78
- "input": "./src/index.ts"
79
- },
80
- "typescript": {
81
- "definition": "dist/index.d.mts"
82
62
  }
83
- }
63
+ }
@@ -0,0 +1,45 @@
1
+ import { ExecutionArgs } from 'graphql';
2
+ import { Plugin } from '@envelop/core';
3
+ import { ExtendedValidationRule } from '@envelop/extended-validation';
4
+ export declare const defaultNodeCostLimit = 500000;
5
+ export declare const defaultPaginationArgumentMaximum = 100;
6
+ export declare const defaultPaginationArgumentMinimum = 1;
7
+ export type ResourceLimitationValidationRuleParams = {
8
+ nodeCostLimit: number;
9
+ paginationArgumentMaximum: number;
10
+ paginationArgumentMinimum: number;
11
+ paginationArgumentTypes?: string[];
12
+ reportNodeCost?: (cost: number, executionArgs: ExecutionArgs) => void;
13
+ };
14
+ /**
15
+ * Validate whether a user is allowed to execute a certain GraphQL operation.
16
+ */
17
+ export declare const ResourceLimitationValidationRule: (params: ResourceLimitationValidationRuleParams) => ExtendedValidationRule;
18
+ type UseResourceLimitationsParams = {
19
+ /**
20
+ * The node cost limit for rejecting a operation.
21
+ * @default 500000
22
+ */
23
+ nodeCostLimit?: number;
24
+ /**
25
+ * The custom scalar types accepted for connection arguments.
26
+ */
27
+ paginationArgumentScalars?: string[];
28
+ /**
29
+ * The maximum value accepted for connection arguments.
30
+ * @default 100
31
+ */
32
+ paginationArgumentMaximum?: number;
33
+ /**
34
+ * The minimum value accepted for connection arguments.
35
+ * @default 1
36
+ */
37
+ paginationArgumentMinimum?: number;
38
+ /**
39
+ * Whether the resourceLimitations.nodeCost field should be included within the execution result extensions map.
40
+ * @default false
41
+ */
42
+ extensions?: boolean;
43
+ };
44
+ export declare const useResourceLimitations: (params?: UseResourceLimitationsParams) => Plugin;
45
+ export {};
@@ -0,0 +1,45 @@
1
+ import { ExecutionArgs } from 'graphql';
2
+ import { Plugin } from '@envelop/core';
3
+ import { ExtendedValidationRule } from '@envelop/extended-validation';
4
+ export declare const defaultNodeCostLimit = 500000;
5
+ export declare const defaultPaginationArgumentMaximum = 100;
6
+ export declare const defaultPaginationArgumentMinimum = 1;
7
+ export type ResourceLimitationValidationRuleParams = {
8
+ nodeCostLimit: number;
9
+ paginationArgumentMaximum: number;
10
+ paginationArgumentMinimum: number;
11
+ paginationArgumentTypes?: string[];
12
+ reportNodeCost?: (cost: number, executionArgs: ExecutionArgs) => void;
13
+ };
14
+ /**
15
+ * Validate whether a user is allowed to execute a certain GraphQL operation.
16
+ */
17
+ export declare const ResourceLimitationValidationRule: (params: ResourceLimitationValidationRuleParams) => ExtendedValidationRule;
18
+ type UseResourceLimitationsParams = {
19
+ /**
20
+ * The node cost limit for rejecting a operation.
21
+ * @default 500000
22
+ */
23
+ nodeCostLimit?: number;
24
+ /**
25
+ * The custom scalar types accepted for connection arguments.
26
+ */
27
+ paginationArgumentScalars?: string[];
28
+ /**
29
+ * The maximum value accepted for connection arguments.
30
+ * @default 100
31
+ */
32
+ paginationArgumentMaximum?: number;
33
+ /**
34
+ * The minimum value accepted for connection arguments.
35
+ * @default 1
36
+ */
37
+ paginationArgumentMinimum?: number;
38
+ /**
39
+ * Whether the resourceLimitations.nodeCost field should be included within the execution result extensions map.
40
+ * @default false
41
+ */
42
+ extensions?: boolean;
43
+ };
44
+ export declare const useResourceLimitations: (params?: UseResourceLimitationsParams) => Plugin;
45
+ export {};
package/dist/index.cjs DELETED
@@ -1,134 +0,0 @@
1
- let graphql = require("graphql");
2
- let _envelop_core = require("@envelop/core");
3
- let _envelop_extended_validation = require("@envelop/extended-validation");
4
- let _graphql_tools_utils = require("@graphql-tools/utils");
5
-
6
- //#region src/index.ts
7
- const getWrappedType = (graphqlType) => {
8
- if (graphqlType instanceof graphql.GraphQLList || graphqlType instanceof graphql.GraphQLNonNull) return getWrappedType(graphqlType.ofType);
9
- return graphqlType;
10
- };
11
- const isValidArgType = (type, paginationArgumentTypes) => type === graphql.GraphQLInt || (0, graphql.isScalarType)(type) && !!paginationArgumentTypes && paginationArgumentTypes.includes(type.name);
12
- const hasFieldDefConnectionArgs = (field, argumentTypes) => {
13
- let hasFirst = false;
14
- let hasLast = false;
15
- for (const arg of field.args) if (arg.name === "first" && isValidArgType(arg.type, argumentTypes)) hasFirst = true;
16
- else if (arg.name === "last" && isValidArgType(arg.type, argumentTypes)) hasLast = true;
17
- else if (hasLast && hasFirst) break;
18
- return {
19
- hasFirst,
20
- hasLast
21
- };
22
- };
23
- const buildMissingPaginationFieldErrorMessage = (params) => `Missing pagination argument for field '${params.fieldName}'. Please provide ` + (params.hasFirst && params.hasLast ? "either the 'first' or 'last'" : params.hasFirst ? "the 'first'" : "the 'last'") + " field argument.";
24
- const buildInvalidPaginationRangeErrorMessage = (params) => `Invalid pagination argument for field '${params.fieldName}'. The value for the '${params.argumentName}' argument must be an integer within ${params.paginationArgumentMinimum}-${params.paginationArgumentMaximum}.`;
25
- const defaultNodeCostLimit = 5e5;
26
- const defaultPaginationArgumentMaximum = 100;
27
- const defaultPaginationArgumentMinimum = 1;
28
- /**
29
- * Validate whether a user is allowed to execute a certain GraphQL operation.
30
- */
31
- const ResourceLimitationValidationRule = (params) => (context, executionArgs) => {
32
- const { paginationArgumentMaximum, paginationArgumentMinimum } = params;
33
- const nodeCostStack = [];
34
- let totalNodeCost = 0;
35
- const connectionFieldMap = /* @__PURE__ */ new WeakSet();
36
- return {
37
- Field: {
38
- enter(fieldNode) {
39
- const fieldDef = context.getFieldDef();
40
- if (fieldDef != null) {
41
- const argumentValues = (0, _graphql_tools_utils.getArgumentValues)(fieldDef, fieldNode, executionArgs.variableValues || void 0);
42
- const type = getWrappedType(fieldDef.type);
43
- if (type instanceof graphql.GraphQLObjectType && type.name.endsWith("Connection")) {
44
- let nodeCost = 1;
45
- connectionFieldMap.add(fieldNode);
46
- const { hasFirst, hasLast } = hasFieldDefConnectionArgs(fieldDef, params.paginationArgumentTypes);
47
- if (hasFirst === false && hasLast === false) console.warn("Encountered paginated field without pagination arguments.");
48
- else if (hasFirst === true || hasLast === true) if ("first" in argumentValues === false && "last" in argumentValues === false || argumentValues["first"] === null && argumentValues["last"] === null) context.reportError(new graphql.GraphQLError(buildMissingPaginationFieldErrorMessage({
49
- fieldName: fieldDef.name,
50
- hasFirst,
51
- hasLast
52
- }), fieldNode));
53
- else if ("first" in argumentValues && !argumentValues["last"]) if (argumentValues["first"] < paginationArgumentMinimum || argumentValues["first"] > paginationArgumentMaximum) context.reportError(new graphql.GraphQLError(buildInvalidPaginationRangeErrorMessage({
54
- paginationArgumentMaximum,
55
- paginationArgumentMinimum,
56
- argumentName: "first",
57
- fieldName: fieldDef.name
58
- }), fieldNode));
59
- else nodeCost = argumentValues["first"];
60
- else if (!argumentValues["first"] && "last" in argumentValues) if (argumentValues["last"] < paginationArgumentMinimum || argumentValues["last"] > paginationArgumentMaximum) context.reportError(new graphql.GraphQLError(buildInvalidPaginationRangeErrorMessage({
61
- paginationArgumentMaximum,
62
- paginationArgumentMinimum,
63
- argumentName: "last",
64
- fieldName: fieldDef.name
65
- }), fieldNode));
66
- else nodeCost = argumentValues["last"];
67
- else context.reportError(new graphql.GraphQLError(buildMissingPaginationFieldErrorMessage({
68
- fieldName: fieldDef.name,
69
- hasFirst,
70
- hasLast
71
- }), fieldNode));
72
- nodeCostStack.push(nodeCost);
73
- }
74
- }
75
- },
76
- leave(node) {
77
- if (connectionFieldMap.delete(node)) {
78
- totalNodeCost = totalNodeCost + nodeCostStack.reduce((a, b) => a * b, 1);
79
- nodeCostStack.pop();
80
- }
81
- }
82
- },
83
- Document: { leave(documentNode) {
84
- if (totalNodeCost === 0) totalNodeCost = 1;
85
- if (totalNodeCost > params.nodeCostLimit) context.reportError(new graphql.GraphQLError(`Cannot request more than ${params.nodeCostLimit} nodes in a single document. Please split your operation into multiple sub operations or reduce the amount of requested nodes.`, documentNode));
86
- params.reportNodeCost?.(totalNodeCost, executionArgs);
87
- } }
88
- };
89
- };
90
- const useResourceLimitations = (params) => {
91
- const paginationArgumentMaximum = params?.paginationArgumentMaximum ?? defaultPaginationArgumentMaximum;
92
- const paginationArgumentMinimum = params?.paginationArgumentMinimum ?? defaultPaginationArgumentMinimum;
93
- const nodeCostLimit = params?.nodeCostLimit ?? defaultNodeCostLimit;
94
- const extensions = params?.extensions ?? false;
95
- const nodeCostMap = /* @__PURE__ */ new WeakMap();
96
- const handleResult = ({ result, args }) => {
97
- const nodeCost = nodeCostMap.get(args);
98
- if (nodeCost != null) result.extensions = {
99
- ...result.extensions,
100
- resourceLimitations: { nodeCost }
101
- };
102
- };
103
- return {
104
- onPluginInit({ addPlugin }) {
105
- addPlugin((0, _envelop_extended_validation.useExtendedValidation)({
106
- rules: [ResourceLimitationValidationRule({
107
- nodeCostLimit,
108
- paginationArgumentMaximum,
109
- paginationArgumentMinimum,
110
- paginationArgumentTypes: params?.paginationArgumentScalars,
111
- reportNodeCost: extensions ? (nodeCost, ref) => {
112
- nodeCostMap.set(ref, nodeCost);
113
- } : void 0
114
- })],
115
- onValidationFailed: (params$1) => handleResult(params$1)
116
- }));
117
- },
118
- onExecute({ args }) {
119
- return { onExecuteDone(payload) {
120
- return (0, _envelop_core.handleStreamOrSingleExecutionResult)(payload, ({ result }) => handleResult({
121
- result,
122
- args
123
- }));
124
- } };
125
- }
126
- };
127
- };
128
-
129
- //#endregion
130
- exports.ResourceLimitationValidationRule = ResourceLimitationValidationRule;
131
- exports.defaultNodeCostLimit = defaultNodeCostLimit;
132
- exports.defaultPaginationArgumentMaximum = defaultPaginationArgumentMaximum;
133
- exports.defaultPaginationArgumentMinimum = defaultPaginationArgumentMinimum;
134
- exports.useResourceLimitations = useResourceLimitations;
package/dist/index.d.cts DELETED
@@ -1,48 +0,0 @@
1
- import { ExecutionArgs } from "graphql";
2
- import { Plugin } from "@envelop/core";
3
- import { ExtendedValidationRule } from "@envelop/extended-validation";
4
-
5
- //#region src/index.d.ts
6
- declare const defaultNodeCostLimit = 500000;
7
- declare const defaultPaginationArgumentMaximum = 100;
8
- declare const defaultPaginationArgumentMinimum = 1;
9
- type ResourceLimitationValidationRuleParams = {
10
- nodeCostLimit: number;
11
- paginationArgumentMaximum: number;
12
- paginationArgumentMinimum: number;
13
- paginationArgumentTypes?: string[];
14
- reportNodeCost?: (cost: number, executionArgs: ExecutionArgs) => void;
15
- };
16
- /**
17
- * Validate whether a user is allowed to execute a certain GraphQL operation.
18
- */
19
- declare const ResourceLimitationValidationRule: (params: ResourceLimitationValidationRuleParams) => ExtendedValidationRule;
20
- type UseResourceLimitationsParams = {
21
- /**
22
- * The node cost limit for rejecting a operation.
23
- * @default 500000
24
- */
25
- nodeCostLimit?: number;
26
- /**
27
- * The custom scalar types accepted for connection arguments.
28
- */
29
- paginationArgumentScalars?: string[];
30
- /**
31
- * The maximum value accepted for connection arguments.
32
- * @default 100
33
- */
34
- paginationArgumentMaximum?: number;
35
- /**
36
- * The minimum value accepted for connection arguments.
37
- * @default 1
38
- */
39
- paginationArgumentMinimum?: number;
40
- /**
41
- * Whether the resourceLimitations.nodeCost field should be included within the execution result extensions map.
42
- * @default false
43
- */
44
- extensions?: boolean;
45
- };
46
- declare const useResourceLimitations: (params?: UseResourceLimitationsParams) => Plugin;
47
- //#endregion
48
- export { ResourceLimitationValidationRule, ResourceLimitationValidationRuleParams, defaultNodeCostLimit, defaultPaginationArgumentMaximum, defaultPaginationArgumentMinimum, useResourceLimitations };
package/dist/index.d.mts DELETED
@@ -1,48 +0,0 @@
1
- import { ExecutionArgs } from "graphql";
2
- import { Plugin } from "@envelop/core";
3
- import { ExtendedValidationRule } from "@envelop/extended-validation";
4
-
5
- //#region src/index.d.ts
6
- declare const defaultNodeCostLimit = 500000;
7
- declare const defaultPaginationArgumentMaximum = 100;
8
- declare const defaultPaginationArgumentMinimum = 1;
9
- type ResourceLimitationValidationRuleParams = {
10
- nodeCostLimit: number;
11
- paginationArgumentMaximum: number;
12
- paginationArgumentMinimum: number;
13
- paginationArgumentTypes?: string[];
14
- reportNodeCost?: (cost: number, executionArgs: ExecutionArgs) => void;
15
- };
16
- /**
17
- * Validate whether a user is allowed to execute a certain GraphQL operation.
18
- */
19
- declare const ResourceLimitationValidationRule: (params: ResourceLimitationValidationRuleParams) => ExtendedValidationRule;
20
- type UseResourceLimitationsParams = {
21
- /**
22
- * The node cost limit for rejecting a operation.
23
- * @default 500000
24
- */
25
- nodeCostLimit?: number;
26
- /**
27
- * The custom scalar types accepted for connection arguments.
28
- */
29
- paginationArgumentScalars?: string[];
30
- /**
31
- * The maximum value accepted for connection arguments.
32
- * @default 100
33
- */
34
- paginationArgumentMaximum?: number;
35
- /**
36
- * The minimum value accepted for connection arguments.
37
- * @default 1
38
- */
39
- paginationArgumentMinimum?: number;
40
- /**
41
- * Whether the resourceLimitations.nodeCost field should be included within the execution result extensions map.
42
- * @default false
43
- */
44
- extensions?: boolean;
45
- };
46
- declare const useResourceLimitations: (params?: UseResourceLimitationsParams) => Plugin;
47
- //#endregion
48
- export { ResourceLimitationValidationRule, ResourceLimitationValidationRuleParams, defaultNodeCostLimit, defaultPaginationArgumentMaximum, defaultPaginationArgumentMinimum, useResourceLimitations };
package/dist/index.mjs DELETED
@@ -1,130 +0,0 @@
1
- import { GraphQLError, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, isScalarType } from "graphql";
2
- import { handleStreamOrSingleExecutionResult } from "@envelop/core";
3
- import { useExtendedValidation } from "@envelop/extended-validation";
4
- import { getArgumentValues } from "@graphql-tools/utils";
5
-
6
- //#region src/index.ts
7
- const getWrappedType = (graphqlType) => {
8
- if (graphqlType instanceof GraphQLList || graphqlType instanceof GraphQLNonNull) return getWrappedType(graphqlType.ofType);
9
- return graphqlType;
10
- };
11
- const isValidArgType = (type, paginationArgumentTypes) => type === GraphQLInt || isScalarType(type) && !!paginationArgumentTypes && paginationArgumentTypes.includes(type.name);
12
- const hasFieldDefConnectionArgs = (field, argumentTypes) => {
13
- let hasFirst = false;
14
- let hasLast = false;
15
- for (const arg of field.args) if (arg.name === "first" && isValidArgType(arg.type, argumentTypes)) hasFirst = true;
16
- else if (arg.name === "last" && isValidArgType(arg.type, argumentTypes)) hasLast = true;
17
- else if (hasLast && hasFirst) break;
18
- return {
19
- hasFirst,
20
- hasLast
21
- };
22
- };
23
- const buildMissingPaginationFieldErrorMessage = (params) => `Missing pagination argument for field '${params.fieldName}'. Please provide ` + (params.hasFirst && params.hasLast ? "either the 'first' or 'last'" : params.hasFirst ? "the 'first'" : "the 'last'") + " field argument.";
24
- const buildInvalidPaginationRangeErrorMessage = (params) => `Invalid pagination argument for field '${params.fieldName}'. The value for the '${params.argumentName}' argument must be an integer within ${params.paginationArgumentMinimum}-${params.paginationArgumentMaximum}.`;
25
- const defaultNodeCostLimit = 5e5;
26
- const defaultPaginationArgumentMaximum = 100;
27
- const defaultPaginationArgumentMinimum = 1;
28
- /**
29
- * Validate whether a user is allowed to execute a certain GraphQL operation.
30
- */
31
- const ResourceLimitationValidationRule = (params) => (context, executionArgs) => {
32
- const { paginationArgumentMaximum, paginationArgumentMinimum } = params;
33
- const nodeCostStack = [];
34
- let totalNodeCost = 0;
35
- const connectionFieldMap = /* @__PURE__ */ new WeakSet();
36
- return {
37
- Field: {
38
- enter(fieldNode) {
39
- const fieldDef = context.getFieldDef();
40
- if (fieldDef != null) {
41
- const argumentValues = getArgumentValues(fieldDef, fieldNode, executionArgs.variableValues || void 0);
42
- const type = getWrappedType(fieldDef.type);
43
- if (type instanceof GraphQLObjectType && type.name.endsWith("Connection")) {
44
- let nodeCost = 1;
45
- connectionFieldMap.add(fieldNode);
46
- const { hasFirst, hasLast } = hasFieldDefConnectionArgs(fieldDef, params.paginationArgumentTypes);
47
- if (hasFirst === false && hasLast === false) console.warn("Encountered paginated field without pagination arguments.");
48
- else if (hasFirst === true || hasLast === true) if ("first" in argumentValues === false && "last" in argumentValues === false || argumentValues["first"] === null && argumentValues["last"] === null) context.reportError(new GraphQLError(buildMissingPaginationFieldErrorMessage({
49
- fieldName: fieldDef.name,
50
- hasFirst,
51
- hasLast
52
- }), fieldNode));
53
- else if ("first" in argumentValues && !argumentValues["last"]) if (argumentValues["first"] < paginationArgumentMinimum || argumentValues["first"] > paginationArgumentMaximum) context.reportError(new GraphQLError(buildInvalidPaginationRangeErrorMessage({
54
- paginationArgumentMaximum,
55
- paginationArgumentMinimum,
56
- argumentName: "first",
57
- fieldName: fieldDef.name
58
- }), fieldNode));
59
- else nodeCost = argumentValues["first"];
60
- else if (!argumentValues["first"] && "last" in argumentValues) if (argumentValues["last"] < paginationArgumentMinimum || argumentValues["last"] > paginationArgumentMaximum) context.reportError(new GraphQLError(buildInvalidPaginationRangeErrorMessage({
61
- paginationArgumentMaximum,
62
- paginationArgumentMinimum,
63
- argumentName: "last",
64
- fieldName: fieldDef.name
65
- }), fieldNode));
66
- else nodeCost = argumentValues["last"];
67
- else context.reportError(new GraphQLError(buildMissingPaginationFieldErrorMessage({
68
- fieldName: fieldDef.name,
69
- hasFirst,
70
- hasLast
71
- }), fieldNode));
72
- nodeCostStack.push(nodeCost);
73
- }
74
- }
75
- },
76
- leave(node) {
77
- if (connectionFieldMap.delete(node)) {
78
- totalNodeCost = totalNodeCost + nodeCostStack.reduce((a, b) => a * b, 1);
79
- nodeCostStack.pop();
80
- }
81
- }
82
- },
83
- Document: { leave(documentNode) {
84
- if (totalNodeCost === 0) totalNodeCost = 1;
85
- if (totalNodeCost > params.nodeCostLimit) context.reportError(new GraphQLError(`Cannot request more than ${params.nodeCostLimit} nodes in a single document. Please split your operation into multiple sub operations or reduce the amount of requested nodes.`, documentNode));
86
- params.reportNodeCost?.(totalNodeCost, executionArgs);
87
- } }
88
- };
89
- };
90
- const useResourceLimitations = (params) => {
91
- const paginationArgumentMaximum = params?.paginationArgumentMaximum ?? defaultPaginationArgumentMaximum;
92
- const paginationArgumentMinimum = params?.paginationArgumentMinimum ?? defaultPaginationArgumentMinimum;
93
- const nodeCostLimit = params?.nodeCostLimit ?? defaultNodeCostLimit;
94
- const extensions = params?.extensions ?? false;
95
- const nodeCostMap = /* @__PURE__ */ new WeakMap();
96
- const handleResult = ({ result, args }) => {
97
- const nodeCost = nodeCostMap.get(args);
98
- if (nodeCost != null) result.extensions = {
99
- ...result.extensions,
100
- resourceLimitations: { nodeCost }
101
- };
102
- };
103
- return {
104
- onPluginInit({ addPlugin }) {
105
- addPlugin(useExtendedValidation({
106
- rules: [ResourceLimitationValidationRule({
107
- nodeCostLimit,
108
- paginationArgumentMaximum,
109
- paginationArgumentMinimum,
110
- paginationArgumentTypes: params?.paginationArgumentScalars,
111
- reportNodeCost: extensions ? (nodeCost, ref) => {
112
- nodeCostMap.set(ref, nodeCost);
113
- } : void 0
114
- })],
115
- onValidationFailed: (params$1) => handleResult(params$1)
116
- }));
117
- },
118
- onExecute({ args }) {
119
- return { onExecuteDone(payload) {
120
- return handleStreamOrSingleExecutionResult(payload, ({ result }) => handleResult({
121
- result,
122
- args
123
- }));
124
- } };
125
- }
126
- };
127
- };
128
-
129
- //#endregion
130
- export { ResourceLimitationValidationRule, defaultNodeCostLimit, defaultPaginationArgumentMaximum, defaultPaginationArgumentMinimum, useResourceLimitations };