@orpc/server 1.14.11 → 1.14.13
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 +98 -52
- package/dist/adapters/aws-lambda/index.d.mts +46 -0
- package/dist/adapters/aws-lambda/index.d.ts +46 -0
- package/dist/adapters/aws-lambda/index.mjs +40 -0
- package/dist/adapters/bun-ws/index.d.mts +36 -0
- package/dist/adapters/bun-ws/index.d.ts +36 -0
- package/dist/adapters/bun-ws/index.mjs +47 -0
- package/dist/adapters/crossws/index.d.mts +21 -45
- package/dist/adapters/crossws/index.d.ts +21 -45
- package/dist/adapters/crossws/index.mjs +18 -40
- package/dist/adapters/fastify/index.d.mts +53 -0
- package/dist/adapters/fastify/index.d.ts +53 -0
- package/dist/adapters/fastify/index.mjs +52 -0
- package/dist/adapters/fetch/index.d.mts +96 -64
- package/dist/adapters/fetch/index.d.ts +96 -64
- package/dist/adapters/fetch/index.mjs +139 -34
- package/dist/adapters/message-port/index.d.mts +34 -49
- package/dist/adapters/message-port/index.d.ts +34 -49
- package/dist/adapters/message-port/index.mjs +37 -68
- package/dist/adapters/node/index.d.mts +71 -63
- package/dist/adapters/node/index.d.ts +71 -63
- package/dist/adapters/node/index.mjs +113 -36
- package/dist/adapters/standard/index.d.mts +18 -16
- package/dist/adapters/standard/index.d.ts +18 -16
- package/dist/adapters/standard/index.mjs +5 -5
- package/dist/adapters/standard-peer/index.d.mts +14 -12
- package/dist/adapters/standard-peer/index.d.ts +14 -12
- package/dist/adapters/standard-peer/index.mjs +21 -2
- package/dist/adapters/websocket/index.d.mts +38 -53
- package/dist/adapters/websocket/index.d.ts +38 -53
- package/dist/adapters/websocket/index.mjs +42 -59
- package/dist/adapters/ws/index.d.mts +31 -0
- package/dist/adapters/ws/index.d.ts +31 -0
- package/dist/adapters/ws/index.mjs +41 -0
- package/dist/helpers/index.d.mts +4 -4
- package/dist/helpers/index.d.ts +4 -4
- package/dist/helpers/index.mjs +5 -13
- package/dist/hibernation/index.d.mts +44 -0
- package/dist/hibernation/index.d.ts +44 -0
- package/dist/hibernation/index.mjs +65 -0
- package/dist/index.d.mts +770 -166
- package/dist/index.d.ts +770 -166
- package/dist/index.mjs +399 -306
- package/dist/plugins/index.d.mts +121 -214
- package/dist/plugins/index.d.ts +121 -214
- package/dist/plugins/index.mjs +214 -576
- package/dist/shared/server.7cEtMB30.d.ts +74 -0
- package/dist/shared/server.B8gYOD5g.d.mts +12 -0
- package/dist/shared/server.BqadksTP.d.mts +74 -0
- package/dist/shared/server.C8_sRzQB.d.mts +42 -0
- package/dist/shared/server.CMf4nKky.mjs +230 -0
- package/dist/shared/server.ChyoA9XY.d.ts +42 -0
- package/dist/shared/server.D8IXzfqT.d.ts +32 -0
- package/dist/shared/server.DEBcqOjg.mjs +418 -0
- package/dist/shared/{server.W91HSRkE.mjs → server.DZ5BIITo.mjs} +2 -2
- package/dist/shared/server.DzE6WeuY.d.mts +32 -0
- package/dist/shared/server.EfTOZ2Q7.d.ts +12 -0
- package/dist/shared/server.TEVCLCFC.mjs +39 -0
- package/dist/shared/server.qKsRrdxW.d.mts +193 -0
- package/dist/shared/server.qKsRrdxW.d.ts +193 -0
- package/package.json +56 -26
- package/dist/extensions/callable.d.mts +0 -10
- package/dist/extensions/callable.d.ts +0 -10
- package/dist/extensions/callable.mjs +0 -11
- package/dist/shared/server.15O7oC1p.d.mts +0 -61
- package/dist/shared/server.B4BnEWWm.d.mts +0 -67
- package/dist/shared/server.BhsVw7m7.d.ts +0 -104
- package/dist/shared/server.BoiFpNT7.mjs +0 -224
- package/dist/shared/server.COPVXhDG.mjs +0 -271
- package/dist/shared/server.CX4vUnDk.mjs +0 -11
- package/dist/shared/server.CcR4kgje.d.mts +0 -206
- package/dist/shared/server.CcR4kgje.d.ts +0 -206
- package/dist/shared/server.CnN50DHH.d.ts +0 -67
- package/dist/shared/server.DBs0rrf9.mjs +0 -41
- package/dist/shared/server.DDFizwfU.d.mts +0 -104
- package/dist/shared/server.D_QauotT.mjs +0 -30
- package/dist/shared/server.DofEOwf-.mjs +0 -237
- package/dist/shared/server.m3szVJGU.d.ts +0 -61
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,206 +1,124 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import { toFetchHeaders, toStandardBody, toStandardHeaders } from '@standardserver/fetch';
|
|
1
|
+
import { runWithSpan, value, setSpanError, isAsyncIteratorObject, AsyncIteratorClass, clone } from '@orpc/shared';
|
|
2
|
+
import { flattenHeader } from '@orpc/standard-server';
|
|
3
|
+
import { parseBatchRequest, toBatchResponse } from '@orpc/standard-server/batch';
|
|
4
|
+
import { toFetchHeaders } from '@orpc/standard-server-fetch';
|
|
6
5
|
import { ORPCError } from '@orpc/client';
|
|
6
|
+
export { S as StrictGetMethodPlugin } from '../shared/server.TEVCLCFC.mjs';
|
|
7
|
+
import '@orpc/contract';
|
|
7
8
|
|
|
8
9
|
class BatchHandlerPlugin {
|
|
9
|
-
name = "~batch";
|
|
10
|
-
/**
|
|
11
|
-
* Run batch interceptors before OpenTelemetry interceptors
|
|
12
|
-
* so each subrequest gets its own span instead of sharing one batch-level span.
|
|
13
|
-
*/
|
|
14
|
-
after = ["~opentelemetry"];
|
|
15
10
|
maxSize;
|
|
16
|
-
|
|
11
|
+
mapRequestItem;
|
|
17
12
|
successStatus;
|
|
18
13
|
headers;
|
|
19
|
-
|
|
20
|
-
keepAliveInterval;
|
|
14
|
+
order = 5e6;
|
|
21
15
|
constructor(options = {}) {
|
|
22
16
|
this.maxSize = options.maxSize ?? 10;
|
|
23
|
-
this.
|
|
24
|
-
...
|
|
17
|
+
this.mapRequestItem = options.mapRequestItem ?? ((request, { request: batchRequest }) => ({
|
|
18
|
+
...request,
|
|
25
19
|
headers: {
|
|
26
20
|
...batchRequest.headers,
|
|
27
|
-
...
|
|
28
|
-
"orpc-batch": void 0
|
|
29
|
-
// useful in case batch plugin is used multiple times
|
|
21
|
+
...request.headers
|
|
30
22
|
}
|
|
31
23
|
}));
|
|
32
24
|
this.successStatus = options.successStatus ?? 207;
|
|
33
25
|
this.headers = options.headers ?? {};
|
|
34
|
-
this.keepAliveEnabled = options.keepAlive?.enabled ?? true;
|
|
35
|
-
this.keepAliveInterval = options.keepAlive?.interval ?? 15e3;
|
|
36
26
|
}
|
|
37
27
|
init(options) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
options.rootInterceptors ??= [];
|
|
29
|
+
options.rootInterceptors.unshift(async (options2) => {
|
|
30
|
+
const xHeader = flattenHeader(options2.request.headers["x-orpc-batch"]);
|
|
31
|
+
if (xHeader === void 0) {
|
|
32
|
+
return options2.next();
|
|
42
33
|
}
|
|
43
|
-
|
|
44
|
-
let messages;
|
|
34
|
+
let isParsing = false;
|
|
45
35
|
try {
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
36
|
+
return await runWithSpan({ name: "handle_batch_request" }, async (span) => {
|
|
37
|
+
const mode = xHeader === "buffered" ? "buffered" : "streaming";
|
|
38
|
+
isParsing = true;
|
|
39
|
+
const parsed = parseBatchRequest({ ...options2.request, body: await options2.request.body() });
|
|
40
|
+
isParsing = false;
|
|
41
|
+
span?.setAttribute("batch.mode", mode);
|
|
42
|
+
span?.setAttribute("batch.size", parsed.length);
|
|
43
|
+
const maxSize = await value(this.maxSize, options2);
|
|
44
|
+
if (parsed.length > maxSize) {
|
|
45
|
+
const message = "Batch request size exceeds the maximum allowed size";
|
|
46
|
+
setSpanError(span, message);
|
|
51
47
|
return {
|
|
52
48
|
matched: true,
|
|
53
|
-
response: {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
matched: true,
|
|
60
|
-
response: { status: 400, headers: {}, body: "Invalid batch request data parameter" }
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
messages = mightBeMessages;
|
|
64
|
-
} else {
|
|
65
|
-
const mightBeMessages = await interceptorOptions.request.resolveBody();
|
|
66
|
-
if (!Array.isArray(mightBeMessages)) {
|
|
67
|
-
return {
|
|
68
|
-
matched: true,
|
|
69
|
-
response: { status: 400, headers: {}, body: "Invalid batch request body" }
|
|
49
|
+
response: {
|
|
50
|
+
status: 413,
|
|
51
|
+
headers: {},
|
|
52
|
+
body: message
|
|
53
|
+
}
|
|
70
54
|
};
|
|
71
55
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
56
|
+
const responses = parsed.map(
|
|
57
|
+
(request, index) => {
|
|
58
|
+
const mapped = this.mapRequestItem(request, options2);
|
|
59
|
+
return options2.next({ ...options2, request: { ...mapped, body: () => Promise.resolve(mapped.body) } }).then(({ response: response2, matched }) => {
|
|
60
|
+
span?.addEvent(`response.${index}.${matched ? "success" : "not_matched"}`);
|
|
61
|
+
if (matched) {
|
|
62
|
+
if (response2.body instanceof Blob || response2.body instanceof FormData || isAsyncIteratorObject(response2.body)) {
|
|
63
|
+
return {
|
|
64
|
+
index,
|
|
65
|
+
status: 500,
|
|
66
|
+
headers: {},
|
|
67
|
+
body: "Batch responses do not support file/blob, or event-iterator. Please call this procedure separately outside of the batch request."
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { ...response2, index };
|
|
71
|
+
}
|
|
72
|
+
return { index, status: 404, headers: {}, body: "No procedure matched" };
|
|
73
|
+
}).catch((err) => {
|
|
74
|
+
Promise.reject(err);
|
|
75
|
+
return { index, status: 500, headers: {}, body: "Internal server error" };
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
await Promise.race(responses);
|
|
80
|
+
const status = await value(this.successStatus, responses, options2);
|
|
81
|
+
const headers = await value(this.headers, responses, options2);
|
|
82
|
+
const promises = [...responses];
|
|
83
|
+
const response = await toBatchResponse({
|
|
84
|
+
status,
|
|
85
|
+
headers,
|
|
86
|
+
mode,
|
|
87
|
+
body: new AsyncIteratorClass(
|
|
88
|
+
async () => {
|
|
89
|
+
const handling = promises.filter((p) => p !== void 0);
|
|
90
|
+
if (handling.length <= 0) {
|
|
91
|
+
return { done: true, value: void 0 };
|
|
92
|
+
}
|
|
93
|
+
const value2 = await Promise.race(handling);
|
|
94
|
+
promises[value2.index] = void 0;
|
|
95
|
+
return { done: false, value: value2 };
|
|
96
|
+
},
|
|
97
|
+
async () => {
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
matched: true,
|
|
103
|
+
response
|
|
104
|
+
};
|
|
106
105
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (responseMessages.some((msg) => msg.binary !== void 0)) {
|
|
110
|
-
const chunks = [];
|
|
111
|
-
for (const message of responseMessages) {
|
|
112
|
-
const encoded = await encodePeerMessage(message);
|
|
113
|
-
const bytes = typeof encoded === "string" ? new TextEncoder().encode(encoded) : encoded;
|
|
114
|
-
const lengthBuffer = new ArrayBuffer(4);
|
|
115
|
-
new DataView(lengthBuffer).setUint32(0, bytes.byteLength, false);
|
|
116
|
-
chunks.push(new Uint8Array(lengthBuffer));
|
|
117
|
-
chunks.push(bytes);
|
|
118
|
-
}
|
|
106
|
+
} catch (cause) {
|
|
107
|
+
if (isParsing) {
|
|
119
108
|
return {
|
|
120
109
|
matched: true,
|
|
121
|
-
response: {
|
|
122
|
-
status,
|
|
123
|
-
headers,
|
|
124
|
-
body: new Blob(chunks, { type: "application/octet-stream" })
|
|
125
|
-
}
|
|
110
|
+
response: { status: 400, headers: {}, body: "Invalid batch request, this could be caused by a malformed request body or a missing header" }
|
|
126
111
|
};
|
|
127
112
|
}
|
|
128
|
-
|
|
129
|
-
matched: true,
|
|
130
|
-
response: { status, headers, body: responseMessages }
|
|
131
|
-
};
|
|
113
|
+
throw cause;
|
|
132
114
|
}
|
|
133
|
-
|
|
134
|
-
let keepAliveTimer;
|
|
135
|
-
const clearKeepAlive = () => {
|
|
136
|
-
if (keepAliveTimer !== void 0) {
|
|
137
|
-
clearInterval(keepAliveTimer);
|
|
138
|
-
keepAliveTimer = void 0;
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
const scheduleKeepAlive = () => {
|
|
142
|
-
if (!this.keepAliveEnabled) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
clearKeepAlive();
|
|
146
|
-
keepAliveTimer = setInterval(() => {
|
|
147
|
-
try {
|
|
148
|
-
const lengthBuffer = new ArrayBuffer(4);
|
|
149
|
-
new DataView(lengthBuffer).setUint32(0, 0, false);
|
|
150
|
-
streamController.enqueue(new Uint8Array(lengthBuffer));
|
|
151
|
-
} catch {
|
|
152
|
-
clearKeepAlive();
|
|
153
|
-
}
|
|
154
|
-
}, this.keepAliveInterval);
|
|
155
|
-
};
|
|
156
|
-
const stream = new ReadableStream({
|
|
157
|
-
start(controller) {
|
|
158
|
-
streamController = controller;
|
|
159
|
-
scheduleKeepAlive();
|
|
160
|
-
},
|
|
161
|
-
cancel() {
|
|
162
|
-
clearKeepAlive();
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
const peer = new ServerPeer(async (message) => {
|
|
166
|
-
const encoded = await encodePeerMessage(message);
|
|
167
|
-
const bytes = typeof encoded === "string" ? new TextEncoder().encode(encoded) : encoded;
|
|
168
|
-
const lengthBuffer = new ArrayBuffer(4);
|
|
169
|
-
new DataView(lengthBuffer).setUint32(0, bytes.byteLength, false);
|
|
170
|
-
streamController.enqueue(new Uint8Array(lengthBuffer));
|
|
171
|
-
streamController.enqueue(bytes);
|
|
172
|
-
scheduleKeepAlive();
|
|
173
|
-
});
|
|
174
|
-
Promise.all(messages.map((msg) => peer.message(msg, handleIndividualRequest))).then(async () => {
|
|
175
|
-
clearKeepAlive();
|
|
176
|
-
streamController.close();
|
|
177
|
-
await peer.close();
|
|
178
|
-
}).catch(async (error) => {
|
|
179
|
-
clearKeepAlive();
|
|
180
|
-
streamController.error(error);
|
|
181
|
-
await peer.close(error);
|
|
182
|
-
});
|
|
183
|
-
return {
|
|
184
|
-
matched: true,
|
|
185
|
-
response: { status, headers, body: stream }
|
|
186
|
-
};
|
|
187
|
-
};
|
|
188
|
-
return {
|
|
189
|
-
...options,
|
|
190
|
-
routingInterceptors: [routingInterceptor, ...toArray(options.routingInterceptors)]
|
|
191
|
-
};
|
|
115
|
+
});
|
|
192
116
|
}
|
|
193
117
|
}
|
|
194
118
|
|
|
195
|
-
class
|
|
119
|
+
class CORSPlugin {
|
|
196
120
|
options;
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* - Do not create spans for CORS preflight requests.
|
|
200
|
-
* - Run CORS interceptors before batch interceptors so headers are applied to
|
|
201
|
-
* the actual response rather than sub-responses.
|
|
202
|
-
*/
|
|
203
|
-
after = ["~opentelemetry", "~batch"];
|
|
121
|
+
order = 9e6;
|
|
204
122
|
constructor(options = {}) {
|
|
205
123
|
const defaults = {
|
|
206
124
|
origin: (origin) => origin,
|
|
@@ -212,51 +130,19 @@ class CORSHandlerPlugin {
|
|
|
212
130
|
};
|
|
213
131
|
}
|
|
214
132
|
init(options) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (!result.matched) {
|
|
218
|
-
return result;
|
|
219
|
-
}
|
|
220
|
-
const resHeaders = { ...result.response.headers };
|
|
221
|
-
const origin = flattenStandardHeader(interceptorOptions.request.headers.origin) ?? "";
|
|
222
|
-
const allowedOrigins = toArray(await value(this.options.origin, origin, interceptorOptions));
|
|
223
|
-
if (allowedOrigins.includes("*")) {
|
|
224
|
-
resHeaders["access-control-allow-origin"] = "*";
|
|
225
|
-
} else {
|
|
226
|
-
if (allowedOrigins.includes(origin)) {
|
|
227
|
-
resHeaders["access-control-allow-origin"] = origin;
|
|
228
|
-
}
|
|
229
|
-
const existingVary = flattenStandardHeader(resHeaders.vary);
|
|
230
|
-
if (!existingVary?.split(",").some((v) => v.trim().toLowerCase() === "origin")) {
|
|
231
|
-
resHeaders.vary = existingVary ? `${existingVary}, Origin` : "Origin";
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
const allowedTimingOrigins = toArray(await value(this.options.timingOrigin, origin, interceptorOptions));
|
|
235
|
-
if (allowedTimingOrigins.includes("*")) {
|
|
236
|
-
resHeaders["timing-allow-origin"] = "*";
|
|
237
|
-
} else if (allowedTimingOrigins.includes(origin)) {
|
|
238
|
-
resHeaders["timing-allow-origin"] = origin;
|
|
239
|
-
}
|
|
240
|
-
if (this.options.credentials) {
|
|
241
|
-
resHeaders["access-control-allow-credentials"] = "true";
|
|
242
|
-
}
|
|
243
|
-
if (this.options.exposeHeaders?.length) {
|
|
244
|
-
resHeaders["access-control-expose-headers"] = flattenStandardHeader(this.options.exposeHeaders);
|
|
245
|
-
}
|
|
246
|
-
return { ...result, response: { ...result.response, headers: resHeaders } };
|
|
247
|
-
};
|
|
248
|
-
const preflightInterceptor = async (interceptorOptions) => {
|
|
133
|
+
options.rootInterceptors ??= [];
|
|
134
|
+
options.rootInterceptors.unshift(async (interceptorOptions) => {
|
|
249
135
|
if (interceptorOptions.request.method === "OPTIONS") {
|
|
250
136
|
const resHeaders = {};
|
|
251
137
|
if (this.options.maxAge !== void 0) {
|
|
252
138
|
resHeaders["access-control-max-age"] = this.options.maxAge.toString();
|
|
253
139
|
}
|
|
254
140
|
if (this.options.allowMethods?.length) {
|
|
255
|
-
resHeaders["access-control-allow-methods"] =
|
|
141
|
+
resHeaders["access-control-allow-methods"] = flattenHeader(this.options.allowMethods);
|
|
256
142
|
}
|
|
257
143
|
const allowHeaders = this.options.allowHeaders ?? interceptorOptions.request.headers["access-control-request-headers"];
|
|
258
144
|
if (typeof allowHeaders === "string" || allowHeaders?.length) {
|
|
259
|
-
resHeaders["access-control-allow-headers"] =
|
|
145
|
+
resHeaders["access-control-allow-headers"] = flattenHeader(allowHeaders);
|
|
260
146
|
}
|
|
261
147
|
return {
|
|
262
148
|
matched: true,
|
|
@@ -268,385 +154,106 @@ class CORSHandlerPlugin {
|
|
|
268
154
|
};
|
|
269
155
|
}
|
|
270
156
|
return interceptorOptions.next();
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
157
|
+
});
|
|
158
|
+
options.rootInterceptors.unshift(async (interceptorOptions) => {
|
|
159
|
+
const result = await interceptorOptions.next();
|
|
160
|
+
if (!result.matched) {
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
const origin = flattenHeader(interceptorOptions.request.headers.origin) ?? "";
|
|
164
|
+
const allowedOrigin = await value(this.options.origin, origin, interceptorOptions);
|
|
165
|
+
const allowedOriginArr = Array.isArray(allowedOrigin) ? allowedOrigin : [allowedOrigin];
|
|
166
|
+
if (allowedOriginArr.includes("*")) {
|
|
167
|
+
result.response.headers["access-control-allow-origin"] = "*";
|
|
168
|
+
} else {
|
|
169
|
+
if (allowedOriginArr.includes(origin)) {
|
|
170
|
+
result.response.headers["access-control-allow-origin"] = origin;
|
|
171
|
+
}
|
|
172
|
+
const existingVary = flattenHeader(result.response.headers.vary);
|
|
173
|
+
if (!existingVary?.split(",").some((v) => v.trim().toLowerCase() === "origin")) {
|
|
174
|
+
result.response.headers.vary = existingVary ? `${existingVary}, Origin` : "Origin";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const allowedTimingOrigin = await value(this.options.timingOrigin, origin, interceptorOptions);
|
|
178
|
+
const allowedTimingOriginArr = Array.isArray(allowedTimingOrigin) ? allowedTimingOrigin : [allowedTimingOrigin];
|
|
179
|
+
if (allowedTimingOriginArr.includes("*")) {
|
|
180
|
+
result.response.headers["timing-allow-origin"] = "*";
|
|
181
|
+
} else if (allowedTimingOriginArr.includes(origin)) {
|
|
182
|
+
result.response.headers["timing-allow-origin"] = origin;
|
|
183
|
+
}
|
|
184
|
+
if (this.options.credentials) {
|
|
185
|
+
result.response.headers["access-control-allow-credentials"] = "true";
|
|
186
|
+
}
|
|
187
|
+
if (this.options.exposeHeaders?.length) {
|
|
188
|
+
result.response.headers["access-control-expose-headers"] = flattenHeader(this.options.exposeHeaders);
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
});
|
|
280
192
|
}
|
|
281
193
|
}
|
|
282
194
|
|
|
283
|
-
class
|
|
284
|
-
name = "~request-compression";
|
|
285
|
-
/**
|
|
286
|
-
* Should decompress the original batch request body instead of sub-requests.
|
|
287
|
-
*/
|
|
288
|
-
after = ["~batch"];
|
|
195
|
+
class RequestHeadersPlugin {
|
|
289
196
|
init(options) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (encodings.length === 0 || !encodings.every(isSupportedEncoding)) {
|
|
295
|
-
return next();
|
|
296
|
-
}
|
|
297
|
-
const decompressedHeaders = {
|
|
298
|
-
...interceptorOptions.request.headers,
|
|
299
|
-
"content-length": void 0,
|
|
300
|
-
"content-encoding": void 0
|
|
301
|
-
};
|
|
302
|
-
return next({
|
|
197
|
+
options.rootInterceptors ??= [];
|
|
198
|
+
options.rootInterceptors.push((interceptorOptions) => {
|
|
199
|
+
const reqHeaders = interceptorOptions.context.reqHeaders ?? toFetchHeaders(interceptorOptions.request.headers);
|
|
200
|
+
return interceptorOptions.next({
|
|
303
201
|
...interceptorOptions,
|
|
304
|
-
|
|
305
|
-
...interceptorOptions.
|
|
306
|
-
|
|
307
|
-
async resolveBody(hint) {
|
|
308
|
-
const stream = await interceptorOptions.request.resolveBody("octet-stream");
|
|
309
|
-
if (!(stream instanceof ReadableStream)) {
|
|
310
|
-
return stream;
|
|
311
|
-
}
|
|
312
|
-
let decompressedStream = stream;
|
|
313
|
-
for (let i = encodings.length - 1; i >= 0; i--) {
|
|
314
|
-
decompressedStream = decompressedStream.pipeThrough(
|
|
315
|
-
new DecompressionStream(encodings[i])
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
const response = new Response(decompressedStream, {
|
|
319
|
-
headers: toFetchHeaders(decompressedHeaders)
|
|
320
|
-
});
|
|
321
|
-
return toStandardBody(response, { hint });
|
|
322
|
-
}
|
|
202
|
+
context: {
|
|
203
|
+
...interceptorOptions.context,
|
|
204
|
+
reqHeaders
|
|
323
205
|
}
|
|
324
206
|
});
|
|
325
|
-
};
|
|
326
|
-
return {
|
|
327
|
-
...options,
|
|
328
|
-
routingInterceptors: [
|
|
329
|
-
routingInterceptor,
|
|
330
|
-
...toArray(options.routingInterceptors)
|
|
331
|
-
]
|
|
332
|
-
};
|
|
207
|
+
});
|
|
333
208
|
}
|
|
334
209
|
}
|
|
335
|
-
const SUPPORTED_ENCODINGS = ["gzip", "deflate", "deflate-raw"];
|
|
336
|
-
function isSupportedEncoding(encoding) {
|
|
337
|
-
return SUPPORTED_ENCODINGS.includes(encoding);
|
|
338
|
-
}
|
|
339
|
-
function parseContentEncodings(header) {
|
|
340
|
-
if (header === void 0) {
|
|
341
|
-
return [];
|
|
342
|
-
}
|
|
343
|
-
return header.split(",").map((part) => part.trim().toLowerCase());
|
|
344
|
-
}
|
|
345
210
|
|
|
346
|
-
class
|
|
347
|
-
name = "~request-headers";
|
|
211
|
+
class ResponseHeadersPlugin {
|
|
348
212
|
init(options) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const reqHeaders = interceptorOptions.context.reqHeaders ?? toFetchHeaders(interceptorOptions.request.headers);
|
|
354
|
-
return interceptorOptions.next({
|
|
355
|
-
...interceptorOptions,
|
|
356
|
-
context: {
|
|
357
|
-
...interceptorOptions.context,
|
|
358
|
-
reqHeaders
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
},
|
|
362
|
-
...toArray(options.interceptors)
|
|
363
|
-
]
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
class RequestLimitHandlerPlugin {
|
|
369
|
-
name = "~request-limit";
|
|
370
|
-
/**
|
|
371
|
-
* Should limit the original batch request body instead of sub-requests.
|
|
372
|
-
*/
|
|
373
|
-
after = ["~batch"];
|
|
374
|
-
/**
|
|
375
|
-
* Should limit the final body size instead of the compressed one.
|
|
376
|
-
*/
|
|
377
|
-
before = ["~request-compression"];
|
|
378
|
-
maxBodySize;
|
|
379
|
-
constructor(options) {
|
|
380
|
-
this.maxBodySize = options.maxBodySize;
|
|
381
|
-
}
|
|
382
|
-
init(options) {
|
|
383
|
-
const maxBodySize = this.maxBodySize;
|
|
384
|
-
const routingInterceptor = async ({ next, ...interceptorOptions }) => {
|
|
385
|
-
return next({
|
|
213
|
+
options.rootInterceptors ??= [];
|
|
214
|
+
options.rootInterceptors.push(async (interceptorOptions) => {
|
|
215
|
+
const resHeaders = interceptorOptions.context.resHeaders ?? new Headers();
|
|
216
|
+
const result = await interceptorOptions.next({
|
|
386
217
|
...interceptorOptions,
|
|
387
|
-
|
|
388
|
-
...interceptorOptions.
|
|
389
|
-
|
|
390
|
-
const contentLength = Number(
|
|
391
|
-
flattenStandardHeader(interceptorOptions.request.headers["content-length"])
|
|
392
|
-
);
|
|
393
|
-
if (Number.isFinite(contentLength) && contentLength > maxBodySize) {
|
|
394
|
-
throw new ORPCError("PAYLOAD_TOO_LARGE");
|
|
395
|
-
}
|
|
396
|
-
const stream = await interceptorOptions.request.resolveBody("octet-stream");
|
|
397
|
-
if (!(stream instanceof ReadableStream)) {
|
|
398
|
-
return stream;
|
|
399
|
-
}
|
|
400
|
-
let currentBodySize = 0;
|
|
401
|
-
const limitedStream = stream.pipeThrough(
|
|
402
|
-
new TransformStream({
|
|
403
|
-
transform(chunk, controller) {
|
|
404
|
-
currentBodySize += chunk.byteLength;
|
|
405
|
-
if (currentBodySize > maxBodySize) {
|
|
406
|
-
controller.error(new ORPCError("PAYLOAD_TOO_LARGE"));
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
controller.enqueue(chunk);
|
|
410
|
-
}
|
|
411
|
-
})
|
|
412
|
-
);
|
|
413
|
-
const response = new Response(limitedStream, {
|
|
414
|
-
headers: toFetchHeaders(interceptorOptions.request.headers)
|
|
415
|
-
});
|
|
416
|
-
return toStandardBody(response, { hint });
|
|
417
|
-
}
|
|
218
|
+
context: {
|
|
219
|
+
...interceptorOptions.context,
|
|
220
|
+
resHeaders
|
|
418
221
|
}
|
|
419
222
|
});
|
|
420
|
-
};
|
|
421
|
-
return {
|
|
422
|
-
...options,
|
|
423
|
-
routingInterceptors: [
|
|
424
|
-
routingInterceptor,
|
|
425
|
-
...toArray(options.routingInterceptors)
|
|
426
|
-
]
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const AVG_BYTES_PER_CHAR = 1.2;
|
|
432
|
-
class ResponseCompressionHandlerPlugin {
|
|
433
|
-
name = "~response-compression";
|
|
434
|
-
/**
|
|
435
|
-
* Compression should be done after batching, to compress the final response.
|
|
436
|
-
* Compression should also be done after response headers are set, to access final headers like Content-Type and Cache-Control.
|
|
437
|
-
*/
|
|
438
|
-
after = ["~batch", "~response-headers"];
|
|
439
|
-
encodings;
|
|
440
|
-
threshold;
|
|
441
|
-
constructor(options = {}) {
|
|
442
|
-
this.encodings = options.encodings ?? ["gzip", "deflate"];
|
|
443
|
-
this.threshold = options.threshold ?? 1024;
|
|
444
|
-
}
|
|
445
|
-
init(options) {
|
|
446
|
-
const routingInterceptor = async ({ next, ...interceptorOptions }) => {
|
|
447
|
-
const result = await next();
|
|
448
223
|
if (!result.matched) {
|
|
449
224
|
return result;
|
|
450
225
|
}
|
|
451
|
-
const
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const acceptEncodings = parseAcceptEncodings(
|
|
460
|
-
flattenStandardHeader(interceptorOptions.request.headers["accept-encoding"])
|
|
461
|
-
);
|
|
462
|
-
const encoding = this.encodings.find((enc) => acceptEncodings.includes(enc));
|
|
463
|
-
if (encoding === void 0) {
|
|
464
|
-
return result;
|
|
465
|
-
}
|
|
466
|
-
const body = response.body;
|
|
467
|
-
const headers = response.headers;
|
|
468
|
-
if (body instanceof ReadableStream) {
|
|
469
|
-
const contentLength = Number(flattenStandardHeader(headers["content-length"]));
|
|
470
|
-
if ((!Number.isFinite(contentLength) || contentLength >= this.threshold) && isCompressibleContentType(flattenStandardHeader(headers["content-type"]))) {
|
|
471
|
-
return {
|
|
472
|
-
...result,
|
|
473
|
-
response: {
|
|
474
|
-
...response,
|
|
475
|
-
body: body.pipeThrough(new CompressionStream(encoding)),
|
|
476
|
-
headers: {
|
|
477
|
-
...headers,
|
|
478
|
-
"standard-server": "octet-stream",
|
|
479
|
-
"content-length": [],
|
|
480
|
-
"content-encoding": encoding
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
} else if (body instanceof Blob) {
|
|
486
|
-
if ((!Number.isFinite(body.size) || body.size >= this.threshold) && isCompressibleContentType(body.type)) {
|
|
487
|
-
const contentDisposition = headers["content-disposition"] ?? generateContentDisposition(
|
|
488
|
-
body instanceof File ? body.name : "blob"
|
|
489
|
-
);
|
|
490
|
-
return {
|
|
491
|
-
...result,
|
|
492
|
-
response: {
|
|
493
|
-
...response,
|
|
494
|
-
body: body.stream().pipeThrough(new CompressionStream(encoding)),
|
|
495
|
-
headers: {
|
|
496
|
-
...headers,
|
|
497
|
-
"standard-server": "file",
|
|
498
|
-
"content-type": body.type,
|
|
499
|
-
"content-length": [],
|
|
500
|
-
"content-disposition": contentDisposition,
|
|
501
|
-
"content-encoding": encoding
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
} else if (body instanceof FormData) {
|
|
507
|
-
const PART_OVERHEAD = 64;
|
|
508
|
-
let contentLength = 0;
|
|
509
|
-
for (const [key, value] of body) {
|
|
510
|
-
contentLength += PART_OVERHEAD + key.length;
|
|
511
|
-
if (value instanceof Blob) {
|
|
512
|
-
if (!Number.isFinite(value.size)) {
|
|
513
|
-
if (!isCompressibleContentType(value.type)) {
|
|
514
|
-
contentLength = -Infinity;
|
|
515
|
-
break;
|
|
516
|
-
}
|
|
517
|
-
contentLength = Infinity;
|
|
518
|
-
} else {
|
|
519
|
-
contentLength += isCompressibleContentType(value.type) ? value.size : -value.size;
|
|
520
|
-
}
|
|
521
|
-
} else {
|
|
522
|
-
contentLength += value.length * AVG_BYTES_PER_CHAR;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
if (contentLength >= this.threshold) {
|
|
526
|
-
const res = new Response(body);
|
|
527
|
-
const compressedStream = res.body.pipeThrough(new CompressionStream(encoding));
|
|
528
|
-
return {
|
|
529
|
-
...result,
|
|
530
|
-
response: {
|
|
531
|
-
...response,
|
|
532
|
-
body: compressedStream,
|
|
533
|
-
headers: {
|
|
534
|
-
...headers,
|
|
535
|
-
"standard-server": [],
|
|
536
|
-
"content-type": res.headers.get("content-type"),
|
|
537
|
-
"content-length": [],
|
|
538
|
-
"content-encoding": encoding
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
} else if (body instanceof URLSearchParams) {
|
|
544
|
-
const string = body.toString();
|
|
545
|
-
if (string.length * AVG_BYTES_PER_CHAR >= this.threshold) {
|
|
546
|
-
return {
|
|
547
|
-
...result,
|
|
548
|
-
response: {
|
|
549
|
-
...response,
|
|
550
|
-
body: new Blob([string]).stream().pipeThrough(new CompressionStream(encoding)),
|
|
551
|
-
headers: {
|
|
552
|
-
...headers,
|
|
553
|
-
"standard-server": [],
|
|
554
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
555
|
-
"content-length": [],
|
|
556
|
-
"content-encoding": encoding
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
} else if (body !== void 0 && !isAsyncIteratorObject(body)) {
|
|
562
|
-
const string = stringifyJSON(body);
|
|
563
|
-
if (string.length * AVG_BYTES_PER_CHAR >= this.threshold) {
|
|
564
|
-
return {
|
|
565
|
-
...result,
|
|
566
|
-
response: {
|
|
567
|
-
...response,
|
|
568
|
-
body: new Blob([string]).stream().pipeThrough(new CompressionStream(encoding)),
|
|
569
|
-
headers: {
|
|
570
|
-
...headers,
|
|
571
|
-
"standard-server": [],
|
|
572
|
-
"content-type": "application/json",
|
|
573
|
-
"content-length": [],
|
|
574
|
-
"content-encoding": encoding
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
};
|
|
226
|
+
const responseHeaders = clone(result.response.headers);
|
|
227
|
+
for (const [key, value] of resHeaders) {
|
|
228
|
+
if (Array.isArray(responseHeaders[key])) {
|
|
229
|
+
responseHeaders[key].push(value);
|
|
230
|
+
} else if (responseHeaders[key] !== void 0) {
|
|
231
|
+
responseHeaders[key] = [responseHeaders[key], value];
|
|
232
|
+
} else {
|
|
233
|
+
responseHeaders[key] = value;
|
|
578
234
|
}
|
|
579
235
|
}
|
|
580
|
-
return
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
function parseAcceptEncodings(header) {
|
|
592
|
-
if (header === void 0) {
|
|
593
|
-
return [];
|
|
594
|
-
}
|
|
595
|
-
return header.split(",").map((part) => part.trim().split(";")[0].trim().toLowerCase()).filter(Boolean);
|
|
596
|
-
}
|
|
597
|
-
const CACHE_CONTROL_NO_TRANSFORM_REGEX = /(?:^|,)\s*no-transform\s*(?:,|$)/i;
|
|
598
|
-
function isNoTransformCacheControl(cacheControl) {
|
|
599
|
-
if (cacheControl === void 0) {
|
|
600
|
-
return false;
|
|
601
|
-
}
|
|
602
|
-
return CACHE_CONTROL_NO_TRANSFORM_REGEX.test(cacheControl);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
class ResponseHeadersHandlerPlugin {
|
|
606
|
-
name = "~response-headers";
|
|
607
|
-
/**
|
|
608
|
-
* Interceptors should run after batch interceptors so headers are applied to each sub-response.
|
|
609
|
-
*/
|
|
610
|
-
before = ["~batch"];
|
|
611
|
-
init(options) {
|
|
612
|
-
return {
|
|
613
|
-
...options,
|
|
614
|
-
routingInterceptors: [
|
|
615
|
-
async (interceptorOptions) => {
|
|
616
|
-
const resHeaders = new Headers(interceptorOptions.context.resHeaders);
|
|
617
|
-
const result = await interceptorOptions.next({
|
|
618
|
-
...interceptorOptions,
|
|
619
|
-
context: {
|
|
620
|
-
...interceptorOptions.context,
|
|
621
|
-
resHeaders
|
|
622
|
-
}
|
|
623
|
-
});
|
|
624
|
-
if (!result.response) {
|
|
625
|
-
return result;
|
|
626
|
-
}
|
|
627
|
-
return {
|
|
628
|
-
...result,
|
|
629
|
-
response: {
|
|
630
|
-
...result.response,
|
|
631
|
-
headers: mergeStandardHeaders(result.response.headers, toStandardHeaders(resHeaders))
|
|
632
|
-
}
|
|
633
|
-
};
|
|
634
|
-
},
|
|
635
|
-
...toArray(options.routingInterceptors)
|
|
636
|
-
]
|
|
637
|
-
};
|
|
236
|
+
return {
|
|
237
|
+
...result,
|
|
238
|
+
response: {
|
|
239
|
+
...result.response,
|
|
240
|
+
headers: responseHeaders
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
});
|
|
638
244
|
}
|
|
639
245
|
}
|
|
640
246
|
|
|
641
|
-
class
|
|
642
|
-
name = "~rethrow";
|
|
247
|
+
class experimental_RethrowHandlerPlugin {
|
|
643
248
|
filter;
|
|
644
249
|
CONTEXT_SYMBOL = Symbol("ORPC_RETHROW_HANDLER_PLUGIN_CONTEXT");
|
|
645
250
|
constructor(options) {
|
|
646
251
|
this.filter = options.filter;
|
|
647
252
|
}
|
|
648
253
|
init(options) {
|
|
649
|
-
|
|
254
|
+
options.rootInterceptors ??= [];
|
|
255
|
+
options.interceptors ??= [];
|
|
256
|
+
options.rootInterceptors.push(async (options2) => {
|
|
650
257
|
const pluginContext = {};
|
|
651
258
|
const result = await options2.next({
|
|
652
259
|
...options2,
|
|
@@ -659,8 +266,8 @@ class RethrowHandlerPlugin {
|
|
|
659
266
|
throw pluginContext.error.value;
|
|
660
267
|
}
|
|
661
268
|
return result;
|
|
662
|
-
};
|
|
663
|
-
|
|
269
|
+
});
|
|
270
|
+
options.interceptors.unshift(async (options2) => {
|
|
664
271
|
const pluginContext = options2.context[this.CONTEXT_SYMBOL];
|
|
665
272
|
if (!pluginContext) {
|
|
666
273
|
throw new TypeError("[RethrowHandlerPlugin] Rethrow handler context has been corrupted or modified by another plugin or interceptor");
|
|
@@ -668,26 +275,57 @@ class RethrowHandlerPlugin {
|
|
|
668
275
|
try {
|
|
669
276
|
return await options2.next();
|
|
670
277
|
} catch (error) {
|
|
671
|
-
if (this.filter
|
|
278
|
+
if (value(this.filter, error, options2)) {
|
|
672
279
|
pluginContext.error = { value: error };
|
|
280
|
+
return { matched: false, response: void 0 };
|
|
673
281
|
}
|
|
674
282
|
throw error;
|
|
675
283
|
}
|
|
676
|
-
};
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const SIMPLE_CSRF_PROTECTION_CONTEXT_SYMBOL = Symbol("SIMPLE_CSRF_PROTECTION_CONTEXT");
|
|
289
|
+
class SimpleCsrfProtectionHandlerPlugin {
|
|
290
|
+
headerName;
|
|
291
|
+
headerValue;
|
|
292
|
+
exclude;
|
|
293
|
+
error;
|
|
294
|
+
constructor(options = {}) {
|
|
295
|
+
this.headerName = options.headerName ?? "x-csrf-token";
|
|
296
|
+
this.headerValue = options.headerValue ?? "orpc";
|
|
297
|
+
this.exclude = options.exclude ?? false;
|
|
298
|
+
this.error = options.error ?? new ORPCError("CSRF_TOKEN_MISMATCH", {
|
|
299
|
+
status: 403,
|
|
300
|
+
message: "Invalid CSRF token"
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
order = 8e6;
|
|
304
|
+
init(options) {
|
|
305
|
+
options.rootInterceptors ??= [];
|
|
306
|
+
options.clientInterceptors ??= [];
|
|
307
|
+
options.rootInterceptors.unshift(async (options2) => {
|
|
308
|
+
const headerName = await value(this.headerName, options2);
|
|
309
|
+
const headerValue = await value(this.headerValue, options2);
|
|
310
|
+
return options2.next({
|
|
311
|
+
...options2,
|
|
312
|
+
context: {
|
|
313
|
+
...options2.context,
|
|
314
|
+
[SIMPLE_CSRF_PROTECTION_CONTEXT_SYMBOL]: options2.request.headers[headerName] === headerValue
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
options.clientInterceptors.unshift(async (options2) => {
|
|
319
|
+
if (typeof options2.context[SIMPLE_CSRF_PROTECTION_CONTEXT_SYMBOL] !== "boolean") {
|
|
320
|
+
throw new TypeError("[SimpleCsrfProtectionHandlerPlugin] CSRF protection context has been corrupted or modified by another plugin or interceptor");
|
|
321
|
+
}
|
|
322
|
+
const excluded = await value(this.exclude, options2);
|
|
323
|
+
if (!excluded && !options2.context[SIMPLE_CSRF_PROTECTION_CONTEXT_SYMBOL]) {
|
|
324
|
+
throw this.error;
|
|
325
|
+
}
|
|
326
|
+
return options2.next();
|
|
327
|
+
});
|
|
690
328
|
}
|
|
691
329
|
}
|
|
692
330
|
|
|
693
|
-
export { BatchHandlerPlugin,
|
|
331
|
+
export { BatchHandlerPlugin, CORSPlugin, RequestHeadersPlugin, ResponseHeadersPlugin, SimpleCsrfProtectionHandlerPlugin, experimental_RethrowHandlerPlugin };
|