UNPKG

@envelop/rate-limiter

Version:

This plugins uses [`graphql-rate-limit`](https://github.com/teamplanes/graphql-rate-limit#readme) in order to limit the rate of calling queries and mutations.

126 lines (125 loc) 6.48 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useRateLimiter = exports.defaultInterpolateMessageFn = exports.DIRECTIVE_SDL = exports.Store = exports.RedisStore = exports.RateLimitError = exports.InMemoryStore = void 0; const graphql_1 = require("graphql"); const minimatch_1 = require("minimatch"); const on_resolve_1 = require("@envelop/on-resolve"); const utils_1 = require("@graphql-tools/utils"); const promise_helpers_1 = require("@whatwg-node/promise-helpers"); const get_graphql_rate_limiter_js_1 = require("./get-graphql-rate-limiter.js"); const in_memory_store_js_1 = require("./in-memory-store.js"); Object.defineProperty(exports, "InMemoryStore", { enumerable: true, get: function () { return in_memory_store_js_1.InMemoryStore; } }); const rate_limit_error_js_1 = require("./rate-limit-error.js"); Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return rate_limit_error_js_1.RateLimitError; } }); const redis_store_js_1 = require("./redis-store.js"); Object.defineProperty(exports, "RedisStore", { enumerable: true, get: function () { return redis_store_js_1.RedisStore; } }); const store_js_1 = require("./store.js"); Object.defineProperty(exports, "Store", { enumerable: true, get: function () { return store_js_1.Store; } }); exports.DIRECTIVE_SDL = ` directive @rateLimit( max: Int window: String message: String identityArgs: [String] arrayLengthField: String readOnly: Boolean uncountRejected: Boolean ) on FIELD_DEFINITION `; const defaultInterpolateMessageFn = (message, identifier) => interpolateByArgs(message, { id: identifier }); exports.defaultInterpolateMessageFn = defaultInterpolateMessageFn; const useRateLimiter = (options) => { const rateLimiterFn = (0, get_graphql_rate_limiter_js_1.getGraphQLRateLimiter)({ ...options, identifyContext: options.identifyFn, }); const interpolateMessage = options.interpolateMessage || exports.defaultInterpolateMessageFn; return { onPluginInit({ addPlugin }) { addPlugin((0, on_resolve_1.useOnResolve)(({ root, args, context, info }) => { const field = info.parentType.getFields()[info.fieldName]; if (field) { const directives = (0, utils_1.getDirectiveExtensions)(field); const rateLimitDefs = directives?.rateLimit; let rateLimitDef = rateLimitDefs?.[0]; let identifyFn = options.identifyFn; let fieldIdentity = false; if (!rateLimitDef) { const foundConfig = options.configByField?.find(({ type, field }) => (0, minimatch_1.minimatch)(info.parentType.name, type) && (0, minimatch_1.minimatch)(info.fieldName, field)); if (foundConfig) { rateLimitDef = foundConfig; if (foundConfig.identifyFn) { identifyFn = foundConfig.identifyFn; fieldIdentity = true; } } } if (rateLimitDef) { const message = rateLimitDef.message; const max = rateLimitDef.max && Number(rateLimitDef.max); const window = rateLimitDef.window; const identifier = identifyFn(context); return (0, promise_helpers_1.handleMaybePromise)(() => rateLimiterFn({ parent: root, args: fieldIdentity ? { ...args, identifier } : args, context, info, }, { max, window, identityArgs: fieldIdentity ? ['identifier', ...(rateLimitDef.identityArgs || [])] : rateLimitDef.identityArgs, arrayLengthField: rateLimitDef.arrayLengthField, uncountRejected: rateLimitDef.uncountRejected, readOnly: rateLimitDef.readOnly, message: message && identifier ? interpolateMessage(message, identifier, { root, args, context, info, }) : undefined, }), errorMessage => { if (errorMessage) { if (options.onRateLimitError) { options.onRateLimitError({ error: errorMessage, identifier, context, info, }); } if (options.transformError) { throw options.transformError(errorMessage); } throw (0, utils_1.createGraphQLError)(errorMessage, { extensions: { http: { statusCode: 429, headers: { 'Retry-After': window, }, }, }, path: (0, graphql_1.responsePathAsArray)(info.path), nodes: info.fieldNodes, }); } }); } } })); }, onContextBuilding({ extendContext }) { extendContext({ rateLimiterFn, }); }, }; }; exports.useRateLimiter = useRateLimiter; function interpolateByArgs(message, args) { return message.replace(/\{{([^)]*)\}}/g, (_, key) => args[key.trim()]); }