diff --git a/.changeset/happy-pillows-train.md b/.changeset/happy-pillows-train.md new file mode 100644 index 0000000..00627cd --- /dev/null +++ b/.changeset/happy-pillows-train.md @@ -0,0 +1,8 @@ +--- +'@trpc-limiter/core': patch +'@trpc-limiter/memory': patch +'@trpc-limiter/redis': patch +'@trpc-limiter/upstash': patch +--- + +fix: type-safed hitinfo diff --git a/README.MD b/README.MD index d0bb296..fbefe6e 100644 --- a/README.MD +++ b/README.MD @@ -62,10 +62,7 @@ const t = initTRPC.context().create() const rateLimiter = createTrpcRedisLimiter({ fingerprint: (ctx) => defaultFingerPrint(ctx.req), - message: (hitInfo) => - `Too many requests, please try again later. ${Math.ceil( - (hitInfo.reset - Date.now()) / 1000 - )}`, + message: (hitInfo) => `Too many requests, please try again later. ${hitInfo}`, max: 15, windowMs: 10000, redisClient: redis, diff --git a/examples/nextjs-redis/src/server/api/trpc.ts b/examples/nextjs-redis/src/server/api/trpc.ts index feddfdf..4057d87 100644 --- a/examples/nextjs-redis/src/server/api/trpc.ts +++ b/examples/nextjs-redis/src/server/api/trpc.ts @@ -83,10 +83,7 @@ const rateLimiter = createTrpcRedisLimiter({ console.log("c$", c); return c; }, - message: (hitInfo) => - `Too many requests, please try again later. ${Math.ceil( - (hitInfo.reset - Date.now()) / 1000, - )}`, + message: (hitInfo) => `Too many requests, please try again later. ${hitInfo}`, max: 5, windowMs: 10000, redisClient: redis, diff --git a/package.json b/package.json index f79ae22..060d830 100644 --- a/package.json +++ b/package.json @@ -14,15 +14,6 @@ "prepare": "husky install" }, "lint-staged": { - "*.{js,json}": "prettier --write", - "packages/**/*.{js,jsx,ts,tsx}": [ - "eslint --fix", - "prettier --write" - ], - "examples/**/*.{js,jsx,ts,tsx}": [ - "eslint --fix", - "prettier --write" - ], "package.json": "sort-package-json" }, "devDependencies": { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f8bdffa..b9a1761 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -76,8 +76,8 @@ export const defineTRPCLimiter = < export const defineLimiterWithProps = < T, - Store extends IStoreCallback = IStoreCallback, - Res = any + Res, + Store extends IStoreCallback = IStoreCallback >( adapter: ILimiterAdapter, getDefaultOptions: ( diff --git a/packages/memory/src/store.ts b/packages/memory/src/store.ts index 1328c8f..c9c8d5d 100644 --- a/packages/memory/src/store.ts +++ b/packages/memory/src/store.ts @@ -22,7 +22,7 @@ export class MemoryStore { /** Reference to the active timer. */ interval: NodeJS.Timer - constructor(options: Required>) { + constructor(options: Required>) { this.windowMs = options.windowMs this.resetTime = calculateNextResetTime(this.windowMs) this.hits = {} diff --git a/packages/redis/README.md b/packages/redis/README.md index ed8f561..e0c35dd 100644 --- a/packages/redis/README.md +++ b/packages/redis/README.md @@ -35,10 +35,7 @@ const t = initTRPC.context().create() const rateLimiter = createTrpcRedisLimiter({ fingerprint: (ctx) => defaultFingerPrint(ctx.req), - message: (hitInfo) => - `Too many requests, please try again later. ${Math.ceil( - (hitInfo.reset - Date.now()) / 1000 - )}`, + message: (hitInfo) => `Too many requests, please try again later. ${hitInfo}`, max: 15, windowMs: 10000, redisClient: redis, diff --git a/packages/redis/src/index.ts b/packages/redis/src/index.ts index 483fdf8..9b20156 100644 --- a/packages/redis/src/index.ts +++ b/packages/redis/src/index.ts @@ -10,12 +10,28 @@ import { IRateLimiterStoreOptions, } from 'rate-limiter-flexible' -export const createTrpcRedisLimiter = defineLimiterWithProps<{ - redisClient: IRateLimiterStoreOptions['storeClient'] - limiter?: ( - opts: Required> - ) => IRateLimiterStoreOptions['insuranceLimiter'] -}>( +const isBlocked = async (store: RateLimiterRedis, fingerprint: string) => { + try { + await store.consume(fingerprint) + return null + } catch (error) { + if (error instanceof RateLimiterRes) { + return Math.round(error.msBeforeNext / 1000) || 1 + } + // Should not happen with `insuranceLimiter` + throw error + } +} + +export const createTrpcRedisLimiter = defineLimiterWithProps< + { + redisClient: IRateLimiterStoreOptions['storeClient'] + limiter?: ( + opts: Required> + ) => IRateLimiterStoreOptions['insuranceLimiter'] + }, + NonNullable>> +>( { store: (opts) => { return new RateLimiterRedis({ @@ -26,18 +42,7 @@ export const createTrpcRedisLimiter = defineLimiterWithProps<{ insuranceLimiter: opts.limiter(opts), }) }, - async isBlocked(store, fingerprint) { - try { - await store.consume(fingerprint) - return null - } catch (error) { - if (error instanceof RateLimiterRes) { - return Math.round(error.msBeforeNext / 1000) || 1 - } - // Should not happen with `insuranceLimiter` - throw error - } - }, + isBlocked, }, (currentState) => { return { diff --git a/packages/upstash/src/index.ts b/packages/upstash/src/index.ts index 20caf80..4d8da54 100644 --- a/packages/upstash/src/index.ts +++ b/packages/upstash/src/index.ts @@ -2,22 +2,28 @@ import { defineLimiterWithProps, BaseOpts, AnyRootConfig, + defaultFingerPrint, } from '@trpc-limiter/core' import { Ratelimit } from '@upstash/ratelimit' import { RegionRatelimitConfig } from '@upstash/ratelimit/types/single' -export const createTRPCUpstashLimiter = defineLimiterWithProps<{ - rateLimitOpts: ( - opts: Required> - ) => RegionRatelimitConfig -}>( +const isBlocked = async (store: Ratelimit, fingerprint: string) => { + const { success, pending, ...rest } = await store.limit(fingerprint) + await pending + return success ? null : rest +} + +export const createTRPCUpstashLimiter = defineLimiterWithProps< + { + rateLimitOpts: ( + opts: Required> + ) => RegionRatelimitConfig + }, + NonNullable>> +>( { store: (opts) => new Ratelimit(opts.rateLimitOpts(opts)), - async isBlocked(store, fingerprint) { - const { success, pending, ...rest } = await store.limit(fingerprint) - await pending - return success ? null : rest - }, + isBlocked, }, (currnetState) => { return { rateLimitOpts: currnetState.rateLimitOpts }