@orpc/ratelimit 2.0.0-beta.4 → 2.0.0-beta.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,9 @@
7
7
  <a href="https://www.npmjs.com/package/@orpc/ratelimit">
8
8
  <img alt="weekly downloads" src="https://img.shields.io/npm/dw/%40orpc%2Fratelimit?logo=npm" />
9
9
  </a>
10
+ <a href="https://app.codspeed.io/middleapi/orpc?utm_source=badge">
11
+ <img alt="CodSpeed" src="https://img.shields.io/endpoint?url=https://codspeed.io/badge.json" />
12
+ </a>
10
13
  <a href="https://github.com/middleapi/orpc/blob/main/LICENSE">
11
14
  <img alt="MIT License" src="https://img.shields.io/github/license/middleapi/orpc?logo=open-source-initiative" />
12
15
  </a>
@@ -26,61 +29,93 @@ You can read the documentation [here](https://orpc.dev).
26
29
 
27
30
  **Core**
28
31
 
29
- - [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Define type-safe contracts for your API.
30
- - [@orpc/server](https://www.npmjs.com/package/@orpc/server): Implement your API or contract on the server.
31
- - [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API from the client with full type safety.
32
- - [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Bring OpenAPI compatibility to your APIs.
32
+ | Package | Purpose | Docs |
33
+ | -------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
34
+ | [@orpc/contract](https://www.npmjs.com/package/@orpc/contract) | Define API contract as the single source of truth. | [Procedure Contract](https://orpc.dev/docs/contract/procedure), [Contract Router](https://orpc.dev/docs/contract/router), [Implementation](https://orpc.dev/docs/contract/implementation), [Generate from OpenAPI](https://orpc.dev/docs/contract/generate-from-openapi) |
35
+ | [@orpc/server](https://www.npmjs.com/package/@orpc/server) | Build APIs or implement contracts. | [Procedure](https://orpc.dev/docs/procedure), [Router](https://orpc.dev/docs/router), [Middleware](https://orpc.dev/docs/middleware), [Context](https://orpc.dev/docs/context), [RPC Handler](https://orpc.dev/docs/rpc/handler) |
36
+ | [@orpc/client](https://www.npmjs.com/package/@orpc/client) | Consume APIs with end-to-end type safety. | [Client-Side Clients](https://orpc.dev/docs/client/client-side), [Server-Side Clients](https://orpc.dev/docs/client/server-side), [RPC Link](https://orpc.dev/docs/rpc/link), [Error Handling](https://orpc.dev/docs/client/error-handling) |
37
+ | [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi) | Add OpenAPI compatibility to APIs. | [OpenAPI Handler](https://orpc.dev/docs/openapi/handler), [OpenAPI Link](https://orpc.dev/docs/openapi/link), [Specification](https://orpc.dev/docs/openapi/specification), [Scalar](https://orpc.dev/docs/openapi/scalar) |
33
38
 
34
39
  **Schema validation**
35
40
 
36
- - [@orpc/zod](https://www.npmjs.com/package/@orpc/zod): Integrate with [Zod](https://zod.dev/).
37
- - [@orpc/valibot](https://www.npmjs.com/package/@orpc/valibot): Integrate with [Valibot](https://valibot.dev/).
38
- - [@orpc/arktype](https://www.npmjs.com/package/@orpc/arktype): Integrate with [ArkType](https://arktype.io/).
41
+ | Package | Purpose | Docs |
42
+ | ------------------------------------------------------------ | ---------------------------------------------------------- | ----------------------------------------------------- |
43
+ | [@orpc/zod](https://www.npmjs.com/package/@orpc/zod) | JSON Schema converter for [Zod](https://zod.dev/). | [Zod](https://orpc.dev/docs/integrations/zod) |
44
+ | [@orpc/valibot](https://www.npmjs.com/package/@orpc/valibot) | JSON Schema converter for [Valibot](https://valibot.dev/). | [Valibot](https://orpc.dev/docs/integrations/valibot) |
45
+ | [@orpc/arktype](https://www.npmjs.com/package/@orpc/arktype) | JSON Schema converter for [ArkType](https://arktype.io/). | [ArkType](https://orpc.dev/docs/integrations/arktype) |
39
46
 
40
47
  **Built-in features**
41
48
 
42
- - [@orpc/publisher](https://www.npmjs.com/package/@orpc/publisher): Pub/Sub.
43
- - [@orpc/ratelimit](https://www.npmjs.com/package/@orpc/ratelimit): Rate limiting.
44
- - [@orpc/json-schema](https://www.npmjs.com/package/@orpc/json-schema): Smart coercion for OpenAPI requests.
49
+ | Package | Purpose | Docs |
50
+ | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
51
+ | [@orpc/publisher](https://www.npmjs.com/package/@orpc/publisher) | Pub/Sub with memory, Redis, and Upstash adapters. | [Publisher](https://orpc.dev/docs/helpers/publisher) |
52
+ | [@orpc/ratelimit](https://www.npmjs.com/package/@orpc/ratelimit) | Rate limiting with memory, Redis, and Upstash adapters. | [Rate Limit](https://orpc.dev/docs/helpers/ratelimit) |
53
+ | [@orpc/experimental-lock](https://www.npmjs.com/package/@orpc/experimental-lock) | Locking with memory, Redis, and Upstash adapters. | [Lock](https://orpc.dev/docs/helpers/lock) |
54
+ | [@orpc/hibernation](https://www.npmjs.com/package/@orpc/hibernation) | Leverage Hibernation APIs like [Cloudflare's Hibernation WebSocket](https://developers.cloudflare.com/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api). | [Hibernation](https://orpc.dev/docs/integrations/hibernation) |
55
+ | [@orpc/json-schema](https://www.npmjs.com/package/@orpc/json-schema) | Smart coercion for OpenAPI requests. | [Smart Coercion](https://orpc.dev/docs/plugins/smart-coercion) |
45
56
 
46
57
  **Framework & ecosystem integrations**
47
58
 
48
- - [@orpc/next](https://www.npmjs.com/package/@orpc/react): Use oRPC with [Next.js Server Functions](https://nextjs.org/docs/app/getting-started/mutating-data).
49
- - [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query): Integrate with [TanStack Query](https://tanstack.com/query/latest).
50
- - [@orpc/experimental-effect](https://www.npmjs.com/package/@orpc/experimental-effect): Integrate with [Effect](https://effect.website/).
51
- - [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Implement your contract with [NestJS](https://nestjs.com/).
59
+ | Package | Purpose | Docs |
60
+ | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
61
+ | [@orpc/next](https://www.npmjs.com/package/@orpc/next) | Integrate with [Next.js Server Functions](https://nextjs.org/docs/app/getting-started/mutating-data). | [Next.js](https://orpc.dev/docs/integrations/next) |
62
+ | [@orpc/ai-sdk](https://www.npmjs.com/package/@orpc/ai-sdk) | Turn contracts and procedures into [AI SDK](https://ai-sdk.dev/) tools. | [AI SDK](https://orpc.dev/docs/integrations/ai-sdk) |
63
+ | [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query) | Integrate with [TanStack Query](https://tanstack.com/query/latest). | [TanStack Query](https://orpc.dev/docs/integrations/tanstack-query) |
64
+ | [@orpc/pinia-colada](https://www.npmjs.com/package/@orpc/pinia-colada) | Integrate with [Pinia Colada](https://pinia-colada.esm.dev/). | [Pinia Colada](https://orpc.dev/docs/integrations/pinia-colada) |
65
+ | [@orpc/swr](https://www.npmjs.com/package/@orpc/swr) | Integrate with [SWR](https://swr.vercel.app/). | [SWR](https://orpc.dev/docs/integrations/swr) |
66
+ | [@orpc/experimental-msw](https://www.npmjs.com/package/@orpc/experimental-msw) | Mock procedures with [Mock Service Worker](https://mswjs.io/). | [MSW](https://orpc.dev/docs/integrations/msw) |
67
+ | [@orpc/experimental-effect](https://www.npmjs.com/package/@orpc/experimental-effect) | Integrate with [Effect](https://effect.website/). | [Effect](https://orpc.dev/docs/integrations/effect) |
68
+ | [@orpc/nest](https://www.npmjs.com/package/@orpc/nest) | Implement your contract with [NestJS](https://nestjs.com/). | [NestJS](https://orpc.dev/docs/integrations/nest) |
69
+ | [@orpc/node](https://www.npmjs.com/package/@orpc/node) | [Node.js](https://nodejs.org/) plugins for static file serving and large uploads. | [Static File](https://orpc.dev/docs/plugins/static-file), [Tmp File Upload](https://orpc.dev/docs/plugins/tmp-file-upload), [Batch Response Compression](https://orpc.dev/docs/plugins/batch-response-compression) |
70
+ | [@orpc/bun](https://www.npmjs.com/package/@orpc/bun) | Adapters for [Bun's Redis](https://bun.sh/). | [Publisher](https://orpc.dev/docs/helpers/publisher), [Rate Limit](https://orpc.dev/docs/helpers/ratelimit), [Lock](https://orpc.dev/docs/helpers/lock) |
71
+ | [@orpc/cloudflare](https://www.npmjs.com/package/@orpc/cloudflare) | Adapters for [Cloudflare Workers](https://developers.cloudflare.com/workers/). | [Publisher](https://orpc.dev/docs/helpers/publisher), [Rate Limit](https://orpc.dev/docs/helpers/ratelimit), [Lock](https://orpc.dev/docs/helpers/lock), [Traces](https://orpc.dev/docs/integrations/cloudflare-traces) |
72
+ | [@orpc/trpc](https://www.npmjs.com/package/@orpc/trpc) | Reuse existing [tRPC](https://trpc.io/) routers within oRPC. | [tRPC](https://orpc.dev/docs/integrations/trpc) |
52
73
 
53
74
  **Observability**
54
75
 
55
- - [@orpc/opentelemetry](https://www.npmjs.com/package/@orpc/opentelemetry): Instrument your API with [OpenTelemetry](https://opentelemetry.io/).
56
- - [@orpc/pino](https://www.npmjs.com/package/@orpc/pino): Log with [Pino](https://getpino.io/).
57
- - [@orpc/evlog](https://www.npmjs.com/package/@orpc/evlog): Log with [Evlog](https://evlog.dev/).
76
+ | Package | Purpose | Docs |
77
+ | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
78
+ | [@orpc/opentelemetry](https://www.npmjs.com/package/@orpc/opentelemetry) | Integrate with [OpenTelemetry](https://opentelemetry.io/) for distributed tracing. | [OpenTelemetry](https://orpc.dev/docs/integrations/opentelemetry) |
79
+ | [@orpc/pino](https://www.npmjs.com/package/@orpc/pino) | Integrate with [Pino](https://getpino.io/) for logging. | [Pino](https://orpc.dev/docs/integrations/pino) |
80
+ | [@orpc/evlog](https://www.npmjs.com/package/@orpc/evlog) | Integrate with [Evlog](https://evlog.dev/) for logging. | [Evlog](https://orpc.dev/docs/integrations/evlog) |
58
81
 
59
82
  ## Sponsors
60
83
 
61
- Like what we build over at [middleapi](https://github.com/middleapi)? You can help keep it going here: [GitHub Sponsors](https://github.com/sponsors/dinwwwh). Every bit helps! 🚀
84
+ Like what we build over at [middleapi](https://github.com/middleapi)? You can help keep it going through [GitHub Sponsors](https://github.com/sponsors/dinwwwh) or [Open Collective](https://opencollective.com/middleapi). Every bit helps! 🚀
85
+
86
+ <table>
87
+ <tr>
88
+ <td width="2000"><a href="https://screenshotone.com/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener" title="The screenshot API for developers"><img src="https://avatars.githubusercontent.com/u/97035603?v=4" width="64" align="left" hspace="12" alt="ScreenshotOne.com"/><b>ScreenshotOne.com</b></a><br /><sub>The screenshot API for developers</sub></td>
89
+ </tr>
90
+ <tr>
91
+ <td width="2000"><a href="https://yuzu.health/careers?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="We&#39;re hiring NYC based engineers"><img src="https://avatars.githubusercontent.com/u/102488956?v=4" width="64" align="left" hspace="12" alt="Yuzu"/><b>Yuzu</b></a><br /><sub>We&#39;re hiring NYC based engineers</sub></td>
92
+ </tr>
93
+ <tr>
94
+ <td width="2000"><a href="https://misskey.io/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Decentralized microblogging SNS born on Earth"><img src="https://github.com/MisskeyIO.png" width="64" align="left" hspace="12" alt="MisskeyHQ"/><b>MisskeyHQ</b></a><br /><sub>Decentralized microblogging SNS born on Earth</sub></td>
95
+ </tr>
96
+ </table>
62
97
 
63
- ### 🏆 Platinum Sponsor
98
+ ### Special Sponsors
64
99
 
65
100
  <table>
66
101
  <tr>
67
- <td align="center"><a href="https://screenshotone.com/?ref=orpc" target="_blank" rel="noopener" title="ScreenshotOne.com"><img src="https://avatars.githubusercontent.com/u/97035603?v=4" width="279" alt="ScreenshotOne.com"/><br />ScreenshotOne.com</a></td>
102
+ <td align="center"><a href="http://twitter.com/rauchg?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Guillermo Rauch"><img src="https://avatars.githubusercontent.com/u/13041?u=1ee8d111657cdd02ff6d253df00978d17ee6d722&amp;v=4" width="279" alt="Guillermo Rauch"/><br />Guillermo Rauch</a></td>
68
103
  </tr>
69
104
  </table>
70
105
 
71
- ### 🥈 Silver Sponsor
106
+ ### Premium Sponsors
72
107
 
73
108
  <table>
74
109
  <tr>
75
- <td align="center"><a href="https://misskey.io/?ref=orpc" target="_blank" rel="noopener" title="村上さん"><img src="https://avatars.githubusercontent.com/u/37681609?u=0dd4c7e4ba937cbb52b068c55914b1d8164dc0c7&amp;v=4" width="209" alt="村上さん"/><br />村上さん</a></td>
110
+ <td align="center"><a href="https://github.com/nexa-ca?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Nexa"><img src="https://avatars.githubusercontent.com/u/199146462?v=4" width="209" alt="Nexa"/><br />Nexa</a></td>
76
111
  </tr>
77
112
  </table>
78
113
 
79
- ### Generous Sponsors
114
+ ### Organization Sponsors
80
115
 
81
116
  <table>
82
117
  <tr>
83
- <td align="center"><a href="https://github.com/ln-markets?ref=orpc" target="_blank" rel="noopener" title="LN Markets"><img src="https://avatars.githubusercontent.com/u/70597625?v=4" width="167" alt="LN Markets"/><br />LN Markets</a></td>
118
+ <td align="center"><a href="https://lnmarkets.com/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="LN Markets"><img src="https://avatars.githubusercontent.com/u/70597625?v=4" width="167" alt="LN Markets"/><br />LN Markets</a></td>
84
119
  </tr>
85
120
  </table>
86
121
 
@@ -88,26 +123,28 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
88
123
 
89
124
  <table>
90
125
  <tr>
91
- <td align="center"><a href="https://github.com/hrmcdonald?ref=orpc" target="_blank" rel="noopener" title="Reece McDonald"><img src="https://avatars.githubusercontent.com/u/39349270?v=4" width="139" alt="Reece McDonald"/><br />Reece McDonald</a></td>
92
- <td align="center"><a href="https://github.com/nicognaW?ref=orpc" target="_blank" rel="noopener" title="nk"><img src="https://avatars.githubusercontent.com/u/66731869?u=4699bda3a9092d3ec34fbd959450767bcc8b8b6d&amp;v=4" width="139" alt="nk"/><br />nk</a></td>
93
- <td align="center"><a href="https://github.com/supastarter?ref=orpc" target="_blank" rel="noopener" title="supastarter"><img src="https://avatars.githubusercontent.com/u/110960143?v=4" width="139" alt="supastarter"/><br />supastarter</a></td>
94
- <td align="center"><a href="https://github.com/divmgl?ref=orpc" target="_blank" rel="noopener" title="Dexter Miguel"><img src="https://avatars.githubusercontent.com/u/5452298?u=645993204be8696c085ecf0d228c3062efe2ed65&amp;v=4" width="139" alt="Dexter Miguel"/><br />Dexter Miguel</a></td>
95
- <td align="center"><a href="https://github.com/herrfugbaum?ref=orpc" target="_blank" rel="noopener" title="herrfugbaum"><img src="https://avatars.githubusercontent.com/u/12859776?u=644dc1666d0220bc0468eb0de3c56b919f635b16&amp;v=4" width="139" alt="herrfugbaum"/><br />herrfugbaum</a></td>
96
- <td align="center"><a href="https://github.com/ryota-murakami?ref=orpc" target="_blank" rel="noopener" title="Ryota Murakami"><img src="https://avatars.githubusercontent.com/u/5501268?u=599389e03340734325726ca3f8f423c021d47d7f&amp;v=4" width="139" alt="Ryota Murakami"/><br />Ryota Murakami</a></td>
126
+ <td align="center"><a href="https://github.com/hrmcdonald?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Reece McDonald"><img src="https://avatars.githubusercontent.com/u/39349270?v=4" width="139" alt="Reece McDonald"/><br />Reece McDonald</a></td>
127
+ <td align="center"><a href="https://soymilk.party/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="あわわわとーにゅ"><img src="https://avatars.githubusercontent.com/u/17376330?u=de3353804be889f009f7e0a1582daf04d0ab292d&amp;v=4" width="139" alt="あわわわとーにゅ"/><br />あわわわとーにゅ</a></td>
128
+ <td align="center"><a href="https://github.com/nicognaW?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="nk"><img src="https://avatars.githubusercontent.com/u/66731869?u=4699bda3a9092d3ec34fbd959450767bcc8b8b6d&amp;v=4" width="139" alt="nk"/><br />nk</a></td>
129
+ <td align="center"><a href="https://supastarter.dev/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="supastarter"><img src="https://avatars.githubusercontent.com/u/110960143?v=4" width="139" alt="supastarter"/><br />supastarter</a></td>
130
+ <td align="center"><a href="https://github.com/divmgl?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Dexter Miguel"><img src="https://avatars.githubusercontent.com/u/5452298?u=645993204be8696c085ecf0d228c3062efe2ed65&amp;v=4" width="139" alt="Dexter Miguel"/><br />Dexter Miguel</a></td>
131
+ <td align="center"><a href="https://github.com/herrfugbaum?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="herrfugbaum"><img src="https://avatars.githubusercontent.com/u/12859776?u=644dc1666d0220bc0468eb0de3c56b919f635b16&amp;v=4" width="139" alt="herrfugbaum"/><br />herrfugbaum</a></td>
97
132
  </tr>
98
133
  <tr>
99
- <td align="center"><a href="https://github.com/dcramer?ref=orpc" target="_blank" rel="noopener" title="David Cramer"><img src="https://avatars.githubusercontent.com/u/23610?v=4" width="139" alt="David Cramer"/><br />David Cramer</a></td>
100
- <td align="center"><a href="https://github.com/valerii15298?ref=orpc" target="_blank" rel="noopener" title="Valerii Petryniak"><img src="https://avatars.githubusercontent.com/u/44531564?u=88ac74d9bacd20401518441907acad21063cd397&amp;v=4" width="139" alt="Valerii Petryniak"/><br />Valerii Petryniak</a></td>
101
- <td align="center"><a href="https://github.com/letstri?ref=orpc" target="_blank" rel="noopener" title="Valerii Strilets"><img src="https://avatars.githubusercontent.com/u/13253748?u=c7b10399ccc8f8081e24db94ec32cd9858e86ac3&amp;v=4" width="139" alt="Valerii Strilets"/><br />Valerii Strilets</a></td>
102
- <td align="center"><a href="https://github.com/K-Mistele?ref=orpc" target="_blank" rel="noopener" title="Kyle Mistele"><img src="https://avatars.githubusercontent.com/u/18430555?u=3afebeb81de666e35aaac3ed46f14159d7603ffb&amp;v=4" width="139" alt="Kyle Mistele"/><br />Kyle Mistele</a></td>
103
- <td align="center"><a href="https://github.com/andrewpeters9?ref=orpc" target="_blank" rel="noopener" title="Andrew Peters"><img src="https://avatars.githubusercontent.com/u/36251325?v=4" width="139" alt="Andrew Peters"/><br />Andrew Peters</a></td>
104
- <td align="center"><a href="https://github.com/R44VC0RP?ref=orpc" target="_blank" rel="noopener" title="Ryan Vogel"><img src="https://avatars.githubusercontent.com/u/89211796?u=1857347b9787d8d8a7ea5bfc333f96be92d5a683&amp;v=4" width="139" alt="Ryan Vogel"/><br />Ryan Vogel</a></td>
134
+ <td align="center"><a href="https://laststance.io/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Ryota Murakami"><img src="https://avatars.githubusercontent.com/u/5501268?u=599389e03340734325726ca3f8f423c021d47d7f&amp;v=4" width="139" alt="Ryota Murakami"/><br />Ryota Murakami</a></td>
135
+ <td align="center"><a href="https://cra.mr/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="David Cramer"><img src="https://avatars.githubusercontent.com/u/23610?v=4" width="139" alt="David Cramer"/><br />David Cramer</a></td>
136
+ <td align="center"><a href="https://valerii15298.github.io/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Valerii Petryniak"><img src="https://avatars.githubusercontent.com/u/44531564?u=88ac74d9bacd20401518441907acad21063cd397&amp;v=4" width="139" alt="Valerii Petryniak"/><br />Valerii Petryniak</a></td>
137
+ <td align="center"><a href="https://letstri.dev/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Valerii Strilets"><img src="https://avatars.githubusercontent.com/u/13253748?u=c7b10399ccc8f8081e24db94ec32cd9858e86ac3&amp;v=4" width="139" alt="Valerii Strilets"/><br />Valerii Strilets</a></td>
138
+ <td align="center"><a href="https://blacklight.sh/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Kyle Mistele"><img src="https://avatars.githubusercontent.com/u/18430555?u=3afebeb81de666e35aaac3ed46f14159d7603ffb&amp;v=4" width="139" alt="Kyle Mistele"/><br />Kyle Mistele</a></td>
139
+ <td align="center"><a href="https://github.com/christ12938?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="christ12938"><img src="https://avatars.githubusercontent.com/u/25758598?v=4" width="139" alt="christ12938"/><br />christ12938</a></td>
105
140
  </tr>
106
141
  <tr>
107
- <td align="center"><a href="https://github.com/christ12938?ref=orpc" target="_blank" rel="noopener" title="christ12938"><img src="https://avatars.githubusercontent.com/u/25758598?v=4" width="139" alt="christ12938"/><br />christ12938</a></td>
108
- <td align="center"><a href="https://github.com/peter-adam-dy?ref=orpc" target="_blank" rel="noopener" title="Peter Adam"><img src="https://avatars.githubusercontent.com/u/132129459?u=4f3dbbb3b443990b56acb7d6a5d11ed2c555f6db&amp;v=4" width="139" alt="Peter Adam"/><br />Peter Adam</a></td>
109
- <td align="center"><a href="https://github.com/Ryanjso?ref=orpc" target="_blank" rel="noopener" title="Ryan Soderberg"><img src="https://avatars.githubusercontent.com/u/39172778?u=5ed913c31d57e7221b75784abcad48c7ebddde27&amp;v=4" width="139" alt="Ryan Soderberg"/><br />Ryan Soderberg</a></td>
110
- <td align="center"><a href="https://github.com/itigoore01?ref=orpc" target="_blank" rel="noopener" title="shota"><img src="https://avatars.githubusercontent.com/u/11831107?u=c976a6dc7e055eb026304c46c99100ed22b0c8e0&amp;v=4" width="139" alt="shota"/><br />shota</a></td>
142
+ <td align="center"><a href="https://github.com/Ryanjso?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Ryan Soderberg"><img src="https://avatars.githubusercontent.com/u/39172778?u=5ed913c31d57e7221b75784abcad48c7ebddde27&amp;v=4" width="139" alt="Ryan Soderberg"/><br />Ryan Soderberg</a></td>
143
+ <td align="center"><a href="https://github.com/itigoore01?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="shota"><img src="https://avatars.githubusercontent.com/u/11831107?u=c976a6dc7e055eb026304c46c99100ed22b0c8e0&amp;v=4" width="139" alt="shota"/><br />shota</a></td>
144
+ <td align="center"><a href="https://github.com/ellis-driscoll?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Ellis Driscoll"><img src="https://avatars.githubusercontent.com/u/70685966?u=c5f95bc33b5991d9744abe00052542e4a2ed3cb9&amp;v=4" width="139" alt="Ellis Driscoll"/><br />Ellis Driscoll</a></td>
145
+ <td align="center"><a href="https://github.com/hoangbn?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Hoang Nguyen"><img src="https://avatars.githubusercontent.com/u/38968280?u=c90084c6de65c56facabab7ba13a72a49ddbc3e4&amp;v=4" width="139" alt="Hoang Nguyen"/><br />Hoang Nguyen</a></td>
146
+ <td align="center"><a href="https://opencollective.com/guest-ac41de3b?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Orestis Ioannou"><img src="https://images.opencollective.com/guest-ac41de3b/avatar/460.png" width="139" alt="Orestis Ioannou"/><br />Orestis Ioannou</a></td>
147
+ <td align="center"><a href="https://automatio.ai/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Stefan Smiljkovic"><img src="https://avatars.githubusercontent.com/u/1984909?u=b7bf5bc40ed49df3c22f69d2da7b8d78709c49ed&amp;v=4" width="139" alt="Stefan Smiljkovic"/><br />Stefan Smiljkovic</a></td>
111
148
  </tr>
112
149
  </table>
113
150
 
@@ -115,67 +152,33 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
115
152
 
116
153
  <table>
117
154
  <tr>
118
- <td align="center"><a href="https://github.com/rhinodavid?ref=orpc" target="_blank" rel="noopener" title="David Walsh"><img src="https://avatars.githubusercontent.com/u/5778036?u=b5521f07d2f88c3db2a0dae62b5f2f8357214af0&amp;v=4" width="119" alt="David Walsh"/><br />David Walsh</a></td>
119
- <td align="center"><a href="https://github.com/Robbe95?ref=orpc" target="_blank" rel="noopener" title="Robbe Vaes"><img src="https://avatars.githubusercontent.com/u/44748019?u=e0232402c045ad4eac7cbd217f1f47e083103b89&amp;v=4" width="119" alt="Robbe Vaes"/><br />Robbe Vaes</a></td>
120
- <td align="center"><a href="https://github.com/aidansunbury?ref=orpc" target="_blank" rel="noopener" title="Aidan Sunbury"><img src="https://avatars.githubusercontent.com/u/64103161?v=4" width="119" alt="Aidan Sunbury"/><br />Aidan Sunbury</a></td>
121
- <td align="center"><a href="https://github.com/soonoo?ref=orpc" target="_blank" rel="noopener" title="soonoo"><img src="https://avatars.githubusercontent.com/u/5436405?u=5d0b4aa955c87e30e6bda7f0cccae5402da99528&amp;v=4" width="119" alt="soonoo"/><br />soonoo</a></td>
122
- <td align="center"><a href="https://github.com/kporten?ref=orpc" target="_blank" rel="noopener" title="Kevin Porten"><img src="https://avatars.githubusercontent.com/u/1839345?u=dc2263d5cfe0d927ce1a0be04a1d55dd6b55405c&amp;v=4" width="119" alt="Kevin Porten"/><br />Kevin Porten</a></td>
123
- <td align="center"><a href="https://github.com/pumpkinlink?ref=orpc" target="_blank" rel="noopener" title="Denis"><img src="https://avatars.githubusercontent.com/u/11864620?u=5f47bbe6c65d0f6f5cf011021490238e4b0593d0&amp;v=4" width="119" alt="Denis"/><br />Denis</a></td>
124
- <td align="center"><a href="https://github.com/christopher-kapic?ref=orpc" target="_blank" rel="noopener" title="Christopher Kapic"><img src="https://avatars.githubusercontent.com/u/59740769?u=e7ad4b72b5bf6c9eb1644c26dbf3332a8f987377&amp;v=4" width="119" alt="Christopher Kapic"/><br />Christopher Kapic</a></td>
155
+ <td align="center"><a href="https://github.com/rhinodavid?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="David Walsh"><img src="https://avatars.githubusercontent.com/u/5778036?u=b5521f07d2f88c3db2a0dae62b5f2f8357214af0&amp;v=4" width="119" alt="David Walsh"/><br />David Walsh</a></td>
156
+ <td align="center"><a href="https://github.com/IPv4Addr?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="IPv4Addr"><img src="https://avatars.githubusercontent.com/u/100147665?u=59996b72f69bb53063cb7e9ff8b8f898616cd94d&amp;v=4" width="119" alt="IPv4Addr"/><br />IPv4Addr</a></td>
157
+ <td align="center"><a href="https://robbevaes.be/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Robbe Vaes"><img src="https://avatars.githubusercontent.com/u/44748019?u=e0232402c045ad4eac7cbd217f1f47e083103b89&amp;v=4" width="119" alt="Robbe Vaes"/><br />Robbe Vaes</a></td>
158
+ <td align="center"><a href="https://github.com/aidansunbury?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Aidan Sunbury"><img src="https://avatars.githubusercontent.com/u/64103161?v=4" width="119" alt="Aidan Sunbury"/><br />Aidan Sunbury</a></td>
159
+ <td align="center"><a href="https://github.com/soonoo?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="soonoo"><img src="https://avatars.githubusercontent.com/u/5436405?u=5d0b4aa955c87e30e6bda7f0cccae5402da99528&amp;v=4" width="119" alt="soonoo"/><br />soonoo</a></td>
160
+ <td align="center"><a href="https://kevinporten.dev/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Kevin Porten"><img src="https://avatars.githubusercontent.com/u/1839345?u=dc2263d5cfe0d927ce1a0be04a1d55dd6b55405c&amp;v=4" width="119" alt="Kevin Porten"/><br />Kevin Porten</a></td>
161
+ <td align="center"><a href="https://github.com/pumpkinlink?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Denis"><img src="https://avatars.githubusercontent.com/u/11864620?u=5f47bbe6c65d0f6f5cf011021490238e4b0593d0&amp;v=4" width="119" alt="Denis"/><br />Denis</a></td>
125
162
  </tr>
126
163
  <tr>
127
- <td align="center"><a href="https://github.com/thomasballinger?ref=orpc" target="_blank" rel="noopener" title="Tom Ballinger"><img src="https://avatars.githubusercontent.com/u/458879?u=4b045ac75d721b6ac2b42a74d7d37f61f0414031&amp;v=4" width="119" alt="Tom Ballinger"/><br />Tom Ballinger</a></td>
128
- <td align="center"><a href="https://github.com/SSam0419?ref=orpc" target="_blank" rel="noopener" title="Sam"><img src="https://avatars.githubusercontent.com/u/102863520?u=3c89611f549d5070be232eb4532f690c8f2e7a65&amp;v=4" width="119" alt="Sam"/><br />Sam</a></td>
129
- <td align="center"><a href="https://github.com/Titoine?ref=orpc" target="_blank" rel="noopener" title="Titoine"><img src="https://avatars.githubusercontent.com/u/3514286?u=1bb1e86b0c99c8a1121372e56d51a177eea12191&amp;v=4" width="119" alt="Titoine"/><br />Titoine</a></td>
130
- <td align="center"><a href="https://github.com/Mnigos?ref=orpc" target="_blank" rel="noopener" title="Igor Makowski"><img src="https://avatars.githubusercontent.com/u/56691628?u=ee8c879478f7c151b9156aef6c74243fa3e247a8&amp;v=4" width="119" alt="Igor Makowski"/><br />Igor Makowski</a></td>
131
- <td align="center"><a href="https://github.com/hanayashiki?ref=orpc" target="_blank" rel="noopener" title="hanayashiki"><img src="https://avatars.githubusercontent.com/u/26056783?u=06c3b9205a16fd41a871e82da1cc2a09306d53f5&amp;v=4" width="119" alt="hanayashiki"/><br />hanayashiki</a></td>
132
- <td align="center"><a href="https://github.com/ldub?ref=orpc" target="_blank" rel="noopener" title="Lev Dubinets"><img src="https://avatars.githubusercontent.com/u/3114081?u=f547f5d5012cab54851f1b1ad72d10e537f78fc2&amp;v=4" width="119" alt="Lev Dubinets"/><br />Lev Dubinets</a></td>
133
- <td align="center"><a href="https://github.com/mr-kelly?ref=orpc" target="_blank" rel="noopener" title="Kelly Peilin Chan"><img src="https://avatars.githubusercontent.com/u/520852?u=6b0f7105f694e7b5cacf410a3f04c7044b469dc8&amp;v=4" width="119" alt="Kelly Peilin Chan"/><br />Kelly Peilin Chan</a></td>
164
+ <td align="center"><a href="https://github.com/christopher-kapic?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Christopher Kapic"><img src="https://avatars.githubusercontent.com/u/59740769?u=e7ad4b72b5bf6c9eb1644c26dbf3332a8f987377&amp;v=4" width="119" alt="Christopher Kapic"/><br />Christopher Kapic</a></td>
165
+ <td align="center"><a href="http://ballingt.com/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Tom Ballinger"><img src="https://avatars.githubusercontent.com/u/458879?u=4b045ac75d721b6ac2b42a74d7d37f61f0414031&amp;v=4" width="119" alt="Tom Ballinger"/><br />Tom Ballinger</a></td>
166
+ <td align="center"><a href="https://lee-sam.com/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Sam"><img src="https://avatars.githubusercontent.com/u/102863520?u=3c89611f549d5070be232eb4532f690c8f2e7a65&amp;v=4" width="119" alt="Sam"/><br />Sam</a></td>
167
+ <td align="center"><a href="https://github.com/Titoine?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Titoine"><img src="https://avatars.githubusercontent.com/u/3514286?u=1bb1e86b0c99c8a1121372e56d51a177eea12191&amp;v=4" width="119" alt="Titoine"/><br />Titoine</a></td>
168
+ <td align="center"><a href="https://rigtch.fm/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Igor Makowski"><img src="https://avatars.githubusercontent.com/u/56691628?u=ee8c879478f7c151b9156aef6c74243fa3e247a8&amp;v=4" width="119" alt="Igor Makowski"/><br />Igor Makowski</a></td>
169
+ <td align="center"><a href="https://blog.cwang.io/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="hanayashiki"><img src="https://avatars.githubusercontent.com/u/26056783?u=06c3b9205a16fd41a871e82da1cc2a09306d53f5&amp;v=4" width="119" alt="hanayashiki"/><br />hanayashiki</a></td>
170
+ <td align="center"><a href="https://dubinets.io/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Lev Dubinets"><img src="https://avatars.githubusercontent.com/u/3114081?u=f547f5d5012cab54851f1b1ad72d10e537f78fc2&amp;v=4" width="119" alt="Lev Dubinets"/><br />Lev Dubinets</a></td>
134
171
  </tr>
135
172
  <tr>
136
- <td align="center"><a href="https://github.com/piscis?ref=orpc" target="_blank" rel="noopener" title="Alex"><img src="https://avatars.githubusercontent.com/u/326163?u=b245f368bd940cf51d08c0b6bf55f8257f359437&amp;v=4" width="119" alt="Alex"/><br />Alex</a></td>
137
- <td align="center"><a href="https://github.com/nattstack?ref=orpc" target="_blank" rel="noopener" title="nattstack"><img src="https://avatars.githubusercontent.com/u/31426677?u=fa9dbb8b3e66eb0ea3c88db5dc07f31c8c5418fe&amp;v=4" width="119" alt="nattstack"/><br />nattstack</a></td>
138
- <td align="center"><a href="https://github.com/finom?ref=orpc" target="_blank" rel="noopener" title="Andrey Gubanov"><img src="https://avatars.githubusercontent.com/u/1082083?u=29e91400dbd4a9c217048a8f59562c4f740498e6&amp;v=4" width="119" alt="Andrey Gubanov"/><br />Andrey Gubanov</a></td>
173
+ <td align="center"><a href="https://kellychan.im/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Kelly Peilin Chan"><img src="https://avatars.githubusercontent.com/u/520852?u=6b0f7105f694e7b5cacf410a3f04c7044b469dc8&amp;v=4" width="119" alt="Kelly Peilin Chan"/><br />Kelly Peilin Chan</a></td>
174
+ <td align="center"><a href="https://guyariely.com/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Guy Ariely"><img src="https://avatars.githubusercontent.com/u/42813496?u=edb6b7f563bf28e160a290832e7da57c0506f8ca&amp;v=4" width="119" alt="Guy Ariely"/><br />Guy Ariely</a></td>
175
+ <td align="center"><a href="https://paulsenon.com/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="PaulSenon"><img src="https://avatars.githubusercontent.com/u/19531087?u=6385741eb91d200b8c513ec6045482e301767ca6&amp;v=4" width="119" alt="PaulSenon"/><br />PaulSenon</a></td>
176
+ <td align="center"><a href="https://piscis.dev/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Alex"><img src="https://avatars.githubusercontent.com/u/326163?u=b245f368bd940cf51d08c0b6bf55f8257f359437&amp;v=4" width="119" alt="Alex"/><br />Alex</a></td>
177
+ <td align="center"><a href="https://opensource.gubanov.eu/?ref=middleapi&amp;utm_source=middleapi&amp;utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Andrey Gubanov"><img src="https://avatars.githubusercontent.com/u/1082083?u=c5f2daf7ebece498e85c83367bb37b4e10e2649d&amp;v=4" width="119" alt="Andrey Gubanov"/><br />Andrey Gubanov</a></td>
139
178
  </tr>
140
179
  </table>
141
180
 
142
- ### Past Sponsors
143
-
144
- <p>
145
- <a href="https://github.com/MrMaxie?ref=orpc" target="_blank" rel="noopener" title="Maxie"><img src="https://avatars.githubusercontent.com/u/3857836?u=5e6b57973d4385d655663ffdd836e487856f2984&amp;v=4" width="32" height="32" alt="Maxie" /></a>
146
- <a href="https://github.com/Stijn-Timmer?ref=orpc" target="_blank" rel="noopener" title="Stijn Timmer"><img src="https://avatars.githubusercontent.com/u/100147665?u=106b2c18e9c98a61861b4ee7fc100f5b9906a6c9&amp;v=4" width="32" height="32" alt="Stijn Timmer" /></a>
147
- <a href="https://github.com/u1-liquid?ref=orpc" target="_blank" rel="noopener" title="あわわわとーにゅ"><img src="https://avatars.githubusercontent.com/u/17376330?u=de3353804be889f009f7e0a1582daf04d0ab292d&amp;v=4" width="32" height="32" alt="あわわわとーにゅ" /></a>
148
- <a href="https://github.com/zuplo?ref=orpc" target="_blank" rel="noopener" title="Zuplo"><img src="https://avatars.githubusercontent.com/u/85497839?v=4" width="32" height="32" alt="Zuplo" /></a>
149
- <a href="https://github.com/motopods?ref=orpc" target="_blank" rel="noopener" title="motopods"><img src="https://avatars.githubusercontent.com/u/58200641?v=4" width="32" height="32" alt="motopods" /></a>
150
- <a href="https://github.com/franciscohermida?ref=orpc" target="_blank" rel="noopener" title="Francisco Hermida"><img src="https://avatars.githubusercontent.com/u/483242?u=bbcbc80eb9d8781ff401f7dafc3b59cd7bea0561&amp;v=4" width="32" height="32" alt="Francisco Hermida" /></a>
151
- <a href="https://github.com/theoludwig?ref=orpc" target="_blank" rel="noopener" title="Théo LUDWIG"><img src="https://avatars.githubusercontent.com/u/25207499?u=a6a9653725a2f574c07893748806668e0598cdbe&amp;v=4" width="32" height="32" alt="Théo LUDWIG" /></a>
152
- <a href="https://github.com/abhay-ramesh?ref=orpc" target="_blank" rel="noopener" title="Abhay Ramesh"><img src="https://avatars.githubusercontent.com/u/66196314?u=c5c2b0327b26606c2efcfaf17046ab18c3d25c57&amp;v=4" width="32" height="32" alt="Abhay Ramesh" /></a>
153
- <a href="https://github.com/shr-ink?ref=orpc" target="_blank" rel="noopener" title="shr.ink oü"><img src="https://avatars.githubusercontent.com/u/139700438?v=4" width="32" height="32" alt="shr.ink oü" /></a>
154
- <a href="https://github.com/johngerome?ref=orpc" target="_blank" rel="noopener" title="0x4e32"><img src="https://avatars.githubusercontent.com/u/2002000?u=505e54608466ab53754f702973687b04c6424c1f&amp;v=4" width="32" height="32" alt="0x4e32" /></a>
155
- <a href="https://github.com/yzuyr?ref=orpc" target="_blank" rel="noopener" title="Ryuz"><img src="https://avatars.githubusercontent.com/u/196539378?u=d38374588d219b6748b16406982f6559411466d4&amp;v=4" width="32" height="32" alt="Ryuz" /></a>
156
- <a href="https://github.com/happyboy2022?ref=orpc" target="_blank" rel="noopener" title="happyboy"><img src="https://avatars.githubusercontent.com/u/103669586?u=65b49c4b893ed3703909fbb3a7a22313f3f9c121&amp;v=4" width="32" height="32" alt="happyboy" /></a>
157
- <a href="https://github.com/YiCChi?ref=orpc" target="_blank" rel="noopener" title="yicchi"><img src="https://avatars.githubusercontent.com/u/86967274?u=6c2756f09fe15dd94d572f560e979cd157982852&amp;v=4" width="32" height="32" alt="yicchi" /></a>
158
- <a href="https://github.com/cloudycotton?ref=orpc" target="_blank" rel="noopener" title="Saksham"><img src="https://avatars.githubusercontent.com/u/168998965?u=9b9634a5aed66a51c1b880663272725b00b92b14&amp;v=4" width="32" height="32" alt="Saksham" /></a>
159
- <a href="https://github.com/hrynevychroman?ref=orpc" target="_blank" rel="noopener" title="Roman Hrynevych"><img src="https://avatars.githubusercontent.com/u/82209198?u=1a1d111ab3d589855b9cc8a7fefb1b5c6a4fbbaf&amp;v=4" width="32" height="32" alt="Roman Hrynevych" /></a>
160
- <a href="https://github.com/rokitgg?ref=orpc" target="_blank" rel="noopener" title="rokitg"><img src="https://avatars.githubusercontent.com/u/125133357?u=06c74aefaa2236b06a2e5fba5a5c612339f45912&amp;v=4" width="32" height="32" alt="rokitg" /></a>
161
- <a href="https://github.com/omarkhatibgg?ref=orpc" target="_blank" rel="noopener" title="Omar Khatib"><img src="https://avatars.githubusercontent.com/u/9054278?u=afbba7331b85c51b8eee4130f5fd31b1017dc919&amp;v=4" width="32" height="32" alt="Omar Khatib" /></a>
162
- <a href="https://github.com/YuSabo90002?ref=orpc" target="_blank" rel="noopener" title="Yu-Sabo"><img src="https://avatars.githubusercontent.com/u/13120582?v=4" width="32" height="32" alt="Yu-Sabo" /></a>
163
- <a href="https://github.com/bapspatil?ref=orpc" target="_blank" rel="noopener" title="Bapusaheb Patil"><img src="https://avatars.githubusercontent.com/u/16699418?u=6d9d8e0a64a6f91ca1c4d559c72d931172bdcbbd&amp;v=4" width="32" height="32" alt="Bapusaheb Patil" /></a>
164
- <a href="https://github.com/ripgrim?ref=orpc" target="_blank" rel="noopener" title="grim"><img src="https://avatars.githubusercontent.com/u/75869731?u=b17c42ec2309552fdb822a86b25a2f99146a4d72&amp;v=4" width="32" height="32" alt="grim" /></a>
165
- <a href="https://github.com/nelsonlaidev?ref=orpc" target="_blank" rel="noopener" title="Nelson Lai"><img src="https://avatars.githubusercontent.com/u/75498339?u=2fc0e0b95dd184c5ffb744df977cb15a18b60672&amp;v=4" width="32" height="32" alt="Nelson Lai" /></a>
166
- <a href="https://github.com/nguyenlc1993?ref=orpc" target="_blank" rel="noopener" title="Lê Cao Nguyên"><img src="https://avatars.githubusercontent.com/u/13871971?u=83c8b69d9e35b589c4e1f066cc113b1d9461386f&amp;v=4" width="32" height="32" alt="Lê Cao Nguyên" /></a>
167
- <a href="https://github.com/wobsoriano?ref=orpc" target="_blank" rel="noopener" title="Robert Soriano"><img src="https://avatars.githubusercontent.com/u/13049130?u=6d72104182e7c9ed25934815313fb69107332111&amp;v=4" width="32" height="32" alt="Robert Soriano" /></a>
168
- <a href="https://github.com/SKostyukovich?ref=orpc" target="_blank" rel="noopener" title="SKostyukovich"><img src="https://avatars.githubusercontent.com/u/10700067?v=4" width="32" height="32" alt="SKostyukovich" /></a>
169
- <a href="https://github.com/FabworksHQ?ref=orpc" target="_blank" rel="noopener" title="Fabworks"><img src="https://avatars.githubusercontent.com/u/160179500?v=4" width="32" height="32" alt="Fabworks" /></a>
170
- <a href="https://github.com/NovakAnton?ref=orpc" target="_blank" rel="noopener" title="Novak Antonijevic"><img src="https://avatars.githubusercontent.com/u/157126729?u=ae49fa22292d55c0434ff0ca008206155b18663b&amp;v=4" width="32" height="32" alt="Novak Antonijevic" /></a>
171
- <a href="https://github.com/laduniestu?ref=orpc" target="_blank" rel="noopener" title="Laduni Estu Syalwa"><img src="https://avatars.githubusercontent.com/u/44757637?u=a2fc1ea8f7d827a96721176f79d30592d1c48059&amp;v=4" width="32" height="32" alt="Laduni Estu Syalwa" /></a>
172
- <a href="https://github.com/yukimotochern?ref=orpc" target="_blank" rel="noopener" title="Chen, Zhi-Yuan"><img src="https://avatars.githubusercontent.com/u/20896173?u=945c33fc21725e4d566a0d02afc54b136ca1d67a&amp;v=4" width="32" height="32" alt="Chen, Zhi-Yuan" /></a>
173
- <a href="https://github.com/illarionvk?ref=orpc" target="_blank" rel="noopener" title="Illarion Koperski"><img src="https://avatars.githubusercontent.com/u/5012724?u=7cfa13652f7ac5fb3c56d880e3eb3fbe40c3ea34&amp;v=4" width="32" height="32" alt="Illarion Koperski" /></a>
174
- <a href="https://github.com/steelbrain?ref=orpc" target="_blank" rel="noopener" title="Anees Iqbal"><img src="https://avatars.githubusercontent.com/u/4278113?u=22b80b5399eed68ac76cd58b02961b0481f1db11&amp;v=4" width="32" height="32" alt="Anees Iqbal" /></a>
175
- <a href="https://github.com/Scrumplex?ref=orpc" target="_blank" rel="noopener" title="Sefa Eyeoglu"><img src="https://avatars.githubusercontent.com/u/11587657?u=ab503582165c0bbff0cca47ce31c9450bb1553c9&amp;v=4" width="32" height="32" alt="Sefa Eyeoglu" /></a>
176
- <a href="https://github.com/ChromeGG?ref=orpc" target="_blank" rel="noopener" title="Adam Tkaczyk"><img src="https://avatars.githubusercontent.com/u/39050595?u=a58ca6042a6950e94e6e92442db76ef584279bc0&amp;v=4" width="32" height="32" alt="Adam Tkaczyk" /></a>
177
- <a href="https://github.com/plancraft?ref=orpc" target="_blank" rel="noopener" title="plancraft"><img src="https://avatars.githubusercontent.com/u/46482287?v=4" width="32" height="32" alt="plancraft" /></a>
178
- </p>
181
+ With thanks to [36 past sponsors](https://htmlpreview.github.io/?https://github.com/middleapi/static/blob/main/sponsors.svg) who helped get oRPC here.
179
182
 
180
183
  ## References
181
184
 
@@ -0,0 +1,67 @@
1
+ import { RateLimiter, RateLimitOptions, RateLimitResult } from '../index.mjs';
2
+
3
+ /**
4
+ * Options shared by every Redis-backed rate limiter adapter.
5
+ *
6
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#adapters | Rate Limit Helpers - Adapters}
7
+ */
8
+ interface BaseRedisRateLimiterOptions {
9
+ /**
10
+ * The prefix to use for Redis keys.
11
+ *
12
+ * @default ''
13
+ */
14
+ prefix?: string;
15
+ /**
16
+ * Maximum number of requests allowed within the window.
17
+ */
18
+ maxRequests: number;
19
+ /**
20
+ * The duration of the fixed window in milliseconds.
21
+ */
22
+ window: number;
23
+ /**
24
+ * Block until the request may pass or timeout is reached.
25
+ *
26
+ * @default { enabled: false }
27
+ */
28
+ blockingUntilReady?: {
29
+ /**
30
+ * Block until the request may pass or timeout is reached.
31
+ *
32
+ * @default false
33
+ */
34
+ enabled: boolean;
35
+ /**
36
+ * milliseconds
37
+ */
38
+ timeout: number;
39
+ };
40
+ }
41
+ /**
42
+ * Base class for Redis-backed rate limiter adapters. It owns the key naming,
43
+ * the fixed-window script, and the blocking logic, so every adapter built on
44
+ * it shares counters with the others regardless of the Redis client in use.
45
+ *
46
+ * Extend it and implement `evalScript` to support another Redis client.
47
+ *
48
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#adapters | Rate Limit Helpers - Adapters}
49
+ */
50
+ declare abstract class BaseRedisRateLimiter implements RateLimiter {
51
+ protected readonly prefix: string;
52
+ protected readonly maxRequests: number;
53
+ protected readonly window: number;
54
+ protected readonly blockingUntilReady: BaseRedisRateLimiterOptions['blockingUntilReady'];
55
+ constructor(options: BaseRedisRateLimiterOptions);
56
+ /**
57
+ * Runs a Lua script (`EVAL script numkeys key [key ...] arg [arg ...]`) and resolves with its reply.
58
+ */
59
+ protected abstract evalScript(script: string, keys: string[], args: string[]): Promise<unknown>;
60
+ limit(key: string, options?: RateLimitOptions): Promise<Required<RateLimitResult>>;
61
+ private checkLimit;
62
+ private blockUntilReady;
63
+ private resolveWeight;
64
+ }
65
+
66
+ export { BaseRedisRateLimiter };
67
+ export type { BaseRedisRateLimiterOptions };
@@ -0,0 +1,67 @@
1
+ import { RateLimiter, RateLimitOptions, RateLimitResult } from '../index.js';
2
+
3
+ /**
4
+ * Options shared by every Redis-backed rate limiter adapter.
5
+ *
6
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#adapters | Rate Limit Helpers - Adapters}
7
+ */
8
+ interface BaseRedisRateLimiterOptions {
9
+ /**
10
+ * The prefix to use for Redis keys.
11
+ *
12
+ * @default ''
13
+ */
14
+ prefix?: string;
15
+ /**
16
+ * Maximum number of requests allowed within the window.
17
+ */
18
+ maxRequests: number;
19
+ /**
20
+ * The duration of the fixed window in milliseconds.
21
+ */
22
+ window: number;
23
+ /**
24
+ * Block until the request may pass or timeout is reached.
25
+ *
26
+ * @default { enabled: false }
27
+ */
28
+ blockingUntilReady?: {
29
+ /**
30
+ * Block until the request may pass or timeout is reached.
31
+ *
32
+ * @default false
33
+ */
34
+ enabled: boolean;
35
+ /**
36
+ * milliseconds
37
+ */
38
+ timeout: number;
39
+ };
40
+ }
41
+ /**
42
+ * Base class for Redis-backed rate limiter adapters. It owns the key naming,
43
+ * the fixed-window script, and the blocking logic, so every adapter built on
44
+ * it shares counters with the others regardless of the Redis client in use.
45
+ *
46
+ * Extend it and implement `evalScript` to support another Redis client.
47
+ *
48
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#adapters | Rate Limit Helpers - Adapters}
49
+ */
50
+ declare abstract class BaseRedisRateLimiter implements RateLimiter {
51
+ protected readonly prefix: string;
52
+ protected readonly maxRequests: number;
53
+ protected readonly window: number;
54
+ protected readonly blockingUntilReady: BaseRedisRateLimiterOptions['blockingUntilReady'];
55
+ constructor(options: BaseRedisRateLimiterOptions);
56
+ /**
57
+ * Runs a Lua script (`EVAL script numkeys key [key ...] arg [arg ...]`) and resolves with its reply.
58
+ */
59
+ protected abstract evalScript(script: string, keys: string[], args: string[]): Promise<unknown>;
60
+ limit(key: string, options?: RateLimitOptions): Promise<Required<RateLimitResult>>;
61
+ private checkLimit;
62
+ private blockUntilReady;
63
+ private resolveWeight;
64
+ }
65
+
66
+ export { BaseRedisRateLimiter };
67
+ export type { BaseRedisRateLimiterOptions };
@@ -0,0 +1,52 @@
1
+ import { sleep } from '@orpc/shared';
2
+
3
+ const FIXED_WINDOW_SCRIPT = `local c=redis.call('INCRBY',KEYS[1],ARGV[1]) if c==tonumber(ARGV[1]) then redis.call('PEXPIRE',KEYS[1],ARGV[2]) end return {c,redis.call('PTTL',KEYS[1])}`;
4
+ class BaseRedisRateLimiter {
5
+ prefix;
6
+ maxRequests;
7
+ window;
8
+ blockingUntilReady;
9
+ constructor(options) {
10
+ this.prefix = options.prefix ?? "";
11
+ this.maxRequests = options.maxRequests;
12
+ this.window = options.window;
13
+ this.blockingUntilReady = options.blockingUntilReady;
14
+ }
15
+ async limit(key, options) {
16
+ key = `${this.prefix}${key}`;
17
+ const weight = this.resolveWeight(options);
18
+ return this.blockingUntilReady?.enabled ? this.blockUntilReady(key, this.blockingUntilReady.timeout, weight) : this.checkLimit(key, weight);
19
+ }
20
+ async checkLimit(key, weight) {
21
+ const [used, ttl] = await this.evalScript(
22
+ FIXED_WINDOW_SCRIPT,
23
+ [key],
24
+ [String(weight), String(this.window)]
25
+ );
26
+ return {
27
+ success: used <= this.maxRequests,
28
+ limit: this.maxRequests,
29
+ remaining: Math.max(0, this.maxRequests - used),
30
+ reset: Date.now() + ttl
31
+ };
32
+ }
33
+ async blockUntilReady(key, timeoutMs, weight) {
34
+ const deadlineAtMs = Date.now() + timeoutMs;
35
+ while (true) {
36
+ const result = await this.checkLimit(key, weight);
37
+ if (result.success || result.reset > deadlineAtMs) {
38
+ return result;
39
+ }
40
+ await sleep(result.reset - Date.now());
41
+ }
42
+ }
43
+ resolveWeight(options) {
44
+ const weight = options?.weight ?? 1;
45
+ if (!Number.isInteger(weight) || weight <= 0) {
46
+ throw new TypeError("Rate limit weight must be an integer greater than 0");
47
+ }
48
+ return weight;
49
+ }
50
+ }
51
+
52
+ export { BaseRedisRateLimiter };
@@ -1,14 +1,6 @@
1
- import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.mjs';
1
+ import { RateLimiter, RateLimitOptions, RateLimitResult } from '../index.mjs';
2
2
 
3
3
  interface MemoryRateLimiterOptions {
4
- /**
5
- * Enable this if you don't want to reject a request immediately
6
- * and prefer to wait until it can be retried.
7
- */
8
- blockingUntilReady?: {
9
- enabled: boolean;
10
- timeout: number;
11
- };
12
4
  /**
13
5
  * Maximum number of requests allowed within the window.
14
6
  */
@@ -17,7 +9,30 @@ interface MemoryRateLimiterOptions {
17
9
  * The duration of the fixed window in milliseconds.
18
10
  */
19
11
  window: number;
12
+ /**
13
+ * Block until the request may pass or timeout is reached.
14
+ *
15
+ * @default { enabled: false }
16
+ */
17
+ blockingUntilReady?: {
18
+ /**
19
+ * Block until the request may pass or timeout is reached.
20
+ *
21
+ * @default false
22
+ */
23
+ enabled: boolean;
24
+ /**
25
+ * milliseconds
26
+ */
27
+ timeout: number;
28
+ };
20
29
  }
30
+ /**
31
+ * Rate limiter adapter backed by in-memory storage. Enforces a fixed-window
32
+ * limit, with optional blocking mode.
33
+ *
34
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#adapters | Rate Limit Helpers - Adapters}
35
+ */
21
36
  declare class MemoryRateLimiter implements RateLimiter {
22
37
  private readonly maxRequests;
23
38
  private readonly window;
@@ -1,14 +1,6 @@
1
- import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.js';
1
+ import { RateLimiter, RateLimitOptions, RateLimitResult } from '../index.js';
2
2
 
3
3
  interface MemoryRateLimiterOptions {
4
- /**
5
- * Enable this if you don't want to reject a request immediately
6
- * and prefer to wait until it can be retried.
7
- */
8
- blockingUntilReady?: {
9
- enabled: boolean;
10
- timeout: number;
11
- };
12
4
  /**
13
5
  * Maximum number of requests allowed within the window.
14
6
  */
@@ -17,7 +9,30 @@ interface MemoryRateLimiterOptions {
17
9
  * The duration of the fixed window in milliseconds.
18
10
  */
19
11
  window: number;
12
+ /**
13
+ * Block until the request may pass or timeout is reached.
14
+ *
15
+ * @default { enabled: false }
16
+ */
17
+ blockingUntilReady?: {
18
+ /**
19
+ * Block until the request may pass or timeout is reached.
20
+ *
21
+ * @default false
22
+ */
23
+ enabled: boolean;
24
+ /**
25
+ * milliseconds
26
+ */
27
+ timeout: number;
28
+ };
20
29
  }
30
+ /**
31
+ * Rate limiter adapter backed by in-memory storage. Enforces a fixed-window
32
+ * limit, with optional blocking mode.
33
+ *
34
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#adapters | Rate Limit Helpers - Adapters}
35
+ */
21
36
  declare class MemoryRateLimiter implements RateLimiter {
22
37
  private readonly maxRequests;
23
38
  private readonly window;
@@ -1,41 +1,19 @@
1
- import { RedisClientType } from 'redis';
2
- import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.mjs';
1
+ import { RedisClientType, RedisClusterType } from 'redis';
2
+ import { BaseRedisRateLimiter, BaseRedisRateLimiterOptions } from './base-redis.mjs';
3
+ import '../shared/ratelimit.DwqoOfMm.mjs';
3
4
 
4
- interface RedisRateLimiterOptions {
5
- /**
6
- * The prefix to use for Redis keys.
7
- */
8
- prefix?: string;
9
- /**
10
- * Maximum number of requests allowed within the window.
11
- */
12
- maxRequests: number;
13
- /**
14
- * The duration of the fixed window in milliseconds.
15
- */
16
- window: number;
17
- /**
18
- * Block until the request may pass or timeout is reached.
19
- */
20
- blockingUntilReady?: {
21
- enabled: boolean;
22
- timeout: number;
23
- };
5
+ interface RedisRateLimiterOptions extends BaseRedisRateLimiterOptions {
24
6
  }
25
- declare class RedisRateLimiter implements RateLimiter {
7
+ /**
8
+ * Rate limiter adapter for Redis. Enforces a fixed-window limit using a
9
+ * Redis Lua script, with optional blocking mode.
10
+ *
11
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#adapters | Rate Limit Helpers - Adapters}
12
+ */
13
+ declare class RedisRateLimiter extends BaseRedisRateLimiter {
26
14
  private readonly redis;
27
- private readonly prefix;
28
- private readonly maxRequests;
29
- private readonly window;
30
- private readonly blockingUntilReady;
31
- private scriptSha;
32
- constructor(redis: RedisClientType<any, any, any, any, any>, options: RedisRateLimiterOptions);
33
- limit(key: string, options?: RateLimitOptions): Promise<Required<RateLimitResult>>;
34
- private checkLimit;
35
- private blockUntilReady;
36
- private executeScript;
37
- private evalSha;
38
- private resolveWeight;
15
+ constructor(redis: RedisClientType<any, any, any, any, any> | RedisClusterType<any, any, any, any, any>, options: RedisRateLimiterOptions);
16
+ protected evalScript(script: string, keys: string[], args: string[]): Promise<unknown>;
39
17
  }
40
18
 
41
19
  export { RedisRateLimiter };
@@ -1,41 +1,19 @@
1
- import { RedisClientType } from 'redis';
2
- import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.js';
1
+ import { RedisClientType, RedisClusterType } from 'redis';
2
+ import { BaseRedisRateLimiter, BaseRedisRateLimiterOptions } from './base-redis.js';
3
+ import '../shared/ratelimit.DwqoOfMm.js';
3
4
 
4
- interface RedisRateLimiterOptions {
5
- /**
6
- * The prefix to use for Redis keys.
7
- */
8
- prefix?: string;
9
- /**
10
- * Maximum number of requests allowed within the window.
11
- */
12
- maxRequests: number;
13
- /**
14
- * The duration of the fixed window in milliseconds.
15
- */
16
- window: number;
17
- /**
18
- * Block until the request may pass or timeout is reached.
19
- */
20
- blockingUntilReady?: {
21
- enabled: boolean;
22
- timeout: number;
23
- };
5
+ interface RedisRateLimiterOptions extends BaseRedisRateLimiterOptions {
24
6
  }
25
- declare class RedisRateLimiter implements RateLimiter {
7
+ /**
8
+ * Rate limiter adapter for Redis. Enforces a fixed-window limit using a
9
+ * Redis Lua script, with optional blocking mode.
10
+ *
11
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#adapters | Rate Limit Helpers - Adapters}
12
+ */
13
+ declare class RedisRateLimiter extends BaseRedisRateLimiter {
26
14
  private readonly redis;
27
- private readonly prefix;
28
- private readonly maxRequests;
29
- private readonly window;
30
- private readonly blockingUntilReady;
31
- private scriptSha;
32
- constructor(redis: RedisClientType<any, any, any, any, any>, options: RedisRateLimiterOptions);
33
- limit(key: string, options?: RateLimitOptions): Promise<Required<RateLimitResult>>;
34
- private checkLimit;
35
- private blockUntilReady;
36
- private executeScript;
37
- private evalSha;
38
- private resolveWeight;
15
+ constructor(redis: RedisClientType<any, any, any, any, any> | RedisClusterType<any, any, any, any, any>, options: RedisRateLimiterOptions);
16
+ protected evalScript(script: string, keys: string[], args: string[]): Promise<unknown>;
39
17
  }
40
18
 
41
19
  export { RedisRateLimiter };
@@ -1,90 +1,16 @@
1
- import { sleep } from '@orpc/shared';
1
+ import { BaseRedisRateLimiter } from './base-redis.mjs';
2
+ import '@orpc/shared';
2
3
 
3
- const FIXED_WINDOW_RATELIMIT_SCRIPT = `
4
- local key = KEYS[1]
5
- local weight = tonumber(ARGV[1])
6
- local window = tonumber(ARGV[2])
7
-
8
- local current = redis.call('INCRBY', key, weight)
9
-
10
- if current == weight then
11
- redis.call('PEXPIRE', key, window)
12
- end
13
-
14
- local ttl = redis.call('PTTL', key)
15
-
16
- return { current, ttl }
17
- `;
18
- class RedisRateLimiter {
19
- redis;
20
- prefix;
21
- maxRequests;
22
- window;
23
- blockingUntilReady;
24
- scriptSha;
4
+ class RedisRateLimiter extends BaseRedisRateLimiter {
25
5
  constructor(redis, options) {
6
+ super(options);
26
7
  this.redis = redis;
27
- this.prefix = options.prefix ?? "";
28
- this.maxRequests = options.maxRequests;
29
- this.window = options.window;
30
- this.blockingUntilReady = options.blockingUntilReady;
31
8
  }
32
- async limit(key, options) {
33
- key = `${this.prefix}${key}`;
34
- const weight = this.resolveWeight(options);
9
+ async evalScript(script, keys, args) {
35
10
  if (!this.redis.isOpen) {
36
11
  await this.redis.connect();
37
12
  }
38
- return this.blockingUntilReady?.enabled ? this.blockUntilReady(key, this.blockingUntilReady.timeout, weight) : this.checkLimit(key, weight);
39
- }
40
- async checkLimit(key, weight) {
41
- const [used, ttl] = await this.executeScript(key, weight);
42
- return {
43
- success: used <= this.maxRequests,
44
- limit: this.maxRequests,
45
- remaining: Math.max(0, this.maxRequests - used),
46
- reset: Date.now() + ttl
47
- };
48
- }
49
- async blockUntilReady(key, timeoutMs, weight) {
50
- const deadlineAtMs = Date.now() + timeoutMs;
51
- while (true) {
52
- const result = await this.checkLimit(key, weight);
53
- if (result.success || result.reset > deadlineAtMs) {
54
- return result;
55
- }
56
- await sleep(result.reset - Date.now());
57
- }
58
- }
59
- async executeScript(key, weight) {
60
- try {
61
- return await this.evalSha(key, weight);
62
- } catch (error) {
63
- if (error instanceof Error && error.message.startsWith("NOSCRIPT")) {
64
- this.scriptSha = void 0;
65
- return await this.evalSha(key, weight);
66
- }
67
- throw error;
68
- }
69
- }
70
- async evalSha(key, weight) {
71
- this.scriptSha ??= await this.redis.scriptLoad(FIXED_WINDOW_RATELIMIT_SCRIPT);
72
- return await this.redis.evalSha(this.scriptSha, {
73
- keys: [key],
74
- arguments: [
75
- String(weight),
76
- String(this.window)
77
- ]
78
- });
79
- }
80
- resolveWeight(options) {
81
- const weight = options?.weight ?? 1;
82
- if (!Number.isInteger(weight) || weight <= 0) {
83
- throw new TypeError(
84
- "Rate limit weight must be an integer greater than 0"
85
- );
86
- }
87
- return weight;
13
+ return await this.redis.eval(script, { keys, arguments: args });
88
14
  }
89
15
  }
90
16
 
@@ -1,5 +1,5 @@
1
1
  import { Ratelimit } from '@upstash/ratelimit';
2
- import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.mjs';
2
+ import { RateLimiter, RateLimitOptions, RateLimitResult } from '../index.mjs';
3
3
 
4
4
  interface UpstashRateLimiterOptions {
5
5
  /**
@@ -26,6 +26,12 @@ interface UpstashRateLimiterOptions {
26
26
  */
27
27
  waitUntil?: (promise: Promise<any>) => any;
28
28
  }
29
+ /**
30
+ * Rate limiter adapter for Upstash Rate Limit. Delegates limit checks to a
31
+ * configured `Ratelimit` instance, with optional blocking mode.
32
+ *
33
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#adapters | Rate Limit Helpers - Adapters}
34
+ */
29
35
  declare class UpstashRateLimiter implements RateLimiter {
30
36
  private readonly ratelimit;
31
37
  private blockingUntilReady;
@@ -1,5 +1,5 @@
1
1
  import { Ratelimit } from '@upstash/ratelimit';
2
- import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.js';
2
+ import { RateLimiter, RateLimitOptions, RateLimitResult } from '../index.js';
3
3
 
4
4
  interface UpstashRateLimiterOptions {
5
5
  /**
@@ -26,6 +26,12 @@ interface UpstashRateLimiterOptions {
26
26
  */
27
27
  waitUntil?: (promise: Promise<any>) => any;
28
28
  }
29
+ /**
30
+ * Rate limiter adapter for Upstash Rate Limit. Delegates limit checks to a
31
+ * configured `Ratelimit` instance, with optional blocking mode.
32
+ *
33
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#adapters | Rate Limit Helpers - Adapters}
34
+ */
29
35
  declare class UpstashRateLimiter implements RateLimiter {
30
36
  private readonly ratelimit;
31
37
  private blockingUntilReady;
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { Context, MiddlewareOptions, Middleware } from '@orpc/server';
1
+ import { Context, AnyProcedure, MiddlewareOptions, Middleware } from '@orpc/server';
2
2
  import { StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
3
- import { b as RateLimitResult, R as RateLimiter } from './shared/ratelimit.BtcMexUf.mjs';
4
- export { a as RateLimitOptions } from './shared/ratelimit.BtcMexUf.mjs';
3
+ import { b as RateLimitResult, R as RateLimiter } from './shared/ratelimit.DwqoOfMm.mjs';
4
+ export { a as RateLimitOptions } from './shared/ratelimit.DwqoOfMm.mjs';
5
5
  import { Value, Promisable } from '@orpc/shared';
6
6
 
7
7
  declare const RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL: unique symbol;
@@ -10,14 +10,18 @@ interface RateLimitHandlerPluginContext {
10
10
  /**
11
11
  * The result of the ratelimiter after applying limits
12
12
  */
13
- results: RateLimitResult[];
13
+ checks: {
14
+ procedure: AnyProcedure;
15
+ path: string[];
16
+ result: RateLimitResult;
17
+ }[];
14
18
  };
15
19
  }
16
20
  /**
17
- * Automatically adds HTTP rate-limiting headers (RateLimit-* and Retry-After) to responses
18
- * when used with middleware created by createRatelimitMiddleware.
21
+ * Automatically adds HTTP rate limiting headers (`RateLimit-*` and `Retry-After`)
22
+ * to responses when used with the `ratelimit` middleware.
19
23
  *
20
- * @see {@link https://orpc.dev/docs/helpers/ratelimit#handler-plugin Ratelimit handler plugin}
24
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#handler-plugin | Rate Limit Helpers - Handler Plugin}
21
25
  */
22
26
  declare class RateLimitHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
23
27
  name: string;
@@ -66,7 +70,7 @@ interface RateLimitMiddlewareOptions<TInContext extends Context, TInput> {
66
70
  * Creates a middleware that enforces rate limits in oRPC procedures.
67
71
  * Supports per-request deduplication and integrates with the ratelimit handler plugin.
68
72
  *
69
- * @see {@link https://orpc.dev/docs/helpers/ratelimit#createratelimitmiddleware Ratelimit middleware}
73
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#ratelimit-middleware | Rate Limit Helpers - Ratelimit Middleware}
70
74
  */
71
75
  declare function ratelimit<TInContext extends Context, TInput>({ dedupe, ...options }: RateLimitMiddlewareOptions<TInContext, TInput>): Middleware<TInContext, object, TInput, any, object>;
72
76
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Context, MiddlewareOptions, Middleware } from '@orpc/server';
1
+ import { Context, AnyProcedure, MiddlewareOptions, Middleware } from '@orpc/server';
2
2
  import { StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
3
- import { b as RateLimitResult, R as RateLimiter } from './shared/ratelimit.BtcMexUf.js';
4
- export { a as RateLimitOptions } from './shared/ratelimit.BtcMexUf.js';
3
+ import { b as RateLimitResult, R as RateLimiter } from './shared/ratelimit.DwqoOfMm.js';
4
+ export { a as RateLimitOptions } from './shared/ratelimit.DwqoOfMm.js';
5
5
  import { Value, Promisable } from '@orpc/shared';
6
6
 
7
7
  declare const RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL: unique symbol;
@@ -10,14 +10,18 @@ interface RateLimitHandlerPluginContext {
10
10
  /**
11
11
  * The result of the ratelimiter after applying limits
12
12
  */
13
- results: RateLimitResult[];
13
+ checks: {
14
+ procedure: AnyProcedure;
15
+ path: string[];
16
+ result: RateLimitResult;
17
+ }[];
14
18
  };
15
19
  }
16
20
  /**
17
- * Automatically adds HTTP rate-limiting headers (RateLimit-* and Retry-After) to responses
18
- * when used with middleware created by createRatelimitMiddleware.
21
+ * Automatically adds HTTP rate limiting headers (`RateLimit-*` and `Retry-After`)
22
+ * to responses when used with the `ratelimit` middleware.
19
23
  *
20
- * @see {@link https://orpc.dev/docs/helpers/ratelimit#handler-plugin Ratelimit handler plugin}
24
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#handler-plugin | Rate Limit Helpers - Handler Plugin}
21
25
  */
22
26
  declare class RateLimitHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
23
27
  name: string;
@@ -66,7 +70,7 @@ interface RateLimitMiddlewareOptions<TInContext extends Context, TInput> {
66
70
  * Creates a middleware that enforces rate limits in oRPC procedures.
67
71
  * Supports per-request deduplication and integrates with the ratelimit handler plugin.
68
72
  *
69
- * @see {@link https://orpc.dev/docs/helpers/ratelimit#createratelimitmiddleware Ratelimit middleware}
73
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#ratelimit-middleware | Rate Limit Helpers - Ratelimit Middleware}
70
74
  */
71
75
  declare function ratelimit<TInContext extends Context, TInput>({ dedupe, ...options }: RateLimitMiddlewareOptions<TInContext, TInput>): Middleware<TInContext, object, TInput, any, object>;
72
76
 
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ class RateLimitHandlerPlugin {
6
6
  name = "~ratelimit";
7
7
  init(options) {
8
8
  const routingInterceptor = async (interceptorOptions) => {
9
- const pluginContext = { results: [] };
9
+ const pluginContext = { checks: [] };
10
10
  const result = await interceptorOptions.next({
11
11
  ...interceptorOptions,
12
12
  context: {
@@ -14,19 +14,19 @@ class RateLimitHandlerPlugin {
14
14
  [RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL]: pluginContext
15
15
  }
16
16
  });
17
- if (result.matched && pluginContext.results.length) {
18
- const exceededResults = pluginContext.results.filter((r) => !r.success);
19
- const mostConstrained = (exceededResults.length ? exceededResults : pluginContext.results).reduce(
20
- (a, b) => (a.remaining ?? Infinity) <= (b.remaining ?? Infinity) ? a : b
17
+ if (result.matched && pluginContext.checks.length) {
18
+ const exceededChecks = pluginContext.checks.filter((r) => !r.result.success);
19
+ const mostConstrained = (exceededChecks.length ? exceededChecks : pluginContext.checks).reduce(
20
+ (a, b) => (a.result.remaining ?? Infinity) <= (b.result.remaining ?? Infinity) ? a : b
21
21
  );
22
22
  const headers = {
23
23
  ...result.response.headers,
24
- "ratelimit-limit": mostConstrained.limit?.toString(),
25
- "ratelimit-remaining": mostConstrained.remaining?.toString(),
26
- "ratelimit-reset": mostConstrained.reset?.toString()
24
+ "ratelimit-limit": mostConstrained.result.limit?.toString(),
25
+ "ratelimit-remaining": mostConstrained.result.remaining?.toString(),
26
+ "ratelimit-reset": mostConstrained.result.reset?.toString()
27
27
  };
28
- if (result.response.status >= 400 && !mostConstrained.success && mostConstrained.reset !== void 0) {
29
- headers["retry-after"] = Math.max(0, Math.ceil((mostConstrained.reset - Date.now()) / 1e3)).toString();
28
+ if (result.response.status >= 400 && !mostConstrained.result.success && mostConstrained.result.reset !== void 0) {
29
+ headers["retry-after"] = Math.max(0, Math.ceil((mostConstrained.result.reset - Date.now()) / 1e3)).toString();
30
30
  }
31
31
  return {
32
32
  ...result,
@@ -63,7 +63,7 @@ function ratelimit({ dedupe = true, ...options }) {
63
63
  const result = await limiter.limit(key, { weight });
64
64
  const pluginContext = middlewareOptions.context[RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL];
65
65
  if (pluginContext) {
66
- pluginContext.results.push(result);
66
+ pluginContext.checks.push({ path: middlewareOptions.path, procedure: middlewareOptions.procedure, result });
67
67
  }
68
68
  if (!result.success) {
69
69
  throw new ORPCError("TOO_MANY_REQUESTS", {
@@ -25,6 +25,12 @@ interface RateLimitOptions {
25
25
  */
26
26
  weight?: number;
27
27
  }
28
+ /**
29
+ * Standard interface for checking and enforcing rate limits.
30
+ * Implement it for custom strategies, or use one of the provided adapters.
31
+ *
32
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit | Rate Limit Helpers}
33
+ */
28
34
  interface RateLimiter {
29
35
  limit(key: string, options?: RateLimitOptions): Promise<RateLimitResult>;
30
36
  }
@@ -25,6 +25,12 @@ interface RateLimitOptions {
25
25
  */
26
26
  weight?: number;
27
27
  }
28
+ /**
29
+ * Standard interface for checking and enforcing rate limits.
30
+ * Implement it for custom strategies, or use one of the provided adapters.
31
+ *
32
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit | Rate Limit Helpers}
33
+ */
28
34
  interface RateLimiter {
29
35
  limit(key: string, options?: RateLimitOptions): Promise<RateLimitResult>;
30
36
  }
package/package.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "name": "@orpc/ratelimit",
3
3
  "type": "module",
4
- "version": "2.0.0-beta.4",
4
+ "version": "2.0.0-beta.40",
5
+ "description": "Rate limiting for oRPC procedures, with memory, Redis, and Upstash adapters",
5
6
  "license": "MIT",
7
+ "funding": [
8
+ "https://github.com/sponsors/dinwwwh",
9
+ "https://opencollective.com/middleapi"
10
+ ],
6
11
  "homepage": "https://orpc.dev",
7
12
  "repository": {
8
13
  "type": "git",
@@ -11,7 +16,13 @@
11
16
  },
12
17
  "keywords": [
13
18
  "orpc",
14
- "ratelimit"
19
+ "ratelimit",
20
+ "rate-limiting",
21
+ "redis",
22
+ "upstash",
23
+ "middleware",
24
+ "api",
25
+ "typescript"
15
26
  ],
16
27
  "sideEffects": false,
17
28
  "exports": {
@@ -21,6 +32,11 @@
21
32
  "import": "./dist/index.mjs",
22
33
  "default": "./dist/index.mjs"
23
34
  },
35
+ "./base-redis": {
36
+ "types": "./dist/adapters/base-redis.d.mts",
37
+ "import": "./dist/adapters/base-redis.mjs",
38
+ "default": "./dist/adapters/base-redis.mjs"
39
+ },
24
40
  "./memory": {
25
41
  "types": "./dist/adapters/memory.d.mts",
26
42
  "import": "./dist/adapters/memory.mjs",
@@ -53,18 +69,16 @@
53
69
  }
54
70
  },
55
71
  "dependencies": {
56
- "@standardserver/core": "^0.0.25",
57
- "@orpc/server": "2.0.0-beta.4",
58
- "@orpc/shared": "2.0.0-beta.4"
72
+ "@orpc/server": "2.0.0-beta.40",
73
+ "@orpc/shared": "2.0.0-beta.40",
74
+ "@standard-server/core": "~0.9.2"
59
75
  },
60
76
  "devDependencies": {
61
77
  "@upstash/ratelimit": "^2.0.8",
62
- "@upstash/redis": "^1.38.0",
63
- "ioredis": "^5.9.1",
64
- "redis": "^6.0.0"
78
+ "@upstash/redis": "^1.38.2",
79
+ "redis": "^6.2.1"
65
80
  },
66
81
  "scripts": {
67
- "build": "unbuild",
68
82
  "type:check": "tsc -b"
69
83
  }
70
84
  }