@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
text/typescript
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 };