(
{ dedupe = true, ...options }: CreateRatelimitMiddlewareOptions<TInContext, TInput, TMeta>,
)
| 49 | * @see {@link https://orpc.dev/docs/helpers/ratelimit#createratelimitmiddleware Ratelimit middleware} |
| 50 | */ |
| 51 | export function createRatelimitMiddleware< |
| 52 | TInContext extends Context, |
| 53 | TInput = unknown, |
| 54 | TMeta extends Meta = Record<never, never>, |
| 55 | >( |
| 56 | { dedupe = true, ...options }: CreateRatelimitMiddlewareOptions<TInContext, TInput, TMeta>, |
| 57 | ): Middleware<TInContext, Record<never, never>, TInput, any, any, TMeta> { |
| 58 | return async function ratelimit(middlewareOptions, input) { |
| 59 | const [limiter, key] = await Promise.all([ |
| 60 | value(options.limiter, middlewareOptions, input), |
| 61 | value(options.key, middlewareOptions, input), |
| 62 | ]) |
| 63 | |
| 64 | const middlewareContext = (middlewareOptions.context as RatelimiterMiddlewareContext)[RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL] |
| 65 | if (dedupe && middlewareContext?.limits.some(l => l.key === key && l.limiter === limiter)) { |
| 66 | return middlewareOptions.next() |
| 67 | } |
| 68 | |
| 69 | const result = await limiter.limit(key) |
| 70 | |
| 71 | const pluginContext = (middlewareOptions.context as RatelimitHandlerPluginContext)[RATELIMIT_HANDLER_CONTEXT_SYMBOL] |
| 72 | if (pluginContext) { |
| 73 | pluginContext.ratelimitResult = result |
| 74 | } |
| 75 | |
| 76 | if (!result.success) { |
| 77 | throw new ORPCError('TOO_MANY_REQUESTS', { |
| 78 | data: { |
| 79 | limit: result.limit, |
| 80 | remaining: result.remaining, |
| 81 | reset: result.reset, |
| 82 | }, |
| 83 | }) |
| 84 | } |
| 85 | |
| 86 | return middlewareOptions.next({ |
| 87 | context: { |
| 88 | [RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL]: { |
| 89 | ...middlewareContext, |
| 90 | limits: [ |
| 91 | ...toArray(middlewareContext?.limits), |
| 92 | { limiter, key }, |
| 93 | ], |
| 94 | }, |
| 95 | }, |
| 96 | }) |
| 97 | } |
| 98 | } |
no test coverage detected