@orpc/server 2.0.0-beta.42 → 2.0.0-beta.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -10
- package/dist/adapters/aws-lambda/index.mjs +1 -1
- package/dist/adapters/crossws/index.mjs +1 -1
- package/dist/adapters/fastify/index.mjs +1 -1
- package/dist/adapters/fetch/index.mjs +1 -1
- package/dist/adapters/message-port/index.mjs +1 -1
- package/dist/adapters/node/index.mjs +1 -1
- package/dist/adapters/standard/index.d.mts +2 -1
- package/dist/adapters/standard/index.d.ts +2 -1
- package/dist/adapters/standard/index.mjs +1 -1
- package/dist/adapters/websocket/index.mjs +1 -1
- package/dist/helpers/index.d.mts +2 -1
- package/dist/helpers/index.d.ts +2 -1
- package/dist/helpers/index.mjs +1 -1
- package/dist/plugins/index.d.mts +14 -28
- package/dist/plugins/index.d.ts +14 -28
- package/dist/plugins/index.mjs +88 -140
- package/dist/shared/{server.BFEVk-EI.mjs → server.B34dZrzY.mjs} +7 -13
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<img alt="Discord" src="https://img.shields.io/discord/1308966753044398161?color=7389D8&label&logo=discord&logoColor=ffffff" />
|
|
18
18
|
</a>
|
|
19
19
|
<a href="https://deepwiki.com/middleapi/orpc">
|
|
20
|
-
<img src="https://
|
|
20
|
+
<img alt="Ask DeepWiki" src="https://img.shields.io/badge/Ask-DeepWiki-blue" />
|
|
21
21
|
</a>
|
|
22
22
|
</div>
|
|
23
23
|
|
|
@@ -95,14 +95,6 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
|
|
|
95
95
|
</tr>
|
|
96
96
|
</table>
|
|
97
97
|
|
|
98
|
-
### Special Sponsors
|
|
99
|
-
|
|
100
|
-
<table>
|
|
101
|
-
<tr>
|
|
102
|
-
<td align="center"><a href="http://twitter.com/rauchg?ref=middleapi&utm_source=middleapi&utm_medium=sponsor" target="_blank" rel="noopener sponsored" title="Guillermo Rauch"><img src="https://avatars.githubusercontent.com/u/13041?u=1ee8d111657cdd02ff6d253df00978d17ee6d722&v=4" width="279" alt="Guillermo Rauch"/><br />Guillermo Rauch</a></td>
|
|
103
|
-
</tr>
|
|
104
|
-
</table>
|
|
105
|
-
|
|
106
98
|
### Premium Sponsors
|
|
107
99
|
|
|
108
100
|
<table>
|
|
@@ -178,7 +170,7 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
|
|
|
178
170
|
</tr>
|
|
179
171
|
</table>
|
|
180
172
|
|
|
181
|
-
With thanks to [
|
|
173
|
+
With thanks to [39 past sponsors](https://orpc.dev/sponsors#past-sponsors) who helped get oRPC here.
|
|
182
174
|
|
|
183
175
|
## References
|
|
184
176
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolveMaybeOptionalOptions } from '@orpc/shared';
|
|
2
2
|
import { toStandardLazyRequest, sendStandardResponse } from '@standard-server/aws-lambda';
|
|
3
3
|
import { r as resolveFriendlyStandardHandlerHandleOptions } from '../../shared/server.W91HSRkE.mjs';
|
|
4
|
-
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.
|
|
4
|
+
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.B34dZrzY.mjs';
|
|
5
5
|
import '@orpc/client';
|
|
6
6
|
import '@standard-server/core';
|
|
7
7
|
import '../../shared/server.FEqy7Hrj.mjs';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolveMaybeOptionalOptions } from '@orpc/shared';
|
|
2
2
|
import { ServerPeer, encodePeerMessage, decodePeerMessage, isClientPeerSendMessage } from '@standard-server/peer';
|
|
3
3
|
import { createStandardPeerRequestHandler } from '../standard-peer/index.mjs';
|
|
4
|
-
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.
|
|
4
|
+
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.B34dZrzY.mjs';
|
|
5
5
|
import '../../shared/server.W91HSRkE.mjs';
|
|
6
6
|
import '@orpc/client';
|
|
7
7
|
import '@standard-server/core';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolveMaybeOptionalOptions } from '@orpc/shared';
|
|
2
2
|
import { toStandardLazyRequest, sendStandardResponse } from '@standard-server/fastify';
|
|
3
3
|
import { r as resolveFriendlyStandardHandlerHandleOptions } from '../../shared/server.W91HSRkE.mjs';
|
|
4
|
-
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.
|
|
4
|
+
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.B34dZrzY.mjs';
|
|
5
5
|
import '@orpc/client';
|
|
6
6
|
import '@standard-server/core';
|
|
7
7
|
import '../../shared/server.FEqy7Hrj.mjs';
|
|
@@ -2,7 +2,7 @@ export { R as BodyLimitPlugin } from '../../shared/server.1ExWFMze.mjs';
|
|
|
2
2
|
import { resolveMaybeOptionalOptions } from '@orpc/shared';
|
|
3
3
|
import { toStandardLazyRequest, toFetchResponse } from '@standard-server/fetch';
|
|
4
4
|
import { r as resolveFriendlyStandardHandlerHandleOptions } from '../../shared/server.W91HSRkE.mjs';
|
|
5
|
-
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.
|
|
5
|
+
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.B34dZrzY.mjs';
|
|
6
6
|
import '@orpc/client';
|
|
7
7
|
import '@standard-server/core';
|
|
8
8
|
import '../../shared/server.FEqy7Hrj.mjs';
|
|
@@ -2,7 +2,7 @@ import { onMessagePortMessage, onMessagePortClose, postMessagePortMessage } from
|
|
|
2
2
|
import { value, resolveMaybeOptionalOptions } from '@orpc/shared';
|
|
3
3
|
import { ServerPeer, encodePeerMessage, decodePeerMessage, isClientPeerSendMessage, isPeerMessage } from '@standard-server/peer';
|
|
4
4
|
import { createStandardPeerRequestHandler } from '../standard-peer/index.mjs';
|
|
5
|
-
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.
|
|
5
|
+
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.B34dZrzY.mjs';
|
|
6
6
|
import '../../shared/server.W91HSRkE.mjs';
|
|
7
7
|
import '@orpc/client';
|
|
8
8
|
import '@standard-server/core';
|
|
@@ -2,7 +2,7 @@ export { R as BodyLimitPlugin } from '../../shared/server.1ExWFMze.mjs';
|
|
|
2
2
|
import { resolveMaybeOptionalOptions } from '@orpc/shared';
|
|
3
3
|
import { toStandardLazyRequest, sendStandardResponse } from '@standard-server/node';
|
|
4
4
|
import { r as resolveFriendlyStandardHandlerHandleOptions } from '../../shared/server.W91HSRkE.mjs';
|
|
5
|
-
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.
|
|
5
|
+
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.B34dZrzY.mjs';
|
|
6
6
|
import '@orpc/client';
|
|
7
7
|
import '@standard-server/core';
|
|
8
8
|
import '@standard-server/fetch';
|
|
@@ -43,6 +43,8 @@ declare class RPCMatcher {
|
|
|
43
43
|
private readonly rootRouter;
|
|
44
44
|
private readonly tree;
|
|
45
45
|
private readonly pendingLazyRouters;
|
|
46
|
+
/** segment count of the deepest pending key, never lowered so it stays an upper bound */
|
|
47
|
+
private maxPendingLazyRouterDepth;
|
|
46
48
|
constructor(router: AnyRouter, options?: RPCMatcherOptions);
|
|
47
49
|
private index;
|
|
48
50
|
match(method: StandardMethod, pathname: `/${string}`, prefix: `/${string}` | undefined): Promise<{
|
|
@@ -50,7 +52,6 @@ declare class RPCMatcher {
|
|
|
50
52
|
procedure: AnyProcedure;
|
|
51
53
|
} | undefined>;
|
|
52
54
|
private resolvePendingLazyRouters;
|
|
53
|
-
private loadPendingLazyRouters;
|
|
54
55
|
private loadPendingLazyRouter;
|
|
55
56
|
private indexPendingLazyRouter;
|
|
56
57
|
private resolveProcedure;
|
|
@@ -43,6 +43,8 @@ declare class RPCMatcher {
|
|
|
43
43
|
private readonly rootRouter;
|
|
44
44
|
private readonly tree;
|
|
45
45
|
private readonly pendingLazyRouters;
|
|
46
|
+
/** segment count of the deepest pending key, never lowered so it stays an upper bound */
|
|
47
|
+
private maxPendingLazyRouterDepth;
|
|
46
48
|
constructor(router: AnyRouter, options?: RPCMatcherOptions);
|
|
47
49
|
private index;
|
|
48
50
|
match(method: StandardMethod, pathname: `/${string}`, prefix: `/${string}` | undefined): Promise<{
|
|
@@ -50,7 +52,6 @@ declare class RPCMatcher {
|
|
|
50
52
|
procedure: AnyProcedure;
|
|
51
53
|
} | undefined>;
|
|
52
54
|
private resolvePendingLazyRouters;
|
|
53
|
-
private loadPendingLazyRouters;
|
|
54
55
|
private loadPendingLazyRouter;
|
|
55
56
|
private indexPendingLazyRouter;
|
|
56
57
|
private resolveProcedure;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { C as CompositeStandardHandlerPlugin, R as RPCHandlerCodec, a as RPCMatcher, b as RPC_DEFAULT_ALLOW_METHODS, S as StandardHandler, T as TracingHandlerPlugin } from '../../shared/server.
|
|
1
|
+
export { C as CompositeStandardHandlerPlugin, R as RPCHandlerCodec, a as RPCMatcher, b as RPC_DEFAULT_ALLOW_METHODS, S as StandardHandler, T as TracingHandlerPlugin } from '../../shared/server.B34dZrzY.mjs';
|
|
2
2
|
export { r as resolveFriendlyStandardHandlerHandleOptions } from '../../shared/server.W91HSRkE.mjs';
|
|
3
3
|
import '@orpc/client';
|
|
4
4
|
import '@orpc/shared';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { toStringOrBytes, resolveMaybeOptionalOptions, sequential, loadBytes } from '@orpc/shared';
|
|
2
2
|
import { ServerPeer, encodePeerMessage, decodePeerMessage, isClientPeerSendMessage } from '@standard-server/peer';
|
|
3
3
|
import { createStandardPeerRequestHandler } from '../standard-peer/index.mjs';
|
|
4
|
-
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.
|
|
4
|
+
import { R as RPCHandlerCodec, S as StandardHandler } from '../../shared/server.B34dZrzY.mjs';
|
|
5
5
|
import '../../shared/server.W91HSRkE.mjs';
|
|
6
6
|
import '@orpc/client';
|
|
7
7
|
import '@standard-server/core';
|
package/dist/helpers/index.d.mts
CHANGED
|
@@ -17,7 +17,8 @@ import { ParseOptions, SetCookie, StringifyOptions } from 'cookie';
|
|
|
17
17
|
declare function encodeBase64url(data: Uint8Array): string;
|
|
18
18
|
/**
|
|
19
19
|
* Decodes a base64url string to Uint8Array
|
|
20
|
-
*
|
|
20
|
+
* Only accepts canonical, unpadded base64url (the form produced by `encodeBase64url`),
|
|
21
|
+
* returns undefined for anything else, including padded or standard base64 input
|
|
21
22
|
*
|
|
22
23
|
* @example
|
|
23
24
|
* ```ts
|
package/dist/helpers/index.d.ts
CHANGED
|
@@ -17,7 +17,8 @@ import { ParseOptions, SetCookie, StringifyOptions } from 'cookie';
|
|
|
17
17
|
declare function encodeBase64url(data: Uint8Array): string;
|
|
18
18
|
/**
|
|
19
19
|
* Decodes a base64url string to Uint8Array
|
|
20
|
-
*
|
|
20
|
+
* Only accepts canonical, unpadded base64url (the form produced by `encodeBase64url`),
|
|
21
|
+
* returns undefined for anything else, including padded or standard base64 input
|
|
21
22
|
*
|
|
22
23
|
* @example
|
|
23
24
|
* ```ts
|
package/dist/helpers/index.mjs
CHANGED
package/dist/plugins/index.d.mts
CHANGED
|
@@ -8,7 +8,7 @@ import '@orpc/contract';
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Content type for batch responses that use the length-prefixed binary framing
|
|
11
|
-
* (streaming mode, and buffered mode when any sub-response contains binary).
|
|
11
|
+
* (streaming mode, and buffered mode when any sub-response contains binary or is a stream).
|
|
12
12
|
*
|
|
13
13
|
* Decoding is driven by the `standard-server` body hint, not this header,
|
|
14
14
|
* so it only serves to describe the payload to logs, proxies, and dev tools.
|
|
@@ -45,7 +45,7 @@ interface BatchHandlerPluginOptions<T extends Context> {
|
|
|
45
45
|
*
|
|
46
46
|
* When enabled, a zero-length length-prefixed frame is sent periodically while the
|
|
47
47
|
* stream is idle (no message sent for `interval` ms). Clients ignore these frames.
|
|
48
|
-
* Only applies to streaming mode.
|
|
48
|
+
* Only applies to streaming responses: streaming mode, or a buffered batch with a stream sub-response.
|
|
49
49
|
*
|
|
50
50
|
* @default { enabled: true, interval: 15000 }
|
|
51
51
|
*/
|
|
@@ -69,6 +69,9 @@ interface BatchHandlerPluginOptions<T extends Context> {
|
|
|
69
69
|
* batch into sub-requests and streaming their responses back together.
|
|
70
70
|
*
|
|
71
71
|
* @remarks
|
|
72
|
+
* In buffered mode, the batch still streams once a sub-response is a stream
|
|
73
|
+
* (an event iterator or a `ReadableStream`), since it may never end.
|
|
74
|
+
*
|
|
72
75
|
* **Note**: HTTP/2 and later already multiplex requests over a single connection, which often makes this plugin unnecessary.
|
|
73
76
|
*
|
|
74
77
|
* @see {@link https://orpc.dev/docs/plugins/batch | Batch Plugin}
|
|
@@ -95,6 +98,9 @@ interface CORSHandlerPluginOptions<T extends Context> {
|
|
|
95
98
|
* Configures the `Access-Control-Allow-Origin` header.
|
|
96
99
|
* Can be a string, an array of allowed origins, or a function (optionally async) that returns the allowed origin(s).
|
|
97
100
|
*
|
|
101
|
+
* A function must only return origins it has validated: returning the incoming origin
|
|
102
|
+
* unchecked allows every origin.
|
|
103
|
+
*
|
|
98
104
|
* @default '*'
|
|
99
105
|
*/
|
|
100
106
|
origin?: Value<Promisable<string | readonly string[] | null | undefined>, [origin: string, options: StandardHandlerRoutingInterceptorOptions<T>]>;
|
|
@@ -112,6 +118,8 @@ interface CORSHandlerPluginOptions<T extends Context> {
|
|
|
112
118
|
/**
|
|
113
119
|
* Configures the `Access-Control-Allow-Headers` header for preflight requests.
|
|
114
120
|
* Falls back to the request's `Access-Control-Request-Headers` if not set.
|
|
121
|
+
* When set, it replaces that fallback, so include every non-safelisted header
|
|
122
|
+
* clients send, such as `Content-Type`.
|
|
115
123
|
*/
|
|
116
124
|
allowHeaders?: readonly string[];
|
|
117
125
|
/**
|
|
@@ -120,6 +128,9 @@ interface CORSHandlerPluginOptions<T extends Context> {
|
|
|
120
128
|
maxAge?: number;
|
|
121
129
|
/**
|
|
122
130
|
* Configures the `Access-Control-Allow-Credentials` header.
|
|
131
|
+
*
|
|
132
|
+
* When enabled, `origin` must allow trusted origins only: every allowed origin can send
|
|
133
|
+
* authenticated requests as your users and read the responses.
|
|
123
134
|
*/
|
|
124
135
|
credentials?: boolean;
|
|
125
136
|
/**
|
|
@@ -205,31 +216,6 @@ declare class MethodOverrideHandlerPlugin<T extends Context> implements Standard
|
|
|
205
216
|
init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
|
|
206
217
|
}
|
|
207
218
|
|
|
208
|
-
/**
|
|
209
|
-
* Rejects requests whose decoded input contains prototype-polluting keys: an own
|
|
210
|
-
* `__proto__` key, or an own `constructor` key holding a `prototype` key. oRPC's own
|
|
211
|
-
* decoding never assigns through the prototype chain, so this plugin exists to stop such
|
|
212
|
-
* keys from reaching application code that merges, clones, or path-sets input with a
|
|
213
|
-
* library vulnerable to prototype pollution. An `AsyncIteratorObject` input is checked
|
|
214
|
-
* value by value as it arrives, and a polluting value fails that iteration instead.
|
|
215
|
-
*
|
|
216
|
-
* @see {@link https://orpc.dev/docs/plugins/prototype-pollution-protection | Prototype Pollution Protection Plugin}
|
|
217
|
-
*/
|
|
218
|
-
declare class PrototypePollutionProtectionHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
|
|
219
|
-
name: string;
|
|
220
|
-
init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
|
|
221
|
-
private rejectPollutingInput;
|
|
222
|
-
/**
|
|
223
|
-
* Walks the containers the built-in codecs can produce: arrays, maps, sets, and plain
|
|
224
|
-
* objects, including null-prototype ones. Arrays get the same key checks as plain objects,
|
|
225
|
-
* since structured clone, as used over MessagePort, keeps named keys like `__proto__` on
|
|
226
|
-
* them. Other objects, such as files and dates, carry no attacker-authored keys and are
|
|
227
|
-
* left alone. The walk is iterative, so input nested deeper than the call stack allows
|
|
228
|
-
* still gets the intended verdict instead of a `RangeError`.
|
|
229
|
-
*/
|
|
230
|
-
private containsPollutingKey;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
219
|
/**
|
|
234
220
|
* Decompresses incoming request bodies based on the Content-Encoding header,
|
|
235
221
|
* supporting gzip, deflate, and deflate-raw.
|
|
@@ -417,5 +403,5 @@ declare class TimeoutHandlerPlugin<T extends Context> implements StandardHandler
|
|
|
417
403
|
init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
|
|
418
404
|
}
|
|
419
405
|
|
|
420
|
-
export { BATCH_CONTENT_TYPE, BatchHandlerPlugin, CORSHandlerPlugin, CORSHandlerPlugin as CORSPlugin, GetMethodCsrfProtectionHandlerPlugin, MethodOverrideHandlerPlugin,
|
|
406
|
+
export { BATCH_CONTENT_TYPE, BatchHandlerPlugin, CORSHandlerPlugin, CORSHandlerPlugin as CORSPlugin, GetMethodCsrfProtectionHandlerPlugin, MethodOverrideHandlerPlugin, RequestCompressionHandlerPlugin, RequestHeadersHandlerPlugin, RequestHeadersHandlerPlugin as RequestHeadersPlugin, ResponseCompressionHandlerPlugin, ResponseHeadersHandlerPlugin, ResponseHeadersHandlerPlugin as ResponseHeadersPlugin, RethrowHandlerPlugin, TimeoutHandlerPlugin, negotiateResponseCompressionEncoding, toCompressedHeaders };
|
|
421
407
|
export type { BatchHandlerPluginOptions, CORSHandlerPluginOptions, MethodOverrideHandlerPluginOptions, RequestHeadersHandlerPluginContext, RequestHeadersHandlerPluginContext as RequestHeadersPluginContext, ResponseCompressionHandlerPluginOptions, ResponseHeadersHandlerPluginContext, ResponseHeadersHandlerPluginContext as ResponseHeadersPluginContext, RethrowHandlerPluginOptions, TimeoutHandlerPluginOptions };
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import '@orpc/contract';
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Content type for batch responses that use the length-prefixed binary framing
|
|
11
|
-
* (streaming mode, and buffered mode when any sub-response contains binary).
|
|
11
|
+
* (streaming mode, and buffered mode when any sub-response contains binary or is a stream).
|
|
12
12
|
*
|
|
13
13
|
* Decoding is driven by the `standard-server` body hint, not this header,
|
|
14
14
|
* so it only serves to describe the payload to logs, proxies, and dev tools.
|
|
@@ -45,7 +45,7 @@ interface BatchHandlerPluginOptions<T extends Context> {
|
|
|
45
45
|
*
|
|
46
46
|
* When enabled, a zero-length length-prefixed frame is sent periodically while the
|
|
47
47
|
* stream is idle (no message sent for `interval` ms). Clients ignore these frames.
|
|
48
|
-
* Only applies to streaming mode.
|
|
48
|
+
* Only applies to streaming responses: streaming mode, or a buffered batch with a stream sub-response.
|
|
49
49
|
*
|
|
50
50
|
* @default { enabled: true, interval: 15000 }
|
|
51
51
|
*/
|
|
@@ -69,6 +69,9 @@ interface BatchHandlerPluginOptions<T extends Context> {
|
|
|
69
69
|
* batch into sub-requests and streaming their responses back together.
|
|
70
70
|
*
|
|
71
71
|
* @remarks
|
|
72
|
+
* In buffered mode, the batch still streams once a sub-response is a stream
|
|
73
|
+
* (an event iterator or a `ReadableStream`), since it may never end.
|
|
74
|
+
*
|
|
72
75
|
* **Note**: HTTP/2 and later already multiplex requests over a single connection, which often makes this plugin unnecessary.
|
|
73
76
|
*
|
|
74
77
|
* @see {@link https://orpc.dev/docs/plugins/batch | Batch Plugin}
|
|
@@ -95,6 +98,9 @@ interface CORSHandlerPluginOptions<T extends Context> {
|
|
|
95
98
|
* Configures the `Access-Control-Allow-Origin` header.
|
|
96
99
|
* Can be a string, an array of allowed origins, or a function (optionally async) that returns the allowed origin(s).
|
|
97
100
|
*
|
|
101
|
+
* A function must only return origins it has validated: returning the incoming origin
|
|
102
|
+
* unchecked allows every origin.
|
|
103
|
+
*
|
|
98
104
|
* @default '*'
|
|
99
105
|
*/
|
|
100
106
|
origin?: Value<Promisable<string | readonly string[] | null | undefined>, [origin: string, options: StandardHandlerRoutingInterceptorOptions<T>]>;
|
|
@@ -112,6 +118,8 @@ interface CORSHandlerPluginOptions<T extends Context> {
|
|
|
112
118
|
/**
|
|
113
119
|
* Configures the `Access-Control-Allow-Headers` header for preflight requests.
|
|
114
120
|
* Falls back to the request's `Access-Control-Request-Headers` if not set.
|
|
121
|
+
* When set, it replaces that fallback, so include every non-safelisted header
|
|
122
|
+
* clients send, such as `Content-Type`.
|
|
115
123
|
*/
|
|
116
124
|
allowHeaders?: readonly string[];
|
|
117
125
|
/**
|
|
@@ -120,6 +128,9 @@ interface CORSHandlerPluginOptions<T extends Context> {
|
|
|
120
128
|
maxAge?: number;
|
|
121
129
|
/**
|
|
122
130
|
* Configures the `Access-Control-Allow-Credentials` header.
|
|
131
|
+
*
|
|
132
|
+
* When enabled, `origin` must allow trusted origins only: every allowed origin can send
|
|
133
|
+
* authenticated requests as your users and read the responses.
|
|
123
134
|
*/
|
|
124
135
|
credentials?: boolean;
|
|
125
136
|
/**
|
|
@@ -205,31 +216,6 @@ declare class MethodOverrideHandlerPlugin<T extends Context> implements Standard
|
|
|
205
216
|
init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
|
|
206
217
|
}
|
|
207
218
|
|
|
208
|
-
/**
|
|
209
|
-
* Rejects requests whose decoded input contains prototype-polluting keys: an own
|
|
210
|
-
* `__proto__` key, or an own `constructor` key holding a `prototype` key. oRPC's own
|
|
211
|
-
* decoding never assigns through the prototype chain, so this plugin exists to stop such
|
|
212
|
-
* keys from reaching application code that merges, clones, or path-sets input with a
|
|
213
|
-
* library vulnerable to prototype pollution. An `AsyncIteratorObject` input is checked
|
|
214
|
-
* value by value as it arrives, and a polluting value fails that iteration instead.
|
|
215
|
-
*
|
|
216
|
-
* @see {@link https://orpc.dev/docs/plugins/prototype-pollution-protection | Prototype Pollution Protection Plugin}
|
|
217
|
-
*/
|
|
218
|
-
declare class PrototypePollutionProtectionHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
|
|
219
|
-
name: string;
|
|
220
|
-
init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
|
|
221
|
-
private rejectPollutingInput;
|
|
222
|
-
/**
|
|
223
|
-
* Walks the containers the built-in codecs can produce: arrays, maps, sets, and plain
|
|
224
|
-
* objects, including null-prototype ones. Arrays get the same key checks as plain objects,
|
|
225
|
-
* since structured clone, as used over MessagePort, keeps named keys like `__proto__` on
|
|
226
|
-
* them. Other objects, such as files and dates, carry no attacker-authored keys and are
|
|
227
|
-
* left alone. The walk is iterative, so input nested deeper than the call stack allows
|
|
228
|
-
* still gets the intended verdict instead of a `RangeError`.
|
|
229
|
-
*/
|
|
230
|
-
private containsPollutingKey;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
219
|
/**
|
|
234
220
|
* Decompresses incoming request bodies based on the Content-Encoding header,
|
|
235
221
|
* supporting gzip, deflate, and deflate-raw.
|
|
@@ -417,5 +403,5 @@ declare class TimeoutHandlerPlugin<T extends Context> implements StandardHandler
|
|
|
417
403
|
init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
|
|
418
404
|
}
|
|
419
405
|
|
|
420
|
-
export { BATCH_CONTENT_TYPE, BatchHandlerPlugin, CORSHandlerPlugin, CORSHandlerPlugin as CORSPlugin, GetMethodCsrfProtectionHandlerPlugin, MethodOverrideHandlerPlugin,
|
|
406
|
+
export { BATCH_CONTENT_TYPE, BatchHandlerPlugin, CORSHandlerPlugin, CORSHandlerPlugin as CORSPlugin, GetMethodCsrfProtectionHandlerPlugin, MethodOverrideHandlerPlugin, RequestCompressionHandlerPlugin, RequestHeadersHandlerPlugin, RequestHeadersHandlerPlugin as RequestHeadersPlugin, ResponseCompressionHandlerPlugin, ResponseHeadersHandlerPlugin, ResponseHeadersHandlerPlugin as ResponseHeadersPlugin, RethrowHandlerPlugin, TimeoutHandlerPlugin, negotiateResponseCompressionEncoding, toCompressedHeaders };
|
|
421
407
|
export type { BatchHandlerPluginOptions, CORSHandlerPluginOptions, MethodOverrideHandlerPluginOptions, RequestHeadersHandlerPluginContext, RequestHeadersHandlerPluginContext as RequestHeadersPluginContext, ResponseCompressionHandlerPluginOptions, ResponseHeadersHandlerPluginContext, ResponseHeadersHandlerPluginContext as ResponseHeadersPluginContext, RethrowHandlerPluginOptions, TimeoutHandlerPluginOptions };
|
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { ORPCError } from '@orpc/client';
|
|
2
|
-
import { toArray, value,
|
|
2
|
+
import { toArray, value, promiseWithResolvers, isAsyncIteratorObject, isCompressibleContentType, isNoTransformCacheControl, parseAcceptEncodingQualities, isAcceptableEncoding, varyByAcceptEncoding, stringifyJSON, anyAbortSignal, override, wrapAsyncIterator, wrapReadableStream, AbortError } from '@orpc/shared';
|
|
3
3
|
import { flattenStandardHeader, parseStandardUrl, generateContentDisposition, mergeStandardHeaders } from '@standard-server/core';
|
|
4
4
|
import { isClientPeerSendMessage, ServerPeer, encodePeerMessage } from '@standard-server/peer';
|
|
5
5
|
import { toFetchHeaders, toStandardBody, toStandardHeaders } from '@standard-server/fetch';
|
|
6
6
|
export { R as RequestLimitHandlerPlugin } from '../shared/server.1ExWFMze.mjs';
|
|
7
7
|
|
|
8
8
|
const BATCH_CONTENT_TYPE = "application/vnd.orpc.batch";
|
|
9
|
+
const textEncoder = new TextEncoder();
|
|
10
|
+
function encodeLengthPrefix(length) {
|
|
11
|
+
const prefix = new Uint8Array(4);
|
|
12
|
+
new DataView(prefix.buffer).setUint32(0, length, false);
|
|
13
|
+
return prefix;
|
|
14
|
+
}
|
|
15
|
+
async function encodeFrame(message) {
|
|
16
|
+
const encoded = await encodePeerMessage(message);
|
|
17
|
+
const bytes = typeof encoded === "string" ? textEncoder.encode(encoded) : encoded;
|
|
18
|
+
return [encodeLengthPrefix(bytes.byteLength), bytes];
|
|
19
|
+
}
|
|
9
20
|
class BatchHandlerPlugin {
|
|
10
21
|
name = "~batch";
|
|
11
22
|
/**
|
|
@@ -101,6 +112,63 @@ class BatchHandlerPlugin {
|
|
|
101
112
|
response: { status: 413, headers: {}, body: "Batch request size exceeds the maximum allowed size" }
|
|
102
113
|
};
|
|
103
114
|
}
|
|
115
|
+
const status = await value(this.successStatus, interceptorOptions);
|
|
116
|
+
const headers = await value(this.headers, interceptorOptions);
|
|
117
|
+
const bufferedMessages = [];
|
|
118
|
+
let streamController;
|
|
119
|
+
let keepAliveTimer;
|
|
120
|
+
const clearKeepAlive = () => {
|
|
121
|
+
if (keepAliveTimer !== void 0) {
|
|
122
|
+
clearInterval(keepAliveTimer);
|
|
123
|
+
keepAliveTimer = void 0;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const scheduleKeepAlive = () => {
|
|
127
|
+
if (!this.keepAliveEnabled) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
clearKeepAlive();
|
|
131
|
+
keepAliveTimer = setInterval(() => {
|
|
132
|
+
try {
|
|
133
|
+
streamController.enqueue(encodeLengthPrefix(0));
|
|
134
|
+
} catch {
|
|
135
|
+
clearKeepAlive();
|
|
136
|
+
}
|
|
137
|
+
}, this.keepAliveInterval);
|
|
138
|
+
};
|
|
139
|
+
const enqueueMessage = async (message) => {
|
|
140
|
+
for (const chunk of await encodeFrame(message)) {
|
|
141
|
+
streamController.enqueue(chunk);
|
|
142
|
+
}
|
|
143
|
+
scheduleKeepAlive();
|
|
144
|
+
};
|
|
145
|
+
const peer = new ServerPeer(async (message) => {
|
|
146
|
+
if (mode === "streaming") {
|
|
147
|
+
startStreaming();
|
|
148
|
+
}
|
|
149
|
+
if (streamController === void 0) {
|
|
150
|
+
bufferedMessages.push(message);
|
|
151
|
+
} else {
|
|
152
|
+
await enqueueMessage(message);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
const streaming = promiseWithResolvers();
|
|
156
|
+
function startStreaming() {
|
|
157
|
+
if (streamController !== void 0) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const body = new ReadableStream({
|
|
161
|
+
start(controller) {
|
|
162
|
+
streamController = controller;
|
|
163
|
+
scheduleKeepAlive();
|
|
164
|
+
},
|
|
165
|
+
async cancel(reason) {
|
|
166
|
+
clearKeepAlive();
|
|
167
|
+
await peer.close(reason);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
streaming.resolve({ body, flushed: Promise.all(bufferedMessages.splice(0).map(enqueueMessage)) });
|
|
171
|
+
}
|
|
104
172
|
const handleIndividualRequest = async (request) => {
|
|
105
173
|
try {
|
|
106
174
|
request = this.mapSubrequest(request, interceptorOptions);
|
|
@@ -108,16 +176,19 @@ class BatchHandlerPlugin {
|
|
|
108
176
|
if (!matched) {
|
|
109
177
|
return { status: 404, headers: {}, body: "No procedure matched" };
|
|
110
178
|
}
|
|
179
|
+
if (response.body instanceof ReadableStream || isAsyncIteratorObject(response.body)) {
|
|
180
|
+
startStreaming();
|
|
181
|
+
}
|
|
111
182
|
return response;
|
|
112
183
|
} catch (err) {
|
|
113
184
|
Promise.reject(err);
|
|
114
185
|
return { status: 500, headers: {}, body: "Internal server error" };
|
|
115
186
|
}
|
|
116
187
|
};
|
|
117
|
-
const runSubrequests = async (
|
|
118
|
-
const promise = Promise.all(messages.map((msg) =>
|
|
188
|
+
const runSubrequests = async () => {
|
|
189
|
+
const promise = Promise.all(messages.map((msg) => peer.message(msg, handleIndividualRequest)));
|
|
119
190
|
const signal = interceptorOptions.request.signal;
|
|
120
|
-
const closePeer = () =>
|
|
191
|
+
const closePeer = () => peer.close(signal?.reason);
|
|
121
192
|
if (signal?.aborted) {
|
|
122
193
|
closePeer();
|
|
123
194
|
}
|
|
@@ -128,25 +199,12 @@ class BatchHandlerPlugin {
|
|
|
128
199
|
signal?.removeEventListener("abort", closePeer);
|
|
129
200
|
}
|
|
130
201
|
};
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
});
|
|
138
|
-
await runSubrequests(peer2);
|
|
139
|
-
await peer2.close();
|
|
140
|
-
if (responseMessages.some((msg) => msg.binary !== void 0)) {
|
|
141
|
-
const chunks = [];
|
|
142
|
-
for (const message of responseMessages) {
|
|
143
|
-
const encoded = await encodePeerMessage(message);
|
|
144
|
-
const bytes = typeof encoded === "string" ? new TextEncoder().encode(encoded) : encoded;
|
|
145
|
-
const lengthBuffer = new ArrayBuffer(4);
|
|
146
|
-
new DataView(lengthBuffer).setUint32(0, bytes.byteLength, false);
|
|
147
|
-
chunks.push(new Uint8Array(lengthBuffer));
|
|
148
|
-
chunks.push(bytes);
|
|
149
|
-
}
|
|
202
|
+
const subrequests = runSubrequests();
|
|
203
|
+
const started = await Promise.race([streaming.promise, subrequests.then(() => void 0)]);
|
|
204
|
+
if (started === void 0) {
|
|
205
|
+
await peer.close();
|
|
206
|
+
if (bufferedMessages.some((msg) => msg.binary !== void 0)) {
|
|
207
|
+
const chunks = (await Promise.all(bufferedMessages.map(encodeFrame))).flat();
|
|
150
208
|
return {
|
|
151
209
|
matched: true,
|
|
152
210
|
response: {
|
|
@@ -158,52 +216,10 @@ class BatchHandlerPlugin {
|
|
|
158
216
|
}
|
|
159
217
|
return {
|
|
160
218
|
matched: true,
|
|
161
|
-
response: { status, headers, body:
|
|
219
|
+
response: { status, headers, body: bufferedMessages }
|
|
162
220
|
};
|
|
163
221
|
}
|
|
164
|
-
|
|
165
|
-
let keepAliveTimer;
|
|
166
|
-
const clearKeepAlive = () => {
|
|
167
|
-
if (keepAliveTimer !== void 0) {
|
|
168
|
-
clearInterval(keepAliveTimer);
|
|
169
|
-
keepAliveTimer = void 0;
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
const scheduleKeepAlive = () => {
|
|
173
|
-
if (!this.keepAliveEnabled) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
clearKeepAlive();
|
|
177
|
-
keepAliveTimer = setInterval(() => {
|
|
178
|
-
try {
|
|
179
|
-
const lengthBuffer = new ArrayBuffer(4);
|
|
180
|
-
new DataView(lengthBuffer).setUint32(0, 0, false);
|
|
181
|
-
streamController.enqueue(new Uint8Array(lengthBuffer));
|
|
182
|
-
} catch {
|
|
183
|
-
clearKeepAlive();
|
|
184
|
-
}
|
|
185
|
-
}, this.keepAliveInterval);
|
|
186
|
-
};
|
|
187
|
-
const peer = new ServerPeer(async (message) => {
|
|
188
|
-
const encoded = await encodePeerMessage(message);
|
|
189
|
-
const bytes = typeof encoded === "string" ? new TextEncoder().encode(encoded) : encoded;
|
|
190
|
-
const lengthBuffer = new ArrayBuffer(4);
|
|
191
|
-
new DataView(lengthBuffer).setUint32(0, bytes.byteLength, false);
|
|
192
|
-
streamController.enqueue(new Uint8Array(lengthBuffer));
|
|
193
|
-
streamController.enqueue(bytes);
|
|
194
|
-
scheduleKeepAlive();
|
|
195
|
-
});
|
|
196
|
-
const stream = new ReadableStream({
|
|
197
|
-
start(controller) {
|
|
198
|
-
streamController = controller;
|
|
199
|
-
scheduleKeepAlive();
|
|
200
|
-
},
|
|
201
|
-
async cancel(reason) {
|
|
202
|
-
clearKeepAlive();
|
|
203
|
-
await peer.close(reason);
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
runSubrequests(peer).then(async () => {
|
|
222
|
+
Promise.all([started.flushed, subrequests]).then(async () => {
|
|
207
223
|
clearKeepAlive();
|
|
208
224
|
streamController.close();
|
|
209
225
|
await peer.close();
|
|
@@ -217,7 +233,7 @@ class BatchHandlerPlugin {
|
|
|
217
233
|
response: {
|
|
218
234
|
status,
|
|
219
235
|
headers: { ...headers, "content-type": BATCH_CONTENT_TYPE },
|
|
220
|
-
body:
|
|
236
|
+
body: started.body
|
|
221
237
|
}
|
|
222
238
|
};
|
|
223
239
|
};
|
|
@@ -405,75 +421,6 @@ class MethodOverrideHandlerPlugin {
|
|
|
405
421
|
}
|
|
406
422
|
}
|
|
407
423
|
|
|
408
|
-
class PrototypePollutionProtectionHandlerPlugin {
|
|
409
|
-
name = "~prototype-pollution-protection";
|
|
410
|
-
init(options) {
|
|
411
|
-
const interceptor = (interceptorOptions) => {
|
|
412
|
-
const input = interceptorOptions.input;
|
|
413
|
-
if (isAsyncIteratorObject(input)) {
|
|
414
|
-
const guardedInput = override(input, wrapAsyncIterator(input, {
|
|
415
|
-
mapResult: (result) => {
|
|
416
|
-
this.rejectPollutingInput(result.value);
|
|
417
|
-
return result;
|
|
418
|
-
}
|
|
419
|
-
}));
|
|
420
|
-
return interceptorOptions.next({ ...interceptorOptions, input: guardedInput });
|
|
421
|
-
}
|
|
422
|
-
this.rejectPollutingInput(input);
|
|
423
|
-
return interceptorOptions.next();
|
|
424
|
-
};
|
|
425
|
-
return {
|
|
426
|
-
...options,
|
|
427
|
-
clientInterceptors: [interceptor, ...toArray(options.clientInterceptors)]
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
rejectPollutingInput(input) {
|
|
431
|
-
if (this.containsPollutingKey(input)) {
|
|
432
|
-
throw new ORPCError("BAD_REQUEST", { message: "Request blocked by prototype pollution protection." });
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* Walks the containers the built-in codecs can produce: arrays, maps, sets, and plain
|
|
437
|
-
* objects, including null-prototype ones. Arrays get the same key checks as plain objects,
|
|
438
|
-
* since structured clone, as used over MessagePort, keeps named keys like `__proto__` on
|
|
439
|
-
* them. Other objects, such as files and dates, carry no attacker-authored keys and are
|
|
440
|
-
* left alone. The walk is iterative, so input nested deeper than the call stack allows
|
|
441
|
-
* still gets the intended verdict instead of a `RangeError`.
|
|
442
|
-
*/
|
|
443
|
-
containsPollutingKey(root) {
|
|
444
|
-
const visited = /* @__PURE__ */ new WeakSet();
|
|
445
|
-
const stack = [root];
|
|
446
|
-
while (stack.length !== 0) {
|
|
447
|
-
const value = stack.pop();
|
|
448
|
-
if (typeof value !== "object" || value === null || visited.has(value)) {
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
visited.add(value);
|
|
452
|
-
if (value instanceof Map || value instanceof Set) {
|
|
453
|
-
for (const entry of value) {
|
|
454
|
-
stack.push(entry);
|
|
455
|
-
}
|
|
456
|
-
continue;
|
|
457
|
-
}
|
|
458
|
-
if (!Array.isArray(value) && !isPlainObject(value)) {
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
if (Object.hasOwn(value, "__proto__")) {
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
if (Object.hasOwn(value, "constructor") && isTypescriptObject(value.constructor) && Object.hasOwn(value.constructor, "prototype")) {
|
|
465
|
-
return true;
|
|
466
|
-
}
|
|
467
|
-
for (const item of Object.values(value)) {
|
|
468
|
-
if (typeof item === "object" && item !== null) {
|
|
469
|
-
stack.push(item);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
424
|
const MAX_CONTENT_ENCODINGS = 5;
|
|
478
425
|
class RequestCompressionHandlerPlugin {
|
|
479
426
|
name = "~request-compression";
|
|
@@ -608,7 +555,8 @@ class ResponseCompressionHandlerPlugin {
|
|
|
608
555
|
};
|
|
609
556
|
}
|
|
610
557
|
} else if (body instanceof Blob) {
|
|
611
|
-
|
|
558
|
+
const contentType = headers["content-type"] ?? body.type;
|
|
559
|
+
if ((!Number.isFinite(body.size) || body.size >= this.threshold) && this.isCompressibleContentType(flattenStandardHeader(contentType), interceptorOptions)) {
|
|
612
560
|
const contentDisposition = headers["content-disposition"] ?? generateContentDisposition(
|
|
613
561
|
body instanceof File ? body.name : "blob"
|
|
614
562
|
);
|
|
@@ -620,7 +568,7 @@ class ResponseCompressionHandlerPlugin {
|
|
|
620
568
|
headers: {
|
|
621
569
|
...toCompressedHeaders(headers, encoding),
|
|
622
570
|
"standard-server": "file",
|
|
623
|
-
"content-type":
|
|
571
|
+
"content-type": contentType,
|
|
624
572
|
"content-disposition": contentDisposition
|
|
625
573
|
}
|
|
626
574
|
}
|
|
@@ -875,4 +823,4 @@ class TimeoutHandlerPlugin {
|
|
|
875
823
|
}
|
|
876
824
|
}
|
|
877
825
|
|
|
878
|
-
export { BATCH_CONTENT_TYPE, BatchHandlerPlugin, CORSHandlerPlugin, CORSHandlerPlugin as CORSPlugin, GetMethodCsrfProtectionHandlerPlugin, MethodOverrideHandlerPlugin,
|
|
826
|
+
export { BATCH_CONTENT_TYPE, BatchHandlerPlugin, CORSHandlerPlugin, CORSHandlerPlugin as CORSPlugin, GetMethodCsrfProtectionHandlerPlugin, MethodOverrideHandlerPlugin, RequestCompressionHandlerPlugin, RequestHeadersHandlerPlugin, RequestHeadersHandlerPlugin as RequestHeadersPlugin, ResponseCompressionHandlerPlugin, ResponseHeadersHandlerPlugin, ResponseHeadersHandlerPlugin as ResponseHeadersPlugin, RethrowHandlerPlugin, TimeoutHandlerPlugin, negotiateResponseCompressionEncoding, toCompressedHeaders };
|
|
@@ -183,6 +183,7 @@ class TracingHandlerPlugin {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
const RPC_DEFAULT_ALLOW_METHODS = ["POST", "PUT", "PATCH", "DELETE"];
|
|
186
|
+
const NORMALIZABLE_PATHNAME_REGEX = /[^\w\-.!~*'()/]/;
|
|
186
187
|
class RPCMatcher {
|
|
187
188
|
filter;
|
|
188
189
|
allowMethodsSet;
|
|
@@ -190,6 +191,8 @@ class RPCMatcher {
|
|
|
190
191
|
rootRouter;
|
|
191
192
|
tree = /* @__PURE__ */ new Map();
|
|
192
193
|
pendingLazyRouters = /* @__PURE__ */ new Map();
|
|
194
|
+
/** segment count of the deepest pending key, never lowered so it stays an upper bound */
|
|
195
|
+
maxPendingLazyRouterDepth = 0;
|
|
193
196
|
constructor(router, options = {}) {
|
|
194
197
|
this.filter = options.filter ?? true;
|
|
195
198
|
const allowMethods = options.allowMethods ?? RPC_DEFAULT_ALLOW_METHODS;
|
|
@@ -215,6 +218,7 @@ class RPCMatcher {
|
|
|
215
218
|
}, path);
|
|
216
219
|
for (const result of lazyResults) {
|
|
217
220
|
this.pendingLazyRouters.set(pathToHttpPath(result.path), result);
|
|
221
|
+
this.maxPendingLazyRouterDepth = Math.max(this.maxPendingLazyRouterDepth, result.path.length);
|
|
218
222
|
}
|
|
219
223
|
}
|
|
220
224
|
async match(method, pathname, prefix) {
|
|
@@ -244,7 +248,7 @@ class RPCMatcher {
|
|
|
244
248
|
await loading;
|
|
245
249
|
}
|
|
246
250
|
let entry = this.tree.get(pathname);
|
|
247
|
-
if (entry === void 0 &&
|
|
251
|
+
if (entry === void 0 && NORMALIZABLE_PATHNAME_REGEX.test(pathname)) {
|
|
248
252
|
const normalizedPathname = normalizeHttpPath(pathname);
|
|
249
253
|
const normalizedLoading = this.resolvePendingLazyRouters(normalizedPathname);
|
|
250
254
|
if (normalizedLoading !== void 0) {
|
|
@@ -269,22 +273,12 @@ class RPCMatcher {
|
|
|
269
273
|
return;
|
|
270
274
|
}
|
|
271
275
|
let slashIndex = 0;
|
|
272
|
-
|
|
273
|
-
const nextSlashIndex = pathname.indexOf("/", slashIndex + 1);
|
|
274
|
-
const httpPathPrefix = nextSlashIndex === -1 ? pathname : pathname.slice(0, nextSlashIndex);
|
|
275
|
-
if (this.pendingLazyRouters.has(httpPathPrefix)) {
|
|
276
|
-
return this.loadPendingLazyRouters(pathname, slashIndex);
|
|
277
|
-
}
|
|
278
|
-
slashIndex = nextSlashIndex;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
async loadPendingLazyRouters(pathname, slashIndex) {
|
|
282
|
-
while (slashIndex !== -1) {
|
|
276
|
+
for (let depth = 1; slashIndex !== -1 && depth <= this.maxPendingLazyRouterDepth; depth++) {
|
|
283
277
|
const nextSlashIndex = pathname.indexOf("/", slashIndex + 1);
|
|
284
278
|
const httpPathPrefix = nextSlashIndex === -1 ? pathname : pathname.slice(0, nextSlashIndex);
|
|
285
279
|
const pending = this.pendingLazyRouters.get(httpPathPrefix);
|
|
286
280
|
if (pending !== void 0) {
|
|
287
|
-
|
|
281
|
+
return this.loadPendingLazyRouter(httpPathPrefix, pending).then(() => this.resolvePendingLazyRouters(pathname));
|
|
288
282
|
}
|
|
289
283
|
slashIndex = nextSlashIndex;
|
|
290
284
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orpc/server",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.0.0-beta.
|
|
4
|
+
"version": "2.0.0-beta.44",
|
|
5
5
|
"description": "Build typesafe APIs with oRPC: RPC and OpenAPI support, middleware, and adapters for Node.js, Bun, Deno, Fetch, Fastify, WebSocket, AWS Lambda, and more",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": [
|
|
@@ -117,23 +117,23 @@
|
|
|
117
117
|
}
|
|
118
118
|
},
|
|
119
119
|
"dependencies": {
|
|
120
|
-
"@orpc/client": "2.0.0-beta.
|
|
121
|
-
"@orpc/contract": "2.0.0-beta.
|
|
122
|
-
"@orpc/shared": "2.0.0-beta.
|
|
123
|
-
"@standard-server/aws-lambda": "~0.10.
|
|
124
|
-
"@standard-server/core": "~0.10.
|
|
125
|
-
"@standard-server/fastify": "~0.10.
|
|
126
|
-
"@standard-server/fetch": "~0.10.
|
|
127
|
-
"@standard-server/node": "~0.10.
|
|
128
|
-
"@standard-server/peer": "~0.10.
|
|
120
|
+
"@orpc/client": "2.0.0-beta.44",
|
|
121
|
+
"@orpc/contract": "2.0.0-beta.44",
|
|
122
|
+
"@orpc/shared": "2.0.0-beta.44",
|
|
123
|
+
"@standard-server/aws-lambda": "~0.10.3",
|
|
124
|
+
"@standard-server/core": "~0.10.3",
|
|
125
|
+
"@standard-server/fastify": "~0.10.3",
|
|
126
|
+
"@standard-server/fetch": "~0.10.3",
|
|
127
|
+
"@standard-server/node": "~0.10.3",
|
|
128
|
+
"@standard-server/peer": "~0.10.3",
|
|
129
129
|
"cookie": "^2.0.1"
|
|
130
130
|
},
|
|
131
131
|
"devDependencies": {
|
|
132
|
-
"crossws": "^0.4.
|
|
133
|
-
"fastify": "^5.12.
|
|
134
|
-
"supertest": "^7.
|
|
135
|
-
"ws": "^8.
|
|
136
|
-
"zod": "^4.5
|
|
132
|
+
"crossws": "^0.4.15",
|
|
133
|
+
"fastify": "^5.12.5",
|
|
134
|
+
"supertest": "^7.3.1",
|
|
135
|
+
"ws": "^8.22.0",
|
|
136
|
+
"zod": "^4.6.5"
|
|
137
137
|
},
|
|
138
138
|
"scripts": {
|
|
139
139
|
"type:check": "tsc -b"
|