@trpc-rate-limiter/cloudflare
Advanced tools
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
+29
| { | ||
| "name": "cloudflare", | ||
| "$schema": "../../node_modules/nx/schemas/project-schema.json", | ||
| "sourceRoot": "packages/cloudflare/src", | ||
| "projectType": "library", | ||
| "release": { | ||
| "version": { | ||
| "generatorOptions": { | ||
| "packageRoot": "dist/{projectRoot}", | ||
| "currentVersionResolver": "git-tag" | ||
| } | ||
| } | ||
| }, | ||
| "tags": [], | ||
| "targets": { | ||
| "nx-release-publish": { | ||
| "options": { | ||
| "packageRoot": "dist/{projectRoot}" | ||
| } | ||
| }, | ||
| "test": { | ||
| "executor": "@nx/vite:test", | ||
| "outputs": ["{options.reportsDirectory}"], | ||
| "options": { | ||
| "reportsDirectory": "../../coverage/packages/cloudflare" | ||
| } | ||
| } | ||
| } | ||
| } |
| const { withNx } = require("@nx/rollup/with-nx"); | ||
| const terser = require("@rollup/plugin-terser"); | ||
| const pkg = require("./package.json"); | ||
| module.exports = withNx( | ||
| { | ||
| main: "./src/index.ts", | ||
| outputPath: "../../dist/packages/cloudflare", | ||
| tsConfig: "./tsconfig.lib.json", | ||
| compiler: "swc", | ||
| format: ["cjs", "esm"], | ||
| assets: [ | ||
| { input: "./packages/cloudflare", output: ".", glob: "README.md" }, | ||
| ], | ||
| }, | ||
| { | ||
| plugins: [terser()], | ||
| }, | ||
| ); |
| export * from './stores' | ||
| export * from './types' |
| import { DurableObject } from 'cloudflare:workers' | ||
| import type { ClientRateLimitInfo } from '@trpc-rate-limiter/hono' | ||
| const initialState: ClientRateLimitInfo = { | ||
| totalHits: 0, | ||
| } | ||
| export class DurableObjectRateLimiter extends DurableObject { | ||
| value() { | ||
| return this.ctx.storage.get<ClientRateLimitInfo>('value') | ||
| } | ||
| async update(hits: number, windowMs: number) { | ||
| let payload = (await this.ctx.storage.get<ClientRateLimitInfo>('value')) || initialState | ||
| let alarmTimestamp = await this.ctx.storage.getAlarm() | ||
| const currentTime = Date.now() | ||
| if (!alarmTimestamp) { | ||
| alarmTimestamp = currentTime + windowMs | ||
| await this.ctx.storage.setAlarm(alarmTimestamp) | ||
| } | ||
| // if windowMs is changed while the alarm is active, the alarm will not be reset (bug) | ||
| if (alarmTimestamp <= currentTime) { | ||
| await this.reset() | ||
| payload = { totalHits: 0 } | ||
| alarmTimestamp = currentTime + windowMs | ||
| await this.ctx.storage.setAlarm(alarmTimestamp) | ||
| } | ||
| payload = { | ||
| totalHits: payload.totalHits + hits, | ||
| resetTime: new Date(alarmTimestamp), | ||
| } | ||
| await this.ctx.storage.put('value', payload) | ||
| return payload | ||
| } | ||
| async reset() { | ||
| await this.ctx.storage.put('value', initialState) | ||
| } | ||
| override async alarm() { | ||
| await this.reset() | ||
| } | ||
| } |
| import type { DurableObjectNamespace } from '@cloudflare/workers-types' | ||
| import type { ClientRateLimitInfo, InitStoreOptions, Store } from '@trpc-rate-limiter/hono' | ||
| import type { Options } from '../types' | ||
| import type { DurableObjectRateLimiter } from './DurableObjectClass' | ||
| export class DurableObjectStore implements Store { | ||
| /** | ||
| * The text to prepend to the key in Redis. | ||
| */ | ||
| prefix: string | ||
| /** | ||
| * The Durable Object namespace to use. | ||
| */ | ||
| namespace: DurableObjectNamespace<DurableObjectRateLimiter> | ||
| /** | ||
| * The number of milliseconds to remember that user's requests. | ||
| */ | ||
| windowMs!: number | ||
| /** | ||
| * @constructor for `DurableObjectStore`. | ||
| * | ||
| * @param options {Options} - The configuration options for the store. | ||
| */ | ||
| constructor(options: Options<DurableObjectNamespace<DurableObjectRateLimiter>>) { | ||
| this.namespace = options.namespace | ||
| this.prefix = options.prefix ?? 'hrl:' | ||
| } | ||
| /** | ||
| * Method to prefix the keys with the given text and return a `DurableObjectId`. | ||
| * | ||
| * @param key {string} - The key. | ||
| * | ||
| * @returns {DurableObjectId} - The text + the key. | ||
| */ | ||
| prefixKey(key: string): DurableObjectId { | ||
| return this.namespace.idFromName(`${this.prefix}${key}`) | ||
| } | ||
| /** | ||
| * Method that actually initializes the store. | ||
| * | ||
| * @param options {RateLimitConfiguration} - The options used to setup the middleware. | ||
| */ | ||
| init(options: InitStoreOptions) { | ||
| this.windowMs = options.windowMs | ||
| } | ||
| /** | ||
| * Method to fetch a client's hit count and reset time. | ||
| * | ||
| * @param key {string} - The identifier for a client. | ||
| * | ||
| * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client. | ||
| */ | ||
| async get(key: string): Promise<ClientRateLimitInfo | undefined> { | ||
| return (await this.namespace.get(this.prefixKey(key)).value()) as | ||
| | ClientRateLimitInfo | ||
| | undefined | ||
| } | ||
| /** | ||
| * Method to increment a client's hit counter. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| * | ||
| * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client | ||
| */ | ||
| async increment(key: string): Promise<ClientRateLimitInfo> { | ||
| return (await this.namespace | ||
| .get(this.prefixKey(key)) | ||
| .update(1, this.windowMs)) as ClientRateLimitInfo | ||
| } | ||
| /** | ||
| * Method to decrement a client's hit counter. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| */ | ||
| async decrement(key: string): Promise<void> { | ||
| await this.namespace.get(this.prefixKey(key)).update(-1, this.windowMs) | ||
| } | ||
| /** | ||
| * Method to reset a client's hit counter. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| */ | ||
| async resetKey(key: string): Promise<void> { | ||
| await this.namespace.get(this.prefixKey(key)).reset() | ||
| } | ||
| } |
| export * from "./KVStore"; | ||
| export * from "./DurableObjectStore"; | ||
| export * from "./DurableObjectClass"; |
| import type { ClientRateLimitInfo, InitStoreOptions, Store } from '@trpc-rate-limiter/hono' | ||
| import type { Options } from '../types' | ||
| export class WorkersKVStore implements Store { | ||
| /** | ||
| * Expiration targets that are less than 60 seconds into the future are not supported. This is true for both expiration methods. | ||
| * | ||
| * see: https://developers.cloudflare.com/kv/api/write-key-value-pairs/#expiring-keys | ||
| * | ||
| */ | ||
| private static readonly KV_MIN_EXPIRATION_BUFFER = 60 | ||
| /** | ||
| * The text to prepend to the key in Redis. | ||
| */ | ||
| prefix: string | ||
| /** | ||
| * The KV namespace to use. | ||
| */ | ||
| namespace: KVNamespace | ||
| /** | ||
| * The number of milliseconds to remember that user's requests. | ||
| */ | ||
| windowMs!: number | ||
| /** | ||
| * @constructor for `WorkersKVStore`. | ||
| * | ||
| * @param options {Options} - The configuration options for the store. | ||
| */ | ||
| constructor(options: Options<KVNamespace>) { | ||
| this.namespace = options.namespace | ||
| this.prefix = options.prefix ?? 'hrl:' | ||
| } | ||
| /** | ||
| * Method to prefix the keys with the given text. | ||
| * | ||
| * @param key {string} - The key. | ||
| * | ||
| * @returns {string} - The text + the key. | ||
| */ | ||
| prefixKey(key: string): string { | ||
| return `${this.prefix}${key}` | ||
| } | ||
| /** | ||
| * Method that actually initializes the store. | ||
| * | ||
| * @param options {RateLimitConfiguration} - The options used to setup the middleware. | ||
| */ | ||
| init(options: InitStoreOptions) { | ||
| this.windowMs = options.windowMs | ||
| } | ||
| /** | ||
| * Method to fetch a client's hit count and reset time. | ||
| * | ||
| * @param key {string} - The identifier for a client. | ||
| * | ||
| * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client. | ||
| */ | ||
| async get(key: string): Promise<ClientRateLimitInfo | undefined> { | ||
| const result = await this.namespace.get<ClientRateLimitInfo>(this.prefixKey(key), 'json') | ||
| if (result) return result | ||
| return undefined | ||
| } | ||
| /** | ||
| * Method to increment a client's hit counter. If the current time is within an active window, | ||
| * it increments the existing hit count. Otherwise, it starts a new window with a hit count of 1. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| * | ||
| * @returns {ClientRateLimitInfo} - An object containing: | ||
| * - totalHits: The updated number of hits for the client | ||
| * - resetTime: The time when the current rate limit window expires | ||
| */ | ||
| async increment(key: string): Promise<ClientRateLimitInfo> { | ||
| const nowMS = Date.now() | ||
| const record = await this.get(key) | ||
| const defaultResetTime = new Date(nowMS + this.windowMs) | ||
| const existingResetTimeMS = record?.resetTime && new Date(record.resetTime).getTime() | ||
| const isActiveWindow = existingResetTimeMS && existingResetTimeMS > nowMS | ||
| const payload: ClientRateLimitInfo = { | ||
| totalHits: isActiveWindow ? record.totalHits + 1 : 1, | ||
| resetTime: | ||
| isActiveWindow && existingResetTimeMS ? new Date(existingResetTimeMS) : defaultResetTime, | ||
| } | ||
| await this.updateRecord(key, payload) | ||
| return payload | ||
| } | ||
| /** | ||
| * Method to decrement a client's hit counter. Only decrements if there is an active time window. | ||
| * The hit counter will never go below 0. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| * @returns {Promise<void>} - Returns void after attempting to decrement the counter | ||
| */ | ||
| async decrement(key: string): Promise<void> { | ||
| const nowMS = Date.now() | ||
| const record = await this.get(key) | ||
| const existingResetTimeMS = record?.resetTime && new Date(record.resetTime).getTime() | ||
| const isActiveWindow = existingResetTimeMS && existingResetTimeMS > nowMS | ||
| // Only decrement if in active window | ||
| if (isActiveWindow && record) { | ||
| const payload: ClientRateLimitInfo = { | ||
| totalHits: Math.max(0, record.totalHits - 1), // Never go below 0 | ||
| resetTime: new Date(existingResetTimeMS), | ||
| } | ||
| await this.updateRecord(key, payload) | ||
| } | ||
| return | ||
| } | ||
| /** | ||
| * Method to reset a client's hit counter. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| */ | ||
| async resetKey(key: string): Promise<void> { | ||
| await this.namespace.delete(this.prefixKey(key)) | ||
| } | ||
| /** | ||
| * Method to calculate expiration. | ||
| * | ||
| * @param resetTime {Date} - The reset time. | ||
| * | ||
| * @returns {number} - The expiration in seconds. | ||
| * | ||
| * Note: KV expiration is always set to 60s after resetTime or nowSeconds to meet Cloudflare's minimum requirement. | ||
| * This doesn't affect rate limiting behavior which is controlled by resetTime. | ||
| */ | ||
| private calculateExpiration(resetTime: Date): number { | ||
| const resetTimeSeconds = Math.floor(resetTime.getTime() / 1000) | ||
| const nowSeconds = Math.floor(Date.now() / 1000) | ||
| return Math.max(resetTimeSeconds, nowSeconds) + WorkersKVStore.KV_MIN_EXPIRATION_BUFFER | ||
| } | ||
| /** | ||
| * Method to update a record. | ||
| * | ||
| * @param key {string} - The identifier for a client. | ||
| * @param payload {ClientRateLimitInfo} - The payload to update. | ||
| */ | ||
| private async updateRecord(key: string, payload: ClientRateLimitInfo): Promise<void> { | ||
| await this.namespace.put(this.prefixKey(key), JSON.stringify(payload), { | ||
| expiration: this.calculateExpiration(payload.resetTime as Date), | ||
| }) | ||
| } | ||
| } |
+14
| /** | ||
| * The configuration options for the store. | ||
| */ | ||
| export type Options<Binding> = { | ||
| /** | ||
| * The KV namespace to use. | ||
| */ | ||
| namespace: Binding | ||
| /** | ||
| * The text to prepend to the key in Redis. | ||
| */ | ||
| readonly prefix?: string | ||
| } |
| { | ||
| "extends": "../../tsconfig.base.json", | ||
| "compilerOptions": { | ||
| "module": "esnext", | ||
| "forceConsistentCasingInFileNames": true, | ||
| "strict": true, | ||
| "noImplicitOverride": true, | ||
| "noPropertyAccessFromIndexSignature": true, | ||
| "noImplicitReturns": true, | ||
| "noFallthroughCasesInSwitch": true | ||
| }, | ||
| "files": [], | ||
| "include": [], | ||
| "references": [ | ||
| { | ||
| "path": "./tsconfig.lib.json" | ||
| }, | ||
| { | ||
| "path": "./tsconfig.spec.json" | ||
| } | ||
| ] | ||
| } |
| { | ||
| "extends": "./tsconfig.json", | ||
| "compilerOptions": { | ||
| "outDir": "../../dist/out-tsc", | ||
| "declaration": true, | ||
| "types": ["node", "@cloudflare/workers-types/2023-07-01"] | ||
| }, | ||
| "include": ["src/**/*.ts"], | ||
| "exclude": [ | ||
| "vite.config.ts", | ||
| "src/**/*.spec.ts", | ||
| "src/**/*.test.ts", | ||
| "./**/__tests__/*" | ||
| ] | ||
| } |
| { | ||
| "extends": "./tsconfig.json", | ||
| "compilerOptions": { | ||
| "outDir": "../../dist/out-tsc", | ||
| "types": [ | ||
| "vitest/globals", | ||
| "vitest/importMeta", | ||
| "vite/client", | ||
| "node", | ||
| "vitest", | ||
| "@cloudflare/workers-types/2023-07-01" | ||
| ] | ||
| }, | ||
| "include": [ | ||
| "vite.config.ts", | ||
| "vitest.config.ts", | ||
| "src/**/*.test.ts", | ||
| "src/**/*.spec.ts", | ||
| "src/**/*.test.tsx", | ||
| "src/**/*.spec.tsx", | ||
| "src/**/*.test.js", | ||
| "src/**/*.spec.js", | ||
| "src/**/*.test.jsx", | ||
| "src/**/*.spec.jsx", | ||
| "src/**/*.d.ts" | ||
| ] | ||
| } |
| import { defineConfig } from "vite"; | ||
| import { nxViteTsPaths } from "@nx/vite/plugins/nx-tsconfig-paths.plugin"; | ||
| export default defineConfig({ | ||
| root: __dirname, | ||
| cacheDir: "../../node_modules/.vite/packages/cloudflare", | ||
| plugins: [nxViteTsPaths()], | ||
| // Uncomment this if you are using workers. | ||
| // worker: { | ||
| // plugins: [ nxViteTsPaths() ], | ||
| // }, | ||
| test: { | ||
| globals: true, | ||
| cache: { dir: "../../node_modules/.vitest" }, | ||
| environment: "node", | ||
| include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], | ||
| reporters: ["default"], | ||
| coverage: { | ||
| reportsDirectory: "../../coverage/packages/cloudflare", | ||
| provider: "v8", | ||
| }, | ||
| }, | ||
| }); |
+3
-5
| { | ||
| "name": "@trpc-rate-limiter/cloudflare", | ||
| "description": "Cloudflare stores for trpc-rate-limiter.", | ||
| "version": "0.1.1", | ||
| "version": "0.1.2", | ||
| "license": "MIT", | ||
@@ -35,5 +35,3 @@ "keywords": [ | ||
| }, | ||
| "types": "./index.cjs.d.ts", | ||
| "module": "./index.esm.js", | ||
| "main": "./index.cjs.js" | ||
| } | ||
| "types": "./index.cjs.d.ts" | ||
| } |
| export * from "./src/index"; |
| "use strict";var e=require("cloudflare:workers");class t{static{this.KV_MIN_EXPIRATION_BUFFER=60}constructor(e){this.namespace=e.namespace,this.prefix=e.prefix??"hrl:"}prefixKey(e){return`${this.prefix}${e}`}init(e){this.windowMs=e.windowMs}async get(e){const t=await this.namespace.get(this.prefixKey(e),"json");if(t)return t}async increment(e){const t=Date.now(),a=await this.get(e),s=new Date(t+this.windowMs),i=a?.resetTime&&new Date(a.resetTime).getTime(),r=i&&i>t,n={totalHits:r?a.totalHits+1:1,resetTime:r&&i?new Date(i):s};return await this.updateRecord(e,n),n}async decrement(e){const t=Date.now(),a=await this.get(e),s=a?.resetTime&&new Date(a.resetTime).getTime();if(s&&s>t&&a){const t={totalHits:Math.max(0,a.totalHits-1),resetTime:new Date(s)};await this.updateRecord(e,t)}}async resetKey(e){await this.namespace.delete(this.prefixKey(e))}calculateExpiration(e){const a=Math.floor(e.getTime()/1e3),s=Math.floor(Date.now()/1e3);return Math.max(a,s)+t.KV_MIN_EXPIRATION_BUFFER}async updateRecord(e,t){await this.namespace.put(this.prefixKey(e),JSON.stringify(t),{expiration:this.calculateExpiration(t.resetTime)})}}const a={totalHits:0};class s extends e.DurableObject{value(){return this.ctx.storage.get("value")}async update(e,t){let s=await this.ctx.storage.get("value")||a;const i=new Date(s.resetTime??Date.now()+t);s={totalHits:s.totalHits+e,resetTime:i};return null==await this.ctx.storage.getAlarm()&&this.ctx.storage.setAlarm(i.getTime()),await this.ctx.storage.put("value",s),s}async reset(){await this.ctx.storage.put("value",a)}async alarm(){await this.reset()}}exports.DurableObjectRateLimiter=s,exports.DurableObjectStore=class{constructor(e){this.namespace=e.namespace,this.prefix=e.prefix??"hrl:"}prefixKey(e){return this.namespace.idFromName(`${this.prefix}${e}`)}init(e){this.windowMs=e.windowMs}async get(e){return await this.namespace.get(this.prefixKey(e)).value()}async increment(e){return await this.namespace.get(this.prefixKey(e)).update(1,this.windowMs)}async decrement(e){await this.namespace.get(this.prefixKey(e)).update(-1,this.windowMs)}async resetKey(e){await this.namespace.get(this.prefixKey(e)).reset()}},exports.WorkersKVStore=t; |
| export * from "./src/index"; |
| import{DurableObject as t}from"cloudflare:workers";class e{static{this.KV_MIN_EXPIRATION_BUFFER=60}constructor(t){this.namespace=t.namespace,this.prefix=t.prefix??"hrl:"}prefixKey(t){return`${this.prefix}${t}`}init(t){this.windowMs=t.windowMs}async get(t){const e=await this.namespace.get(this.prefixKey(t),"json");if(e)return e}async increment(t){const e=Date.now(),a=await this.get(t),s=new Date(e+this.windowMs),i=a?.resetTime&&new Date(a.resetTime).getTime(),r=i&&i>e,n={totalHits:r?a.totalHits+1:1,resetTime:r&&i?new Date(i):s};return await this.updateRecord(t,n),n}async decrement(t){const e=Date.now(),a=await this.get(t),s=a?.resetTime&&new Date(a.resetTime).getTime();if(s&&s>e&&a){const e={totalHits:Math.max(0,a.totalHits-1),resetTime:new Date(s)};await this.updateRecord(t,e)}}async resetKey(t){await this.namespace.delete(this.prefixKey(t))}calculateExpiration(t){const a=Math.floor(t.getTime()/1e3),s=Math.floor(Date.now()/1e3);return Math.max(a,s)+e.KV_MIN_EXPIRATION_BUFFER}async updateRecord(t,e){await this.namespace.put(this.prefixKey(t),JSON.stringify(e),{expiration:this.calculateExpiration(e.resetTime)})}}class a{constructor(t){this.namespace=t.namespace,this.prefix=t.prefix??"hrl:"}prefixKey(t){return this.namespace.idFromName(`${this.prefix}${t}`)}init(t){this.windowMs=t.windowMs}async get(t){return await this.namespace.get(this.prefixKey(t)).value()}async increment(t){return await this.namespace.get(this.prefixKey(t)).update(1,this.windowMs)}async decrement(t){await this.namespace.get(this.prefixKey(t)).update(-1,this.windowMs)}async resetKey(t){await this.namespace.get(this.prefixKey(t)).reset()}}const s={totalHits:0};class i extends t{value(){return this.ctx.storage.get("value")}async update(t,e){let a=await this.ctx.storage.get("value")||s;const i=new Date(a.resetTime??Date.now()+e);a={totalHits:a.totalHits+t,resetTime:i};return null==await this.ctx.storage.getAlarm()&&this.ctx.storage.setAlarm(i.getTime()),await this.ctx.storage.put("value",a),a}async reset(){await this.ctx.storage.put("value",s)}async alarm(){await this.reset()}}export{i as DurableObjectRateLimiter,a as DurableObjectStore,e as WorkersKVStore}; |
| export * from './stores'; | ||
| export * from './types'; |
| /// <reference types="@cloudflare/workers-types/2023-07-01" /> | ||
| import { DurableObject } from 'cloudflare:workers'; | ||
| import type { ClientRateLimitInfo } from '@trpc-rate-limiter/hono'; | ||
| export declare class DurableObjectRateLimiter extends DurableObject { | ||
| value(): Promise<ClientRateLimitInfo | undefined>; | ||
| update(hits: number, windowMs: number): Promise<ClientRateLimitInfo>; | ||
| reset(): Promise<void>; | ||
| alarm(): Promise<void>; | ||
| } |
| /// <reference types="@cloudflare/workers-types/2023-07-01" /> | ||
| import type { DurableObjectNamespace } from '@cloudflare/workers-types'; | ||
| import type { ClientRateLimitInfo, InitStoreOptions, Store } from '@trpc-rate-limiter/hono'; | ||
| import type { Options } from '../types'; | ||
| import type { DurableObjectRateLimiter } from './DurableObjectClass'; | ||
| export declare class DurableObjectStore implements Store { | ||
| /** | ||
| * The text to prepend to the key in Redis. | ||
| */ | ||
| prefix: string; | ||
| /** | ||
| * The Durable Object namespace to use. | ||
| */ | ||
| namespace: DurableObjectNamespace<DurableObjectRateLimiter>; | ||
| /** | ||
| * The number of milliseconds to remember that user's requests. | ||
| */ | ||
| windowMs: number; | ||
| /** | ||
| * @constructor for `DurableObjectStore`. | ||
| * | ||
| * @param options {Options} - The configuration options for the store. | ||
| */ | ||
| constructor(options: Options<DurableObjectNamespace<DurableObjectRateLimiter>>); | ||
| /** | ||
| * Method to prefix the keys with the given text and return a `DurableObjectId`. | ||
| * | ||
| * @param key {string} - The key. | ||
| * | ||
| * @returns {DurableObjectId} - The text + the key. | ||
| */ | ||
| prefixKey(key: string): DurableObjectId; | ||
| /** | ||
| * Method that actually initializes the store. | ||
| * | ||
| * @param options {RateLimitConfiguration} - The options used to setup the middleware. | ||
| */ | ||
| init(options: InitStoreOptions): void; | ||
| /** | ||
| * Method to fetch a client's hit count and reset time. | ||
| * | ||
| * @param key {string} - The identifier for a client. | ||
| * | ||
| * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client. | ||
| */ | ||
| get(key: string): Promise<ClientRateLimitInfo | undefined>; | ||
| /** | ||
| * Method to increment a client's hit counter. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| * | ||
| * @returns {ClientRateLimitInfo} - The number of hits and reset time for that client | ||
| */ | ||
| increment(key: string): Promise<ClientRateLimitInfo>; | ||
| /** | ||
| * Method to decrement a client's hit counter. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| */ | ||
| decrement(key: string): Promise<void>; | ||
| /** | ||
| * Method to reset a client's hit counter. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| */ | ||
| resetKey(key: string): Promise<void>; | ||
| } |
| export * from "./KVStore"; | ||
| export * from "./DurableObjectStore"; | ||
| export * from "./DurableObjectClass"; |
| /// <reference types="@cloudflare/workers-types/2023-07-01" /> | ||
| import type { ClientRateLimitInfo, InitStoreOptions, Store } from '@trpc-rate-limiter/hono'; | ||
| import type { Options } from '../types'; | ||
| export declare class WorkersKVStore implements Store { | ||
| /** | ||
| * Expiration targets that are less than 60 seconds into the future are not supported. This is true for both expiration methods. | ||
| * | ||
| * see: https://developers.cloudflare.com/kv/api/write-key-value-pairs/#expiring-keys | ||
| * | ||
| */ | ||
| private static readonly KV_MIN_EXPIRATION_BUFFER; | ||
| /** | ||
| * The text to prepend to the key in Redis. | ||
| */ | ||
| prefix: string; | ||
| /** | ||
| * The KV namespace to use. | ||
| */ | ||
| namespace: KVNamespace; | ||
| /** | ||
| * The number of milliseconds to remember that user's requests. | ||
| */ | ||
| windowMs: number; | ||
| /** | ||
| * @constructor for `WorkersKVStore`. | ||
| * | ||
| * @param options {Options} - The configuration options for the store. | ||
| */ | ||
| constructor(options: Options<KVNamespace>); | ||
| /** | ||
| * Method to prefix the keys with the given text. | ||
| * | ||
| * @param key {string} - The key. | ||
| * | ||
| * @returns {string} - The text + the key. | ||
| */ | ||
| prefixKey(key: string): string; | ||
| /** | ||
| * Method that actually initializes the store. | ||
| * | ||
| * @param options {RateLimitConfiguration} - The options used to setup the middleware. | ||
| */ | ||
| init(options: InitStoreOptions): void; | ||
| /** | ||
| * Method to fetch a client's hit count and reset time. | ||
| * | ||
| * @param key {string} - The identifier for a client. | ||
| * | ||
| * @returns {ClientRateLimitInfo | undefined} - The number of hits and reset time for that client. | ||
| */ | ||
| get(key: string): Promise<ClientRateLimitInfo | undefined>; | ||
| /** | ||
| * Method to increment a client's hit counter. If the current time is within an active window, | ||
| * it increments the existing hit count. Otherwise, it starts a new window with a hit count of 1. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| * | ||
| * @returns {ClientRateLimitInfo} - An object containing: | ||
| * - totalHits: The updated number of hits for the client | ||
| * - resetTime: The time when the current rate limit window expires | ||
| */ | ||
| increment(key: string): Promise<ClientRateLimitInfo>; | ||
| /** | ||
| * Method to decrement a client's hit counter. Only decrements if there is an active time window. | ||
| * The hit counter will never go below 0. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| * @returns {Promise<void>} - Returns void after attempting to decrement the counter | ||
| */ | ||
| decrement(key: string): Promise<void>; | ||
| /** | ||
| * Method to reset a client's hit counter. | ||
| * | ||
| * @param key {string} - The identifier for a client | ||
| */ | ||
| resetKey(key: string): Promise<void>; | ||
| /** | ||
| * Method to calculate expiration. | ||
| * | ||
| * @param resetTime {Date} - The reset time. | ||
| * | ||
| * @returns {number} - The expiration in seconds. | ||
| * | ||
| * Note: KV expiration is always set to 60s after resetTime or nowSeconds to meet Cloudflare's minimum requirement. | ||
| * This doesn't affect rate limiting behavior which is controlled by resetTime. | ||
| */ | ||
| private calculateExpiration; | ||
| /** | ||
| * Method to update a record. | ||
| * | ||
| * @param key {string} - The identifier for a client. | ||
| * @param payload {ClientRateLimitInfo} - The payload to update. | ||
| */ | ||
| private updateRecord; | ||
| } |
| /** | ||
| * The configuration options for the store. | ||
| */ | ||
| export type Options<Binding> = { | ||
| /** | ||
| * The KV namespace to use. | ||
| */ | ||
| namespace: Binding; | ||
| /** | ||
| * The text to prepend to the key in Redis. | ||
| */ | ||
| readonly prefix?: string; | ||
| }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
20115
30.13%16
33.33%416
99.04%2
-50%2
Infinity%3
50%