@orpc/experimental-ratelimit 0.0.0 → 1.11.0
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/dist/adapters/memory.mjs +4 -2
- package/dist/adapters/redis.d.mts +1 -1
- package/dist/adapters/redis.d.ts +1 -1
- package/dist/adapters/redis.mjs +8 -1
- package/dist/adapters/upstash-ratelimit.d.mts +1 -1
- package/dist/adapters/upstash-ratelimit.d.ts +1 -1
- package/dist/index.d.mts +15 -8
- package/dist/index.d.ts +15 -8
- package/dist/index.mjs +4 -8
- package/package.json +5 -5
package/dist/adapters/memory.mjs
CHANGED
|
@@ -25,7 +25,8 @@ class MemoryRatelimiter {
|
|
|
25
25
|
this.lastCleanupTime = now;
|
|
26
26
|
const windowStart = now - this.window;
|
|
27
27
|
for (const [key, timestamps] of this.store) {
|
|
28
|
-
|
|
28
|
+
const idx = timestamps.findIndex((timestamp) => timestamp >= windowStart);
|
|
29
|
+
timestamps.splice(0, idx === -1 ? timestamps.length : idx);
|
|
29
30
|
if (timestamps.length === 0) {
|
|
30
31
|
this.store.delete(key);
|
|
31
32
|
}
|
|
@@ -36,7 +37,8 @@ class MemoryRatelimiter {
|
|
|
36
37
|
const windowStart = now - this.window;
|
|
37
38
|
let timestamps = this.store.get(key);
|
|
38
39
|
if (timestamps) {
|
|
39
|
-
|
|
40
|
+
const idx = timestamps.findIndex((timestamp) => timestamp >= windowStart);
|
|
41
|
+
timestamps.splice(0, idx === -1 ? timestamps.length : idx);
|
|
40
42
|
} else {
|
|
41
43
|
this.store.set(key, timestamps = []);
|
|
42
44
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as Ratelimiter, R as RatelimiterLimitResult } from '../shared/experimental-ratelimit.C8zlaHwR.mjs';
|
|
2
2
|
|
|
3
3
|
interface RedisRatelimiterOptions {
|
|
4
|
-
eval: (script: string, numKeys: number, ...
|
|
4
|
+
eval: (script: string, numKeys: number, ...rest: string[]) => Promise<unknown>;
|
|
5
5
|
/**
|
|
6
6
|
* Block until the request may pass or timeout is reached.
|
|
7
7
|
*/
|
package/dist/adapters/redis.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as Ratelimiter, R as RatelimiterLimitResult } from '../shared/experimental-ratelimit.C8zlaHwR.js';
|
|
2
2
|
|
|
3
3
|
interface RedisRatelimiterOptions {
|
|
4
|
-
eval: (script: string, numKeys: number, ...
|
|
4
|
+
eval: (script: string, numKeys: number, ...rest: string[]) => Promise<unknown>;
|
|
5
5
|
/**
|
|
6
6
|
* Block until the request may pass or timeout is reached.
|
|
7
7
|
*/
|
package/dist/adapters/redis.mjs
CHANGED
|
@@ -65,7 +65,14 @@ class RedisRatelimiter {
|
|
|
65
65
|
if (!Array.isArray(result) || result.length !== 4) {
|
|
66
66
|
throw new TypeError("Invalid response from rate limit script");
|
|
67
67
|
}
|
|
68
|
-
const
|
|
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;
|
|
69
76
|
return {
|
|
70
77
|
success: success === 1,
|
|
71
78
|
limit,
|
|
@@ -17,7 +17,7 @@ interface UpstashRatelimiterOptions {
|
|
|
17
17
|
* On Vercel Edge or Cloudflare workers, you might need `.bind` before assign:
|
|
18
18
|
* ```ts
|
|
19
19
|
* const ratelimiter = new UpstashRatelimiter(ratelimit, {
|
|
20
|
-
*
|
|
20
|
+
* waitUntil: ctx.waitUntil.bind(ctx),
|
|
21
21
|
* })
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
@@ -17,7 +17,7 @@ interface UpstashRatelimiterOptions {
|
|
|
17
17
|
* On Vercel Edge or Cloudflare workers, you might need `.bind` before assign:
|
|
18
18
|
* ```ts
|
|
19
19
|
* const ratelimiter = new UpstashRatelimiter(ratelimit, {
|
|
20
|
-
*
|
|
20
|
+
* waitUntil: ctx.waitUntil.bind(ctx),
|
|
21
21
|
* })
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
package/dist/index.d.mts
CHANGED
|
@@ -4,7 +4,7 @@ import { R as RatelimiterLimitResult, a as Ratelimiter } from './shared/experime
|
|
|
4
4
|
import { Value, Promisable } from '@orpc/shared';
|
|
5
5
|
|
|
6
6
|
declare const RATELIMIT_HANDLER_CONTEXT_SYMBOL: unique symbol;
|
|
7
|
-
interface RatelimitHandlerPluginContext
|
|
7
|
+
interface RatelimitHandlerPluginContext {
|
|
8
8
|
[RATELIMIT_HANDLER_CONTEXT_SYMBOL]?: {
|
|
9
9
|
/**
|
|
10
10
|
* The result of the ratelimiter after applying limits
|
|
@@ -12,12 +12,13 @@ interface RatelimitHandlerPluginContext extends Context {
|
|
|
12
12
|
ratelimitResult?: RatelimiterLimitResult;
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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> {
|
|
21
22
|
init(options: StandardHandlerOptions<T>): void;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -43,7 +44,7 @@ interface CreateRatelimitMiddlewareOptions<TInContext extends Context, TInput, T
|
|
|
43
44
|
*/
|
|
44
45
|
key: Value<Promisable<string>, [middlewareOptions: MiddlewareOptions<TInContext, unknown, Record<never, never>, TMeta>, input: TInput]>;
|
|
45
46
|
/**
|
|
46
|
-
* If
|
|
47
|
+
* If your ratelimit middleware is used multiple times
|
|
47
48
|
* or you invoke a procedure inside another procedure (shared the same context) that also has
|
|
48
49
|
* ratelimit middleware **with the same limiter and key**, this option
|
|
49
50
|
* will ensure that the limit is only applied once per request.
|
|
@@ -52,6 +53,12 @@ interface CreateRatelimitMiddlewareOptions<TInContext extends Context, TInput, T
|
|
|
52
53
|
*/
|
|
53
54
|
dedupe?: boolean;
|
|
54
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
|
+
*/
|
|
55
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>;
|
|
56
63
|
|
|
57
64
|
export { RATELIMIT_HANDLER_CONTEXT_SYMBOL, RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL, RatelimitHandlerPlugin, Ratelimiter, RatelimiterLimitResult, createRatelimitMiddleware };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { R as RatelimiterLimitResult, a as Ratelimiter } from './shared/experime
|
|
|
4
4
|
import { Value, Promisable } from '@orpc/shared';
|
|
5
5
|
|
|
6
6
|
declare const RATELIMIT_HANDLER_CONTEXT_SYMBOL: unique symbol;
|
|
7
|
-
interface RatelimitHandlerPluginContext
|
|
7
|
+
interface RatelimitHandlerPluginContext {
|
|
8
8
|
[RATELIMIT_HANDLER_CONTEXT_SYMBOL]?: {
|
|
9
9
|
/**
|
|
10
10
|
* The result of the ratelimiter after applying limits
|
|
@@ -12,12 +12,13 @@ interface RatelimitHandlerPluginContext extends Context {
|
|
|
12
12
|
ratelimitResult?: RatelimiterLimitResult;
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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> {
|
|
21
22
|
init(options: StandardHandlerOptions<T>): void;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -43,7 +44,7 @@ interface CreateRatelimitMiddlewareOptions<TInContext extends Context, TInput, T
|
|
|
43
44
|
*/
|
|
44
45
|
key: Value<Promisable<string>, [middlewareOptions: MiddlewareOptions<TInContext, unknown, Record<never, never>, TMeta>, input: TInput]>;
|
|
45
46
|
/**
|
|
46
|
-
* If
|
|
47
|
+
* If your ratelimit middleware is used multiple times
|
|
47
48
|
* or you invoke a procedure inside another procedure (shared the same context) that also has
|
|
48
49
|
* ratelimit middleware **with the same limiter and key**, this option
|
|
49
50
|
* will ensure that the limit is only applied once per request.
|
|
@@ -52,6 +53,12 @@ interface CreateRatelimitMiddlewareOptions<TInContext extends Context, TInput, T
|
|
|
52
53
|
*/
|
|
53
54
|
dedupe?: boolean;
|
|
54
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
|
+
*/
|
|
55
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>;
|
|
56
63
|
|
|
57
64
|
export { RATELIMIT_HANDLER_CONTEXT_SYMBOL, RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL, RatelimitHandlerPlugin, Ratelimiter, RatelimiterLimitResult, createRatelimitMiddleware };
|
package/dist/index.mjs
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
+
import { COMMON_ORPC_ERROR_DEFS } from '@orpc/client';
|
|
1
2
|
import { ORPCError } from '@orpc/server';
|
|
2
3
|
import { value, toArray } from '@orpc/shared';
|
|
3
4
|
|
|
4
5
|
const RATELIMIT_HANDLER_CONTEXT_SYMBOL = Symbol("ORPC_RATE_LIMIT_HANDLER_CONTEXT");
|
|
5
6
|
class RatelimitHandlerPlugin {
|
|
6
|
-
/**
|
|
7
|
-
* this plugin should lower priority than response headers plugin,
|
|
8
|
-
* if user want override rate limit headers
|
|
9
|
-
*/
|
|
10
|
-
order = 1e5;
|
|
11
7
|
init(options) {
|
|
12
8
|
options.rootInterceptors ??= [];
|
|
13
|
-
options.rootInterceptors.
|
|
9
|
+
options.rootInterceptors.unshift(async (interceptorOptions) => {
|
|
14
10
|
const handlerContext = {};
|
|
15
11
|
const result = await interceptorOptions.next({
|
|
16
12
|
...interceptorOptions,
|
|
@@ -29,7 +25,7 @@ class RatelimitHandlerPlugin {
|
|
|
29
25
|
"ratelimit-limit": handlerContext.ratelimitResult.limit?.toString(),
|
|
30
26
|
"ratelimit-remaining": handlerContext.ratelimitResult.remaining?.toString(),
|
|
31
27
|
"ratelimit-reset": handlerContext.ratelimitResult.reset?.toString(),
|
|
32
|
-
"retry-after": !handlerContext.ratelimitResult.success && result.response.status ===
|
|
28
|
+
"retry-after": !handlerContext.ratelimitResult.success && result.response.status === COMMON_ORPC_ERROR_DEFS.TOO_MANY_REQUESTS.status && handlerContext.ratelimitResult.reset !== void 0 ? Math.max(0, Math.ceil((handlerContext.ratelimitResult.reset - Date.now()) / 1e3)).toString() : void 0
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
31
|
};
|
|
@@ -67,7 +63,7 @@ function createRatelimitMiddleware({ dedupe = true, ...options }) {
|
|
|
67
63
|
return middlewareOptions.next({
|
|
68
64
|
context: {
|
|
69
65
|
[RATELIMIT_MIDDLEWARE_CONTEXT_SYMBOL]: {
|
|
70
|
-
...
|
|
66
|
+
...middlewareContext,
|
|
71
67
|
limits: [
|
|
72
68
|
...toArray(middlewareContext?.limits),
|
|
73
69
|
{ limiter, key }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orpc/experimental-ratelimit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "1.11.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://orpc.unnoq.com",
|
|
7
7
|
"repository": {
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@orpc/client": "1.
|
|
51
|
-
"@orpc/server": "1.
|
|
52
|
-
"@orpc/
|
|
53
|
-
"@orpc/
|
|
50
|
+
"@orpc/client": "1.11.0",
|
|
51
|
+
"@orpc/server": "1.11.0",
|
|
52
|
+
"@orpc/standard-server": "1.11.0",
|
|
53
|
+
"@orpc/shared": "1.11.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@upstash/ratelimit": "^2.0.7",
|