UNPKG

@unkey/ratelimit

Version:

<div align="center"> <h1 align="center">@unkey/ratelimit</h1> <h5>@unkey/ratelimit is a library for fast global ratelimiting in serverless functions.</h5> </div>

339 lines (331 loc) 9.91 kB
import * as _unkey_api from '@unkey/api'; type Unit = "ms" | "s" | "m" | "h" | "d"; type Duration = `${number} ${Unit}` | `${number}${Unit}`; type Limit = { /** * How many requests may pass in the given duration */ limit: number; /** * Either a type string literal or milliseconds */ duration: Duration | number; }; type RatelimitResponse = { /** * Whether the request may pass(true) or exceeded the limit(false) */ success: boolean; /** * Maximum number of requests allowed within a window. */ limit: number; /** * How many requests the user has left within the current window. */ remaining: number; /** * Unix timestamp in milliseconds when the limits are reset. */ reset: number; }; type LimitOptions = { /** * Separate requests into groups, groups are combined with your identifier and can be filtered * and searched later. * * @example `group: "send.email"` -> `send.email_${userId}` * */ /** * Expensive requests may use up more resources. You can specify a cost to the request and * we'll deduct this many tokens in the current window. If there are not enough tokens left, * the request is denied. * * @example * * 1. You have a limit of 10 requests per second you already used 4 of them in the current * window. * * 2. Now a new request comes in with a higher cost: * ```ts * const res = await rl.limit("identifier", { cost: 4 }) * ``` * * 3. The request passes and the current limit is now at `8` * * 4. The same request happens again, but would not be rejected, because it would exceed the * limit in the current window: `8 + 4 > 10` * * * @default 1 */ cost?: number; /** * Override the default limit. * * This takes precedence over the limit defined in the constructor as well as any limits defined * for this identifier in Unkey. */ /** * Do not wait for a response from the origin. Faster but less accurate. */ async?: boolean; /** * Record arbitrary data about this request. This does not affect the limit itself but can help * you debug later. */ meta?: Record<string, string | number | boolean | null>; /** * Specify which resources this request would access and we'll create a papertrail for you. * * @see https://unkey.dev/app/audit */ resources?: { type: string; id: string; name?: string; meta?: Record<string, string | number | boolean | null>; }[]; }; interface Ratelimiter { limit: (identifier: string, opts?: LimitOptions) => Promise<RatelimitResponse>; } declare class NoopRatelimit implements Ratelimiter { limit(_identifier: string, _opts?: LimitOptions): Promise<RatelimitResponse>; } type RatelimitConfig = Limit & { /** * @default https://api.unkey.dev */ baseUrl?: string; /** * The unkey root key. You can create one at https://unkey.dev/app/settings/root-keys * * Make sure the root key has permissions to use ratelimiting. */ rootKey: string; /** * Namespaces allow you to separate different areas of your app and have isolated limits. * * @example tRPC-routes */ namespace: string; /** * Configure a timeout to prevent network issues from blocking your function for too long. * * Disable it by setting `timeout: false` * * @default * ```ts * { * // 5 seconds * ms: 5000, * fallback: { success: false, limit: 0, remaining: 0, reset: Date.now()} * } * ``` */ timeout?: { /** * Time in milliseconds until the response is returned */ ms: number | Duration; /** * A custom response to return when the timeout is reached. * * The important bit is the `success` value, choose whether you want to let requests pass or not. * * @example With a static response * ```ts * { * // 5 seconds * ms: 5000 * fallback: () => ({ success: true, limit: 0, remaining: 0, reset: 0 }) * } * ``` * @example With a dynamic response * ```ts * { * // 5 seconds * ms: 5000 * fallback: (identifier: string) => { * if (someCheck(identifier)) { * return { success: false, limit: 0, remaining: 0, reset: 0 } * } * return { success: true, limit: 0, remaining: 0, reset: 0 } * } * } * ``` */ fallback: RatelimitResponse | ((identifier: string) => RatelimitResponse | Promise<RatelimitResponse>); } | false; /** * Configure what happens for unforeseen errors * * @example Letting requests pass * ```ts * onError: () => ({ success: true, limit: 0, remaining: 0, reset: 0 }) * ``` * * @example Rejecting the request * ```ts * onError: () => ({ success: true, limit: 0, remaining: 0, reset: 0 }) * ``` * * @example Dynamic response * ```ts * onError: (error, identifier) => { * if (someCheck(error, identifier)) { * return { success: false, limit: 0, remaining: 0, reset: 0 } * } * return { success: true, limit: 0, remaining: 0, reset: 0 } * } * ``` */ onError?: (err: Error, identifier: string) => RatelimitResponse | Promise<RatelimitResponse>; /** * Do not wait for a response from the origin. Faster but less accurate. */ async?: boolean; /** * * By default telemetry data is enabled, and sends: * runtime (Node.js / Edge) * platform (Node.js / Vercel / AWS) * SDK version */ disableTelemetry?: boolean; }; declare class Ratelimit implements Ratelimiter { private readonly config; private readonly unkey; constructor(config: RatelimitConfig); /** * Limit a specific identifier, you can override a lot of things about this specific request using * the 2nd argument. * * @example * ```ts * const identifier = getIpAddress() // or userId or anything else * const res = await unkey.limit(identifier) * * if (!res.success){ * // reject request * } * // handle request * ``` */ limit(identifier: string, opts?: LimitOptions): Promise<RatelimitResponse>; private _limit; } type OverrideConfig = { /** * @default https://api.unkey.dev */ baseUrl?: string; /** * The unkey root key. You can create one at https://unkey.dev/app/settings/root-keys * * Make sure the root key has permissions to use overrides. */ rootKey: string; /** * * By default telemetry data is enabled, and sends: * runtime (Node.js / Edge) * platform (Node.js / Vercel / AWS) * SDK version */ disableTelemetry?: boolean; }; declare class Overrides { private readonly unkey; constructor(config: OverrideConfig); get getOverride(): (req: { namespaceId?: string; namespaceName?: string; identifier: string; }) => Promise<{ result?: never; error: { code: _unkey_api.ErrorResponse["error"]["code"]; message: _unkey_api.ErrorResponse["error"]["message"]; docs: _unkey_api.ErrorResponse["error"]["docs"]; requestId: string; }; } | { result: { id: string; identifier: string; limit: number; duration: number; async?: boolean | null; }; error?: never; }>; get setOverride(): (req: { namespaceId?: string; namespaceName?: string; identifier: string; limit: number; duration: number; async?: boolean; }) => Promise<{ result?: never; error: { code: _unkey_api.ErrorResponse["error"]["code"]; message: _unkey_api.ErrorResponse["error"]["message"]; docs: _unkey_api.ErrorResponse["error"]["docs"]; requestId: string; }; } | { result: { overrideId: string; }; error?: never; }>; get deleteOverride(): (req: { namespaceId?: string; namespaceName?: string; identifier: string; }) => Promise<{ result?: never; error: { code: _unkey_api.ErrorResponse["error"]["code"]; message: _unkey_api.ErrorResponse["error"]["message"]; docs: _unkey_api.ErrorResponse["error"]["docs"]; requestId: string; }; } | { result: Record<string, never>; error?: never; }>; get listOverrides(): (req: { namespaceId?: string; namespaceName?: string; limit?: number; cursor?: string; } | undefined) => Promise<{ result?: never; error: { code: _unkey_api.ErrorResponse["error"]["code"]; message: _unkey_api.ErrorResponse["error"]["message"]; docs: _unkey_api.ErrorResponse["error"]["docs"]; requestId: string; }; } | { result: { overrides: ({ id: string; identifier: string; limit: number; duration: number; async?: boolean | null; })[]; cursor?: string; total: number; }; error?: never; }>; } export { type Duration, type Limit, type LimitOptions, NoopRatelimit, type OverrideConfig, Overrides, Ratelimit, type RatelimitConfig, type RatelimitResponse, type Ratelimiter };