@atproto/xrpc-server 0.7.19 → 0.8.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/CHANGELOG.md +18 -0
- package/dist/rate-limiter.d.ts +48 -16
- package/dist/rate-limiter.d.ts.map +1 -1
- package/dist/rate-limiter.js +130 -53
- package/dist/rate-limiter.js.map +1 -1
- package/dist/server.d.ts +5 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +67 -116
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +7 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +3 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +12 -1
- package/dist/util.js.map +1 -1
- package/package.json +1 -1
- package/src/rate-limiter.ts +125 -51
- package/src/server.ts +94 -149
- package/src/types.ts +12 -2
- package/src/util.ts +15 -1
- package/tests/rate-limiter.test.ts +2 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @atproto/xrpc-server
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#3886](https://github.com/bluesky-social/atproto/pull/3886) [`0286f7ee3`](https://github.com/bluesky-social/atproto/commit/0286f7ee3d56ae50cfe0b70add60cf4785587b3c) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove `bypassSecret` and `bypassIps` from rate limiter options.
|
|
8
|
+
|
|
9
|
+
- [#3886](https://github.com/bluesky-social/atproto/pull/3886) [`0286f7ee3`](https://github.com/bluesky-social/atproto/commit/0286f7ee3d56ae50cfe0b70add60cf4785587b3c) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Now applies global rate limiter to every single route. Previously, the global rate limiter was not applied if a route defined a local rate limit option.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#3884](https://github.com/bluesky-social/atproto/pull/3884) [`b675fbbf1`](https://github.com/bluesky-social/atproto/commit/b675fbbf17e000fad2b38a52db550702830a807d) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Return an error if the wrong HTTP verb is used for a known XRPC method, even when a `catchall` is provided.
|
|
14
|
+
|
|
15
|
+
- [#3886](https://github.com/bluesky-social/atproto/pull/3886) [`0286f7ee3`](https://github.com/bluesky-social/atproto/commit/0286f7ee3d56ae50cfe0b70add60cf4785587b3c) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add optional `bypass` callback to global rate limits options
|
|
16
|
+
|
|
17
|
+
- [#3886](https://github.com/bluesky-social/atproto/pull/3886) [`0286f7ee3`](https://github.com/bluesky-social/atproto/commit/0286f7ee3d56ae50cfe0b70add60cf4785587b3c) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Refactor route rate limiter builder
|
|
18
|
+
|
|
19
|
+
- [#3886](https://github.com/bluesky-social/atproto/pull/3886) [`0286f7ee3`](https://github.com/bluesky-social/atproto/commit/0286f7ee3d56ae50cfe0b70add60cf4785587b3c) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Performance improvement: avoid computing rate limit bypass multiple times per request
|
|
20
|
+
|
|
3
21
|
## 0.7.19
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/rate-limiter.d.ts
CHANGED
|
@@ -1,36 +1,68 @@
|
|
|
1
1
|
import { RateLimiterAbstract, RateLimiterRes } from 'rate-limiter-flexible';
|
|
2
|
-
import { CalcKeyFn, CalcPointsFn, RateLimitExceededError, RateLimiterConsume, RateLimiterI, RateLimiterReset, RateLimiterStatus, XRPCReqContext } from './types';
|
|
2
|
+
import { CalcKeyFn, CalcPointsFn, RateLimitExceededError, RateLimiterConsume, RateLimiterConsumeOptions, RateLimiterI, RateLimiterReset, RateLimiterResetOptions, RateLimiterStatus, XRPCReqContext } from './types';
|
|
3
3
|
export type RateLimiterOpts = {
|
|
4
4
|
keyPrefix: string;
|
|
5
5
|
durationMs: number;
|
|
6
6
|
points: number;
|
|
7
|
-
bypassSecret?: string;
|
|
8
|
-
bypassIps?: string[];
|
|
9
7
|
calcKey?: CalcKeyFn;
|
|
10
8
|
calcPoints?: CalcPointsFn;
|
|
11
9
|
failClosed?: boolean;
|
|
12
10
|
};
|
|
13
11
|
export declare class RateLimiter implements RateLimiterI {
|
|
14
12
|
limiter: RateLimiterAbstract;
|
|
15
|
-
private bypassSecret?;
|
|
16
|
-
private bypassIps?;
|
|
17
13
|
private failClosed?;
|
|
18
14
|
calcKey: CalcKeyFn;
|
|
19
15
|
calcPoints: CalcPointsFn;
|
|
20
16
|
constructor(limiter: RateLimiterAbstract, opts: RateLimiterOpts);
|
|
21
17
|
static memory(opts: RateLimiterOpts): RateLimiter;
|
|
22
18
|
static redis(storeClient: unknown, opts: RateLimiterOpts): RateLimiter;
|
|
23
|
-
consume(ctx: XRPCReqContext, opts?:
|
|
24
|
-
|
|
25
|
-
calcPoints?: CalcPointsFn;
|
|
26
|
-
}): Promise<RateLimiterStatus | RateLimitExceededError | null>;
|
|
27
|
-
reset(ctx: XRPCReqContext, opts?: {
|
|
28
|
-
calcKey?: CalcKeyFn;
|
|
29
|
-
}): Promise<void>;
|
|
19
|
+
consume(ctx: XRPCReqContext, opts?: RateLimiterConsumeOptions): Promise<RateLimiterStatus | RateLimitExceededError | null>;
|
|
20
|
+
reset(ctx: XRPCReqContext, opts?: RateLimiterResetOptions): Promise<void>;
|
|
30
21
|
}
|
|
31
22
|
export declare const formatLimiterStatus: (limiter: RateLimiterAbstract, res: RateLimiterRes) => RateLimiterStatus;
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
export type WrappedRateLimiterOptions = {
|
|
24
|
+
calcKey?: CalcKeyFn;
|
|
25
|
+
calcPoints?: CalcPointsFn;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Wraps a {@link RateLimiterI} instance with custom key and points calculation
|
|
29
|
+
* functions.
|
|
30
|
+
*/
|
|
31
|
+
export declare class WrappedRateLimiter implements RateLimiterI {
|
|
32
|
+
private readonly rateLimiter;
|
|
33
|
+
private readonly options;
|
|
34
|
+
private constructor();
|
|
35
|
+
consume(ctx: XRPCReqContext, opts?: RateLimiterConsumeOptions): Promise<RateLimiterStatus | RateLimitExceededError | null>;
|
|
36
|
+
reset(ctx: XRPCReqContext, opts?: RateLimiterResetOptions): Promise<void>;
|
|
37
|
+
static from(rateLimiter: RateLimiterI, { calcKey, calcPoints }?: WrappedRateLimiterOptions): RateLimiterI;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Combines multiple rate limiters into one.
|
|
41
|
+
*
|
|
42
|
+
* The combined rate limiter will return the tightest (most restrictive) of all
|
|
43
|
+
* the provided rate limiters.
|
|
44
|
+
*/
|
|
45
|
+
export declare class CombinedRateLimiter implements RateLimiterI {
|
|
46
|
+
private readonly rateLimiters;
|
|
47
|
+
private constructor();
|
|
48
|
+
consume(ctx: XRPCReqContext, opts?: RateLimiterConsumeOptions): Promise<RateLimiterStatus | RateLimitExceededError | null>;
|
|
49
|
+
reset(ctx: XRPCReqContext, opts?: RateLimiterResetOptions): Promise<void>;
|
|
50
|
+
static from(rateLimiters: readonly RateLimiterI[]): RateLimiterI | undefined;
|
|
51
|
+
}
|
|
52
|
+
export type RouteRateLimiterOptions = {
|
|
53
|
+
bypass?: (ctx: XRPCReqContext) => boolean;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Wraps a {@link RateLimiterI} interface into a class that will apply the
|
|
57
|
+
* appropriate headers to the response if a limit is exceeded.
|
|
58
|
+
*/
|
|
59
|
+
export declare class RouteRateLimiter implements RateLimiterI {
|
|
60
|
+
private readonly rateLimiter;
|
|
61
|
+
private readonly options;
|
|
62
|
+
constructor(rateLimiter: RateLimiterI, options?: Readonly<RouteRateLimiterOptions>);
|
|
63
|
+
handle(ctx: XRPCReqContext): Promise<RateLimiterStatus | null>;
|
|
64
|
+
consume(...args: Parameters<RateLimiterConsume>): Promise<RateLimiterStatus | RateLimitExceededError | null>;
|
|
65
|
+
reset(...args: Parameters<RateLimiterReset>): Promise<void>;
|
|
66
|
+
static from(rateLimiters: readonly RateLimiterI[], { bypass }?: RouteRateLimiterOptions): RouteRateLimiter | undefined;
|
|
67
|
+
}
|
|
36
68
|
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EAGnB,cAAc,EACf,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,SAAS,EACT,YAAY,EACZ,sBAAsB,EACtB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACf,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"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"}
|
package/dist/rate-limiter.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.RouteRateLimiter = exports.CombinedRateLimiter = exports.WrappedRateLimiter = exports.formatLimiterStatus = exports.RateLimiter = void 0;
|
|
4
4
|
const rate_limiter_flexible_1 = require("rate-limiter-flexible");
|
|
5
5
|
const logger_1 = require("./logger");
|
|
6
6
|
const types_1 = require("./types");
|
|
7
|
+
const util_1 = require("./util");
|
|
7
8
|
class RateLimiter {
|
|
8
9
|
constructor(limiter, opts) {
|
|
9
10
|
Object.defineProperty(this, "limiter", {
|
|
@@ -12,18 +13,6 @@ class RateLimiter {
|
|
|
12
13
|
writable: true,
|
|
13
14
|
value: void 0
|
|
14
15
|
});
|
|
15
|
-
Object.defineProperty(this, "bypassSecret", {
|
|
16
|
-
enumerable: true,
|
|
17
|
-
configurable: true,
|
|
18
|
-
writable: true,
|
|
19
|
-
value: void 0
|
|
20
|
-
});
|
|
21
|
-
Object.defineProperty(this, "bypassIps", {
|
|
22
|
-
enumerable: true,
|
|
23
|
-
configurable: true,
|
|
24
|
-
writable: true,
|
|
25
|
-
value: void 0
|
|
26
|
-
});
|
|
27
16
|
Object.defineProperty(this, "failClosed", {
|
|
28
17
|
enumerable: true,
|
|
29
18
|
configurable: true,
|
|
@@ -43,8 +32,6 @@ class RateLimiter {
|
|
|
43
32
|
value: void 0
|
|
44
33
|
});
|
|
45
34
|
this.limiter = limiter;
|
|
46
|
-
this.bypassSecret = opts.bypassSecret;
|
|
47
|
-
this.bypassIps = opts.bypassIps;
|
|
48
35
|
this.calcKey = opts.calcKey ?? defaultKey;
|
|
49
36
|
this.calcPoints = opts.calcPoints ?? defaultPoints;
|
|
50
37
|
}
|
|
@@ -66,13 +53,6 @@ class RateLimiter {
|
|
|
66
53
|
return new RateLimiter(limiter, opts);
|
|
67
54
|
}
|
|
68
55
|
async consume(ctx, opts) {
|
|
69
|
-
if (this.bypassSecret &&
|
|
70
|
-
ctx.req.header('x-ratelimit-bypass') === this.bypassSecret) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
if (this.bypassIps && this.bypassIps.includes(ctx.req.ip)) {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
56
|
const key = opts?.calcKey ? opts.calcKey(ctx) : this.calcKey(ctx);
|
|
77
57
|
if (key === null) {
|
|
78
58
|
return null;
|
|
@@ -132,37 +112,79 @@ const formatLimiterStatus = (limiter, res) => {
|
|
|
132
112
|
};
|
|
133
113
|
};
|
|
134
114
|
exports.formatLimiterStatus = formatLimiterStatus;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Wraps a {@link RateLimiterI} instance with custom key and points calculation
|
|
117
|
+
* functions.
|
|
118
|
+
*/
|
|
119
|
+
class WrappedRateLimiter {
|
|
120
|
+
constructor(rateLimiter, options) {
|
|
121
|
+
Object.defineProperty(this, "rateLimiter", {
|
|
122
|
+
enumerable: true,
|
|
123
|
+
configurable: true,
|
|
124
|
+
writable: true,
|
|
125
|
+
value: rateLimiter
|
|
126
|
+
});
|
|
127
|
+
Object.defineProperty(this, "options", {
|
|
128
|
+
enumerable: true,
|
|
129
|
+
configurable: true,
|
|
130
|
+
writable: true,
|
|
131
|
+
value: options
|
|
132
|
+
});
|
|
150
133
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
134
|
+
async consume(ctx, opts) {
|
|
135
|
+
return this.rateLimiter.consume(ctx, {
|
|
136
|
+
calcKey: opts?.calcKey ?? this.options.calcKey,
|
|
137
|
+
calcPoints: opts?.calcPoints ?? this.options.calcPoints,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async reset(ctx, opts) {
|
|
141
|
+
return this.rateLimiter.reset(ctx, {
|
|
142
|
+
calcKey: opts?.calcKey ?? this.options.calcKey,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
static from(rateLimiter, { calcKey, calcPoints } = {}) {
|
|
146
|
+
if (!calcKey && !calcPoints)
|
|
147
|
+
return rateLimiter;
|
|
148
|
+
return new WrappedRateLimiter(rateLimiter, { calcKey, calcPoints });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
exports.WrappedRateLimiter = WrappedRateLimiter;
|
|
152
|
+
/**
|
|
153
|
+
* Combines multiple rate limiters into one.
|
|
154
|
+
*
|
|
155
|
+
* The combined rate limiter will return the tightest (most restrictive) of all
|
|
156
|
+
* the provided rate limiters.
|
|
157
|
+
*/
|
|
158
|
+
class CombinedRateLimiter {
|
|
159
|
+
constructor(rateLimiters) {
|
|
160
|
+
Object.defineProperty(this, "rateLimiters", {
|
|
161
|
+
enumerable: true,
|
|
162
|
+
configurable: true,
|
|
163
|
+
writable: true,
|
|
164
|
+
value: rateLimiters
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async consume(ctx, opts) {
|
|
168
|
+
const promises = [];
|
|
169
|
+
for (const rl of this.rateLimiters)
|
|
170
|
+
promises.push(rl.consume(ctx, opts));
|
|
171
|
+
return Promise.all(promises).then(getTightestLimit);
|
|
172
|
+
}
|
|
173
|
+
async reset(ctx, opts) {
|
|
174
|
+
const promises = [];
|
|
175
|
+
for (const rl of this.rateLimiters)
|
|
176
|
+
promises.push(rl.reset(ctx, opts));
|
|
177
|
+
await Promise.all(promises);
|
|
178
|
+
}
|
|
179
|
+
static from(rateLimiters) {
|
|
180
|
+
if (rateLimiters.length === 0)
|
|
181
|
+
return undefined;
|
|
182
|
+
if (rateLimiters.length === 1)
|
|
183
|
+
return rateLimiters[0];
|
|
184
|
+
return new CombinedRateLimiter(rateLimiters);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
exports.CombinedRateLimiter = CombinedRateLimiter;
|
|
166
188
|
const getTightestLimit = (resps) => {
|
|
167
189
|
let lowest = null;
|
|
168
190
|
for (const resp of resps) {
|
|
@@ -176,7 +198,62 @@ const getTightestLimit = (resps) => {
|
|
|
176
198
|
}
|
|
177
199
|
return lowest;
|
|
178
200
|
};
|
|
179
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Wraps a {@link RateLimiterI} interface into a class that will apply the
|
|
203
|
+
* appropriate headers to the response if a limit is exceeded.
|
|
204
|
+
*/
|
|
205
|
+
class RouteRateLimiter {
|
|
206
|
+
constructor(rateLimiter, options = {}) {
|
|
207
|
+
Object.defineProperty(this, "rateLimiter", {
|
|
208
|
+
enumerable: true,
|
|
209
|
+
configurable: true,
|
|
210
|
+
writable: true,
|
|
211
|
+
value: rateLimiter
|
|
212
|
+
});
|
|
213
|
+
Object.defineProperty(this, "options", {
|
|
214
|
+
enumerable: true,
|
|
215
|
+
configurable: true,
|
|
216
|
+
writable: true,
|
|
217
|
+
value: options
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async handle(ctx) {
|
|
221
|
+
const { bypass } = this.options;
|
|
222
|
+
if (bypass && bypass(ctx)) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
const result = await this.consume(ctx);
|
|
226
|
+
if (result instanceof types_1.RateLimitExceededError) {
|
|
227
|
+
setStatusHeaders(ctx, result.status);
|
|
228
|
+
throw result;
|
|
229
|
+
}
|
|
230
|
+
else if (result != null) {
|
|
231
|
+
setStatusHeaders(ctx, result);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
async consume(...args) {
|
|
236
|
+
return this.rateLimiter.consume(...args);
|
|
237
|
+
}
|
|
238
|
+
async reset(...args) {
|
|
239
|
+
return this.rateLimiter.reset(...args);
|
|
240
|
+
}
|
|
241
|
+
static from(rateLimiters, { bypass } = {}) {
|
|
242
|
+
const rateLimiter = CombinedRateLimiter.from(rateLimiters);
|
|
243
|
+
if (!rateLimiter)
|
|
244
|
+
return undefined;
|
|
245
|
+
return new RouteRateLimiter(rateLimiter, { bypass });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
exports.RouteRateLimiter = RouteRateLimiter;
|
|
249
|
+
function setStatusHeaders(ctx, status) {
|
|
250
|
+
(0, util_1.setHeaders)(ctx.res, {
|
|
251
|
+
'RateLimit-Limit': status.limit,
|
|
252
|
+
'RateLimit-Reset': Math.floor((Date.now() + status.msBeforeNext) / 1000),
|
|
253
|
+
'RateLimit-Remaining': status.remainingPoints,
|
|
254
|
+
'RateLimit-Policy': `${status.limit};w=${status.duration}`,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
180
257
|
// when using a proxy, ensure headers are getting forwarded correctly: `app.set('trust proxy', true)`
|
|
181
258
|
// https://expressjs.com/en/guide/behind-proxies.html
|
|
182
259
|
const defaultKey = (ctx) => ctx.req.ip;
|
package/dist/rate-limiter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":";;;AAAA,iEAK8B;AAC9B,qCAAiC;AACjC,
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../src/rate-limiter.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Express, NextFunction, Request, RequestHandler, Response, Router } from 'express';
|
|
2
2
|
import { LexXrpcProcedure, LexXrpcQuery, LexXrpcSubscription, LexiconDoc, Lexicons } from '@atproto/lexicon';
|
|
3
|
+
import { RouteRateLimiter } from './rate-limiter';
|
|
3
4
|
import { XrpcStreamServer } from './stream';
|
|
4
5
|
import { Options, RateLimiterI, XRPCHandler, XRPCHandlerConfig, XRPCStreamHandler, XRPCStreamHandlerConfig } from './types';
|
|
5
6
|
export declare function createServer(lexicons?: LexiconDoc[], options?: Options): Server;
|
|
@@ -10,9 +11,8 @@ export declare class Server {
|
|
|
10
11
|
lex: Lexicons;
|
|
11
12
|
options: Options;
|
|
12
13
|
middleware: Record<'json' | 'text', RequestHandler>;
|
|
13
|
-
|
|
14
|
-
sharedRateLimiters
|
|
15
|
-
routeRateLimiters: Record<string, RateLimiterI[]>;
|
|
14
|
+
globalRateLimiter?: RouteRateLimiter;
|
|
15
|
+
sharedRateLimiters?: Map<string, RateLimiterI>;
|
|
16
16
|
constructor(lexicons?: LexiconDoc[], opts?: Options);
|
|
17
17
|
method(nsid: string, configOrFn: XRPCHandlerConfig | XRPCHandler): void;
|
|
18
18
|
addMethod(nsid: string, configOrFn: XRPCHandlerConfig | XRPCHandler): void;
|
|
@@ -21,10 +21,10 @@ export declare class Server {
|
|
|
21
21
|
addLexicon(doc: LexiconDoc): void;
|
|
22
22
|
addLexicons(docs: LexiconDoc[]): void;
|
|
23
23
|
protected addRoute(nsid: string, def: LexXrpcQuery | LexXrpcProcedure, config: XRPCHandlerConfig): Promise<void>;
|
|
24
|
-
catchall(req: Request, res: Response, next: NextFunction): Promise<
|
|
24
|
+
catchall(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
25
25
|
createHandler(nsid: string, def: LexXrpcQuery | LexXrpcProcedure, routeCfg: XRPCHandlerConfig): RequestHandler;
|
|
26
26
|
protected addSubscription(nsid: string, def: LexXrpcSubscription, config: XRPCStreamHandlerConfig): Promise<void>;
|
|
27
27
|
private enableStreamingOnListen;
|
|
28
|
-
private
|
|
28
|
+
private createRouteRateLimiter;
|
|
29
29
|
}
|
|
30
30
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAgB,EAGd,OAAO,EACP,YAAY,EACZ,OAAO,EACP,cAAc,EACd,QAAQ,EACR,MAAM,EAGP,MAAM,SAAS,CAAA;AAEhB,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,UAAU,EACV,QAAQ,EAET,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,gBAAgB,EAAsB,MAAM,gBAAgB,CAAA;AACrE,OAAO,EAAmC,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAC5E,OAAO,EAOL,OAAO,EAEP,YAAY,EAEZ,WAAW,EACX,iBAAiB,EAEjB,iBAAiB,EACjB,uBAAuB,EAKxB,MAAM,SAAS,CAAA;AAUhB,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,OAAO,UAEtE;AAED,qBAAa,MAAM;IACjB,MAAM,EAAE,OAAO,CAAY;IAC3B,MAAM,EAAE,MAAM,CAAW;IACzB,aAAa,gCAAsC;IACnD,GAAG,WAAiB;IACpB,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,cAAc,CAAC,CAAA;IACnD,iBAAiB,CAAC,EAAE,gBAAgB,CAAA;IACpC,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;gBAElC,QAAQ,CAAC,EAAE,UAAU,EAAE,EAAE,IAAI,GAAE,OAAY;IA0CvD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,GAAG,WAAW;IAIhE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,GAAG,WAAW;IAWnE,YAAY,CACV,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,uBAAuB,GAAG,iBAAiB;IAKzD,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,uBAAuB,GAAG,iBAAiB;IAezD,UAAU,CAAC,GAAG,EAAE,UAAU;IAI1B,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE;cASd,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,YAAY,GAAG,gBAAgB,EACpC,MAAM,EAAE,iBAAiB;IAmBrB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY;IAwC9D,aAAa,CACX,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,YAAY,GAAG,gBAAgB,EACpC,QAAQ,EAAE,iBAAiB,GAC1B,cAAc;cAmGD,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,mBAAmB,EACxB,MAAM,EAAE,uBAAuB;IA4DjC,OAAO,CAAC,uBAAuB;IAmB/B,OAAO,CAAC,sBAAsB;CAsC/B"}
|