{"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(\n    _identifier: string,\n    _opts?: LimitOptions,\n  ): 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 { UnkeyError } from \"@unkey/api/models/errors/unkeyerror\";\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          | ((\n              identifier: string,\n            ) => RatelimitResponse | Promise<RatelimitResponse>);\n      }\n    | false /**\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?: (\n    err: Error,\n    identifier: string,\n  ) => RatelimitResponse | Promise<RatelimitResponse> /**\n   * Cache abusive identifiers and block them immediately without a network request.\n   *\n   * ```ts\n   * // in global scope\n   * const cache = new Map()\n   *\n   * const unkey = new Ratelimit({\n   *   // ...\n   *   cache: cache,\n   * })\n   * ````\n   */;\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   * 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(\n    identifier: string,\n    opts?: LimitOptions,\n  ): Promise<RatelimitResponse> {\n    try {\n      return await this._limit(\n        identifier,\n        opts?.limit?.limit ?? this.config.limit,\n        ms(opts?.limit?.duration ?? this.config.duration),\n        opts?.cost ?? 1,\n      );\n    } catch (e) {\n      if (typeof this.config.onError !== \"function\") {\n        throw e;\n      }\n\n      const err =\n        e instanceof UnkeyError\n          ? new Error(e.message)\n          : e instanceof Error\n            ? e\n            : new Error(String(e));\n\n      return await this.config.onError(err, identifier);\n    }\n  }\n\n  // _limit just handles the racing and caching. It must not handle errors, those are handled by limit\n  private async _limit(\n    identifier: string,\n    limit: number,\n    duration: number,\n    cost: number,\n  ): 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    const timeout =\n      this.config.timeout === false\n        ? null\n        : (this.config.timeout ?? {\n            ms: 5000,\n            fallback: () => ({\n              success: false,\n              limit: 0,\n              remaining: 0,\n              reset: Date.now(),\n            }),\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      ];\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"]}