@envelop/rate-limiter 10.0.0-alpha-20251212015846-91d81e2860e085b5f5e1a780e4cab2f58e349142 → 10.0.0-alpha-20251212093955-081ac8d8cd90f8b5d037dd416f9eca842de513d1

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.
@@ -59,9 +59,9 @@ userConfig) => {
59
59
  * @param args - pass the resolver args as an object
60
60
  * @param config - field level config
61
61
  */
62
- const rateLimiter = (
62
+ const rateLimiter = (fieldName,
63
63
  // Resolver args
64
- { args, context, info, },
64
+ { args, context, },
65
65
  // Field level config (e.g. the directive parameters)
66
66
  { arrayLengthField, identityArgs, max, window, message, readOnly, uncountRejected, }) => {
67
67
  // Identify the user or client on the context
@@ -69,7 +69,7 @@ userConfig) => {
69
69
  // User defined window in ms that this field can be accessed for before the call is expired
70
70
  const windowMs = (window ? (0, ms_1.default)(window) : DEFAULT_WINDOW);
71
71
  // String key for this field
72
- const fieldIdentity = getFieldIdentity(info.fieldName, identityArgs || DEFAULT_FIELD_IDENTITY_ARGS, args);
72
+ const fieldIdentity = getFieldIdentity(fieldName, identityArgs || DEFAULT_FIELD_IDENTITY_ARGS, args);
73
73
  // User configured maximum calls to this field
74
74
  const maxCalls = max || DEFAULT_MAX;
75
75
  // Call count could be determined by the lenght of the array value, but most commonly 1
@@ -107,7 +107,7 @@ userConfig) => {
107
107
  formatError({
108
108
  contextIdentity,
109
109
  fieldIdentity,
110
- fieldName: info.fieldName,
110
+ fieldName,
111
111
  max: maxCalls,
112
112
  window: windowMs,
113
113
  });
package/cjs/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useRateLimiter = exports.defaultInterpolateMessageFn = exports.DIRECTIVE_SDL = exports.Store = exports.RedisStore = exports.RateLimitError = exports.InMemoryStore = void 0;
3
+ exports.useRateLimiter = exports.defaultInterpolateMessageFn = exports.DIRECTIVE_SDL = exports.Store = exports.RedisStore = exports.InMemoryStore = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const types_1 = require("util/types");
5
6
  const graphql_1 = require("graphql");
6
7
  const picomatch_1 = tslib_1.__importDefault(require("picomatch"));
7
8
  const utils_1 = require("@graphql-tools/utils");
@@ -9,8 +10,6 @@ const promise_helpers_1 = require("@whatwg-node/promise-helpers");
9
10
  const get_graphql_rate_limiter_js_1 = require("./get-graphql-rate-limiter.js");
10
11
  const in_memory_store_js_1 = require("./in-memory-store.js");
11
12
  Object.defineProperty(exports, "InMemoryStore", { enumerable: true, get: function () { return in_memory_store_js_1.InMemoryStore; } });
12
- const rate_limit_error_js_1 = require("./rate-limit-error.js");
13
- Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return rate_limit_error_js_1.RateLimitError; } });
14
13
  const redis_store_js_1 = require("./redis-store.js");
15
14
  Object.defineProperty(exports, "RedisStore", { enumerable: true, get: function () { return redis_store_js_1.RedisStore; } });
16
15
  const store_js_1 = require("./store.js");
@@ -28,6 +27,9 @@ exports.DIRECTIVE_SDL = `
28
27
  `;
29
28
  const defaultInterpolateMessageFn = (message, identifier) => interpolateByArgs(message, { id: identifier });
30
29
  exports.defaultInterpolateMessageFn = defaultInterpolateMessageFn;
30
+ const getTypeInfo = (0, utils_1.memoize1)(function getTypeInfo(schema) {
31
+ return new graphql_1.TypeInfo(schema);
32
+ });
31
33
  const useRateLimiter = (options) => {
32
34
  const rateLimiterFn = (0, get_graphql_rate_limiter_js_1.getGraphQLRateLimiter)({
33
35
  ...options,
@@ -40,81 +42,144 @@ const useRateLimiter = (options) => {
40
42
  type: (0, picomatch_1.default)(config.type),
41
43
  field: (0, picomatch_1.default)(config.field),
42
44
  },
43
- }));
45
+ })) || [];
44
46
  const directiveName = options.rateLimitDirectiveName ?? 'rateLimit';
47
+ const getRateLimitConfig = (0, utils_1.memoize4)(function getFieldConfigs(configByField, schema, type, field) {
48
+ const fieldConfigs = configByField?.filter(({ isMatch }) => isMatch.type(type.name) && isMatch.field(field.name));
49
+ if (fieldConfigs && fieldConfigs.length > 1) {
50
+ throw new Error(`Config error: field '${type.name}.${field.name}' has multiple matching configuration`);
51
+ }
52
+ const fieldConfig = fieldConfigs?.[0];
53
+ const rateLimitDirective = (0, utils_1.getDirectiveExtensions)(field, schema)[directiveName]?.[0];
54
+ if (rateLimitDirective && fieldConfig) {
55
+ throw new Error(`Config error: field '${type.name}.${field.name}' has both a configuration and a directive`);
56
+ }
57
+ const rateLimitConfig = rateLimitDirective || fieldConfig;
58
+ if (!rateLimitConfig) {
59
+ return undefined;
60
+ }
61
+ rateLimitConfig.max = Number(rateLimitConfig.max);
62
+ if (rateLimitConfig?.identifyFn) {
63
+ rateLimitConfig.identityArgs = ['identifier', ...(rateLimitConfig.identityArgs ?? [])];
64
+ }
65
+ return rateLimitConfig;
66
+ });
45
67
  return {
46
- onSchemaChange({ schema: _schema }) {
47
- if (!_schema) {
48
- return;
49
- }
50
- const schema = _schema;
51
- for (const type of Object.values(schema.getTypeMap())) {
52
- if (!(0, graphql_1.isObjectType)(type)) {
53
- continue;
54
- }
55
- for (const field of Object.values(type.getFields())) {
56
- const fieldConfigs = configByField?.filter(({ isMatch }) => isMatch.type(type.name) && isMatch.field(field.name));
57
- if (fieldConfigs && fieldConfigs.length > 1) {
58
- throw new Error(`Config error: field '${type.name}.${field.name}' has multiple matching configuration`);
59
- }
60
- const fieldConfig = fieldConfigs?.[0];
61
- const rateLimitDirective = (0, utils_1.getDirectiveExtensions)(field, schema)[directiveName]?.[0];
62
- if (rateLimitDirective && fieldConfig) {
63
- throw new Error(`Config error: field '${type.name}.${field.name}' has both a configuration and a directive`);
64
- }
65
- const baseConfig = rateLimitDirective ?? fieldConfig;
66
- if (baseConfig) {
67
- const rateLimitConfig = { ...baseConfig };
68
- rateLimitConfig.max = rateLimitConfig.max && Number(rateLimitConfig.max);
69
- if (fieldConfig?.identifyFn) {
70
- rateLimitConfig.identityArgs = [
71
- 'identifier',
72
- ...(rateLimitConfig.identityArgs ?? []),
73
- ];
68
+ onExecute({ args, setResultAndStopExecution }) {
69
+ const { document, schema, contextValue: context, variableValues, rootValue: root } = args;
70
+ const typeInfo = getTypeInfo(schema);
71
+ const rateLimitCalls = new Set();
72
+ const errors = [];
73
+ args.document = (0, graphql_1.visit)(document, (0, graphql_1.visitWithTypeInfo)(typeInfo, {
74
+ Field(node, _key, _parent, path, _ancestors) {
75
+ const type = typeInfo.getParentType();
76
+ const field = typeInfo.getFieldDef();
77
+ if (type != null && field != null) {
78
+ const rateLimitConfig = getRateLimitConfig(configByField, schema, type, field);
79
+ if (!rateLimitConfig) {
80
+ return;
74
81
  }
75
- const originalResolver = field.resolve ?? graphql_1.defaultFieldResolver;
76
- field.resolve = (parent, args, context, info) => {
77
- const resolverRateLimitConfig = { ...rateLimitConfig };
78
- const executionArgs = { parent, args, context, info };
79
- const identifier = (fieldConfig?.identifyFn ?? options.identifyFn)(context);
80
- if (fieldConfig?.identifyFn) {
81
- executionArgs.args = { identifier, ...args };
82
+ const resolverRateLimitConfig = { ...rateLimitConfig };
83
+ const identifier = (rateLimitConfig?.identifyFn ?? options.identifyFn)(context);
84
+ let args = null;
85
+ function getArgValues() {
86
+ if (!args) {
87
+ if (field) {
88
+ args = (0, utils_1.getArgumentValues)(field, node, variableValues);
89
+ }
82
90
  }
83
- if (resolverRateLimitConfig.message && identifier) {
84
- const messageArgs = { root: parent, args, context, info };
85
- resolverRateLimitConfig.message = interpolateMessage(resolverRateLimitConfig.message, identifier, messageArgs);
91
+ return args;
92
+ }
93
+ const executionArgs = {
94
+ identifier,
95
+ root,
96
+ get args() {
97
+ return {
98
+ ...(getArgValues() || {}),
99
+ identifier,
100
+ };
101
+ },
102
+ context,
103
+ type,
104
+ field,
105
+ };
106
+ if (resolverRateLimitConfig.message && identifier) {
107
+ resolverRateLimitConfig.message = interpolateMessage(resolverRateLimitConfig.message, identifier, executionArgs);
108
+ }
109
+ const rateLimitResult = (0, promise_helpers_1.handleMaybePromise)(() => rateLimiterFn(field.name, executionArgs, resolverRateLimitConfig), rateLimitError => {
110
+ if (!rateLimitError) {
111
+ return true;
86
112
  }
87
- return (0, promise_helpers_1.handleMaybePromise)(() => rateLimiterFn(executionArgs, resolverRateLimitConfig), rateLimitError => {
88
- if (!rateLimitError) {
89
- return originalResolver(parent, args, context, info);
90
- }
91
- if (options.onRateLimitError) {
92
- options.onRateLimitError({
93
- error: rateLimitError,
94
- identifier,
95
- context,
96
- info,
97
- });
98
- }
99
- if (options.transformError) {
100
- throw options.transformError(rateLimitError);
113
+ if (options.onRateLimitError) {
114
+ options.onRateLimitError({
115
+ error: rateLimitError,
116
+ ...executionArgs,
117
+ });
118
+ }
119
+ if (options.transformError) {
120
+ throw options.transformError(rateLimitError);
121
+ }
122
+ const resolvePath = [];
123
+ let curr = document;
124
+ const operationAST = (0, utils_1.getOperationASTFromDocument)(document);
125
+ let currType = (0, utils_1.getDefinedRootType)(schema, operationAST.operation);
126
+ for (const pathItem of path) {
127
+ curr = curr[pathItem];
128
+ if (curr?.kind === 'Field') {
129
+ const fieldName = curr.name.value;
130
+ const responseKey = curr.alias?.value ?? fieldName;
131
+ let field;
132
+ if ((0, graphql_1.isObjectType)(currType)) {
133
+ field = currType.getFields()[fieldName];
134
+ }
135
+ else if ((0, graphql_1.isAbstractType)(currType)) {
136
+ for (const possibleType of schema.getPossibleTypes(currType)) {
137
+ field = possibleType.getFields()[fieldName];
138
+ if (field) {
139
+ break;
140
+ }
141
+ }
142
+ }
143
+ if ((0, graphql_1.isListType)(field?.type)) {
144
+ resolvePath.push('@');
145
+ }
146
+ resolvePath.push(responseKey);
147
+ if (field?.type) {
148
+ currType = (0, graphql_1.getNamedType)(field.type);
149
+ }
101
150
  }
102
- const errorOptions = {
103
- extensions: { http: { statusCode: 429 } },
104
- path: (0, graphql_1.responsePathAsArray)(info.path),
105
- nodes: info.fieldNodes,
151
+ }
152
+ const errorOptions = {
153
+ extensions: { http: { statusCode: 429 } },
154
+ path: resolvePath,
155
+ nodes: [node],
156
+ };
157
+ if (resolverRateLimitConfig.window) {
158
+ errorOptions.extensions.http.headers = {
159
+ 'Retry-After': resolverRateLimitConfig.window,
106
160
  };
107
- if (resolverRateLimitConfig.window) {
108
- errorOptions.extensions.http.headers = {
109
- 'Retry-After': resolverRateLimitConfig.window,
110
- };
111
- }
112
- throw (0, utils_1.createGraphQLError)(rateLimitError, errorOptions);
113
- });
114
- };
161
+ }
162
+ errors.push((0, utils_1.createGraphQLError)(rateLimitError, errorOptions));
163
+ return false;
164
+ });
165
+ if ((0, types_1.isPromise)(rateLimitResult)) {
166
+ rateLimitCalls.add(rateLimitResult);
167
+ return node;
168
+ }
169
+ else if (rateLimitResult === false) {
170
+ return null;
171
+ }
115
172
  }
173
+ return node;
174
+ },
175
+ }));
176
+ return (0, promise_helpers_1.handleMaybePromise)(() => (rateLimitCalls.size ? Promise.all(rateLimitCalls) : undefined), () => {
177
+ if (errors.length) {
178
+ setResultAndStopExecution({
179
+ errors,
180
+ });
116
181
  }
117
- }
182
+ });
118
183
  },
119
184
  onContextBuilding({ extendContext }) {
120
185
  extendContext({
@@ -54,9 +54,9 @@ userConfig) => {
54
54
  * @param args - pass the resolver args as an object
55
55
  * @param config - field level config
56
56
  */
57
- const rateLimiter = (
57
+ const rateLimiter = (fieldName,
58
58
  // Resolver args
59
- { args, context, info, },
59
+ { args, context, },
60
60
  // Field level config (e.g. the directive parameters)
61
61
  { arrayLengthField, identityArgs, max, window, message, readOnly, uncountRejected, }) => {
62
62
  // Identify the user or client on the context
@@ -64,7 +64,7 @@ userConfig) => {
64
64
  // User defined window in ms that this field can be accessed for before the call is expired
65
65
  const windowMs = (window ? ms(window) : DEFAULT_WINDOW);
66
66
  // String key for this field
67
- const fieldIdentity = getFieldIdentity(info.fieldName, identityArgs || DEFAULT_FIELD_IDENTITY_ARGS, args);
67
+ const fieldIdentity = getFieldIdentity(fieldName, identityArgs || DEFAULT_FIELD_IDENTITY_ARGS, args);
68
68
  // User configured maximum calls to this field
69
69
  const maxCalls = max || DEFAULT_MAX;
70
70
  // Call count could be determined by the lenght of the array value, but most commonly 1
@@ -102,7 +102,7 @@ userConfig) => {
102
102
  formatError({
103
103
  contextIdentity,
104
104
  fieldIdentity,
105
- fieldName: info.fieldName,
105
+ fieldName,
106
106
  max: maxCalls,
107
107
  window: windowMs,
108
108
  });
package/esm/index.js CHANGED
@@ -1,13 +1,13 @@
1
- import { defaultFieldResolver, isObjectType, responsePathAsArray, } from 'graphql';
1
+ import { isPromise } from 'util/types';
2
+ import { getNamedType, isAbstractType, isListType, isObjectType, TypeInfo, visit, visitWithTypeInfo, } from 'graphql';
2
3
  import picomatch from 'picomatch';
3
- import { createGraphQLError, getDirectiveExtensions } from '@graphql-tools/utils';
4
+ import { createGraphQLError, getArgumentValues, getDefinedRootType, getDirectiveExtensions, getOperationASTFromDocument, memoize1, memoize4, } from '@graphql-tools/utils';
4
5
  import { handleMaybePromise } from '@whatwg-node/promise-helpers';
5
6
  import { getGraphQLRateLimiter } from './get-graphql-rate-limiter.js';
6
7
  import { InMemoryStore } from './in-memory-store.js';
7
- import { RateLimitError } from './rate-limit-error.js';
8
8
  import { RedisStore } from './redis-store.js';
9
9
  import { Store } from './store.js';
10
- export { InMemoryStore, RateLimitError, RedisStore, Store, };
10
+ export { InMemoryStore, RedisStore, Store, };
11
11
  export const DIRECTIVE_SDL = /* GraphQL */ `
12
12
  directive @rateLimit(
13
13
  max: Int
@@ -20,6 +20,9 @@ export const DIRECTIVE_SDL = /* GraphQL */ `
20
20
  ) on FIELD_DEFINITION
21
21
  `;
22
22
  export const defaultInterpolateMessageFn = (message, identifier) => interpolateByArgs(message, { id: identifier });
23
+ const getTypeInfo = memoize1(function getTypeInfo(schema) {
24
+ return new TypeInfo(schema);
25
+ });
23
26
  export const useRateLimiter = (options) => {
24
27
  const rateLimiterFn = getGraphQLRateLimiter({
25
28
  ...options,
@@ -32,81 +35,144 @@ export const useRateLimiter = (options) => {
32
35
  type: picomatch(config.type),
33
36
  field: picomatch(config.field),
34
37
  },
35
- }));
38
+ })) || [];
36
39
  const directiveName = options.rateLimitDirectiveName ?? 'rateLimit';
40
+ const getRateLimitConfig = memoize4(function getFieldConfigs(configByField, schema, type, field) {
41
+ const fieldConfigs = configByField?.filter(({ isMatch }) => isMatch.type(type.name) && isMatch.field(field.name));
42
+ if (fieldConfigs && fieldConfigs.length > 1) {
43
+ throw new Error(`Config error: field '${type.name}.${field.name}' has multiple matching configuration`);
44
+ }
45
+ const fieldConfig = fieldConfigs?.[0];
46
+ const rateLimitDirective = getDirectiveExtensions(field, schema)[directiveName]?.[0];
47
+ if (rateLimitDirective && fieldConfig) {
48
+ throw new Error(`Config error: field '${type.name}.${field.name}' has both a configuration and a directive`);
49
+ }
50
+ const rateLimitConfig = rateLimitDirective || fieldConfig;
51
+ if (!rateLimitConfig) {
52
+ return undefined;
53
+ }
54
+ rateLimitConfig.max = Number(rateLimitConfig.max);
55
+ if (rateLimitConfig?.identifyFn) {
56
+ rateLimitConfig.identityArgs = ['identifier', ...(rateLimitConfig.identityArgs ?? [])];
57
+ }
58
+ return rateLimitConfig;
59
+ });
37
60
  return {
38
- onSchemaChange({ schema: _schema }) {
39
- if (!_schema) {
40
- return;
41
- }
42
- const schema = _schema;
43
- for (const type of Object.values(schema.getTypeMap())) {
44
- if (!isObjectType(type)) {
45
- continue;
46
- }
47
- for (const field of Object.values(type.getFields())) {
48
- const fieldConfigs = configByField?.filter(({ isMatch }) => isMatch.type(type.name) && isMatch.field(field.name));
49
- if (fieldConfigs && fieldConfigs.length > 1) {
50
- throw new Error(`Config error: field '${type.name}.${field.name}' has multiple matching configuration`);
51
- }
52
- const fieldConfig = fieldConfigs?.[0];
53
- const rateLimitDirective = getDirectiveExtensions(field, schema)[directiveName]?.[0];
54
- if (rateLimitDirective && fieldConfig) {
55
- throw new Error(`Config error: field '${type.name}.${field.name}' has both a configuration and a directive`);
56
- }
57
- const baseConfig = rateLimitDirective ?? fieldConfig;
58
- if (baseConfig) {
59
- const rateLimitConfig = { ...baseConfig };
60
- rateLimitConfig.max = rateLimitConfig.max && Number(rateLimitConfig.max);
61
- if (fieldConfig?.identifyFn) {
62
- rateLimitConfig.identityArgs = [
63
- 'identifier',
64
- ...(rateLimitConfig.identityArgs ?? []),
65
- ];
61
+ onExecute({ args, setResultAndStopExecution }) {
62
+ const { document, schema, contextValue: context, variableValues, rootValue: root } = args;
63
+ const typeInfo = getTypeInfo(schema);
64
+ const rateLimitCalls = new Set();
65
+ const errors = [];
66
+ args.document = visit(document, visitWithTypeInfo(typeInfo, {
67
+ Field(node, _key, _parent, path, _ancestors) {
68
+ const type = typeInfo.getParentType();
69
+ const field = typeInfo.getFieldDef();
70
+ if (type != null && field != null) {
71
+ const rateLimitConfig = getRateLimitConfig(configByField, schema, type, field);
72
+ if (!rateLimitConfig) {
73
+ return;
66
74
  }
67
- const originalResolver = field.resolve ?? defaultFieldResolver;
68
- field.resolve = (parent, args, context, info) => {
69
- const resolverRateLimitConfig = { ...rateLimitConfig };
70
- const executionArgs = { parent, args, context, info };
71
- const identifier = (fieldConfig?.identifyFn ?? options.identifyFn)(context);
72
- if (fieldConfig?.identifyFn) {
73
- executionArgs.args = { identifier, ...args };
75
+ const resolverRateLimitConfig = { ...rateLimitConfig };
76
+ const identifier = (rateLimitConfig?.identifyFn ?? options.identifyFn)(context);
77
+ let args = null;
78
+ function getArgValues() {
79
+ if (!args) {
80
+ if (field) {
81
+ args = getArgumentValues(field, node, variableValues);
82
+ }
74
83
  }
75
- if (resolverRateLimitConfig.message && identifier) {
76
- const messageArgs = { root: parent, args, context, info };
77
- resolverRateLimitConfig.message = interpolateMessage(resolverRateLimitConfig.message, identifier, messageArgs);
84
+ return args;
85
+ }
86
+ const executionArgs = {
87
+ identifier,
88
+ root,
89
+ get args() {
90
+ return {
91
+ ...(getArgValues() || {}),
92
+ identifier,
93
+ };
94
+ },
95
+ context,
96
+ type,
97
+ field,
98
+ };
99
+ if (resolverRateLimitConfig.message && identifier) {
100
+ resolverRateLimitConfig.message = interpolateMessage(resolverRateLimitConfig.message, identifier, executionArgs);
101
+ }
102
+ const rateLimitResult = handleMaybePromise(() => rateLimiterFn(field.name, executionArgs, resolverRateLimitConfig), rateLimitError => {
103
+ if (!rateLimitError) {
104
+ return true;
78
105
  }
79
- return handleMaybePromise(() => rateLimiterFn(executionArgs, resolverRateLimitConfig), rateLimitError => {
80
- if (!rateLimitError) {
81
- return originalResolver(parent, args, context, info);
82
- }
83
- if (options.onRateLimitError) {
84
- options.onRateLimitError({
85
- error: rateLimitError,
86
- identifier,
87
- context,
88
- info,
89
- });
90
- }
91
- if (options.transformError) {
92
- throw options.transformError(rateLimitError);
106
+ if (options.onRateLimitError) {
107
+ options.onRateLimitError({
108
+ error: rateLimitError,
109
+ ...executionArgs,
110
+ });
111
+ }
112
+ if (options.transformError) {
113
+ throw options.transformError(rateLimitError);
114
+ }
115
+ const resolvePath = [];
116
+ let curr = document;
117
+ const operationAST = getOperationASTFromDocument(document);
118
+ let currType = getDefinedRootType(schema, operationAST.operation);
119
+ for (const pathItem of path) {
120
+ curr = curr[pathItem];
121
+ if (curr?.kind === 'Field') {
122
+ const fieldName = curr.name.value;
123
+ const responseKey = curr.alias?.value ?? fieldName;
124
+ let field;
125
+ if (isObjectType(currType)) {
126
+ field = currType.getFields()[fieldName];
127
+ }
128
+ else if (isAbstractType(currType)) {
129
+ for (const possibleType of schema.getPossibleTypes(currType)) {
130
+ field = possibleType.getFields()[fieldName];
131
+ if (field) {
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ if (isListType(field?.type)) {
137
+ resolvePath.push('@');
138
+ }
139
+ resolvePath.push(responseKey);
140
+ if (field?.type) {
141
+ currType = getNamedType(field.type);
142
+ }
93
143
  }
94
- const errorOptions = {
95
- extensions: { http: { statusCode: 429 } },
96
- path: responsePathAsArray(info.path),
97
- nodes: info.fieldNodes,
144
+ }
145
+ const errorOptions = {
146
+ extensions: { http: { statusCode: 429 } },
147
+ path: resolvePath,
148
+ nodes: [node],
149
+ };
150
+ if (resolverRateLimitConfig.window) {
151
+ errorOptions.extensions.http.headers = {
152
+ 'Retry-After': resolverRateLimitConfig.window,
98
153
  };
99
- if (resolverRateLimitConfig.window) {
100
- errorOptions.extensions.http.headers = {
101
- 'Retry-After': resolverRateLimitConfig.window,
102
- };
103
- }
104
- throw createGraphQLError(rateLimitError, errorOptions);
105
- });
106
- };
154
+ }
155
+ errors.push(createGraphQLError(rateLimitError, errorOptions));
156
+ return false;
157
+ });
158
+ if (isPromise(rateLimitResult)) {
159
+ rateLimitCalls.add(rateLimitResult);
160
+ return node;
161
+ }
162
+ else if (rateLimitResult === false) {
163
+ return null;
164
+ }
107
165
  }
166
+ return node;
167
+ },
168
+ }));
169
+ return handleMaybePromise(() => (rateLimitCalls.size ? Promise.all(rateLimitCalls) : undefined), () => {
170
+ if (errors.length) {
171
+ setResultAndStopExecution({
172
+ errors,
173
+ });
108
174
  }
109
- }
175
+ });
110
176
  },
111
177
  onContextBuilding({ extendContext }) {
112
178
  extendContext({
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@envelop/rate-limiter",
3
- "version": "10.0.0-alpha-20251212015846-91d81e2860e085b5f5e1a780e4cab2f58e349142",
3
+ "version": "10.0.0-alpha-20251212093955-081ac8d8cd90f8b5d037dd416f9eca842de513d1",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0",
7
- "@envelop/core": "^5.5.0-alpha-20251212015846-91d81e2860e085b5f5e1a780e4cab2f58e349142"
7
+ "@envelop/core": "^5.5.0-alpha-20251212093955-081ac8d8cd90f8b5d037dd416f9eca842de513d1"
8
8
  },
9
9
  "dependencies": {
10
10
  "@graphql-tools/utils": "^10.5.4",
@@ -14,7 +14,7 @@
14
14
  "ms": "^2.1.3",
15
15
  "picomatch": "^4.0.3",
16
16
  "tslib": "^2.5.0",
17
- "@envelop/on-resolve": "^8.0.0-alpha-20251212015846-91d81e2860e085b5f5e1a780e4cab2f58e349142"
17
+ "@envelop/on-resolve": "^8.0.0-alpha-20251212093955-081ac8d8cd90f8b5d037dd416f9eca842de513d1"
18
18
  },
19
19
  "repository": {
20
20
  "type": "git",
@@ -1,4 +1,3 @@
1
- import type { GraphQLResolveInfo } from 'graphql';
2
1
  import { MaybePromise } from '@whatwg-node/promise-helpers';
3
2
  import type { GraphQLRateLimitConfig, GraphQLRateLimitDirectiveArgs } from './types.cjs';
4
3
  /**
@@ -20,10 +19,8 @@ declare const getFieldIdentity: (fieldName: string, identityArgs: readonly strin
20
19
  * can wrap this or it can be used directly in resolvers.
21
20
  * @param userConfig - global (usually app-wide) rate limiting config
22
21
  */
23
- declare const getGraphQLRateLimiter: (userConfig: GraphQLRateLimitConfig) => (({ args, context, info, }: {
24
- parent: any;
22
+ declare const getGraphQLRateLimiter: (userConfig: GraphQLRateLimitConfig) => ((fieldName: string, { args, context, }: {
25
23
  args: Record<string, any>;
26
24
  context: any;
27
- info: GraphQLResolveInfo;
28
25
  }, { arrayLengthField, identityArgs, max, window, message, uncountRejected, }: GraphQLRateLimitDirectiveArgs) => MaybePromise<string | undefined>);
29
26
  export { getGraphQLRateLimiter, getFieldIdentity };
@@ -1,4 +1,3 @@
1
- import type { GraphQLResolveInfo } from 'graphql';
2
1
  import { MaybePromise } from '@whatwg-node/promise-helpers';
3
2
  import type { GraphQLRateLimitConfig, GraphQLRateLimitDirectiveArgs } from './types.js';
4
3
  /**
@@ -20,10 +19,8 @@ declare const getFieldIdentity: (fieldName: string, identityArgs: readonly strin
20
19
  * can wrap this or it can be used directly in resolvers.
21
20
  * @param userConfig - global (usually app-wide) rate limiting config
22
21
  */
23
- declare const getGraphQLRateLimiter: (userConfig: GraphQLRateLimitConfig) => (({ args, context, info, }: {
24
- parent: any;
22
+ declare const getGraphQLRateLimiter: (userConfig: GraphQLRateLimitConfig) => ((fieldName: string, { args, context, }: {
25
23
  args: Record<string, any>;
26
24
  context: any;
27
- info: GraphQLResolveInfo;
28
25
  }, { arrayLengthField, identityArgs, max, window, message, uncountRejected, }: GraphQLRateLimitDirectiveArgs) => MaybePromise<string | undefined>);
29
26
  export { getGraphQLRateLimiter, getFieldIdentity };
@@ -1,19 +1,20 @@
1
- import { GraphQLResolveInfo } from 'graphql';
1
+ import { GraphQLField, GraphQLNamedOutputType } from 'graphql';
2
2
  import type { Plugin } from '@envelop/core';
3
3
  import { getGraphQLRateLimiter } from './get-graphql-rate-limiter.cjs';
4
4
  import { InMemoryStore } from './in-memory-store.cjs';
5
- import { RateLimitError } from './rate-limit-error.cjs';
6
5
  import { RedisStore } from './redis-store.cjs';
7
6
  import { Store } from './store.cjs';
8
7
  import { FormatErrorInput, GraphQLRateLimitConfig, GraphQLRateLimitDirectiveArgs, Identity, Options } from './types.cjs';
9
- export { FormatErrorInput, GraphQLRateLimitConfig, GraphQLRateLimitDirectiveArgs, Identity, InMemoryStore, Options, RateLimitError, RedisStore, Store, };
8
+ export { FormatErrorInput, GraphQLRateLimitConfig, GraphQLRateLimitDirectiveArgs, Identity, InMemoryStore, Options, RedisStore, Store, };
10
9
  export type IdentifyFn<ContextType = unknown> = (context: ContextType) => string;
11
- export type MessageInterpolator<ContextType = unknown> = (message: string, identifier: string, params: {
10
+ interface RateLimitExecutionParams<ContextType = unknown> {
12
11
  root: unknown;
13
12
  args: Record<string, unknown>;
14
13
  context: ContextType;
15
- info: GraphQLResolveInfo;
16
- }) => string;
14
+ type: GraphQLNamedOutputType;
15
+ field: GraphQLField<any, any>;
16
+ }
17
+ export type MessageInterpolator<ContextType = unknown> = (message: string, identifier: string, params: RateLimitExecutionParams<ContextType>) => string;
17
18
  export declare const DIRECTIVE_SDL = "\n directive @rateLimit(\n max: Int\n window: String\n message: String\n identityArgs: [String]\n arrayLengthField: String\n readOnly: Boolean\n uncountRejected: Boolean\n ) on FIELD_DEFINITION\n";
18
19
  export type RateLimitDirectiveArgs = {
19
20
  max?: number;
@@ -31,9 +32,7 @@ export type RateLimiterPluginOptions = {
31
32
  onRateLimitError?: (event: {
32
33
  error: string;
33
34
  identifier: string;
34
- context: unknown;
35
- info: GraphQLResolveInfo;
36
- }) => void;
35
+ } & RateLimitExecutionParams) => void;
37
36
  interpolateMessage?: MessageInterpolator;
38
37
  configByField?: ConfigByField[];
39
38
  } & Omit<GraphQLRateLimitConfig, 'identifyContext'>;
@@ -1,19 +1,20 @@
1
- import { GraphQLResolveInfo } from 'graphql';
1
+ import { GraphQLField, GraphQLNamedOutputType } from 'graphql';
2
2
  import type { Plugin } from '@envelop/core';
3
3
  import { getGraphQLRateLimiter } from './get-graphql-rate-limiter.js';
4
4
  import { InMemoryStore } from './in-memory-store.js';
5
- import { RateLimitError } from './rate-limit-error.js';
6
5
  import { RedisStore } from './redis-store.js';
7
6
  import { Store } from './store.js';
8
7
  import { FormatErrorInput, GraphQLRateLimitConfig, GraphQLRateLimitDirectiveArgs, Identity, Options } from './types.js';
9
- export { FormatErrorInput, GraphQLRateLimitConfig, GraphQLRateLimitDirectiveArgs, Identity, InMemoryStore, Options, RateLimitError, RedisStore, Store, };
8
+ export { FormatErrorInput, GraphQLRateLimitConfig, GraphQLRateLimitDirectiveArgs, Identity, InMemoryStore, Options, RedisStore, Store, };
10
9
  export type IdentifyFn<ContextType = unknown> = (context: ContextType) => string;
11
- export type MessageInterpolator<ContextType = unknown> = (message: string, identifier: string, params: {
10
+ interface RateLimitExecutionParams<ContextType = unknown> {
12
11
  root: unknown;
13
12
  args: Record<string, unknown>;
14
13
  context: ContextType;
15
- info: GraphQLResolveInfo;
16
- }) => string;
14
+ type: GraphQLNamedOutputType;
15
+ field: GraphQLField<any, any>;
16
+ }
17
+ export type MessageInterpolator<ContextType = unknown> = (message: string, identifier: string, params: RateLimitExecutionParams<ContextType>) => string;
17
18
  export declare const DIRECTIVE_SDL = "\n directive @rateLimit(\n max: Int\n window: String\n message: String\n identityArgs: [String]\n arrayLengthField: String\n readOnly: Boolean\n uncountRejected: Boolean\n ) on FIELD_DEFINITION\n";
18
19
  export type RateLimitDirectiveArgs = {
19
20
  max?: number;
@@ -31,9 +32,7 @@ export type RateLimiterPluginOptions = {
31
32
  onRateLimitError?: (event: {
32
33
  error: string;
33
34
  identifier: string;
34
- context: unknown;
35
- info: GraphQLResolveInfo;
36
- }) => void;
35
+ } & RateLimitExecutionParams) => void;
37
36
  interpolateMessage?: MessageInterpolator;
38
37
  configByField?: ConfigByField[];
39
38
  } & Omit<GraphQLRateLimitConfig, 'identifyContext'>;
@@ -89,11 +89,5 @@ export interface GraphQLRateLimitConfig {
89
89
  * Custom error messages.
90
90
  */
91
91
  readonly formatError?: (input: FormatErrorInput) => string;
92
- /**
93
- * Return an error.
94
- *
95
- * Defaults to new RateLimitError.
96
- */
97
- readonly createError?: (message: string) => Error;
98
92
  readonly enableBatchRequestCache?: boolean;
99
93
  }
@@ -89,11 +89,5 @@ export interface GraphQLRateLimitConfig {
89
89
  * Custom error messages.
90
90
  */
91
91
  readonly formatError?: (input: FormatErrorInput) => string;
92
- /**
93
- * Return an error.
94
- *
95
- * Defaults to new RateLimitError.
96
- */
97
- readonly createError?: (message: string) => Error;
98
92
  readonly enableBatchRequestCache?: boolean;
99
93
  }
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RateLimitError = void 0;
4
- class RateLimitError extends Error {
5
- isRateLimitError = true;
6
- constructor(message) {
7
- super(message);
8
- Object.setPrototypeOf(this, RateLimitError.prototype);
9
- }
10
- }
11
- exports.RateLimitError = RateLimitError;
@@ -1,8 +0,0 @@
1
- class RateLimitError extends Error {
2
- isRateLimitError = true;
3
- constructor(message) {
4
- super(message);
5
- Object.setPrototypeOf(this, RateLimitError.prototype);
6
- }
7
- }
8
- export { RateLimitError };
@@ -1,5 +0,0 @@
1
- declare class RateLimitError extends Error {
2
- readonly isRateLimitError = true;
3
- constructor(message: string);
4
- }
5
- export { RateLimitError };
@@ -1,5 +0,0 @@
1
- declare class RateLimitError extends Error {
2
- readonly isRateLimitError = true;
3
- constructor(message: string);
4
- }
5
- export { RateLimitError };