@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>
1 lines • 12.1 kB
Source Map (JSON)
{"version":3,"sources":["../src/noop.ts","../src/ratelimit.ts","../src/duration.ts","../src/overrides.ts"],"sourcesContent":["import type { Ratelimiter } from \"./interface\";\nimport type { LimitOptions, RatelimitResponse } from \"./types\";\n\nexport class NoopRatelimit implements Ratelimiter {\n public limit(_identifier: string, _opts?: LimitOptions): Promise<RatelimitResponse> {\n return Promise.resolve({\n limit: 0,\n remaining: 0,\n reset: 0,\n success: true,\n });\n }\n}\n","import { Unkey } from \"@unkey/api\";\nimport { APIError } from \"@unkey/api/models/errors\";\nimport { type Duration, ms } from \"./duration\";\nimport type { Ratelimiter } from \"./interface\";\nimport type { Cache, Limit, LimitOptions, RatelimitResponse } from \"./types\";\n\nexport type RatelimitConfig = Limit & {\n /**\n * @default https://api.unkey.com\n */\n baseUrl?: string;\n\n /**\n * The unkey root key. You can create one at https://unkey.dev/app/settings/root-keys\n *\n * Make sure the root key has permissions to use ratelimiting.\n */\n rootKey: string;\n\n /**\n * Namespaces allow you to separate different areas of your app and have isolated limits.\n *\n * @example tRPC-routes\n */\n namespace: string;\n\n /**\n * Configure a timeout to prevent network issues from blocking your function for too long.\n *\n * Disable it by setting `timeout: false`\n *\n * @default\n * ```ts\n * {\n * // 5 seconds\n * ms: 5000,\n * fallback: { success: false, limit: 0, remaining: 0, reset: Date.now()}\n * }\n * ```\n */\n timeout?:\n | {\n /**\n * Time in milliseconds until the response is returned\n */\n ms: number | Duration;\n\n /**\n * A custom response to return when the timeout is reached.\n *\n * The important bit is the `success` value, choose whether you want to let requests pass or not.\n *\n * @example With a static response\n * ```ts\n * {\n * // 5 seconds\n * ms: 5000\n * fallback: () => ({ success: true, limit: 0, remaining: 0, reset: 0 })\n * }\n * ```\n * @example With a dynamic response\n * ```ts\n * {\n * // 5 seconds\n * ms: 5000\n * fallback: (identifier: string) => {\n * if (someCheck(identifier)) {\n * return { success: false, limit: 0, remaining: 0, reset: 0 }\n * }\n * return { success: true, limit: 0, remaining: 0, reset: 0 }\n * }\n * }\n * ```\n */\n fallback:\n | RatelimitResponse\n | ((identifier: string) => RatelimitResponse | Promise<RatelimitResponse>);\n }\n | false;/**\n\t\t * Configure what happens for unforeseen errors\n\t\t *\n\t\t * @example Letting requests pass\n\t\t * ```ts\n\t\t * onError: () => ({ success: true, limit: 0, remaining: 0, reset: 0 })\n\t\t * ```\n\t\t *\n\t\t * @example Rejecting the request\n\t\t * ```ts\n\t\t * onError: () => ({ success: true, limit: 0, remaining: 0, reset: 0 })\n\t\t * ```\n\t\t *\n\t\t * @example Dynamic response\n\t\t * ```ts\n\t\t * onError: (error, identifier) => {\n\t\t * if (someCheck(error, identifier)) {\n\t\t * return { success: false, limit: 0, remaining: 0, reset: 0 }\n\t\t * }\n\t\t * return { success: true, limit: 0, remaining: 0, reset: 0 }\n\t\t * }\n\t\t * ```\n\t\t */\n onError?: (\n err: Error,\n identifier: string,\n ) => RatelimitResponse | Promise<RatelimitResponse> /**\n\t\t * Cache abusive identifiers and block them immediately without a network request.\n\t\t *\n\t\t * ```ts\n\t\t * // in global scope\n\t\t * const cache = new Map()\n\t\t *\n\t\t * const unkey = new Ratelimit({\n\t\t * // ...\n\t\t * cache: cache,\n\t\t * })\n\t\t * ````\n\t\t */\n cache?: Cache\n\n /**\n *\n * By default telemetry data is enabled, and sends:\n * runtime (Node.js / Edge)\n * platform (Node.js / Vercel / AWS)\n * SDK version\n */\n disableTelemetry?: boolean;\n};\n\nexport class Ratelimit implements Ratelimiter {\n private readonly config: RatelimitConfig;\n private readonly unkey: Unkey;\n private readonly cache: Cache\n\n constructor(config: RatelimitConfig) {\n this.config = config;\n this.unkey = new Unkey({\n serverURL: config.baseUrl,\n rootKey: config.rootKey,\n });\n this.cache = config.cache ?? new Map();\n }\n\n\n /**\n * Limit a specific identifier, you can override a lot of things about this specific request using\n * the 2nd argument.\n *\n * @example\n * ```ts\n * const identifier = getIpAddress() // or userId or anything else\n * const res = await unkey.limit(identifier)\n *\n * if (!res.success){\n * // reject request\n * }\n * // handle request\n * ```\n */\n public async limit(identifier: string, opts?: LimitOptions): Promise<RatelimitResponse> {\n try {\n return this._limit(\n identifier,\n opts?.limit?.limit ?? this.config.limit,\n ms(opts?.limit?.duration ?? this.config.duration),\n opts?.cost ?? 1);\n } catch (e) {\n if (!this.config.onError) {\n throw e;\n }\n const err = e instanceof Error ? e : new Error(String(e));\n\n return await this.config.onError(err, identifier);\n }\n }\n\n\n private async _limit(identifier: string, limit: number, duration: number, cost: number): Promise<RatelimitResponse> {\n const cacheKey = `${this.config.namespace}:${identifier}:${limit}:${duration}`;\n const naughty = this.cache.get(cacheKey)\n if (naughty) {\n if (naughty.reset > Date.now()) {\n return naughty\n } else {\n this.cache.delete(cacheKey)\n }\n\n }\n\n const timeout =\n this.config.timeout === false\n ? null\n : (this.config.timeout ?? {\n ms: 5000,\n fallback: () => ({ success: false, limit: 0, remaining: 0, reset: Date.now() }),\n });\n\n let timeoutId: any = null;\n try {\n const ps: Promise<RatelimitResponse>[] = [\n this.unkey.ratelimit\n .limit({\n namespace: this.config.namespace,\n identifier,\n limit,\n duration,\n cost,\n })\n .then(async (res) => {\n return res.data;\n })\n .catch((err) => {\n if (err instanceof APIError) {\n throw new Error(\n `Ratelimit failed: [${err.statusCode} - ${err.message}]: ${err.body}`,\n );\n }\n\n throw new Error(`Ratelimit failed: ${err}`);\n }),\n ];\n if (timeout) {\n ps.push(\n new Promise((resolve) => {\n timeoutId = setTimeout(async () => {\n const resolvedValue =\n typeof timeout.fallback === \"function\"\n ? await timeout.fallback(identifier)\n : timeout.fallback;\n resolve(resolvedValue);\n }, ms(timeout.ms));\n }),\n );\n }\n\n const res = await Promise.race(ps);\n if (!res.success) {\n this.cache.set(cacheKey, res)\n }\n\n return res\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n }\n}\n","type Unit = \"ms\" | \"s\" | \"m\" | \"h\" | \"d\";\n\nexport type Duration = `${number} ${Unit}` | `${number}${Unit}`;\n\n/**\n * Convert a human readable duration to milliseconds\n */\nexport function ms(d: Duration | number): number {\n if (typeof d === \"number\") {\n return d;\n }\n const match = d.match(/^(\\d+)\\s?(ms|s|m|h|d)$/);\n if (!match) {\n throw new Error(`Unable to parse window size: ${d}`);\n }\n const time = Number.parseInt(match[1]);\n const unit = match[2] as Unit;\n\n switch (unit) {\n case \"ms\":\n return time;\n case \"s\":\n return time * 1000;\n case \"m\":\n return time * 1000 * 60;\n case \"h\":\n return time * 1000 * 60 * 60;\n case \"d\":\n return time * 1000 * 60 * 60 * 24;\n\n default:\n throw new Error(`Unable to parse window size: ${d}`);\n }\n}\n","import { Unkey } from \"@unkey/api\";\n\nexport type OverrideConfig = {\n /**\n * @default https://api.unkey.com\n */\n baseUrl?: string;\n\n /**\n * The unkey root key. You can create one at https://app.unkey.com/settings/root-keys\n *\n * Make sure the root key has permissions to use overrides.\n */\n rootKey: string;\n};\n\nexport class Overrides {\n private readonly unkey: Unkey;\n\n constructor(config: OverrideConfig) {\n this.unkey = new Unkey({\n serverURL: config.baseUrl,\n rootKey: config.rootKey,\n });\n }\n\n public get getOverride() {\n return this.unkey.ratelimit.getOverride;\n }\n public get setOverride() {\n return this.unkey.ratelimit.setOverride;\n }\n public get deleteOverride() {\n return this.unkey.ratelimit.deleteOverride;\n }\n public get listOverrides() {\n return this.unkey.ratelimit.listOverrides;\n }\n}\n"],"mappings":"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","names":["res","Unkey"]}