@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
JavaScript
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()]);
}
;