@orpc/server 2.0.0-beta.43 → 2.0.0-beta.45
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 +56 -57
- package/dist/adapters/aws-lambda/index.mjs +2 -2
- package/dist/adapters/crossws/index.mjs +2 -2
- package/dist/adapters/fastify/index.mjs +2 -2
- package/dist/adapters/fetch/index.d.mts +2 -2
- package/dist/adapters/fetch/index.d.ts +2 -2
- package/dist/adapters/fetch/index.mjs +2 -2
- package/dist/adapters/message-port/index.d.mts +1 -1
- package/dist/adapters/message-port/index.d.ts +1 -1
- package/dist/adapters/message-port/index.mjs +2 -2
- package/dist/adapters/node/index.d.mts +1 -1
- package/dist/adapters/node/index.d.ts +1 -1
- package/dist/adapters/node/index.mjs +2 -2
- package/dist/adapters/standard/index.d.mts +10 -8
- package/dist/adapters/standard/index.d.ts +10 -8
- package/dist/adapters/standard/index.mjs +2 -2
- package/dist/adapters/websocket/index.mjs +2 -2
- package/dist/helpers/index.d.mts +2 -1
- package/dist/helpers/index.d.ts +2 -1
- package/dist/helpers/index.mjs +3 -9
- package/dist/index.d.mts +14 -7
- package/dist/index.d.ts +14 -7
- package/dist/index.mjs +2 -2
- package/dist/plugins/index.d.mts +64 -55
- package/dist/plugins/index.d.ts +64 -55
- package/dist/plugins/index.mjs +232 -146
- package/dist/shared/{server.BllfVJLm.d.mts → server.B4hFgqPr.d.mts} +2 -2
- package/dist/shared/{server.BEdOJs6S.d.mts → server.BWhT-63L.d.mts} +5 -5
- package/dist/shared/{server.BEdOJs6S.d.ts → server.BWhT-63L.d.ts} +5 -5
- package/dist/shared/{server.CVO0hWyj.d.mts → server.BYNQ06wN.d.ts} +5 -5
- package/dist/shared/{server.BgvqVW_v.mjs → server.BcxD3fmU.mjs} +15 -3
- package/dist/shared/{server.COfbQ0D8.d.ts → server.CI0qiBzm.d.ts} +2 -2
- package/dist/shared/{server.DFEuNUt4.d.ts → server.CO_upro1.d.ts} +3 -3
- package/dist/shared/{server.DZeVx8K9.d.ts → server.DcDZdSrg.d.ts} +1 -1
- package/dist/shared/{server.UG_xdTub.d.mts → server.DgcwQ-Wr.d.mts} +3 -3
- package/dist/shared/{server.B1ecixZJ.mjs → server.Kbq-C85k.mjs} +7 -20
- package/dist/shared/{server.DJNkZPfD.d.ts → server.TVSii8T9.d.mts} +5 -5
- package/dist/shared/{server.DD1roZl9.d.mts → server.TjEHK8V3.d.mts} +1 -1
- package/package.json +15 -15
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,11 +1,63 @@
|
|
|
1
1
|
import { ORPCError } from '@orpc/client';
|
|
2
|
-
import {
|
|
2
|
+
import { DEFAULT_BATCH_MAX_SIZE, BATCH_HEADER } from '@orpc/client/plugins';
|
|
3
|
+
export { BATCH_HEADER, DEFAULT_BATCH_MAX_SIZE } from '@orpc/client/plugins';
|
|
4
|
+
import { toArray, value, promiseWithResolvers, isAsyncIteratorObject, isCompressibleContentType, isNoTransformCacheControl, parseAcceptEncodingQualities, isAcceptableEncoding, varyByAcceptEncoding, stringifyJSON, anyAbortSignal, override, AsyncIteratorClass, runWithSignal, AbortError, wrapAsyncIterator, wrapReadableStream } from '@orpc/shared';
|
|
3
5
|
import { flattenStandardHeader, parseStandardUrl, generateContentDisposition, mergeStandardHeaders } from '@standard-server/core';
|
|
4
6
|
import { isClientPeerSendMessage, ServerPeer, encodePeerMessage } from '@standard-server/peer';
|
|
5
7
|
import { toFetchHeaders, toStandardBody, toStandardHeaders } from '@standard-server/fetch';
|
|
6
8
|
export { R as RequestLimitHandlerPlugin } from '../shared/server.1ExWFMze.mjs';
|
|
7
9
|
|
|
8
10
|
const BATCH_CONTENT_TYPE = "application/vnd.orpc.batch";
|
|
11
|
+
const textEncoder = new TextEncoder();
|
|
12
|
+
function encodeLengthPrefix(length) {
|
|
13
|
+
const prefix = new Uint8Array(4);
|
|
14
|
+
new DataView(prefix.buffer).setUint32(0, length, false);
|
|
15
|
+
return prefix;
|
|
16
|
+
}
|
|
17
|
+
async function encodeFrame(message) {
|
|
18
|
+
const encoded = await encodePeerMessage(message);
|
|
19
|
+
const bytes = typeof encoded === "string" ? textEncoder.encode(encoded) : encoded;
|
|
20
|
+
return [encodeLengthPrefix(bytes.byteLength), bytes];
|
|
21
|
+
}
|
|
22
|
+
function isBodyHeader(name) {
|
|
23
|
+
return name.startsWith("content-") || name === "standard-server";
|
|
24
|
+
}
|
|
25
|
+
function internalError(err) {
|
|
26
|
+
Promise.reject(err);
|
|
27
|
+
return { status: 500, headers: {}, body: "Internal server error" };
|
|
28
|
+
}
|
|
29
|
+
function liftSubresponseHeaders(batchHeaders, subresponseHeaders) {
|
|
30
|
+
const [first = {}, ...rest] = subresponseHeaders;
|
|
31
|
+
const shared = {};
|
|
32
|
+
for (const [name, value2] of Object.entries(first)) {
|
|
33
|
+
if (value2 === void 0 || isBodyHeader(name)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const flattened = flattenStandardHeader(value2);
|
|
37
|
+
if (rest.every((other) => flattenStandardHeader(other[name]) === flattened)) {
|
|
38
|
+
shared[name] = value2;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const headers = { ...shared, ...batchHeaders };
|
|
42
|
+
const cookies = new Set([batchHeaders, ...subresponseHeaders].flatMap((other) => toArray(other["set-cookie"])));
|
|
43
|
+
if (cookies.size > 0) {
|
|
44
|
+
headers["set-cookie"] = [...cookies];
|
|
45
|
+
}
|
|
46
|
+
const varies = /* @__PURE__ */ new Map();
|
|
47
|
+
for (const value2 of [batchHeaders, ...subresponseHeaders].flatMap((other) => toArray(other.vary))) {
|
|
48
|
+
for (const field of value2.split(",")) {
|
|
49
|
+
const name = field.trim();
|
|
50
|
+
const key = name.toLowerCase();
|
|
51
|
+
if (name && !varies.has(key)) {
|
|
52
|
+
varies.set(key, name);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (varies.size > 0) {
|
|
57
|
+
headers.vary = varies.has("*") ? "*" : [...varies.values()].join(", ");
|
|
58
|
+
}
|
|
59
|
+
return headers;
|
|
60
|
+
}
|
|
9
61
|
class BatchHandlerPlugin {
|
|
10
62
|
name = "~batch";
|
|
11
63
|
/**
|
|
@@ -15,12 +67,13 @@ class BatchHandlerPlugin {
|
|
|
15
67
|
after = ["~tracing"];
|
|
16
68
|
maxSize;
|
|
17
69
|
mapSubrequest;
|
|
70
|
+
mapSubresponse;
|
|
18
71
|
successStatus;
|
|
19
72
|
headers;
|
|
20
73
|
keepAliveEnabled;
|
|
21
74
|
keepAliveInterval;
|
|
22
75
|
constructor(options = {}) {
|
|
23
|
-
this.maxSize = options.maxSize ??
|
|
76
|
+
this.maxSize = options.maxSize ?? DEFAULT_BATCH_MAX_SIZE;
|
|
24
77
|
this.mapSubrequest = options.mapSubrequest ?? ((subRequest, { request: batchRequest }) => ({
|
|
25
78
|
...subRequest,
|
|
26
79
|
headers: {
|
|
@@ -31,10 +84,19 @@ class BatchHandlerPlugin {
|
|
|
31
84
|
* or `origin`, cannot be overridden by a subrequest, which is just request payload.
|
|
32
85
|
*/
|
|
33
86
|
...batchRequest.headers,
|
|
34
|
-
|
|
87
|
+
[BATCH_HEADER]: void 0
|
|
35
88
|
// useful in case batch plugin is used multiple times
|
|
36
89
|
}
|
|
37
90
|
}));
|
|
91
|
+
this.mapSubresponse = options.mapSubresponse ?? ((subresponse, { headers: batchHeaders }) => {
|
|
92
|
+
const headers = {};
|
|
93
|
+
for (const [name, value2] of Object.entries(subresponse.headers)) {
|
|
94
|
+
if (name !== "set-cookie" && flattenStandardHeader(value2) !== flattenStandardHeader(batchHeaders[name])) {
|
|
95
|
+
headers[name] = value2;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { ...subresponse, headers };
|
|
99
|
+
});
|
|
38
100
|
this.successStatus = options.successStatus ?? 207;
|
|
39
101
|
this.headers = options.headers ?? {};
|
|
40
102
|
this.keepAliveEnabled = options.keepAlive?.enabled ?? true;
|
|
@@ -42,11 +104,9 @@ class BatchHandlerPlugin {
|
|
|
42
104
|
}
|
|
43
105
|
init(options) {
|
|
44
106
|
const routingInterceptor = async (interceptorOptions) => {
|
|
45
|
-
|
|
46
|
-
if (batchHeader === void 0) {
|
|
107
|
+
if (flattenStandardHeader(interceptorOptions.request.headers[BATCH_HEADER]) === void 0) {
|
|
47
108
|
return interceptorOptions.next();
|
|
48
109
|
}
|
|
49
|
-
const mode = batchHeader === "buffered" ? "buffered" : "streaming";
|
|
50
110
|
let messages;
|
|
51
111
|
try {
|
|
52
112
|
if (interceptorOptions.request.method === "GET") {
|
|
@@ -101,23 +161,78 @@ class BatchHandlerPlugin {
|
|
|
101
161
|
response: { status: 413, headers: {}, body: "Batch request size exceeds the maximum allowed size" }
|
|
102
162
|
};
|
|
103
163
|
}
|
|
104
|
-
const
|
|
164
|
+
const status = await value(this.successStatus, interceptorOptions);
|
|
165
|
+
const headers = await value(this.headers, interceptorOptions);
|
|
166
|
+
const bufferedMessages = [];
|
|
167
|
+
let streamController;
|
|
168
|
+
let keepAliveTimer;
|
|
169
|
+
const ready = messages.map(() => promiseWithResolvers());
|
|
170
|
+
const lifting = Promise.all(ready.map(({ promise }) => promise)).then((all) => {
|
|
171
|
+
const heads = all.filter((head) => head !== void 0);
|
|
172
|
+
return {
|
|
173
|
+
batchHeaders: liftSubresponseHeaders(headers, heads.map((head) => head.headers)),
|
|
174
|
+
streams: heads.some((head) => head.stream)
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
const clearKeepAlive = () => {
|
|
178
|
+
if (keepAliveTimer !== void 0) {
|
|
179
|
+
clearInterval(keepAliveTimer);
|
|
180
|
+
keepAliveTimer = void 0;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const scheduleKeepAlive = () => {
|
|
184
|
+
if (!this.keepAliveEnabled) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
clearKeepAlive();
|
|
188
|
+
keepAliveTimer = setInterval(() => {
|
|
189
|
+
try {
|
|
190
|
+
streamController.enqueue(encodeLengthPrefix(0));
|
|
191
|
+
} catch {
|
|
192
|
+
clearKeepAlive();
|
|
193
|
+
}
|
|
194
|
+
}, this.keepAliveInterval);
|
|
195
|
+
};
|
|
196
|
+
const enqueueMessage = async (message) => {
|
|
197
|
+
for (const chunk of await encodeFrame(message)) {
|
|
198
|
+
streamController.enqueue(chunk);
|
|
199
|
+
}
|
|
200
|
+
scheduleKeepAlive();
|
|
201
|
+
};
|
|
202
|
+
const peer = new ServerPeer(async (message) => {
|
|
203
|
+
if (streamController === void 0) {
|
|
204
|
+
bufferedMessages.push(message);
|
|
205
|
+
} else {
|
|
206
|
+
await enqueueMessage(message);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
const handleIndividualRequest = async (request, markReady) => {
|
|
210
|
+
let response;
|
|
105
211
|
try {
|
|
106
212
|
request = this.mapSubrequest(request, interceptorOptions);
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
return { status: 404, headers: {}, body: "No procedure matched" };
|
|
110
|
-
}
|
|
111
|
-
return response;
|
|
213
|
+
const result = await interceptorOptions.next({ ...interceptorOptions, request });
|
|
214
|
+
response = result.matched ? result.response : { status: 404, headers: {}, body: "No procedure matched" };
|
|
112
215
|
} catch (err) {
|
|
113
|
-
|
|
114
|
-
|
|
216
|
+
response = internalError(err);
|
|
217
|
+
}
|
|
218
|
+
markReady({
|
|
219
|
+
headers: response.headers,
|
|
220
|
+
stream: response.body instanceof ReadableStream || isAsyncIteratorObject(response.body)
|
|
221
|
+
});
|
|
222
|
+
const { batchHeaders: batchHeaders2 } = await lifting;
|
|
223
|
+
try {
|
|
224
|
+
return this.mapSubresponse(response, { status, headers: batchHeaders2 }, interceptorOptions);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
return internalError(err);
|
|
115
227
|
}
|
|
116
228
|
};
|
|
117
|
-
const runSubrequests = async (
|
|
118
|
-
const promise = Promise.all(messages.map((msg) =>
|
|
229
|
+
const runSubrequests = async () => {
|
|
230
|
+
const promise = Promise.all(messages.map((msg, i) => {
|
|
231
|
+
const { resolve } = ready[i];
|
|
232
|
+
return peer.message(msg, (request) => handleIndividualRequest(request, resolve)).finally(() => resolve(void 0));
|
|
233
|
+
}));
|
|
119
234
|
const signal = interceptorOptions.request.signal;
|
|
120
|
-
const closePeer = () =>
|
|
235
|
+
const closePeer = () => peer.close(signal?.reason);
|
|
121
236
|
if (signal?.aborted) {
|
|
122
237
|
closePeer();
|
|
123
238
|
}
|
|
@@ -128,72 +243,28 @@ class BatchHandlerPlugin {
|
|
|
128
243
|
signal?.removeEventListener("abort", closePeer);
|
|
129
244
|
}
|
|
130
245
|
};
|
|
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
|
-
}
|
|
246
|
+
const subrequests = runSubrequests();
|
|
247
|
+
const { batchHeaders, streams } = await lifting;
|
|
248
|
+
if (!streams) {
|
|
249
|
+
await subrequests;
|
|
250
|
+
await peer.close();
|
|
251
|
+
if (bufferedMessages.some((msg) => msg.binary !== void 0)) {
|
|
252
|
+
const chunks = (await Promise.all(bufferedMessages.map(encodeFrame))).flat();
|
|
150
253
|
return {
|
|
151
254
|
matched: true,
|
|
152
255
|
response: {
|
|
153
256
|
status,
|
|
154
|
-
headers,
|
|
257
|
+
headers: batchHeaders,
|
|
155
258
|
body: new Blob(chunks, { type: BATCH_CONTENT_TYPE })
|
|
156
259
|
}
|
|
157
260
|
};
|
|
158
261
|
}
|
|
159
262
|
return {
|
|
160
263
|
matched: true,
|
|
161
|
-
response: { status, headers, body:
|
|
264
|
+
response: { status, headers: batchHeaders, body: bufferedMessages }
|
|
162
265
|
};
|
|
163
266
|
}
|
|
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({
|
|
267
|
+
const body = new ReadableStream({
|
|
197
268
|
start(controller) {
|
|
198
269
|
streamController = controller;
|
|
199
270
|
scheduleKeepAlive();
|
|
@@ -203,7 +274,8 @@ class BatchHandlerPlugin {
|
|
|
203
274
|
await peer.close(reason);
|
|
204
275
|
}
|
|
205
276
|
});
|
|
206
|
-
|
|
277
|
+
const flushed = Promise.all(bufferedMessages.splice(0).map(enqueueMessage));
|
|
278
|
+
Promise.all([flushed, subrequests]).then(async () => {
|
|
207
279
|
clearKeepAlive();
|
|
208
280
|
streamController.close();
|
|
209
281
|
await peer.close();
|
|
@@ -216,8 +288,8 @@ class BatchHandlerPlugin {
|
|
|
216
288
|
matched: true,
|
|
217
289
|
response: {
|
|
218
290
|
status,
|
|
219
|
-
headers: { ...
|
|
220
|
-
body
|
|
291
|
+
headers: { ...batchHeaders, "content-type": BATCH_CONTENT_TYPE },
|
|
292
|
+
body
|
|
221
293
|
}
|
|
222
294
|
};
|
|
223
295
|
};
|
|
@@ -405,75 +477,6 @@ class MethodOverrideHandlerPlugin {
|
|
|
405
477
|
}
|
|
406
478
|
}
|
|
407
479
|
|
|
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
480
|
const MAX_CONTENT_ENCODINGS = 5;
|
|
478
481
|
class RequestCompressionHandlerPlugin {
|
|
479
482
|
name = "~request-compression";
|
|
@@ -608,7 +611,8 @@ class ResponseCompressionHandlerPlugin {
|
|
|
608
611
|
};
|
|
609
612
|
}
|
|
610
613
|
} else if (body instanceof Blob) {
|
|
611
|
-
|
|
614
|
+
const contentType = headers["content-type"] ?? body.type;
|
|
615
|
+
if ((!Number.isFinite(body.size) || body.size >= this.threshold) && this.isCompressibleContentType(flattenStandardHeader(contentType), interceptorOptions)) {
|
|
612
616
|
const contentDisposition = headers["content-disposition"] ?? generateContentDisposition(
|
|
613
617
|
body instanceof File ? body.name : "blob"
|
|
614
618
|
);
|
|
@@ -620,7 +624,7 @@ class ResponseCompressionHandlerPlugin {
|
|
|
620
624
|
headers: {
|
|
621
625
|
...toCompressedHeaders(headers, encoding),
|
|
622
626
|
"standard-server": "file",
|
|
623
|
-
"content-type":
|
|
627
|
+
"content-type": contentType,
|
|
624
628
|
"content-disposition": contentDisposition
|
|
625
629
|
}
|
|
626
630
|
}
|
|
@@ -826,6 +830,88 @@ class RethrowHandlerPlugin {
|
|
|
826
830
|
}
|
|
827
831
|
}
|
|
828
832
|
|
|
833
|
+
class SignalSyncHandlerPlugin {
|
|
834
|
+
name = "~signal-sync";
|
|
835
|
+
init(options) {
|
|
836
|
+
const clientInterceptor = async (interceptorOptions) => {
|
|
837
|
+
const controller = new AbortController();
|
|
838
|
+
const signal = anyAbortSignal([interceptorOptions.signal, controller.signal]);
|
|
839
|
+
const cancel = () => controller.abort(new AbortError("The response body was cancelled"));
|
|
840
|
+
const output = await interceptorOptions.next({ ...interceptorOptions, signal });
|
|
841
|
+
if (isAsyncIteratorObject(output)) {
|
|
842
|
+
return override(output, syncAsyncIterator(output, signal, cancel));
|
|
843
|
+
}
|
|
844
|
+
if (output instanceof ReadableStream) {
|
|
845
|
+
return override(output, syncReadableStream(output, signal, cancel));
|
|
846
|
+
}
|
|
847
|
+
return output;
|
|
848
|
+
};
|
|
849
|
+
return { ...options, clientInterceptors: [clientInterceptor, ...toArray(options.clientInterceptors)] };
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
function listenAbort(signal, onAbort) {
|
|
853
|
+
if (signal.aborted) {
|
|
854
|
+
onAbort();
|
|
855
|
+
} else {
|
|
856
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
857
|
+
}
|
|
858
|
+
return () => signal.removeEventListener("abort", onAbort);
|
|
859
|
+
}
|
|
860
|
+
function syncAsyncIterator(iterator, signal, cancel) {
|
|
861
|
+
let cancelling;
|
|
862
|
+
const unlisten = listenAbort(signal, () => {
|
|
863
|
+
cancelling = (async () => iterator.return?.())();
|
|
864
|
+
cancelling.catch(() => {
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
return new AsyncIteratorClass(
|
|
868
|
+
() => runWithSignal(signal, () => iterator.next()),
|
|
869
|
+
async ({ kind }) => {
|
|
870
|
+
if (kind === "cancelled") {
|
|
871
|
+
cancel();
|
|
872
|
+
await cancelling;
|
|
873
|
+
}
|
|
874
|
+
unlisten();
|
|
875
|
+
}
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
function syncReadableStream(stream, signal, cancel) {
|
|
879
|
+
const reader = stream.getReader();
|
|
880
|
+
let cancelling;
|
|
881
|
+
let unlisten;
|
|
882
|
+
return new ReadableStream({
|
|
883
|
+
start(controller) {
|
|
884
|
+
unlisten = listenAbort(signal, () => {
|
|
885
|
+
controller.error(signal.reason);
|
|
886
|
+
cancelling = reader.cancel(signal.reason);
|
|
887
|
+
cancelling.catch(() => {
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
},
|
|
891
|
+
async pull(controller) {
|
|
892
|
+
try {
|
|
893
|
+
const { done, value } = await reader.read();
|
|
894
|
+
if (signal.aborted) {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (done) {
|
|
898
|
+
unlisten();
|
|
899
|
+
controller.close();
|
|
900
|
+
} else {
|
|
901
|
+
controller.enqueue(value);
|
|
902
|
+
}
|
|
903
|
+
} catch (error) {
|
|
904
|
+
unlisten();
|
|
905
|
+
throw error;
|
|
906
|
+
}
|
|
907
|
+
},
|
|
908
|
+
async cancel() {
|
|
909
|
+
cancel();
|
|
910
|
+
await cancelling;
|
|
911
|
+
}
|
|
912
|
+
}, { highWaterMark: 0 });
|
|
913
|
+
}
|
|
914
|
+
|
|
829
915
|
class TimeoutHandlerPlugin {
|
|
830
916
|
timeout;
|
|
831
917
|
streamingTimeout;
|
|
@@ -875,4 +961,4 @@ class TimeoutHandlerPlugin {
|
|
|
875
961
|
}
|
|
876
962
|
}
|
|
877
963
|
|
|
878
|
-
export { BATCH_CONTENT_TYPE, BatchHandlerPlugin, CORSHandlerPlugin, CORSHandlerPlugin as CORSPlugin, GetMethodCsrfProtectionHandlerPlugin, MethodOverrideHandlerPlugin,
|
|
964
|
+
export { BATCH_CONTENT_TYPE, BatchHandlerPlugin, CORSHandlerPlugin, CORSHandlerPlugin as CORSPlugin, GetMethodCsrfProtectionHandlerPlugin, MethodOverrideHandlerPlugin, RequestCompressionHandlerPlugin, RequestHeadersHandlerPlugin, RequestHeadersHandlerPlugin as RequestHeadersPlugin, ResponseCompressionHandlerPlugin, ResponseHeadersHandlerPlugin, ResponseHeadersHandlerPlugin as ResponseHeadersPlugin, RethrowHandlerPlugin, SignalSyncHandlerPlugin, TimeoutHandlerPlugin, negotiateResponseCompressionEncoding, toCompressedHeaders };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as Context } from './server.
|
|
2
|
-
import { b as StandardHandlerPlugin, c as StandardHandlerOptions } from './server.
|
|
1
|
+
import { C as Context } from './server.BWhT-63L.mjs';
|
|
2
|
+
import { b as StandardHandlerPlugin, c as StandardHandlerOptions } from './server.TVSii8T9.mjs';
|
|
3
3
|
|
|
4
4
|
interface RequestLimitHandlerPluginOptions {
|
|
5
5
|
/**
|
|
@@ -13,7 +13,7 @@ type MiddlewareResult<TOutContext extends Context, TOutput> = Promisable<{
|
|
|
13
13
|
context: TOutContext;
|
|
14
14
|
}>;
|
|
15
15
|
type MiddlewareNextOptions<TOutContext extends Context> = object extends TOutContext ? {
|
|
16
|
-
context?: TOutContext;
|
|
16
|
+
context?: TOutContext | undefined;
|
|
17
17
|
} : {
|
|
18
18
|
context: TOutContext;
|
|
19
19
|
};
|
|
@@ -21,7 +21,7 @@ interface MiddlewareNext<TOutput> {
|
|
|
21
21
|
<T extends Context = object>(...rest: MaybeOptionalOptions<MiddlewareNextOptions<T>>): MiddlewareResult<T, TOutput>;
|
|
22
22
|
}
|
|
23
23
|
type MiddlewareDoneOptions<TOutContext, TOutput> = (object extends TOutContext ? {
|
|
24
|
-
context?: TOutContext;
|
|
24
|
+
context?: TOutContext | undefined;
|
|
25
25
|
} : {
|
|
26
26
|
context: TOutContext;
|
|
27
27
|
}) & {
|
|
@@ -150,10 +150,10 @@ interface ProcedureClientInterceptorOptions<TInitialContext extends Context, TEr
|
|
|
150
150
|
}
|
|
151
151
|
type ProcedureClientInterceptor<TInitialContext extends Context, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap> = Interceptor<ProcedureClientInterceptorOptions<TInitialContext, TErrorMap>, PromiseWithError<InferSchemaOutput<TOutputSchema>, ORPCErrorFromErrorMap<TErrorMap> | ThrowableError>>;
|
|
152
152
|
type ProcedureClientOptions<TInitialContext extends Context, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap, TClientContext extends ClientContext> = {
|
|
153
|
-
path?: string[];
|
|
154
|
-
interceptors?: ProcedureClientInterceptor<TInitialContext, TOutputSchema, TErrorMap>[];
|
|
153
|
+
path?: string[] | undefined;
|
|
154
|
+
interceptors?: ProcedureClientInterceptor<TInitialContext, TOutputSchema, TErrorMap>[] | undefined;
|
|
155
155
|
} & (object extends TInitialContext ? {
|
|
156
|
-
context?: Value<Promisable<TInitialContext>, [clientContext: TClientContext]
|
|
156
|
+
context?: Value<Promisable<TInitialContext>, [clientContext: TClientContext]> | undefined;
|
|
157
157
|
} : {
|
|
158
158
|
context: Value<Promisable<TInitialContext>, [clientContext: TClientContext]>;
|
|
159
159
|
});
|
|
@@ -13,7 +13,7 @@ type MiddlewareResult<TOutContext extends Context, TOutput> = Promisable<{
|
|
|
13
13
|
context: TOutContext;
|
|
14
14
|
}>;
|
|
15
15
|
type MiddlewareNextOptions<TOutContext extends Context> = object extends TOutContext ? {
|
|
16
|
-
context?: TOutContext;
|
|
16
|
+
context?: TOutContext | undefined;
|
|
17
17
|
} : {
|
|
18
18
|
context: TOutContext;
|
|
19
19
|
};
|
|
@@ -21,7 +21,7 @@ interface MiddlewareNext<TOutput> {
|
|
|
21
21
|
<T extends Context = object>(...rest: MaybeOptionalOptions<MiddlewareNextOptions<T>>): MiddlewareResult<T, TOutput>;
|
|
22
22
|
}
|
|
23
23
|
type MiddlewareDoneOptions<TOutContext, TOutput> = (object extends TOutContext ? {
|
|
24
|
-
context?: TOutContext;
|
|
24
|
+
context?: TOutContext | undefined;
|
|
25
25
|
} : {
|
|
26
26
|
context: TOutContext;
|
|
27
27
|
}) & {
|
|
@@ -150,10 +150,10 @@ interface ProcedureClientInterceptorOptions<TInitialContext extends Context, TEr
|
|
|
150
150
|
}
|
|
151
151
|
type ProcedureClientInterceptor<TInitialContext extends Context, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap> = Interceptor<ProcedureClientInterceptorOptions<TInitialContext, TErrorMap>, PromiseWithError<InferSchemaOutput<TOutputSchema>, ORPCErrorFromErrorMap<TErrorMap> | ThrowableError>>;
|
|
152
152
|
type ProcedureClientOptions<TInitialContext extends Context, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap, TClientContext extends ClientContext> = {
|
|
153
|
-
path?: string[];
|
|
154
|
-
interceptors?: ProcedureClientInterceptor<TInitialContext, TOutputSchema, TErrorMap>[];
|
|
153
|
+
path?: string[] | undefined;
|
|
154
|
+
interceptors?: ProcedureClientInterceptor<TInitialContext, TOutputSchema, TErrorMap>[] | undefined;
|
|
155
155
|
} & (object extends TInitialContext ? {
|
|
156
|
-
context?: Value<Promisable<TInitialContext>, [clientContext: TClientContext]
|
|
156
|
+
context?: Value<Promisable<TInitialContext>, [clientContext: TClientContext]> | undefined;
|
|
157
157
|
} : {
|
|
158
158
|
context: Value<Promisable<TInitialContext>, [clientContext: TClientContext]>;
|
|
159
159
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AnyORPCError } from '@orpc/client';
|
|
2
2
|
import { OrderablePlugin, Value, Promisable, Interceptor } from '@orpc/shared';
|
|
3
3
|
import { StandardLazyRequest, StandardResponse } from '@standard-server/core';
|
|
4
|
-
import { C as Context, c as ProcedureClientInterceptor, A as AnyProcedure } from './server.
|
|
4
|
+
import { C as Context, c as ProcedureClientInterceptor, A as AnyProcedure } from './server.BWhT-63L.js';
|
|
5
5
|
import { Schema, ErrorMap } from '@orpc/contract';
|
|
6
6
|
|
|
7
7
|
interface StandardHandlerPlugin<T extends Context> extends OrderablePlugin {
|
|
@@ -73,19 +73,19 @@ interface StandardHandlerOptions<TContext extends Context> {
|
|
|
73
73
|
* - batch plugins - separate one request into multiple and call multiple next
|
|
74
74
|
* - openapi spec plugin - to intercept a request and early response
|
|
75
75
|
*/
|
|
76
|
-
routingInterceptors?: StandardHandlerRoutingInterceptor<TContext>[];
|
|
76
|
+
routingInterceptors?: StandardHandlerRoutingInterceptor<TContext>[] | undefined;
|
|
77
77
|
/**
|
|
78
78
|
* interceptor run after routing and before error handler,
|
|
79
79
|
* useful for error handling, logging, metrics, etc.
|
|
80
80
|
*/
|
|
81
|
-
interceptors?: StandardHandlerInterceptor<TContext>[];
|
|
81
|
+
interceptors?: StandardHandlerInterceptor<TContext>[] | undefined;
|
|
82
82
|
/**
|
|
83
83
|
*
|
|
84
84
|
* ClientInterceptor equivalent with createRouterClient.interceptors / createProcedure.interceptors
|
|
85
85
|
* useful for error handling, logging, metrics, etc. (not counting encoding/decoding)
|
|
86
86
|
*/
|
|
87
|
-
clientInterceptors?: ProcedureClientInterceptor<TContext, Schema<unknown>, ErrorMap>[];
|
|
88
|
-
plugins?: StandardHandlerPlugin<TContext>[];
|
|
87
|
+
clientInterceptors?: ProcedureClientInterceptor<TContext, Schema<unknown>, ErrorMap>[] | undefined;
|
|
88
|
+
plugins?: StandardHandlerPlugin<TContext>[] | undefined;
|
|
89
89
|
}
|
|
90
90
|
declare class StandardHandler<T extends Context> {
|
|
91
91
|
private readonly codec;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { c as createProcedureClient, P as Procedure, L as Lazy, u as unlazy } from './server.FEqy7Hrj.mjs';
|
|
2
1
|
import { resolveMetaPlugins, mergeErrorMap, ProcedureContract } from '@orpc/contract';
|
|
3
2
|
import { omit, isTypescriptObject, getOwn } from '@orpc/shared';
|
|
3
|
+
import { c as createProcedureClient, P as Procedure, L as Lazy, u as unlazy } from './server.FEqy7Hrj.mjs';
|
|
4
4
|
|
|
5
5
|
const DEFAULT_SUCCESS_STATUS = 200;
|
|
6
|
-
const DEFAULT_ERROR_STATUS =
|
|
6
|
+
const DEFAULT_ERROR_STATUS = 400;
|
|
7
7
|
|
|
8
8
|
function createGuardedProcedureLazy(lazy) {
|
|
9
9
|
const guarded = new Lazy({
|
|
@@ -130,6 +130,18 @@ function augmentImplementedRouter(router, options) {
|
|
|
130
130
|
}
|
|
131
131
|
return enhanced;
|
|
132
132
|
}
|
|
133
|
+
async function resolveContractProcedure(router, contract, path) {
|
|
134
|
+
if (contract instanceof Procedure) {
|
|
135
|
+
return contract;
|
|
136
|
+
}
|
|
137
|
+
const { default: maybeProcedure } = await unlazy(getRouter(router, path));
|
|
138
|
+
if (!(maybeProcedure instanceof Procedure)) {
|
|
139
|
+
throw new TypeError(
|
|
140
|
+
`[Contract-First] Missing or invalid implementation for procedure at path: "${path.join(".")}". Ensure the procedure is correctly implemented and matches its contract.`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return createContractProcedure(maybeProcedure, contract);
|
|
144
|
+
}
|
|
133
145
|
function getRouter(router, path) {
|
|
134
146
|
let current = router;
|
|
135
147
|
for (let i = 0; i < path.length; i++) {
|
|
@@ -224,4 +236,4 @@ async function unlazyRouter(router) {
|
|
|
224
236
|
return unlazied;
|
|
225
237
|
}
|
|
226
238
|
|
|
227
|
-
export { DEFAULT_SUCCESS_STATUS as D, DEFAULT_ERROR_STATUS as a, augmentRouter as b,
|
|
239
|
+
export { DEFAULT_SUCCESS_STATUS as D, DEFAULT_ERROR_STATUS as a, augmentRouter as b, augmentImplementedRouter as c, withHiddenRouterContract as d, createGuardedProcedureLazy as e, call as f, getRouter as g, createContractProcedure as h, getHiddenRouterContract as i, walkProcedureContractsAsync as j, resolveContractProcedure as r, unlazyRouter as u, walkProcedureContractsSync as w };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as Context } from './server.
|
|
2
|
-
import { b as StandardHandlerPlugin, c as StandardHandlerOptions } from './server.
|
|
1
|
+
import { C as Context } from './server.BWhT-63L.js';
|
|
2
|
+
import { b as StandardHandlerPlugin, c as StandardHandlerOptions } from './server.BYNQ06wN.js';
|
|
3
3
|
|
|
4
4
|
interface RequestLimitHandlerPluginOptions {
|
|
5
5
|
/**
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Value, Promisable } from '@orpc/shared';
|
|
2
2
|
import { StandardLazyRequest } from '@standard-server/core';
|
|
3
|
-
import { C as Context } from './server.
|
|
4
|
-
import { a as StandardHandlerHandleOptions } from './server.
|
|
3
|
+
import { C as Context } from './server.BWhT-63L.js';
|
|
4
|
+
import { a as StandardHandlerHandleOptions } from './server.BYNQ06wN.js';
|
|
5
5
|
|
|
6
6
|
type FriendlyStandardHandlerHandleOptions<T extends Context> = Omit<StandardHandlerHandleOptions<T>, 'context'> & (object extends T ? {
|
|
7
|
-
context?: Value<Promisable<T>, [request: StandardLazyRequest]
|
|
7
|
+
context?: Value<Promisable<T>, [request: StandardLazyRequest]> | undefined;
|
|
8
8
|
} : {
|
|
9
9
|
context: Value<Promisable<T>, [request: StandardLazyRequest]>;
|
|
10
10
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RouterContract, ProcedureContract, ORPCErrorFromErrorMap, ThrowableError, InferSchemaInput, InferSchemaOutput } from '@orpc/contract';
|
|
2
|
-
import { C as Context, a as Procedure, L as Lazyable, M as MergedContext } from './server.
|
|
2
|
+
import { C as Context, a as Procedure, L as Lazyable, M as MergedContext } from './server.BWhT-63L.js';
|
|
3
3
|
|
|
4
4
|
type Router<TInitialContext extends Context> = Procedure<TInitialContext, any, any, any, any> | {
|
|
5
5
|
[k: string]: Lazyable<Router<TInitialContext>>;
|