@orpc/ratelimit 2.0.0-beta.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 oRPC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,188 @@
1
+ <h1 align="center">oRPC - Typesafe APIs Made Simple πŸͺ„</h1>
2
+
3
+ <div align="center">
4
+ <a href="https://codecov.io/gh/middleapi/orpc">
5
+ <img alt="codecov" src="https://codecov.io/gh/middleapi/orpc/branch/main/graph/badge.svg">
6
+ </a>
7
+ <a href="https://www.npmjs.com/package/@orpc/ratelimit">
8
+ <img alt="weekly downloads" src="https://img.shields.io/npm/dw/%40orpc%2Fratelimit?logo=npm" />
9
+ </a>
10
+ <a href="https://github.com/middleapi/orpc/blob/main/LICENSE">
11
+ <img alt="MIT License" src="https://img.shields.io/github/license/middleapi/orpc?logo=open-source-initiative" />
12
+ </a>
13
+ <a href="https://discord.gg/TXEbwRBvQn">
14
+ <img alt="Discord" src="https://img.shields.io/discord/1308966753044398161?color=7389D8&label&logo=discord&logoColor=ffffff" />
15
+ </a>
16
+ <a href="https://deepwiki.com/middleapi/orpc">
17
+ <img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki">
18
+ </a>
19
+ </div>
20
+
21
+ ## Documentation
22
+
23
+ You can read the documentation [here](https://orpc.dev).
24
+
25
+ ## Packages
26
+
27
+ **Core**
28
+
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.
33
+
34
+ **Schema validation**
35
+
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/).
39
+
40
+ **Framework & ecosystem integrations**
41
+
42
+ - [@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).
43
+ - [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query): Integrate with [TanStack Query](https://tanstack.com/query/latest).
44
+ - [@orpc/experimental-effect](https://www.npmjs.com/package/@orpc/experimental-effect): Integrate with [Effect](https://effect.website/).
45
+
46
+ **Built-in features**
47
+
48
+ - [@orpc/publisher](https://www.npmjs.com/package/@orpc/publisher): Pub/Sub.
49
+ - [@orpc/ratelimit](https://www.npmjs.com/package/@orpc/ratelimit): Rate limiting.
50
+ - [@orpc/json-schema](https://www.npmjs.com/package/@orpc/json-schema): Smart coercion for OpenAPI requests.
51
+
52
+ **Observability**
53
+
54
+ - [@orpc/opentelemetry](https://www.npmjs.com/package/@orpc/opentelemetry): Instrument your API with [OpenTelemetry](https://opentelemetry.io/).
55
+ - [@orpc/pino](https://www.npmjs.com/package/@orpc/pino): Log with [Pino](https://getpino.io/).
56
+ - [@orpc/evlog](https://www.npmjs.com/package/@orpc/evlog): Log with [Evlog](https://evlog.dev/).
57
+
58
+ ## Sponsors
59
+
60
+ 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! πŸš€
61
+
62
+ ### πŸ† Platinum Sponsor
63
+
64
+ <table>
65
+ <tr>
66
+ <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>
67
+ </tr>
68
+ </table>
69
+
70
+ ### πŸ₯ˆ Silver Sponsor
71
+
72
+ <table>
73
+ <tr>
74
+ <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>
75
+ </tr>
76
+ </table>
77
+
78
+ ### Generous Sponsors
79
+
80
+ <table>
81
+ <tr>
82
+ <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>
83
+ </tr>
84
+ </table>
85
+
86
+ ### Sponsors
87
+
88
+ <table>
89
+ <tr>
90
+ <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>
91
+ <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>
92
+ <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>
93
+ <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>
94
+ <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>
95
+ <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>
96
+ </tr>
97
+ <tr>
98
+ <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>
99
+ <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>
100
+ <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>
101
+ <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>
102
+ <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>
103
+ <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>
104
+ </tr>
105
+ <tr>
106
+ <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>
107
+ <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>
108
+ <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>
109
+ <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>
110
+ </tr>
111
+ </table>
112
+
113
+ ### Backers
114
+
115
+ <table>
116
+ <tr>
117
+ <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>
118
+ <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>
119
+ <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>
120
+ <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>
121
+ <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>
122
+ <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>
123
+ <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>
124
+ </tr>
125
+ <tr>
126
+ <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>
127
+ <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>
128
+ <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>
129
+ <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>
130
+ <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>
131
+ <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>
132
+ <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>
133
+ </tr>
134
+ <tr>
135
+ <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>
136
+ <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>
137
+ <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>
138
+ </tr>
139
+ </table>
140
+
141
+ ### Past Sponsors
142
+
143
+ <p>
144
+ <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>
145
+ <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>
146
+ <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>
147
+ <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>
148
+ <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>
149
+ <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>
150
+ <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>
151
+ <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>
152
+ <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>
153
+ <a href="https://github.com/johngerome?ref=orpc" target="_blank" rel="noopener" title="0x4e32"><img src="https://avatars.githubusercontent.com/u/2002000?u=24e8dd943cfc862aa284d858a023532c75071ade&amp;v=4" width="32" height="32" alt="0x4e32" /></a>
154
+ <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>
155
+ <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>
156
+ <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>
157
+ <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>
158
+ <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>
159
+ <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>
160
+ <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>
161
+ <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>
162
+ <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>
163
+ <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>
164
+ <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>
165
+ <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>
166
+ <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>
167
+ <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>
168
+ <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>
169
+ <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>
170
+ <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>
171
+ <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>
172
+ <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>
173
+ <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>
174
+ <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>
175
+ <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>
176
+ <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>
177
+ </p>
178
+
179
+ ## References
180
+
181
+ oRPC is inspired by existing solutions that prioritize type safety and developer experience. Special acknowledgments to:
182
+
183
+ - [tRPC](https://trpc.io): For pioneering the concept of end-to-end type-safe RPC and influencing the development of type-safe APIs.
184
+ - [ts-rest](https://ts-rest.com): For its emphasis on contract-first development and OpenAPI integration, which have greatly inspired oRPC's feature set.
185
+
186
+ ## License
187
+
188
+ Distributed under the MIT License. See [LICENSE](https://github.com/middleapi/orpc/blob/main/LICENSE) for more information.
@@ -0,0 +1,36 @@
1
+ import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.mjs';
2
+
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
+ /**
13
+ * Maximum number of requests allowed within the window.
14
+ */
15
+ maxRequests: number;
16
+ /**
17
+ * The duration of the fixed window in milliseconds.
18
+ */
19
+ window: number;
20
+ }
21
+ declare class MemoryRateLimiter implements RateLimiter {
22
+ private readonly maxRequests;
23
+ private readonly window;
24
+ private readonly blockingUntilReady;
25
+ private current;
26
+ private currentEpochStart;
27
+ constructor(options: MemoryRateLimiterOptions);
28
+ limit(key: string, options?: RateLimitOptions): Promise<Required<RateLimitResult>>;
29
+ private rotateIfNeeded;
30
+ private checkLimit;
31
+ private blockUntilReady;
32
+ private resolveWeight;
33
+ }
34
+
35
+ export { MemoryRateLimiter };
36
+ export type { MemoryRateLimiterOptions };
@@ -0,0 +1,36 @@
1
+ import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.js';
2
+
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
+ /**
13
+ * Maximum number of requests allowed within the window.
14
+ */
15
+ maxRequests: number;
16
+ /**
17
+ * The duration of the fixed window in milliseconds.
18
+ */
19
+ window: number;
20
+ }
21
+ declare class MemoryRateLimiter implements RateLimiter {
22
+ private readonly maxRequests;
23
+ private readonly window;
24
+ private readonly blockingUntilReady;
25
+ private current;
26
+ private currentEpochStart;
27
+ constructor(options: MemoryRateLimiterOptions);
28
+ limit(key: string, options?: RateLimitOptions): Promise<Required<RateLimitResult>>;
29
+ private rotateIfNeeded;
30
+ private checkLimit;
31
+ private blockUntilReady;
32
+ private resolveWeight;
33
+ }
34
+
35
+ export { MemoryRateLimiter };
36
+ export type { MemoryRateLimiterOptions };
@@ -0,0 +1,64 @@
1
+ import { sleep } from '@orpc/shared';
2
+
3
+ class MemoryRateLimiter {
4
+ maxRequests;
5
+ window;
6
+ blockingUntilReady;
7
+ current = /* @__PURE__ */ new Map();
8
+ currentEpochStart = Date.now();
9
+ constructor(options) {
10
+ this.maxRequests = options.maxRequests;
11
+ this.window = options.window;
12
+ this.blockingUntilReady = options.blockingUntilReady;
13
+ }
14
+ async limit(key, options) {
15
+ const weight = this.resolveWeight(options);
16
+ return this.blockingUntilReady?.enabled ? this.blockUntilReady(key, this.blockingUntilReady.timeout, weight) : this.checkLimit(key, weight);
17
+ }
18
+ rotateIfNeeded(now) {
19
+ if (now < this.currentEpochStart + this.window) {
20
+ return;
21
+ }
22
+ this.current = /* @__PURE__ */ new Map();
23
+ this.currentEpochStart = now;
24
+ }
25
+ checkLimit(key, weight) {
26
+ const now = Date.now();
27
+ this.rotateIfNeeded(now);
28
+ let entry = this.current.get(key);
29
+ if (!entry) {
30
+ entry = { used: 0 };
31
+ this.current.set(key, entry);
32
+ }
33
+ const reset = this.currentEpochStart + this.window;
34
+ const success = entry.used + weight <= this.maxRequests;
35
+ if (success) {
36
+ entry.used += weight;
37
+ }
38
+ return {
39
+ success,
40
+ limit: this.maxRequests,
41
+ remaining: Math.max(0, this.maxRequests - entry.used),
42
+ reset
43
+ };
44
+ }
45
+ async blockUntilReady(key, timeoutMs, weight) {
46
+ const deadlineAtMs = Date.now() + timeoutMs;
47
+ while (true) {
48
+ const result = this.checkLimit(key, weight);
49
+ if (result.success || result.reset > deadlineAtMs) {
50
+ return result;
51
+ }
52
+ await sleep(result.reset - Date.now());
53
+ }
54
+ }
55
+ resolveWeight(options) {
56
+ const weight = options?.weight ?? 1;
57
+ if (!Number.isInteger(weight) || weight <= 0) {
58
+ throw new TypeError("Rate limit weight must be an integer greater than 0");
59
+ }
60
+ return weight;
61
+ }
62
+ }
63
+
64
+ export { MemoryRateLimiter };
@@ -0,0 +1,42 @@
1
+ import { RedisClientType } from 'redis';
2
+ import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.mjs';
3
+
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
+ };
24
+ }
25
+ declare class RedisRateLimiter implements RateLimiter {
26
+ 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;
39
+ }
40
+
41
+ export { RedisRateLimiter };
42
+ export type { RedisRateLimiterOptions };
@@ -0,0 +1,42 @@
1
+ import { RedisClientType } from 'redis';
2
+ import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.js';
3
+
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
+ };
24
+ }
25
+ declare class RedisRateLimiter implements RateLimiter {
26
+ 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;
39
+ }
40
+
41
+ export { RedisRateLimiter };
42
+ export type { RedisRateLimiterOptions };
@@ -0,0 +1,91 @@
1
+ import { sleep } from '@orpc/shared';
2
+
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;
25
+ constructor(redis, options) {
26
+ this.redis = redis;
27
+ this.prefix = options.prefix ?? "";
28
+ this.maxRequests = options.maxRequests;
29
+ this.window = options.window;
30
+ this.blockingUntilReady = options.blockingUntilReady;
31
+ }
32
+ async limit(key, options) {
33
+ key = `${this.prefix}${key}`;
34
+ const weight = this.resolveWeight(options);
35
+ if (!this.redis.isOpen) {
36
+ await this.redis.connect();
37
+ }
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;
88
+ }
89
+ }
90
+
91
+ export { RedisRateLimiter };
@@ -0,0 +1,41 @@
1
+ import { Ratelimit } from '@upstash/ratelimit';
2
+ import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.mjs';
3
+
4
+ interface UpstashRateLimiterOptions {
5
+ /**
6
+ * Block until the request may pass or timeout is reached.
7
+ */
8
+ blockingUntilReady?: {
9
+ enabled: boolean;
10
+ /**
11
+ * milliseconds
12
+ */
13
+ timeout: number;
14
+ };
15
+ /**
16
+ * For the MultiRegion setup we do some synchronizing in the background, after returning the current limit.
17
+ * Or when analytics is enabled, we send the analytics asynchronously after returning the limit.
18
+ * In most case you can simply ignore this.
19
+ *
20
+ * On Vercel Edge or Cloudflare workers, you might need `.bind` before assign:
21
+ * ```ts
22
+ * const ratelimiter = new UpstashRateLimiter(ratelimit, {
23
+ * waitUntil: ctx.waitUntil.bind(ctx),
24
+ * })
25
+ * ```
26
+ */
27
+ waitUntil?: (promise: Promise<any>) => any;
28
+ }
29
+ declare class UpstashRateLimiter implements RateLimiter {
30
+ private readonly ratelimit;
31
+ private blockingUntilReady;
32
+ private waitUntil;
33
+ constructor(ratelimit: Ratelimit, options?: UpstashRateLimiterOptions);
34
+ limit(key: string, options?: RateLimitOptions): Promise<Required<RateLimitResult>>;
35
+ private checkLimit;
36
+ private blockUntilReady;
37
+ private resolveWeight;
38
+ }
39
+
40
+ export { UpstashRateLimiter };
41
+ export type { UpstashRateLimiterOptions };
@@ -0,0 +1,41 @@
1
+ import { Ratelimit } from '@upstash/ratelimit';
2
+ import { R as RateLimiter, a as RateLimitOptions, b as RateLimitResult } from '../shared/ratelimit.BtcMexUf.js';
3
+
4
+ interface UpstashRateLimiterOptions {
5
+ /**
6
+ * Block until the request may pass or timeout is reached.
7
+ */
8
+ blockingUntilReady?: {
9
+ enabled: boolean;
10
+ /**
11
+ * milliseconds
12
+ */
13
+ timeout: number;
14
+ };
15
+ /**
16
+ * For the MultiRegion setup we do some synchronizing in the background, after returning the current limit.
17
+ * Or when analytics is enabled, we send the analytics asynchronously after returning the limit.
18
+ * In most case you can simply ignore this.
19
+ *
20
+ * On Vercel Edge or Cloudflare workers, you might need `.bind` before assign:
21
+ * ```ts
22
+ * const ratelimiter = new UpstashRateLimiter(ratelimit, {
23
+ * waitUntil: ctx.waitUntil.bind(ctx),
24
+ * })
25
+ * ```
26
+ */
27
+ waitUntil?: (promise: Promise<any>) => any;
28
+ }
29
+ declare class UpstashRateLimiter implements RateLimiter {
30
+ private readonly ratelimit;
31
+ private blockingUntilReady;
32
+ private waitUntil;
33
+ constructor(ratelimit: Ratelimit, options?: UpstashRateLimiterOptions);
34
+ limit(key: string, options?: RateLimitOptions): Promise<Required<RateLimitResult>>;
35
+ private checkLimit;
36
+ private blockUntilReady;
37
+ private resolveWeight;
38
+ }
39
+
40
+ export { UpstashRateLimiter };
41
+ export type { UpstashRateLimiterOptions };
@@ -0,0 +1,47 @@
1
+ import { sleep } from '@orpc/shared';
2
+
3
+ class UpstashRateLimiter {
4
+ constructor(ratelimit, options = {}) {
5
+ this.ratelimit = ratelimit;
6
+ this.blockingUntilReady = options.blockingUntilReady;
7
+ this.waitUntil = options.waitUntil;
8
+ }
9
+ blockingUntilReady;
10
+ waitUntil;
11
+ async limit(key, options = {}) {
12
+ const weight = this.resolveWeight(options);
13
+ if (this.blockingUntilReady?.enabled) {
14
+ return await this.blockUntilReady(key, weight, this.blockingUntilReady.timeout);
15
+ }
16
+ return await this.checkLimit(key, weight);
17
+ }
18
+ async checkLimit(key, weight) {
19
+ const result = await this.ratelimit.limit(key, { rate: weight });
20
+ this.waitUntil?.(result.pending);
21
+ return result;
22
+ }
23
+ async blockUntilReady(key, weight, timeoutMs) {
24
+ if (weight === 1) {
25
+ const result = await this.ratelimit.blockUntilReady(key, timeoutMs);
26
+ this.waitUntil?.(result.pending);
27
+ return result;
28
+ }
29
+ const deadlineAtMs = Date.now() + timeoutMs;
30
+ while (true) {
31
+ const result = await this.checkLimit(key, weight);
32
+ if (result.success || result.reset > deadlineAtMs) {
33
+ return result;
34
+ }
35
+ await sleep(Math.max(0, result.reset - Date.now()));
36
+ }
37
+ }
38
+ resolveWeight(options) {
39
+ const weight = options?.weight ?? 1;
40
+ if (!Number.isInteger(weight) || weight <= 0) {
41
+ throw new TypeError("Rate limit weight must be an integer greater than 0");
42
+ }
43
+ return weight;
44
+ }
45
+ }
46
+
47
+ export { UpstashRateLimiter };
@@ -0,0 +1,74 @@
1
+ import { Context, MiddlewareOptions, Middleware } from '@orpc/server';
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';
5
+ import { Value, Promisable } from '@orpc/shared';
6
+
7
+ declare const RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL: unique symbol;
8
+ interface RateLimitHandlerPluginContext {
9
+ [RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL]?: {
10
+ /**
11
+ * The result of the ratelimiter after applying limits
12
+ */
13
+ results: RateLimitResult[];
14
+ };
15
+ }
16
+ /**
17
+ * Automatically adds HTTP rate-limiting headers (RateLimit-* and Retry-After) to responses
18
+ * when used with middleware created by createRatelimitMiddleware.
19
+ *
20
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#handler-plugin Ratelimit handler plugin}
21
+ */
22
+ declare class RateLimitHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
23
+ name: string;
24
+ init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
25
+ }
26
+
27
+ declare const RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL: unique symbol;
28
+ interface RateLimitMiddlewareContext {
29
+ [RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL]?: {
30
+ /**
31
+ * The applied limits in this request, mainly for deduplication purposes
32
+ */
33
+ applied: {
34
+ limiter: RateLimiter;
35
+ key: string;
36
+ }[];
37
+ };
38
+ }
39
+ interface RateLimitMiddlewareOptions<TInContext extends Context, TInput> {
40
+ /**
41
+ * The rule set to use for rate limiting
42
+ */
43
+ limiter: Value<Promisable<RateLimiter>, [options: MiddlewareOptions<TInContext, unknown, Record<never, never>>, input: TInput]>;
44
+ /**
45
+ * The key to identify the user/requester
46
+ */
47
+ key: Value<Promisable<string>, [options: MiddlewareOptions<TInContext, unknown, Record<never, never>>, input: TInput]>;
48
+ /**
49
+ * The weight of the request. Determines how many tokens or quota
50
+ * units are consumed by this request. Must be an integer greater than 0.
51
+ *
52
+ * @default 1
53
+ */
54
+ weight?: Value<Promisable<number>, [options: MiddlewareOptions<TInContext, unknown, Record<never, never>>, input: TInput]>;
55
+ /**
56
+ * If your ratelimit middleware is used multiple times
57
+ * or you invoke a procedure inside another procedure (shared the same context) that also has
58
+ * ratelimit middleware **with the same limiter and key**, this option
59
+ * will ensure that the limit is only applied once per request.
60
+ *
61
+ * @default true
62
+ */
63
+ dedupe?: boolean;
64
+ }
65
+ /**
66
+ * Creates a middleware that enforces rate limits in oRPC procedures.
67
+ * Supports per-request deduplication and integrates with the ratelimit handler plugin.
68
+ *
69
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#createratelimitmiddleware Ratelimit middleware}
70
+ */
71
+ declare function ratelimit<TInContext extends Context, TInput>({ dedupe, ...options }: RateLimitMiddlewareOptions<TInContext, TInput>): Middleware<TInContext, object, TInput, any, object>;
72
+
73
+ export { RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL, RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL, RateLimitHandlerPlugin, RateLimitResult, RateLimiter, ratelimit };
74
+ export type { RateLimitHandlerPluginContext, RateLimitMiddlewareContext, RateLimitMiddlewareOptions };
@@ -0,0 +1,74 @@
1
+ import { Context, MiddlewareOptions, Middleware } from '@orpc/server';
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';
5
+ import { Value, Promisable } from '@orpc/shared';
6
+
7
+ declare const RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL: unique symbol;
8
+ interface RateLimitHandlerPluginContext {
9
+ [RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL]?: {
10
+ /**
11
+ * The result of the ratelimiter after applying limits
12
+ */
13
+ results: RateLimitResult[];
14
+ };
15
+ }
16
+ /**
17
+ * Automatically adds HTTP rate-limiting headers (RateLimit-* and Retry-After) to responses
18
+ * when used with middleware created by createRatelimitMiddleware.
19
+ *
20
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#handler-plugin Ratelimit handler plugin}
21
+ */
22
+ declare class RateLimitHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
23
+ name: string;
24
+ init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
25
+ }
26
+
27
+ declare const RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL: unique symbol;
28
+ interface RateLimitMiddlewareContext {
29
+ [RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL]?: {
30
+ /**
31
+ * The applied limits in this request, mainly for deduplication purposes
32
+ */
33
+ applied: {
34
+ limiter: RateLimiter;
35
+ key: string;
36
+ }[];
37
+ };
38
+ }
39
+ interface RateLimitMiddlewareOptions<TInContext extends Context, TInput> {
40
+ /**
41
+ * The rule set to use for rate limiting
42
+ */
43
+ limiter: Value<Promisable<RateLimiter>, [options: MiddlewareOptions<TInContext, unknown, Record<never, never>>, input: TInput]>;
44
+ /**
45
+ * The key to identify the user/requester
46
+ */
47
+ key: Value<Promisable<string>, [options: MiddlewareOptions<TInContext, unknown, Record<never, never>>, input: TInput]>;
48
+ /**
49
+ * The weight of the request. Determines how many tokens or quota
50
+ * units are consumed by this request. Must be an integer greater than 0.
51
+ *
52
+ * @default 1
53
+ */
54
+ weight?: Value<Promisable<number>, [options: MiddlewareOptions<TInContext, unknown, Record<never, never>>, input: TInput]>;
55
+ /**
56
+ * If your ratelimit middleware is used multiple times
57
+ * or you invoke a procedure inside another procedure (shared the same context) that also has
58
+ * ratelimit middleware **with the same limiter and key**, this option
59
+ * will ensure that the limit is only applied once per request.
60
+ *
61
+ * @default true
62
+ */
63
+ dedupe?: boolean;
64
+ }
65
+ /**
66
+ * Creates a middleware that enforces rate limits in oRPC procedures.
67
+ * Supports per-request deduplication and integrates with the ratelimit handler plugin.
68
+ *
69
+ * @see {@link https://orpc.dev/docs/helpers/ratelimit#createratelimitmiddleware Ratelimit middleware}
70
+ */
71
+ declare function ratelimit<TInContext extends Context, TInput>({ dedupe, ...options }: RateLimitMiddlewareOptions<TInContext, TInput>): Middleware<TInContext, object, TInput, any, object>;
72
+
73
+ export { RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL, RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL, RateLimitHandlerPlugin, RateLimitResult, RateLimiter, ratelimit };
74
+ export type { RateLimitHandlerPluginContext, RateLimitMiddlewareContext, RateLimitMiddlewareOptions };
package/dist/index.mjs ADDED
@@ -0,0 +1,91 @@
1
+ import { toArray, value } from '@orpc/shared';
2
+ import { ORPCError } from '@orpc/server';
3
+
4
+ const RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL = Symbol.for("ORPC_RATELIMIT_HANDLER_PLUGIN_CONTEXT");
5
+ class RateLimitHandlerPlugin {
6
+ name = "~ratelimit";
7
+ init(options) {
8
+ const routingInterceptor = async (interceptorOptions) => {
9
+ const pluginContext = { results: [] };
10
+ const result = await interceptorOptions.next({
11
+ ...interceptorOptions,
12
+ context: {
13
+ ...interceptorOptions.context,
14
+ [RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL]: pluginContext
15
+ }
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
21
+ );
22
+ const headers = {
23
+ ...result.response.headers,
24
+ "ratelimit-limit": mostConstrained.limit?.toString(),
25
+ "ratelimit-remaining": mostConstrained.remaining?.toString(),
26
+ "ratelimit-reset": mostConstrained.reset?.toString()
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();
30
+ }
31
+ return {
32
+ ...result,
33
+ response: {
34
+ ...result.response,
35
+ headers
36
+ }
37
+ };
38
+ }
39
+ return result;
40
+ };
41
+ return {
42
+ ...options,
43
+ routingInterceptors: [
44
+ ...toArray(options.routingInterceptors),
45
+ routingInterceptor
46
+ ]
47
+ };
48
+ }
49
+ }
50
+
51
+ const RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL = Symbol.for("ORPC_RATELIMIT_MIDDLEWARE_CONTEXT");
52
+ function ratelimit({ dedupe = true, ...options }) {
53
+ return async function ratelimit2(middlewareOptions, input) {
54
+ const [limiter, key, weight] = await Promise.all([
55
+ value(options.limiter, middlewareOptions, input),
56
+ value(options.key, middlewareOptions, input),
57
+ value(options.weight, middlewareOptions, input)
58
+ ]);
59
+ const middlewareContext = middlewareOptions.context[RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL];
60
+ if (dedupe && middlewareContext?.applied.some((l) => l.key === key && l.limiter === limiter)) {
61
+ return middlewareOptions.next();
62
+ }
63
+ const result = await limiter.limit(key, { weight });
64
+ const pluginContext = middlewareOptions.context[RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL];
65
+ if (pluginContext) {
66
+ pluginContext.results.push(result);
67
+ }
68
+ if (!result.success) {
69
+ throw new ORPCError("TOO_MANY_REQUESTS", {
70
+ data: {
71
+ limit: result.limit,
72
+ remaining: result.remaining,
73
+ reset: result.reset
74
+ }
75
+ });
76
+ }
77
+ return middlewareOptions.next({
78
+ context: {
79
+ [RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL]: {
80
+ ...middlewareContext,
81
+ applied: [
82
+ ...toArray(middlewareContext?.applied),
83
+ { limiter, key }
84
+ ]
85
+ }
86
+ }
87
+ });
88
+ };
89
+ }
90
+
91
+ export { RATELIMIT_HANDLER_PLUGIN_CONTEXT_SYMBOL, RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL, RateLimitHandlerPlugin, ratelimit };
@@ -0,0 +1,32 @@
1
+ interface RateLimitResult {
2
+ /**
3
+ * Whether the request may pass(true) or exceeded the limit(false)
4
+ */
5
+ success: boolean;
6
+ /**
7
+ * Maximum number of requests allowed within a window.
8
+ */
9
+ limit?: number;
10
+ /**
11
+ * How many requests the user has left within the current window.
12
+ */
13
+ remaining?: number;
14
+ /**
15
+ * Unix timestamp in milliseconds when the limits are reset.
16
+ */
17
+ reset?: number;
18
+ }
19
+ interface RateLimitOptions {
20
+ /**
21
+ * The weight of the request. Determines how many tokens or quota
22
+ * units are consumed by this request. Must be an integer greater than 0.
23
+ *
24
+ * @default 1
25
+ */
26
+ weight?: number;
27
+ }
28
+ interface RateLimiter {
29
+ limit(key: string, options?: RateLimitOptions): Promise<RateLimitResult>;
30
+ }
31
+
32
+ export type { RateLimiter as R, RateLimitOptions as a, RateLimitResult as b };
@@ -0,0 +1,32 @@
1
+ interface RateLimitResult {
2
+ /**
3
+ * Whether the request may pass(true) or exceeded the limit(false)
4
+ */
5
+ success: boolean;
6
+ /**
7
+ * Maximum number of requests allowed within a window.
8
+ */
9
+ limit?: number;
10
+ /**
11
+ * How many requests the user has left within the current window.
12
+ */
13
+ remaining?: number;
14
+ /**
15
+ * Unix timestamp in milliseconds when the limits are reset.
16
+ */
17
+ reset?: number;
18
+ }
19
+ interface RateLimitOptions {
20
+ /**
21
+ * The weight of the request. Determines how many tokens or quota
22
+ * units are consumed by this request. Must be an integer greater than 0.
23
+ *
24
+ * @default 1
25
+ */
26
+ weight?: number;
27
+ }
28
+ interface RateLimiter {
29
+ limit(key: string, options?: RateLimitOptions): Promise<RateLimitResult>;
30
+ }
31
+
32
+ export type { RateLimiter as R, RateLimitOptions as a, RateLimitResult as b };
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@orpc/ratelimit",
3
+ "type": "module",
4
+ "version": "2.0.0-beta.1",
5
+ "license": "MIT",
6
+ "homepage": "https://orpc.dev",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/middleapi/orpc.git",
10
+ "directory": "packages/ratelimit"
11
+ },
12
+ "keywords": [
13
+ "orpc",
14
+ "ratelimit"
15
+ ],
16
+ "sideEffects": false,
17
+ "exports": {
18
+ "./package.json": "./package.json",
19
+ ".": {
20
+ "types": "./dist/index.d.mts",
21
+ "import": "./dist/index.mjs",
22
+ "default": "./dist/index.mjs"
23
+ },
24
+ "./memory": {
25
+ "types": "./dist/adapters/memory.d.mts",
26
+ "import": "./dist/adapters/memory.mjs",
27
+ "default": "./dist/adapters/memory.mjs"
28
+ },
29
+ "./redis": {
30
+ "types": "./dist/adapters/redis.d.mts",
31
+ "import": "./dist/adapters/redis.mjs",
32
+ "default": "./dist/adapters/redis.mjs"
33
+ },
34
+ "./upstash": {
35
+ "types": "./dist/adapters/upstash.d.mts",
36
+ "import": "./dist/adapters/upstash.mjs",
37
+ "default": "./dist/adapters/upstash.mjs"
38
+ }
39
+ },
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "peerDependencies": {
44
+ "@upstash/ratelimit": ">=2.0.8",
45
+ "redis": ">=6.0.0"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "@upstash/ratelimit": {
49
+ "optional": true
50
+ },
51
+ "redis": {
52
+ "optional": true
53
+ }
54
+ },
55
+ "dependencies": {
56
+ "@standardserver/core": "^0.0.24",
57
+ "@orpc/server": "2.0.0-beta.1",
58
+ "@orpc/shared": "2.0.0-beta.1"
59
+ },
60
+ "devDependencies": {
61
+ "@upstash/ratelimit": "^2.0.8",
62
+ "@upstash/redis": "^1.38.0",
63
+ "ioredis": "^5.9.1",
64
+ "redis": "^6.0.0"
65
+ },
66
+ "scripts": {
67
+ "build": "unbuild",
68
+ "type:check": "tsc -b"
69
+ }
70
+ }