@orpc/experimental-ratelimit 0.0.0-next.3312214
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 +21 -0
- package/README.md +81 -0
- package/dist/adapters/memory.d.mts +34 -0
- package/dist/adapters/memory.d.ts +34 -0
- package/dist/adapters/memory.mjs +74 -0
- package/dist/adapters/redis.d.mts +40 -0
- package/dist/adapters/redis.d.ts +40 -0
- package/dist/adapters/redis.mjs +95 -0
- package/dist/adapters/upstash-ratelimit.d.mts +35 -0
- package/dist/adapters/upstash-ratelimit.d.ts +35 -0
- package/dist/adapters/upstash-ratelimit.mjs +16 -0
- package/dist/index.d.mts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.mjs +76 -0
- package/dist/shared/experimental-ratelimit.C8zlaHwR.d.mts +23 -0
- package/dist/shared/experimental-ratelimit.C8zlaHwR.d.ts +23 -0
- package/package.json +65 -0
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,81 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<image align="center" src="https://orpc.unnoq.com/logo.webp" width=280 alt="oRPC logo" />
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<h1></h1>
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
<a href="https://codecov.io/gh/unnoq/orpc">
|
|
9
|
+
<img alt="codecov" src="https://codecov.io/gh/unnoq/orpc/branch/main/graph/badge.svg">
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@orpc/experimental-ratelimit">
|
|
12
|
+
<img alt="weekly downloads" src="https://img.shields.io/npm/dw/%40orpc%2Fexperimental-ratelimit?logo=npm" />
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://github.com/unnoq/orpc/blob/main/LICENSE">
|
|
15
|
+
<img alt="MIT License" src="https://img.shields.io/github/license/unnoq/orpc?logo=open-source-initiative" />
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://discord.gg/TXEbwRBvQn">
|
|
18
|
+
<img alt="Discord" src="https://img.shields.io/discord/1308966753044398161?color=7389D8&label&logo=discord&logoColor=ffffff" />
|
|
19
|
+
</a>
|
|
20
|
+
<a href="https://deepwiki.com/unnoq/orpc">
|
|
21
|
+
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki">
|
|
22
|
+
</a>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<h3 align="center">Typesafe APIs Made Simple 🪄</h3>
|
|
26
|
+
|
|
27
|
+
**oRPC is a powerful combination of RPC and OpenAPI**, makes it easy to build APIs that are end-to-end type-safe and adhere to OpenAPI standards
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Highlights
|
|
32
|
+
|
|
33
|
+
- **🔗 End-to-End Type Safety**: Ensure type-safe inputs, outputs, and errors from client to server.
|
|
34
|
+
- **📘 First-Class OpenAPI**: Built-in support that fully adheres to the OpenAPI standard.
|
|
35
|
+
- **📝 Contract-First Development**: Optionally define your API contract before implementation.
|
|
36
|
+
- **🔍 First-Class OpenTelemetry**: Seamlessly integrate with OpenTelemetry for observability.
|
|
37
|
+
- **⚙️ Framework Integrations**: Seamlessly integrate with TanStack Query (React, Vue, Solid, Svelte, Angular), SWR, Pinia Colada, and more.
|
|
38
|
+
- **🚀 Server Actions**: Fully compatible with React Server Actions on Next.js, TanStack Start, and other platforms.
|
|
39
|
+
- **🔠 Standard Schema Support**: Works out of the box with Zod, Valibot, ArkType, and other schema validators.
|
|
40
|
+
- **🗃️ Native Types**: Supports native types like Date, File, Blob, BigInt, URL, and more.
|
|
41
|
+
- **⏱️ Lazy Router**: Enhance cold start times with our lazy routing feature.
|
|
42
|
+
- **📡 SSE & Streaming**: Enjoy full type-safe support for SSE and streaming.
|
|
43
|
+
- **🌍 Multi-Runtime Support**: Fast and lightweight on Cloudflare, Deno, Bun, Node.js, and beyond.
|
|
44
|
+
- **🔌 Extendability**: Easily extend functionality with plugins, middleware, and interceptors.
|
|
45
|
+
|
|
46
|
+
## Documentation
|
|
47
|
+
|
|
48
|
+
You can find the full documentation [here](https://orpc.unnoq.com).
|
|
49
|
+
|
|
50
|
+
## Packages
|
|
51
|
+
|
|
52
|
+
- [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Build your API contract.
|
|
53
|
+
- [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract.
|
|
54
|
+
- [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety.
|
|
55
|
+
- [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Generate OpenAPI specs and handle OpenAPI requests.
|
|
56
|
+
- [@orpc/otel](https://www.npmjs.com/package/@orpc/otel): [OpenTelemetry](https://opentelemetry.io/) integration for observability.
|
|
57
|
+
- [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with [NestJS](https://nestjs.com/).
|
|
58
|
+
- [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions.
|
|
59
|
+
- [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query): [TanStack Query](https://tanstack.com/query/latest) integration.
|
|
60
|
+
- [@orpc/experimental-react-swr](https://www.npmjs.com/package/@orpc/experimental-react-swr): [SWR](https://swr.vercel.app/) integration.
|
|
61
|
+
- [@orpc/vue-colada](https://www.npmjs.com/package/@orpc/vue-colada): Integration with [Pinia Colada](https://pinia-colada.esm.dev/).
|
|
62
|
+
- [@orpc/hey-api](https://www.npmjs.com/package/@orpc/hey-api): [Hey API](https://heyapi.dev/) integration.
|
|
63
|
+
- [@orpc/zod](https://www.npmjs.com/package/@orpc/zod): More schemas that [Zod](https://zod.dev/) doesn't support yet.
|
|
64
|
+
- [@orpc/valibot](https://www.npmjs.com/package/@orpc/valibot): OpenAPI spec generation from [Valibot](https://valibot.dev/).
|
|
65
|
+
- [@orpc/arktype](https://www.npmjs.com/package/@orpc/arktype): OpenAPI spec generation from [ArkType](https://arktype.io/).
|
|
66
|
+
|
|
67
|
+
## `@orpc/experimental-ratelimit`
|
|
68
|
+
|
|
69
|
+
Rate Limiting Feature for oRPC
|
|
70
|
+
|
|
71
|
+
## Sponsors
|
|
72
|
+
|
|
73
|
+
<p align="center">
|
|
74
|
+
<a href="https://cdn.jsdelivr.net/gh/unnoq/unnoq/sponsors.svg">
|
|
75
|
+
<img src='https://cdn.jsdelivr.net/gh/unnoq/unnoq/sponsors.svg'/>
|
|
76
|
+
</a>
|
|
77
|
+
</p>
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
Distributed under the MIT License. See [LICENSE](https://github.com/unnoq/orpc/blob/main/LICENSE) for more information.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { a as Ratelimiter, R as RatelimiterLimitResult } from '../shared/experimental-ratelimit.C8zlaHwR.mjs';
|
|
2
|
+
|
|
3
|
+
interface MemoryRatelimiterOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Block until the request may pass or timeout is reached.
|
|
6
|
+
*/
|
|
7
|
+
blockingUntilReady?: {
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
timeout: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Maximum number of requests allowed within the window.
|
|
13
|
+
*/
|
|
14
|
+
maxRequests: number;
|
|
15
|
+
/**
|
|
16
|
+
* The duration of the sliding window in milliseconds.
|
|
17
|
+
*/
|
|
18
|
+
window: number;
|
|
19
|
+
}
|
|
20
|
+
declare class MemoryRatelimiter implements Ratelimiter {
|
|
21
|
+
private readonly maxRequests;
|
|
22
|
+
private readonly window;
|
|
23
|
+
private readonly blockingUntilReady;
|
|
24
|
+
private readonly store;
|
|
25
|
+
private lastCleanupTime;
|
|
26
|
+
constructor(options: MemoryRatelimiterOptions);
|
|
27
|
+
limit(key: string): Promise<Required<RatelimiterLimitResult>>;
|
|
28
|
+
private cleanup;
|
|
29
|
+
private checkLimit;
|
|
30
|
+
private blockUntilReady;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { MemoryRatelimiter };
|
|
34
|
+
export type { MemoryRatelimiterOptions };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { a as Ratelimiter, R as RatelimiterLimitResult } from '../shared/experimental-ratelimit.C8zlaHwR.js';
|
|
2
|
+
|
|
3
|
+
interface MemoryRatelimiterOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Block until the request may pass or timeout is reached.
|
|
6
|
+
*/
|
|
7
|
+
blockingUntilReady?: {
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
timeout: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Maximum number of requests allowed within the window.
|
|
13
|
+
*/
|
|
14
|
+
maxRequests: number;
|
|
15
|
+
/**
|
|
16
|
+
* The duration of the sliding window in milliseconds.
|
|
17
|
+
*/
|
|
18
|
+
window: number;
|
|
19
|
+
}
|
|
20
|
+
declare class MemoryRatelimiter implements Ratelimiter {
|
|
21
|
+
private readonly maxRequests;
|
|
22
|
+
private readonly window;
|
|
23
|
+
private readonly blockingUntilReady;
|
|
24
|
+
private readonly store;
|
|
25
|
+
private lastCleanupTime;
|
|
26
|
+
constructor(options: MemoryRatelimiterOptions);
|
|
27
|
+
limit(key: string): Promise<Required<RatelimiterLimitResult>>;
|
|
28
|
+
private cleanup;
|
|
29
|
+
private checkLimit;
|
|
30
|
+
private blockUntilReady;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { MemoryRatelimiter };
|
|
34
|
+
export type { MemoryRatelimiterOptions };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
class MemoryRatelimiter {
|
|
2
|
+
maxRequests;
|
|
3
|
+
window;
|
|
4
|
+
blockingUntilReady;
|
|
5
|
+
store;
|
|
6
|
+
lastCleanupTime = null;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.maxRequests = options.maxRequests;
|
|
9
|
+
this.window = options.window;
|
|
10
|
+
this.blockingUntilReady = options.blockingUntilReady;
|
|
11
|
+
this.store = /* @__PURE__ */ new Map();
|
|
12
|
+
}
|
|
13
|
+
limit(key) {
|
|
14
|
+
this.cleanup();
|
|
15
|
+
if (this.blockingUntilReady?.enabled) {
|
|
16
|
+
return this.blockUntilReady(key, this.blockingUntilReady.timeout);
|
|
17
|
+
}
|
|
18
|
+
return this.checkLimit(key);
|
|
19
|
+
}
|
|
20
|
+
cleanup() {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
if (this.lastCleanupTime !== null && this.lastCleanupTime + this.window > now) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.lastCleanupTime = now;
|
|
26
|
+
const windowStart = now - this.window;
|
|
27
|
+
for (const [key, timestamps] of this.store) {
|
|
28
|
+
const idx = timestamps.findIndex((timestamp) => timestamp >= windowStart);
|
|
29
|
+
timestamps.splice(0, idx === -1 ? timestamps.length : idx);
|
|
30
|
+
if (timestamps.length === 0) {
|
|
31
|
+
this.store.delete(key);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async checkLimit(key) {
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
const windowStart = now - this.window;
|
|
38
|
+
let timestamps = this.store.get(key);
|
|
39
|
+
if (timestamps) {
|
|
40
|
+
const idx = timestamps.findIndex((timestamp) => timestamp >= windowStart);
|
|
41
|
+
timestamps.splice(0, idx === -1 ? timestamps.length : idx);
|
|
42
|
+
} else {
|
|
43
|
+
this.store.set(key, timestamps = []);
|
|
44
|
+
}
|
|
45
|
+
const reset = timestamps[0] !== void 0 ? timestamps[0] + this.window : now + this.window;
|
|
46
|
+
if (timestamps.length >= this.maxRequests) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
limit: this.maxRequests,
|
|
50
|
+
remaining: 0,
|
|
51
|
+
reset
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
timestamps.push(now);
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
limit: this.maxRequests,
|
|
58
|
+
remaining: this.maxRequests - timestamps.length,
|
|
59
|
+
reset
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async blockUntilReady(key, timeoutMs) {
|
|
63
|
+
const deadlineAtMs = Date.now() + timeoutMs;
|
|
64
|
+
while (true) {
|
|
65
|
+
const result = await this.checkLimit(key);
|
|
66
|
+
if (result.success || result.reset > deadlineAtMs) {
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, result.reset - Date.now()));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { MemoryRatelimiter };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { a as Ratelimiter, R as RatelimiterLimitResult } from '../shared/experimental-ratelimit.C8zlaHwR.mjs';
|
|
2
|
+
|
|
3
|
+
interface RedisRatelimiterOptions {
|
|
4
|
+
eval: (script: string, numKeys: number, ...rest: string[]) => Promise<unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* Block until the request may pass or timeout is reached.
|
|
7
|
+
*/
|
|
8
|
+
blockingUntilReady?: {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
timeout: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The prefix to use for Redis keys.
|
|
14
|
+
*
|
|
15
|
+
* @default orpc:ratelimit:
|
|
16
|
+
*/
|
|
17
|
+
prefix?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Maximum number of requests allowed within the window.
|
|
20
|
+
*/
|
|
21
|
+
maxRequests: number;
|
|
22
|
+
/**
|
|
23
|
+
* The duration of the sliding window in milliseconds.
|
|
24
|
+
*/
|
|
25
|
+
window: number;
|
|
26
|
+
}
|
|
27
|
+
declare class RedisRatelimiter implements Ratelimiter {
|
|
28
|
+
private readonly eval;
|
|
29
|
+
private readonly prefix;
|
|
30
|
+
private readonly maxRequests;
|
|
31
|
+
private readonly window;
|
|
32
|
+
private readonly blockingUntilReady;
|
|
33
|
+
constructor(options: RedisRatelimiterOptions);
|
|
34
|
+
limit(key: string): Promise<Required<RatelimiterLimitResult>>;
|
|
35
|
+
private checkLimit;
|
|
36
|
+
private blockUntilReady;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { RedisRatelimiter };
|
|
40
|
+
export type { RedisRatelimiterOptions };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { a as Ratelimiter, R as RatelimiterLimitResult } from '../shared/experimental-ratelimit.C8zlaHwR.js';
|
|
2
|
+
|
|
3
|
+
interface RedisRatelimiterOptions {
|
|
4
|
+
eval: (script: string, numKeys: number, ...rest: string[]) => Promise<unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* Block until the request may pass or timeout is reached.
|
|
7
|
+
*/
|
|
8
|
+
blockingUntilReady?: {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
timeout: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The prefix to use for Redis keys.
|
|
14
|
+
*
|
|
15
|
+
* @default orpc:ratelimit:
|
|
16
|
+
*/
|
|
17
|
+
prefix?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Maximum number of requests allowed within the window.
|
|
20
|
+
*/
|
|
21
|
+
maxRequests: number;
|
|
22
|
+
/**
|
|
23
|
+
* The duration of the sliding window in milliseconds.
|
|
24
|
+
*/
|
|
25
|
+
window: number;
|
|
26
|
+
}
|
|
27
|
+
declare class RedisRatelimiter implements Ratelimiter {
|
|
28
|
+
private readonly eval;
|
|
29
|
+
private readonly prefix;
|
|
30
|
+
private readonly maxRequests;
|
|
31
|
+
private readonly window;
|
|
32
|
+
private readonly blockingUntilReady;
|
|
33
|
+
constructor(options: RedisRatelimiterOptions);
|
|
34
|
+
limit(key: string): Promise<Required<RatelimiterLimitResult>>;
|
|
35
|
+
private checkLimit;
|
|
36
|
+
private blockUntilReady;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { RedisRatelimiter };
|
|
40
|
+
export type { RedisRatelimiterOptions };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { fallback } from '@orpc/shared';
|
|
2
|
+
|
|
3
|
+
const SLIDING_WINDOW_LOG_LUA_SCRIPT = `
|
|
4
|
+
local key = KEYS[1]
|
|
5
|
+
local now_ms = tonumber(ARGV[1])
|
|
6
|
+
local window_ms = tonumber(ARGV[2])
|
|
7
|
+
local limit = tonumber(ARGV[3])
|
|
8
|
+
|
|
9
|
+
local window_start_ms = now_ms - window_ms
|
|
10
|
+
|
|
11
|
+
-- Remove old entries outside the current window
|
|
12
|
+
redis.call('ZREMRANGEBYSCORE', key, '-inf', window_start_ms)
|
|
13
|
+
|
|
14
|
+
-- Count requests in the current window
|
|
15
|
+
local current_count = redis.call('ZCARD', key)
|
|
16
|
+
|
|
17
|
+
-- Calculate reset time (end of current window)
|
|
18
|
+
local oldest_entry = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')
|
|
19
|
+
local reset_at
|
|
20
|
+
if #oldest_entry > 0 then
|
|
21
|
+
reset_at = tonumber(oldest_entry[2]) + window_ms
|
|
22
|
+
else
|
|
23
|
+
reset_at = now_ms + window_ms
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if current_count < limit then
|
|
27
|
+
-- Add current request
|
|
28
|
+
redis.call('ZADD', key, now_ms, now_ms .. ':' .. math.random())
|
|
29
|
+
redis.call('PEXPIRE', key, window_ms)
|
|
30
|
+
|
|
31
|
+
return {1, limit, limit - current_count - 1, reset_at}
|
|
32
|
+
else
|
|
33
|
+
return {0, limit, 0, reset_at}
|
|
34
|
+
end
|
|
35
|
+
`;
|
|
36
|
+
class RedisRatelimiter {
|
|
37
|
+
eval;
|
|
38
|
+
prefix;
|
|
39
|
+
maxRequests;
|
|
40
|
+
window;
|
|
41
|
+
blockingUntilReady;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.eval = options.eval;
|
|
44
|
+
this.prefix = fallback(options.prefix, "orpc:ratelimit:");
|
|
45
|
+
this.maxRequests = options.maxRequests;
|
|
46
|
+
this.window = options.window;
|
|
47
|
+
this.blockingUntilReady = options.blockingUntilReady;
|
|
48
|
+
}
|
|
49
|
+
async limit(key) {
|
|
50
|
+
const prefixedKey = `${this.prefix}${key}`;
|
|
51
|
+
if (this.blockingUntilReady?.enabled) {
|
|
52
|
+
return await this.blockUntilReady(prefixedKey, this.blockingUntilReady.timeout);
|
|
53
|
+
}
|
|
54
|
+
return await this.checkLimit(prefixedKey);
|
|
55
|
+
}
|
|
56
|
+
async checkLimit(key) {
|
|
57
|
+
const result = await this.eval(
|
|
58
|
+
SLIDING_WINDOW_LOG_LUA_SCRIPT,
|
|
59
|
+
1,
|
|
60
|
+
key,
|
|
61
|
+
Date.now().toString(),
|
|
62
|
+
this.window.toString(),
|
|
63
|
+
this.maxRequests.toString()
|
|
64
|
+
);
|
|
65
|
+
if (!Array.isArray(result) || result.length !== 4) {
|
|
66
|
+
throw new TypeError("Invalid response from rate limit script");
|
|
67
|
+
}
|
|
68
|
+
const numbers = result.map((item) => {
|
|
69
|
+
const num = Number(item);
|
|
70
|
+
if (!Number.isInteger(num)) {
|
|
71
|
+
throw new TypeError("Invalid response from rate limit script");
|
|
72
|
+
}
|
|
73
|
+
return num;
|
|
74
|
+
});
|
|
75
|
+
const [success, limit, remaining, reset] = numbers;
|
|
76
|
+
return {
|
|
77
|
+
success: success === 1,
|
|
78
|
+
limit,
|
|
79
|
+
remaining,
|
|
80
|
+
reset
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async blockUntilReady(key, timeoutMs) {
|
|
84
|
+
const deadlineAtMs = Date.now() + timeoutMs;
|
|
85
|
+
while (true) {
|
|
86
|
+
const result = await this.checkLimit(key);
|
|
87
|
+
if (result.success || result.reset > deadlineAtMs) {
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
await new Promise((resolve) => setTimeout(resolve, result.reset - Date.now()));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { RedisRatelimiter };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Ratelimit } from '@upstash/ratelimit';
|
|
2
|
+
import { a as Ratelimiter, R as RatelimiterLimitResult } from '../shared/experimental-ratelimit.C8zlaHwR.mjs';
|
|
3
|
+
|
|
4
|
+
interface UpstashRatelimiterOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Block until the request may pass or timeout is reached.
|
|
7
|
+
*/
|
|
8
|
+
blockingUntilReady?: {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
timeout: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* For the MultiRegion setup we do some synchronizing in the background, after returning the current limit.
|
|
14
|
+
* Or when analytics is enabled, we send the analytics asynchronously after returning the limit.
|
|
15
|
+
* In most case you can simply ignore this.
|
|
16
|
+
*
|
|
17
|
+
* On Vercel Edge or Cloudflare workers, you might need `.bind` before assign:
|
|
18
|
+
* ```ts
|
|
19
|
+
* const ratelimiter = new UpstashRatelimiter(ratelimit, {
|
|
20
|
+
* waitUntil: ctx.waitUntil.bind(ctx),
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
waitUntil?: (promise: Promise<any>) => any;
|
|
25
|
+
}
|
|
26
|
+
declare class UpstashRatelimiter implements Ratelimiter {
|
|
27
|
+
private readonly ratelimit;
|
|
28
|
+
private blockingUntilReady;
|
|
29
|
+
private waitUntil;
|
|
30
|
+
constructor(ratelimit: Ratelimit, options?: UpstashRatelimiterOptions);
|
|
31
|
+
limit(key: string): Promise<Required<RatelimiterLimitResult>>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { UpstashRatelimiter };
|
|
35
|
+
export type { UpstashRatelimiterOptions };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Ratelimit } from '@upstash/ratelimit';
|
|
2
|
+
import { a as Ratelimiter, R as RatelimiterLimitResult } from '../shared/experimental-ratelimit.C8zlaHwR.js';
|
|
3
|
+
|
|
4
|
+
interface UpstashRatelimiterOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Block until the request may pass or timeout is reached.
|
|
7
|
+
*/
|
|
8
|
+
blockingUntilReady?: {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
timeout: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* For the MultiRegion setup we do some synchronizing in the background, after returning the current limit.
|
|
14
|
+
* Or when analytics is enabled, we send the analytics asynchronously after returning the limit.
|
|
15
|
+
* In most case you can simply ignore this.
|
|
16
|
+
*
|
|
17
|
+
* On Vercel Edge or Cloudflare workers, you might need `.bind` before assign:
|
|
18
|
+
* ```ts
|
|
19
|
+
* const ratelimiter = new UpstashRatelimiter(ratelimit, {
|
|
20
|
+
* waitUntil: ctx.waitUntil.bind(ctx),
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
waitUntil?: (promise: Promise<any>) => any;
|
|
25
|
+
}
|
|
26
|
+
declare class UpstashRatelimiter implements Ratelimiter {
|
|
27
|
+
private readonly ratelimit;
|
|
28
|
+
private blockingUntilReady;
|
|
29
|
+
private waitUntil;
|
|
30
|
+
constructor(ratelimit: Ratelimit, options?: UpstashRatelimiterOptions);
|
|
31
|
+
limit(key: string): Promise<Required<RatelimiterLimitResult>>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { UpstashRatelimiter };
|
|
35
|
+
export type { UpstashRatelimiterOptions };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class UpstashRatelimiter {
|
|
2
|
+
constructor(ratelimit, options = {}) {
|
|
3
|
+
this.ratelimit = ratelimit;
|
|
4
|
+
this.blockingUntilReady = options.blockingUntilReady;
|
|
5
|
+
this.waitUntil = options.waitUntil;
|
|
6
|
+
}
|
|
7
|
+
blockingUntilReady;
|
|
8
|
+
waitUntil;
|
|
9
|
+
async limit(key) {
|
|
10
|
+
const result = this.blockingUntilReady?.enabled ? await this.ratelimit.blockUntilReady(key, this.blockingUntilReady.timeout) : await this.ratelimit.limit(key);
|
|
11
|
+
this.waitUntil?.(result.pending);
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { UpstashRatelimiter };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Context, Meta, MiddlewareOptions, Middleware } from '@orpc/server';
|
|
2
|
+
import { StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
|
|
3
|
+
import { R as RatelimiterLimitResult, a as Ratelimiter } from './shared/experimental-ratelimit.C8zlaHwR.mjs';
|
|
4
|
+
import { Value, Promisable } from '@orpc/shared';
|
|
5
|
+
|
|
6
|
+
declare const RATELIMIT_HANDLER_CONTEXT_SYMBOL: unique symbol;
|
|
7
|
+
interface RatelimitHandlerPluginContext {
|
|
8
|
+
[RATELIMIT_HANDLER_CONTEXT_SYMBOL]?: {
|
|
9
|
+
/**
|
|
10
|
+
* The result of the ratelimiter after applying limits
|
|
11
|
+
*/
|
|
12
|
+
ratelimitResult?: RatelimiterLimitResult;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Automatically adds HTTP rate-limiting headers (RateLimit-* and Retry-After) to responses
|
|
17
|
+
* when used with middleware created by createRatelimitMiddleware.
|
|
18
|
+
*
|
|
19
|
+
* @see {@link https://orpc.unnoq.com/docs/helpers/ratelimit#handler-plugin Ratelimit handler plugin}
|
|
20
|
+
*/
|
|
21
|
+
declare class RatelimitHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
|
|
22
|
+
init(options: StandardHandlerOptions<T>): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
declare const RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL: unique symbol;
|
|
26
|
+
interface RatelimiterMiddlewareContext {
|
|
27
|
+
[RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL]?: {
|
|
28
|
+
/**
|
|
29
|
+
* The applied limits in this request, mainly for deduplication purposes
|
|
30
|
+
*/
|
|
31
|
+
limits: {
|
|
32
|
+
limiter: Ratelimiter;
|
|
33
|
+
key: string;
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
interface CreateRatelimitMiddlewareOptions<TInContext extends Context, TInput, TMeta extends Meta> {
|
|
38
|
+
/**
|
|
39
|
+
* The rule set to use for rate limiting
|
|
40
|
+
*/
|
|
41
|
+
limiter: Value<Promisable<Ratelimiter>, [middlewareOptions: MiddlewareOptions<TInContext, unknown, Record<never, never>, TMeta>, input: TInput]>;
|
|
42
|
+
/**
|
|
43
|
+
* The key to identify the user/requester
|
|
44
|
+
*/
|
|
45
|
+
key: Value<Promisable<string>, [middlewareOptions: MiddlewareOptions<TInContext, unknown, Record<never, never>, TMeta>, input: TInput]>;
|
|
46
|
+
/**
|
|
47
|
+
* If your ratelimit middleware is used multiple times
|
|
48
|
+
* or you invoke a procedure inside another procedure (shared the same context) that also has
|
|
49
|
+
* ratelimit middleware **with the same limiter and key**, this option
|
|
50
|
+
* will ensure that the limit is only applied once per request.
|
|
51
|
+
*
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
54
|
+
dedupe?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates a middleware that enforces rate limits in oRPC procedures.
|
|
58
|
+
* Supports per-request deduplication and integrates with the ratelimit handler plugin.
|
|
59
|
+
*
|
|
60
|
+
* @see {@link https://orpc.unnoq.com/docs/helpers/ratelimit#createratelimitmiddleware Ratelimit middleware}
|
|
61
|
+
*/
|
|
62
|
+
declare function createRatelimitMiddleware<TInContext extends Context, TInput = unknown, TMeta extends Meta = Record<never, never>>({ dedupe, ...options }: CreateRatelimitMiddlewareOptions<TInContext, TInput, TMeta>): Middleware<TInContext, Record<never, never>, TInput, any, any, TMeta>;
|
|
63
|
+
|
|
64
|
+
export { RATELIMIT_HANDLER_CONTEXT_SYMBOL, RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL, RatelimitHandlerPlugin, Ratelimiter, RatelimiterLimitResult, createRatelimitMiddleware };
|
|
65
|
+
export type { CreateRatelimitMiddlewareOptions, RatelimitHandlerPluginContext, RatelimiterMiddlewareContext };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Context, Meta, MiddlewareOptions, Middleware } from '@orpc/server';
|
|
2
|
+
import { StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
|
|
3
|
+
import { R as RatelimiterLimitResult, a as Ratelimiter } from './shared/experimental-ratelimit.C8zlaHwR.js';
|
|
4
|
+
import { Value, Promisable } from '@orpc/shared';
|
|
5
|
+
|
|
6
|
+
declare const RATELIMIT_HANDLER_CONTEXT_SYMBOL: unique symbol;
|
|
7
|
+
interface RatelimitHandlerPluginContext {
|
|
8
|
+
[RATELIMIT_HANDLER_CONTEXT_SYMBOL]?: {
|
|
9
|
+
/**
|
|
10
|
+
* The result of the ratelimiter after applying limits
|
|
11
|
+
*/
|
|
12
|
+
ratelimitResult?: RatelimiterLimitResult;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Automatically adds HTTP rate-limiting headers (RateLimit-* and Retry-After) to responses
|
|
17
|
+
* when used with middleware created by createRatelimitMiddleware.
|
|
18
|
+
*
|
|
19
|
+
* @see {@link https://orpc.unnoq.com/docs/helpers/ratelimit#handler-plugin Ratelimit handler plugin}
|
|
20
|
+
*/
|
|
21
|
+
declare class RatelimitHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
|
|
22
|
+
init(options: StandardHandlerOptions<T>): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
declare const RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL: unique symbol;
|
|
26
|
+
interface RatelimiterMiddlewareContext {
|
|
27
|
+
[RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL]?: {
|
|
28
|
+
/**
|
|
29
|
+
* The applied limits in this request, mainly for deduplication purposes
|
|
30
|
+
*/
|
|
31
|
+
limits: {
|
|
32
|
+
limiter: Ratelimiter;
|
|
33
|
+
key: string;
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
interface CreateRatelimitMiddlewareOptions<TInContext extends Context, TInput, TMeta extends Meta> {
|
|
38
|
+
/**
|
|
39
|
+
* The rule set to use for rate limiting
|
|
40
|
+
*/
|
|
41
|
+
limiter: Value<Promisable<Ratelimiter>, [middlewareOptions: MiddlewareOptions<TInContext, unknown, Record<never, never>, TMeta>, input: TInput]>;
|
|
42
|
+
/**
|
|
43
|
+
* The key to identify the user/requester
|
|
44
|
+
*/
|
|
45
|
+
key: Value<Promisable<string>, [middlewareOptions: MiddlewareOptions<TInContext, unknown, Record<never, never>, TMeta>, input: TInput]>;
|
|
46
|
+
/**
|
|
47
|
+
* If your ratelimit middleware is used multiple times
|
|
48
|
+
* or you invoke a procedure inside another procedure (shared the same context) that also has
|
|
49
|
+
* ratelimit middleware **with the same limiter and key**, this option
|
|
50
|
+
* will ensure that the limit is only applied once per request.
|
|
51
|
+
*
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
54
|
+
dedupe?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates a middleware that enforces rate limits in oRPC procedures.
|
|
58
|
+
* Supports per-request deduplication and integrates with the ratelimit handler plugin.
|
|
59
|
+
*
|
|
60
|
+
* @see {@link https://orpc.unnoq.com/docs/helpers/ratelimit#createratelimitmiddleware Ratelimit middleware}
|
|
61
|
+
*/
|
|
62
|
+
declare function createRatelimitMiddleware<TInContext extends Context, TInput = unknown, TMeta extends Meta = Record<never, never>>({ dedupe, ...options }: CreateRatelimitMiddlewareOptions<TInContext, TInput, TMeta>): Middleware<TInContext, Record<never, never>, TInput, any, any, TMeta>;
|
|
63
|
+
|
|
64
|
+
export { RATELIMIT_HANDLER_CONTEXT_SYMBOL, RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL, RatelimitHandlerPlugin, Ratelimiter, RatelimiterLimitResult, createRatelimitMiddleware };
|
|
65
|
+
export type { CreateRatelimitMiddlewareOptions, RatelimitHandlerPluginContext, RatelimiterMiddlewareContext };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ORPCError } from '@orpc/server';
|
|
2
|
+
import { value, toArray } from '@orpc/shared';
|
|
3
|
+
|
|
4
|
+
const RATELIMIT_HANDLER_CONTEXT_SYMBOL = Symbol("ORPC_RATE_LIMIT_HANDLER_CONTEXT");
|
|
5
|
+
class RatelimitHandlerPlugin {
|
|
6
|
+
init(options) {
|
|
7
|
+
options.rootInterceptors ??= [];
|
|
8
|
+
options.rootInterceptors.unshift(async (interceptorOptions) => {
|
|
9
|
+
const handlerContext = {};
|
|
10
|
+
const result = await interceptorOptions.next({
|
|
11
|
+
...interceptorOptions,
|
|
12
|
+
context: {
|
|
13
|
+
...interceptorOptions.context,
|
|
14
|
+
[RATELIMIT_HANDLER_CONTEXT_SYMBOL]: handlerContext
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
if (result.matched && handlerContext.ratelimitResult) {
|
|
18
|
+
return {
|
|
19
|
+
...result,
|
|
20
|
+
response: {
|
|
21
|
+
...result.response,
|
|
22
|
+
headers: {
|
|
23
|
+
...result.response.headers,
|
|
24
|
+
"ratelimit-limit": handlerContext.ratelimitResult.limit?.toString(),
|
|
25
|
+
"ratelimit-remaining": handlerContext.ratelimitResult.remaining?.toString(),
|
|
26
|
+
"ratelimit-reset": handlerContext.ratelimitResult.reset?.toString(),
|
|
27
|
+
"retry-after": !handlerContext.ratelimitResult.success && result.response.status === 429 && handlerContext.ratelimitResult.reset !== void 0 ? Math.max(0, Math.ceil((handlerContext.ratelimitResult.reset - Date.now()) / 1e3)).toString() : void 0
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL = Symbol("ORPC_RATE_LIMIT_MIDDLEWARE_CONTEXT");
|
|
38
|
+
function createRatelimitMiddleware({ dedupe = true, ...options }) {
|
|
39
|
+
return async function ratelimit(middlewareOptions, input) {
|
|
40
|
+
const [limiter, key] = await Promise.all([
|
|
41
|
+
value(options.limiter, middlewareOptions, input),
|
|
42
|
+
value(options.key, middlewareOptions, input)
|
|
43
|
+
]);
|
|
44
|
+
const middlewareContext = middlewareOptions.context[RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL];
|
|
45
|
+
if (dedupe && middlewareContext?.limits.some((l) => l.key === key && l.limiter === limiter)) {
|
|
46
|
+
return middlewareOptions.next();
|
|
47
|
+
}
|
|
48
|
+
const result = await limiter.limit(key);
|
|
49
|
+
const pluginContext = middlewareOptions.context[RATELIMIT_HANDLER_CONTEXT_SYMBOL];
|
|
50
|
+
if (pluginContext) {
|
|
51
|
+
pluginContext.ratelimitResult = result;
|
|
52
|
+
}
|
|
53
|
+
if (!result.success) {
|
|
54
|
+
throw new ORPCError("TOO_MANY_REQUESTS", {
|
|
55
|
+
data: {
|
|
56
|
+
limit: result.limit,
|
|
57
|
+
remaining: result.remaining,
|
|
58
|
+
reset: result.reset
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return middlewareOptions.next({
|
|
63
|
+
context: {
|
|
64
|
+
[RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL]: {
|
|
65
|
+
...middlewareContext,
|
|
66
|
+
limits: [
|
|
67
|
+
...toArray(middlewareContext?.limits),
|
|
68
|
+
{ limiter, key }
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { RATELIMIT_HANDLER_CONTEXT_SYMBOL, RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL, RatelimitHandlerPlugin, createRatelimitMiddleware };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface RatelimiterLimitResult {
|
|
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 Ratelimiter {
|
|
20
|
+
limit(key: string): Promise<RatelimiterLimitResult>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type { RatelimiterLimitResult as R, Ratelimiter as a };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
interface RatelimiterLimitResult {
|
|
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 Ratelimiter {
|
|
20
|
+
limit(key: string): Promise<RatelimiterLimitResult>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type { RatelimiterLimitResult as R, Ratelimiter as a };
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@orpc/experimental-ratelimit",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.0-next.3312214",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://orpc.unnoq.com",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/unnoq/orpc.git",
|
|
10
|
+
"directory": "packages/ratelimit"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"orpc",
|
|
14
|
+
"ratelimit"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.mts",
|
|
19
|
+
"import": "./dist/index.mjs",
|
|
20
|
+
"default": "./dist/index.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./memory": {
|
|
23
|
+
"types": "./dist/adapters/memory.d.mts",
|
|
24
|
+
"import": "./dist/adapters/memory.mjs",
|
|
25
|
+
"default": "./dist/adapters/memory.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./redis": {
|
|
28
|
+
"types": "./dist/adapters/redis.d.mts",
|
|
29
|
+
"import": "./dist/adapters/redis.mjs",
|
|
30
|
+
"default": "./dist/adapters/redis.mjs"
|
|
31
|
+
},
|
|
32
|
+
"./upstash-ratelimit": {
|
|
33
|
+
"types": "./dist/adapters/upstash-ratelimit.d.mts",
|
|
34
|
+
"import": "./dist/adapters/upstash-ratelimit.mjs",
|
|
35
|
+
"default": "./dist/adapters/upstash-ratelimit.mjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@upstash/ratelimit": ">=2.0.7"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"@upstash/ratelimit": {
|
|
46
|
+
"optional": true
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@orpc/client": "0.0.0-next.3312214",
|
|
51
|
+
"@orpc/standard-server": "0.0.0-next.3312214",
|
|
52
|
+
"@orpc/shared": "0.0.0-next.3312214",
|
|
53
|
+
"@orpc/server": "0.0.0-next.3312214"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@upstash/ratelimit": "^2.0.7",
|
|
57
|
+
"@upstash/redis": "^1.35.6",
|
|
58
|
+
"ioredis": "^5.8.2"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "unbuild",
|
|
62
|
+
"build:watch": "pnpm run build --watch",
|
|
63
|
+
"type:check": "tsc -b"
|
|
64
|
+
}
|
|
65
|
+
}
|