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>

1 lines 12.6 kB
{"version":3,"sources":["../src/noop.ts","../src/ratelimit.ts","../package.json","../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 { version } from \"../package.json\";\nimport { type Duration, ms } from \"./duration\";\nimport type { Ratelimiter } from \"./interface\";\nimport type { Limit, LimitOptions, RatelimitResponse } from \"./types\";\n\nexport type RatelimitConfig = Limit & {\n /**\n * @default https://api.unkey.dev\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\n /**\n * Configure what happens for unforeseen errors\n *\n * @example Letting requests pass\n * ```ts\n * onError: () => ({ success: true, limit: 0, remaining: 0, reset: 0 })\n * ```\n *\n * @example Rejecting the request\n * ```ts\n * onError: () => ({ success: true, limit: 0, remaining: 0, reset: 0 })\n * ```\n *\n * @example Dynamic response\n * ```ts\n * onError: (error, identifier) => {\n * if (someCheck(error, 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 onError?: (err: Error, identifier: string) => RatelimitResponse | Promise<RatelimitResponse>;\n\n /**\n * Do not wait for a response from the origin. Faster but less accurate.\n */\n async?: boolean;\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\n constructor(config: RatelimitConfig) {\n this.config = config;\n this.unkey = new Unkey({\n baseUrl: config.baseUrl,\n rootKey: config.rootKey,\n disableTelemetry: config.disableTelemetry,\n wrapperSdkVersion: `@unkey/ratelimit@${version}`,\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(identifier, opts);\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 private async _limit(identifier: string, opts?: LimitOptions): Promise<RatelimitResponse> {\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.ratelimits\n .limit({\n namespace: this.config.namespace,\n identifier,\n limit: this.config.limit,\n duration: ms(this.config.duration),\n cost: opts?.cost,\n meta: opts?.meta,\n resources: opts?.resources,\n async: typeof opts?.async !== \"undefined\" ? opts.async : this.config.async,\n })\n .then((res) => {\n if (res.error) {\n throw new Error(\n `Ratelimit failed: [${res.error.code} - ${res.error.requestId}]: ${res.error.message}`,\n );\n }\n return res.result;\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 return await Promise.race(ps);\n } finally {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n }\n }\n}\n","{\n \"name\": \"@unkey/ratelimit\",\n \"version\": \"0.5.5\",\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.mjs\",\n \"types\": \"./dist/index.d.ts\",\n \"license\": \"MIT\",\n \"private\": false,\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"keywords\": [\n \"unkey\",\n \"ratelimit\",\n \"global\",\n \"serverless\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/unkeyed/unkey/issues\"\n },\n \"homepage\": \"https://github.com/unkeyed/unkey#readme\",\n \"files\": [\n \"./dist/**\",\n \"README.md\"\n ],\n \"author\": \"Andreas Thomas <andreas@unkey.dev>\",\n \"scripts\": {\n \"build\": \"tsup\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.14.9\",\n \"@unkey/tsconfig\": \"workspace:^\",\n \"tsup\": \"^8.0.2\",\n \"typescript\": \"^5.5.3\"\n },\n \"dependencies\": {\n \"@unkey/api\": \"workspace:^\"\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\";\nimport { version } from \"../package.json\";\n\nexport type OverrideConfig = {\n /**\n * @default https://api.unkey.dev\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 overrides.\n */\n rootKey: string;\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 Overrides {\n private readonly unkey: Unkey;\n\n constructor(config: OverrideConfig) {\n this.unkey = new Unkey({\n baseUrl: config.baseUrl,\n rootKey: config.rootKey,\n disableTelemetry: config.disableTelemetry,\n wrapperSdkVersion: `@unkey/ratelimit@${version}`,\n });\n }\n\n public get getOverride() {\n return this.unkey.ratelimits.getOverride;\n }\n public get setOverride() {\n return this.unkey.ratelimits.setOverride;\n }\n public get deleteOverride() {\n return this.unkey.ratelimits.deleteOverride;\n }\n public get listOverrides() {\n return this.unkey.ratelimits.listOverrides;\n }\n}\n"],"mappings":"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","names":["Unkey","Unkey"]}