import { C as ConcurrencyGuard, j as UnifiedAdmitter, k as UnifiedAxis } from './unified-BouIz5EX.js'; import { C as Clock, D as Decision, d as Strategy, S as Store } from './types-DKirIBQt.js'; import { a as CommonAdapterOptions, N as NodeReqLike, L as LimiterOrStrategy } from './core-DcpxT2lH.js'; import './store-CQjuAFM_.js'; /** * Parse a duration into milliseconds. A number is treated as already-ms; a string is * `""` where `unit ∈ ms|s|m|h|d` (default `ms` if absent). Used by the NestJS * `@RateLimit` decorator and the `.throttlekit.yaml` config loader. */ declare function parseDuration$1(period: string | number): number; /** * Shared lifecycle-wiring helpers for the node-server adapters * (express, fastify, koa, nest). The unifiedAdmission and adaptiveConcurrency * primitives expose a `release()` callback that MUST be invoked exactly once * when the request lifecycle ends; this module centralizes the first-fire-wins * pattern from §6 of `research/bigger-bets/middleware-integration/DESIGN.md`. * * Web-platform adapters (hono, fetch, next, remix, sveltekit, elysia, trpc) * use a different pattern (try/finally around `await next()` or a * TransformStream wrap) — see TK-1326. */ /** * The minimum response surface the lifecycle wiring touches. The fields read * (`statusCode`) and methods called (`on("finish"|"close", ...)`) are * shared by Node's `http.ServerResponse`, Fastify's `reply.raw`, and Koa's * `ctx.res`. The interface is *structural* so we don't import the express * type into this shared module. */ interface LifecycleResponseLike { on(event: "finish" | "close", listener: () => void): unknown; /** Final response status. Read at finish-time for the `dropOn5xx` check. */ statusCode?: number | undefined; } /** * NestJS adapter. Builds a guard (a `CanActivate`) you attach with `@UseGuards(...)`: under the limit * it sets the standards headers on the response and returns `true`; over the limit it **throws** so * Nest's exception layer renders the denial. The key defaults to the same proxy-correct client IP as * the Express/Fastify adapters. The `ExecutionContext`/`CanActivate` shapes are modeled structurally, * so this works on either HTTP platform with **no `@nestjs/common` dependency** — pass * `exceptionFactory` to throw a real `HttpException` for an idiomatic `429`. See THROTTLEKIT.md §§14,15. * * Also exposes {@link nestUnifiedAdmissionMiddleware} and {@link nestAdaptiveConcurrencyMiddleware} * (0.9.2, TK-1325) — Express-style middleware functions registered via NestJS's * `MiddlewareConsumer`. They wire `release()` to `res.on("finish")` + `res.on("close")` and * cover the adaptive-concurrency lifecycle that guards alone cannot (guards run pre-handler). * See `research/bigger-bets/middleware-integration/DESIGN.md` §4 + D-M-11. */ /** The slice of a Nest response the adapter writes headers to (Express `setHeader` / Fastify `header`). */ interface NestResponseLike { setHeader?(name: string, value: string): unknown; header?(name: string, value: string): unknown; } /** * The `HttpArgumentsHost` Nest hands a guard via `switchToHttp()`. Non-generic here for a trivial * structural match; Nest's generic `getRequest()`/`getResponse()` satisfy it (instantiated to * these shapes). */ interface NestHttpArgumentsHostLike { getRequest(): NodeReqLike; getResponse(): NestResponseLike; } /** The slice of a Nest `ExecutionContext` the guard reads (the HTTP request/response). */ interface NestExecutionContextLike { switchToHttp(): NestHttpArgumentsHostLike; /** The route handler (method). Read by {@link createRateLimitGuard} for `@RateLimit` metadata. */ getHandler?(): object; /** The controller class. The fallback metadata source for a class-level `@RateLimit`. */ getClass?(): object; } /** A Nest `CanActivate` guard. */ interface NestCanActivate { canActivate(context: NestExecutionContextLike): boolean | Promise; } type NestRateLimitOptions = LimiterOrStrategy & CommonAdapterOptions & { /** Cost of a request in limiter units. A function computes it per request. Default 1. */ cost?: number | ((req: NodeReqLike) => number); /** Derive the limit key from the request. Default: proxy-correct, aggregated client IP. */ key?: (req: NodeReqLike) => string; /** Observability hook fired on every denial, before the exception is thrown. */ onLimited?: (req: NodeReqLike, decision: Decision) => void; /** Observability hook fired when the store throws (before the fail policy is applied). */ onError?: (req: NodeReqLike, err: unknown) => void; /** * Build the error thrown on a denial. Default: {@link RateLimitExceededError}. For an idiomatic * Nest `429`, pass a factory that returns an `HttpException`: * `(d) => new HttpException({ error: "Too Many Requests", retryAfterMs: d.retryAfterMs }, HttpStatus.TOO_MANY_REQUESTS)`. */ exceptionFactory?: (decision: Decision) => unknown; }; /** * Build a NestJS rate-limit guard. * * @example * ```ts * import { HttpException, HttpStatus } from "@nestjs/common"; * import { nestRateLimit } from "throttlekit/nest"; * import { gcra } from "throttlekit"; * * const RateLimit = nestRateLimit({ * strategy: gcra({ limit: 100, periodMs: 60_000 }), * exceptionFactory: (d) => * new HttpException({ error: "Too Many Requests", retryAfterMs: d.retryAfterMs }, HttpStatus.TOO_MANY_REQUESTS), * }); * * @Controller("posts") * export class PostsController { * @UseGuards(RateLimit) * @Post() create() { ... } * } * ``` */ declare function nestRateLimit(options: NestRateLimitOptions): NestCanActivate; /** Parse a duration into ms; re-exported from `src/core/duration` so the nest API stays stable. */ declare const parseDuration: typeof parseDuration$1; /** Per-route config attached by {@link RateLimit}. */ interface RateLimitMetadata { /** Sustained ceiling for this route. Ignored if `strategy` is supplied. */ limit?: number; /** Period as ms (number) or a duration string (`"1m"`, `"30s"`, `"1h"`). Default `"1m"`. */ period?: string | number; /** GCRA burst allowance (defaults to `limit`). Ignored if `strategy` is supplied. */ burst?: number; /** Provide a full strategy instead of `limit`/`period`/`burst` (e.g. `quota(...)`, `gcra(...)`). */ strategy?: Strategy; /** Per-route cost (default 1). */ cost?: number | ((req: NodeReqLike) => number); /** Per-route key override (default: the guard's key — a proxy-correct client IP). */ key?: (req: NodeReqLike) => string; } /** * Idiomatic NestJS decorator. Annotate a handler or controller, then register **one** * {@link createRateLimitGuard} globally — the guard reads this metadata per route. Mirrors the * `@Throttle` + `ThrottlerGuard` pattern, but dependency-free (it reads the ambient reflect-metadata * that NestJS already loads; no `@nestjs/common` import). * * @example * ```ts * // app.module.ts — register the guard once * import { APP_GUARD } from "@nestjs/core"; * import { createRateLimitGuard } from "throttlekit/nest"; * import { RedisStore } from "throttlekit/redis"; * providers: [{ provide: APP_GUARD, useValue: createRateLimitGuard({ store: new RedisStore({ client }) }) }] * * // any controller * import { RateLimit } from "throttlekit/nest"; * @RateLimit({ limit: 100, period: "1m" }) * @Post() create() { ... } * ``` */ declare function RateLimit(options: RateLimitMetadata): MethodDecorator & ClassDecorator; /** Options for {@link createRateLimitGuard} — the shared store/policy applied to every `@RateLimit`. */ type RateLimitGuardOptions = CommonAdapterOptions & { /** Shared store for every annotated route. Defaults to one in-process store for the whole guard. */ store?: Store; /** Default key (default: proxy-correct, aggregated client IP). A route's own `key` overrides it. */ key?: (req: NodeReqLike) => string; /** Applied to routes with no `@RateLimit` (default: unlimited — only annotated routes are limited). */ defaults?: RateLimitMetadata; /** Fired on every denial, before the exception. */ onLimited?: (req: NodeReqLike, decision: Decision) => void; /** Fired when the store throws, before the fail policy. */ onError?: (req: NodeReqLike, err: unknown) => void; /** Build the thrown error (default {@link RateLimitExceededError}; return an `HttpException` for a real 429). */ exceptionFactory?: (decision: Decision) => unknown; }; /** * Build the single global guard that enforces {@link RateLimit} metadata. Register it once via * `APP_GUARD`; routes without `@RateLimit` (and no `defaults`) pass through untouched. One limiter is * built and cached per distinct `@RateLimit(...)` config, all sharing the guard's `store`. */ declare function createRateLimitGuard(options?: RateLimitGuardOptions): NestCanActivate; /** Per-axis Decision snapshot from `admitter.lastDecisions()`. */ type AxisSnapshot = Readonly>>; /** The slice of an Express-style response the Nest middleware writes through. */ interface NestMiddlewareResLike extends LifecycleResponseLike { setHeader(name: string, value: string): unknown; status(code: number): unknown; json?(body: unknown): unknown; end(body?: unknown): unknown; } /** Generic next function signature; matches Express. */ type NestMiddlewareNext = (err?: unknown) => void; /** Options for {@link nestUnifiedAdmissionMiddleware}. */ type NestUnifiedAdmissionMiddlewareOptions = Pick & { admitter: UnifiedAdmitter; cost?: number | ((req: NodeReqLike) => number); key?: (req: NodeReqLike) => string; clock?: Clock; dropOn5xx?: boolean; onLimited?: (req: NodeReqLike, res: NestMiddlewareResLike, decision: Decision, axes: AxisSnapshot) => void; onError?: (req: NodeReqLike, res: NestMiddlewareResLike, err: unknown) => void; handler?: (req: NodeReqLike, res: NestMiddlewareResLike, decision: Decision, axes: AxisSnapshot) => void; }; /** * Express-style NestJS middleware enforcing a {@link UnifiedAdmitter}. Register via * `MiddlewareConsumer` in your module's `configure` method. * * @example * import type { MiddlewareConsumer, NestModule } from "@nestjs/common"; * import { nestUnifiedAdmissionMiddleware } from "throttlekit/nest"; * * \@Module({ ... }) * export class AppModule implements NestModule { * configure(consumer: MiddlewareConsumer) { * consumer.apply(nestUnifiedAdmissionMiddleware({ admitter })).forRoutes("*"); * } * } */ declare function nestUnifiedAdmissionMiddleware(options: NestUnifiedAdmissionMiddlewareOptions): (req: NodeReqLike, res: NestMiddlewareResLike, next: NestMiddlewareNext) => void; /** Options for {@link nestAdaptiveConcurrencyMiddleware}. */ type NestAdaptiveConcurrencyMiddlewareOptions = Pick & { guard: ConcurrencyGuard; clock?: Clock; dropOn5xx?: boolean; onLimited?: (req: NodeReqLike, res: NestMiddlewareResLike, decision: Decision) => void; handler?: (req: NodeReqLike, res: NestMiddlewareResLike, decision: Decision) => void; }; /** * Express-style NestJS middleware enforcing an adaptive {@link ConcurrencyGuard}. * * @example * consumer.apply(nestAdaptiveConcurrencyMiddleware({ guard })).forRoutes("*"); */ declare function nestAdaptiveConcurrencyMiddleware(options: NestAdaptiveConcurrencyMiddlewareOptions): (req: NodeReqLike, res: NestMiddlewareResLike, next: NestMiddlewareNext) => void; export { CommonAdapterOptions, LimiterOrStrategy, type NestAdaptiveConcurrencyMiddlewareOptions, type NestCanActivate, type NestExecutionContextLike, type NestHttpArgumentsHostLike, type NestRateLimitOptions, type NestResponseLike, type NestUnifiedAdmissionMiddlewareOptions, RateLimit, type RateLimitGuardOptions, type RateLimitMetadata, createRateLimitGuard, nestAdaptiveConcurrencyMiddleware, nestRateLimit, nestUnifiedAdmissionMiddleware, parseDuration };