@orpc/server 1.14.5 → 2.0.0-beta.1
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 +75 -136
- package/dist/adapters/crossws/index.d.mts +42 -21
- package/dist/adapters/crossws/index.d.ts +42 -21
- package/dist/adapters/crossws/index.mjs +37 -18
- package/dist/adapters/fetch/index.d.mts +83 -67
- package/dist/adapters/fetch/index.d.ts +83 -67
- package/dist/adapters/fetch/index.mjs +131 -106
- package/dist/adapters/message-port/index.d.mts +51 -34
- package/dist/adapters/message-port/index.d.ts +51 -34
- package/dist/adapters/message-port/index.mjs +73 -38
- package/dist/adapters/node/index.d.mts +82 -60
- package/dist/adapters/node/index.d.ts +82 -60
- package/dist/adapters/node/index.mjs +127 -98
- package/dist/adapters/standard/index.d.mts +16 -18
- package/dist/adapters/standard/index.d.ts +16 -18
- package/dist/adapters/standard/index.mjs +5 -5
- package/dist/adapters/standard-peer/index.d.mts +12 -14
- package/dist/adapters/standard-peer/index.d.ts +12 -14
- package/dist/adapters/standard-peer/index.mjs +2 -21
- package/dist/adapters/websocket/index.d.mts +39 -34
- package/dist/adapters/websocket/index.d.ts +39 -34
- package/dist/adapters/websocket/index.mjs +42 -33
- package/dist/extensions/callable.d.mts +10 -0
- package/dist/extensions/callable.d.ts +10 -0
- package/dist/extensions/callable.mjs +11 -0
- package/dist/helpers/index.d.mts +2 -2
- package/dist/helpers/index.d.ts +2 -2
- package/dist/helpers/index.mjs +1 -1
- package/dist/index.d.mts +163 -770
- package/dist/index.d.ts +163 -770
- package/dist/index.mjs +296 -403
- package/dist/plugins/index.d.mts +105 -143
- package/dist/plugins/index.d.ts +105 -143
- package/dist/plugins/index.mjs +232 -255
- package/dist/shared/server.BB_Ik9Ph.d.mts +104 -0
- package/dist/shared/server.BL22TloH.d.mts +184 -0
- package/dist/shared/server.BL22TloH.d.ts +184 -0
- package/dist/shared/server.B_U9y00a.d.mts +66 -0
- package/dist/shared/server.BsNNjG5J.d.mts +61 -0
- package/dist/shared/server.BwHnWUuN.mjs +222 -0
- package/dist/shared/server.CX4vUnDk.mjs +11 -0
- package/dist/shared/server.CjOb6ItT.mjs +41 -0
- package/dist/shared/server.CrlKQucM.mjs +233 -0
- package/dist/shared/server.D_QauotT.mjs +30 -0
- package/dist/shared/server.EOHJ3NJr.d.ts +104 -0
- package/dist/shared/server.GDpX6Df8.mjs +271 -0
- package/dist/shared/server.Pa0F03f_.d.ts +61 -0
- package/dist/shared/server.T9F3bzZx.d.ts +66 -0
- package/dist/shared/{server.DZ5BIITo.mjs → server.W91HSRkE.mjs} +2 -2
- package/package.json +26 -55
- package/dist/adapters/aws-lambda/index.d.mts +0 -46
- package/dist/adapters/aws-lambda/index.d.ts +0 -46
- package/dist/adapters/aws-lambda/index.mjs +0 -40
- package/dist/adapters/bun-ws/index.d.mts +0 -36
- package/dist/adapters/bun-ws/index.d.ts +0 -36
- package/dist/adapters/bun-ws/index.mjs +0 -47
- package/dist/adapters/fastify/index.d.mts +0 -53
- package/dist/adapters/fastify/index.d.ts +0 -53
- package/dist/adapters/fastify/index.mjs +0 -52
- package/dist/adapters/ws/index.d.mts +0 -31
- package/dist/adapters/ws/index.d.ts +0 -31
- package/dist/adapters/ws/index.mjs +0 -37
- package/dist/hibernation/index.d.mts +0 -44
- package/dist/hibernation/index.d.ts +0 -44
- package/dist/hibernation/index.mjs +0 -65
- package/dist/shared/server.7cEtMB30.d.ts +0 -74
- package/dist/shared/server.B8gYOD5g.d.mts +0 -12
- package/dist/shared/server.BqadksTP.d.mts +0 -74
- package/dist/shared/server.C8_sRzQB.d.mts +0 -42
- package/dist/shared/server.ChUyt5-i.d.mts +0 -32
- package/dist/shared/server.ChyoA9XY.d.ts +0 -42
- package/dist/shared/server.DEBcqOjg.mjs +0 -418
- package/dist/shared/server.EfTOZ2Q7.d.ts +0 -12
- package/dist/shared/server.TEVCLCFC.mjs +0 -39
- package/dist/shared/server.ZxHCEN1h.mjs +0 -226
- package/dist/shared/server.qKsRrdxW.d.mts +0 -193
- package/dist/shared/server.qKsRrdxW.d.ts +0 -193
- package/dist/shared/server.yoEB3Fx4.d.ts +0 -32
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,124 +1,173 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import '@orpc/contract';
|
|
1
|
+
import { toArray, value } from '@orpc/shared';
|
|
2
|
+
import { flattenStandardHeader, parseStandardUrl, mergeStandardHeaders } from '@standardserver/core';
|
|
3
|
+
import { isClientPeerSendMessage, ServerPeer, encodePeerMessage } from '@standardserver/peer';
|
|
4
|
+
export { C as CSRFGuardHandlerPlugin } from '../shared/server.D_QauotT.mjs';
|
|
5
|
+
import { toFetchHeaders, toStandardHeaders } from '@standardserver/fetch';
|
|
6
|
+
import '@orpc/client';
|
|
8
7
|
|
|
9
8
|
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"];
|
|
10
15
|
maxSize;
|
|
11
|
-
|
|
16
|
+
mapSubrequest;
|
|
12
17
|
successStatus;
|
|
13
18
|
headers;
|
|
14
|
-
order = 5e6;
|
|
15
19
|
constructor(options = {}) {
|
|
16
20
|
this.maxSize = options.maxSize ?? 10;
|
|
17
|
-
this.
|
|
18
|
-
...
|
|
21
|
+
this.mapSubrequest = options.mapSubrequest ?? ((subRequest, { request: batchRequest }) => ({
|
|
22
|
+
...subRequest,
|
|
19
23
|
headers: {
|
|
20
24
|
...batchRequest.headers,
|
|
21
|
-
...
|
|
25
|
+
...subRequest.headers,
|
|
26
|
+
"orpc-batch": void 0
|
|
27
|
+
// useful in case batch plugin is used multiple times
|
|
22
28
|
}
|
|
23
29
|
}));
|
|
24
30
|
this.successStatus = options.successStatus ?? 207;
|
|
25
31
|
this.headers = options.headers ?? {};
|
|
26
32
|
}
|
|
27
33
|
init(options) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return options2.next();
|
|
34
|
+
const routingInterceptor = async (interceptorOptions) => {
|
|
35
|
+
const batchHeader = flattenStandardHeader(interceptorOptions.request.headers["orpc-batch"]);
|
|
36
|
+
if (batchHeader === void 0) {
|
|
37
|
+
return interceptorOptions.next();
|
|
33
38
|
}
|
|
34
|
-
|
|
39
|
+
const mode = batchHeader === "buffered" ? "buffered" : "streaming";
|
|
40
|
+
let messages;
|
|
35
41
|
try {
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
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);
|
|
42
|
+
if (interceptorOptions.request.method === "GET") {
|
|
43
|
+
const [, search] = parseStandardUrl(interceptorOptions.request.url);
|
|
44
|
+
const params = new URLSearchParams(search);
|
|
45
|
+
const data = params.getAll("data").at(-1);
|
|
46
|
+
if (!data) {
|
|
47
47
|
return {
|
|
48
48
|
matched: true,
|
|
49
|
-
response: {
|
|
50
|
-
status: 413,
|
|
51
|
-
headers: {},
|
|
52
|
-
body: message
|
|
53
|
-
}
|
|
49
|
+
response: { status: 400, headers: {}, body: "Missing data parameter for batch request" }
|
|
54
50
|
};
|
|
55
51
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
52
|
+
const mightBeMessages = JSON.parse(data);
|
|
53
|
+
if (!Array.isArray(mightBeMessages) || mightBeMessages.some((m) => !isClientPeerSendMessage(m))) {
|
|
54
|
+
return {
|
|
55
|
+
matched: true,
|
|
56
|
+
response: { status: 400, headers: {}, body: "Invalid batch request data parameter" }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
messages = mightBeMessages;
|
|
60
|
+
} else {
|
|
61
|
+
const mightBeMessages = await interceptorOptions.request.resolveBody();
|
|
62
|
+
if (!Array.isArray(mightBeMessages)) {
|
|
63
|
+
return {
|
|
64
|
+
matched: true,
|
|
65
|
+
response: { status: 400, headers: {}, body: "Invalid batch request body" }
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
messages = mightBeMessages;
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
return {
|
|
72
|
+
matched: true,
|
|
73
|
+
response: { status: 400, headers: {}, body: "Invalid batch request" }
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const maxSize = await value(this.maxSize, interceptorOptions);
|
|
77
|
+
if (messages.length > maxSize) {
|
|
78
|
+
return {
|
|
79
|
+
matched: true,
|
|
80
|
+
response: { status: 413, headers: {}, body: "Batch request size exceeds the maximum allowed size" }
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const handleIndividualRequest = async (request) => {
|
|
84
|
+
try {
|
|
85
|
+
request = this.mapSubrequest(request, interceptorOptions);
|
|
86
|
+
const { matched, response } = await interceptorOptions.next({ ...interceptorOptions, request });
|
|
87
|
+
if (!matched) {
|
|
88
|
+
return { status: 404, headers: {}, body: "No procedure matched" };
|
|
89
|
+
}
|
|
90
|
+
return response;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
Promise.reject(err);
|
|
93
|
+
return { status: 500, headers: {}, body: "Internal server error" };
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const status = await value(this.successStatus, interceptorOptions);
|
|
97
|
+
const headers = await value(this.headers, interceptorOptions);
|
|
98
|
+
if (mode === "buffered") {
|
|
99
|
+
const responseMessages = [];
|
|
100
|
+
const peer2 = new ServerPeer(async (message) => {
|
|
101
|
+
responseMessages.push(message);
|
|
105
102
|
});
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
await Promise.all(messages.map((msg) => peer2.message(msg, handleIndividualRequest)));
|
|
104
|
+
await peer2.close();
|
|
105
|
+
if (responseMessages.some((msg) => msg.binary !== void 0)) {
|
|
106
|
+
const chunks = [];
|
|
107
|
+
for (const message of responseMessages) {
|
|
108
|
+
const encoded = await encodePeerMessage(message);
|
|
109
|
+
const bytes = typeof encoded === "string" ? new TextEncoder().encode(encoded) : encoded;
|
|
110
|
+
const lengthBuffer = new ArrayBuffer(4);
|
|
111
|
+
new DataView(lengthBuffer).setUint32(0, bytes.byteLength, false);
|
|
112
|
+
chunks.push(new Uint8Array(lengthBuffer));
|
|
113
|
+
chunks.push(bytes);
|
|
114
|
+
}
|
|
108
115
|
return {
|
|
109
116
|
matched: true,
|
|
110
|
-
response: {
|
|
117
|
+
response: {
|
|
118
|
+
status,
|
|
119
|
+
headers,
|
|
120
|
+
body: new Blob(chunks, { type: "application/octet-stream" })
|
|
121
|
+
}
|
|
111
122
|
};
|
|
112
123
|
}
|
|
113
|
-
|
|
124
|
+
return {
|
|
125
|
+
matched: true,
|
|
126
|
+
response: { status, headers, body: responseMessages }
|
|
127
|
+
};
|
|
114
128
|
}
|
|
115
|
-
|
|
129
|
+
let streamController;
|
|
130
|
+
const stream = new ReadableStream({
|
|
131
|
+
start(controller) {
|
|
132
|
+
streamController = controller;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
const peer = new ServerPeer(async (message) => {
|
|
136
|
+
const encoded = await encodePeerMessage(message);
|
|
137
|
+
const bytes = typeof encoded === "string" ? new TextEncoder().encode(encoded) : encoded;
|
|
138
|
+
const lengthBuffer = new ArrayBuffer(4);
|
|
139
|
+
new DataView(lengthBuffer).setUint32(0, bytes.byteLength, false);
|
|
140
|
+
streamController.enqueue(new Uint8Array(lengthBuffer));
|
|
141
|
+
streamController.enqueue(bytes);
|
|
142
|
+
});
|
|
143
|
+
Promise.all(messages.map((msg) => peer.message(msg, handleIndividualRequest))).then(async () => {
|
|
144
|
+
streamController.close();
|
|
145
|
+
await peer.close();
|
|
146
|
+
}).catch(async (error) => {
|
|
147
|
+
streamController.error(error);
|
|
148
|
+
await peer.close(error);
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
matched: true,
|
|
152
|
+
response: { status, headers, body: stream }
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
...options,
|
|
157
|
+
routingInterceptors: [routingInterceptor, ...toArray(options.routingInterceptors)]
|
|
158
|
+
};
|
|
116
159
|
}
|
|
117
160
|
}
|
|
118
161
|
|
|
119
|
-
class
|
|
162
|
+
class CORSHandlerPlugin {
|
|
120
163
|
options;
|
|
121
|
-
|
|
164
|
+
name = "~cors";
|
|
165
|
+
/**
|
|
166
|
+
* - Do not create spans for CORS preflight requests.
|
|
167
|
+
* - Run CORS interceptors before batch interceptors so headers are applied to
|
|
168
|
+
* the actual response rather than sub-responses.
|
|
169
|
+
*/
|
|
170
|
+
after = ["~opentelemetry", "~batch"];
|
|
122
171
|
constructor(options = {}) {
|
|
123
172
|
const defaults = {
|
|
124
173
|
origin: (origin) => origin,
|
|
@@ -130,19 +179,48 @@ class CORSPlugin {
|
|
|
130
179
|
};
|
|
131
180
|
}
|
|
132
181
|
init(options) {
|
|
133
|
-
|
|
134
|
-
|
|
182
|
+
const corsHeadersInterceptor = async (interceptorOptions) => {
|
|
183
|
+
const result = await interceptorOptions.next();
|
|
184
|
+
if (!result.matched) {
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
const resHeaders = { ...result.response.headers };
|
|
188
|
+
const origin = flattenStandardHeader(interceptorOptions.request.headers.origin) ?? "";
|
|
189
|
+
const allowedOrigins = toArray(await value(this.options.origin, origin, interceptorOptions));
|
|
190
|
+
if (allowedOrigins.includes("*")) {
|
|
191
|
+
resHeaders["access-control-allow-origin"] = "*";
|
|
192
|
+
} else {
|
|
193
|
+
if (allowedOrigins.includes(origin)) {
|
|
194
|
+
resHeaders["access-control-allow-origin"] = origin;
|
|
195
|
+
}
|
|
196
|
+
resHeaders.vary = interceptorOptions.request.headers.vary ?? "origin";
|
|
197
|
+
}
|
|
198
|
+
const allowedTimingOrigins = toArray(await value(this.options.timingOrigin, origin, interceptorOptions));
|
|
199
|
+
if (allowedTimingOrigins.includes("*")) {
|
|
200
|
+
resHeaders["timing-allow-origin"] = "*";
|
|
201
|
+
} else if (allowedTimingOrigins.includes(origin)) {
|
|
202
|
+
resHeaders["timing-allow-origin"] = origin;
|
|
203
|
+
}
|
|
204
|
+
if (this.options.credentials) {
|
|
205
|
+
resHeaders["access-control-allow-credentials"] = "true";
|
|
206
|
+
}
|
|
207
|
+
if (this.options.exposeHeaders?.length) {
|
|
208
|
+
resHeaders["access-control-expose-headers"] = flattenStandardHeader(this.options.exposeHeaders);
|
|
209
|
+
}
|
|
210
|
+
return { ...result, response: { ...result.response, headers: resHeaders } };
|
|
211
|
+
};
|
|
212
|
+
const preflightInterceptor = async (interceptorOptions) => {
|
|
135
213
|
if (interceptorOptions.request.method === "OPTIONS") {
|
|
136
214
|
const resHeaders = {};
|
|
137
215
|
if (this.options.maxAge !== void 0) {
|
|
138
216
|
resHeaders["access-control-max-age"] = this.options.maxAge.toString();
|
|
139
217
|
}
|
|
140
218
|
if (this.options.allowMethods?.length) {
|
|
141
|
-
resHeaders["access-control-allow-methods"] =
|
|
219
|
+
resHeaders["access-control-allow-methods"] = flattenStandardHeader(this.options.allowMethods);
|
|
142
220
|
}
|
|
143
221
|
const allowHeaders = this.options.allowHeaders ?? interceptorOptions.request.headers["access-control-request-headers"];
|
|
144
222
|
if (typeof allowHeaders === "string" || allowHeaders?.length) {
|
|
145
|
-
resHeaders["access-control-allow-headers"] =
|
|
223
|
+
resHeaders["access-control-allow-headers"] = flattenStandardHeader(allowHeaders);
|
|
146
224
|
}
|
|
147
225
|
return {
|
|
148
226
|
matched: true,
|
|
@@ -154,175 +232,74 @@ class CORSPlugin {
|
|
|
154
232
|
};
|
|
155
233
|
}
|
|
156
234
|
return interceptorOptions.next();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
result.response.headers.vary = interceptorOptions.request.headers.vary ?? "origin";
|
|
173
|
-
}
|
|
174
|
-
const allowedTimingOrigin = await value(this.options.timingOrigin, origin, interceptorOptions);
|
|
175
|
-
const allowedTimingOriginArr = Array.isArray(allowedTimingOrigin) ? allowedTimingOrigin : [allowedTimingOrigin];
|
|
176
|
-
if (allowedTimingOriginArr.includes("*")) {
|
|
177
|
-
result.response.headers["timing-allow-origin"] = "*";
|
|
178
|
-
} else if (allowedTimingOriginArr.includes(origin)) {
|
|
179
|
-
result.response.headers["timing-allow-origin"] = origin;
|
|
180
|
-
}
|
|
181
|
-
if (this.options.credentials) {
|
|
182
|
-
result.response.headers["access-control-allow-credentials"] = "true";
|
|
183
|
-
}
|
|
184
|
-
if (this.options.exposeHeaders?.length) {
|
|
185
|
-
result.response.headers["access-control-expose-headers"] = flattenHeader(this.options.exposeHeaders);
|
|
186
|
-
}
|
|
187
|
-
return result;
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
class RequestHeadersPlugin {
|
|
193
|
-
init(options) {
|
|
194
|
-
options.rootInterceptors ??= [];
|
|
195
|
-
options.rootInterceptors.push((interceptorOptions) => {
|
|
196
|
-
const reqHeaders = interceptorOptions.context.reqHeaders ?? toFetchHeaders(interceptorOptions.request.headers);
|
|
197
|
-
return interceptorOptions.next({
|
|
198
|
-
...interceptorOptions,
|
|
199
|
-
context: {
|
|
200
|
-
...interceptorOptions.context,
|
|
201
|
-
reqHeaders
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
class ResponseHeadersPlugin {
|
|
209
|
-
init(options) {
|
|
210
|
-
options.rootInterceptors ??= [];
|
|
211
|
-
options.rootInterceptors.push(async (interceptorOptions) => {
|
|
212
|
-
const resHeaders = interceptorOptions.context.resHeaders ?? new Headers();
|
|
213
|
-
const result = await interceptorOptions.next({
|
|
214
|
-
...interceptorOptions,
|
|
215
|
-
context: {
|
|
216
|
-
...interceptorOptions.context,
|
|
217
|
-
resHeaders
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
if (!result.matched) {
|
|
221
|
-
return result;
|
|
222
|
-
}
|
|
223
|
-
const responseHeaders = clone(result.response.headers);
|
|
224
|
-
for (const [key, value] of resHeaders) {
|
|
225
|
-
if (Array.isArray(responseHeaders[key])) {
|
|
226
|
-
responseHeaders[key].push(value);
|
|
227
|
-
} else if (responseHeaders[key] !== void 0) {
|
|
228
|
-
responseHeaders[key] = [responseHeaders[key], value];
|
|
229
|
-
} else {
|
|
230
|
-
responseHeaders[key] = value;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return {
|
|
234
|
-
...result,
|
|
235
|
-
response: {
|
|
236
|
-
...result.response,
|
|
237
|
-
headers: responseHeaders
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
});
|
|
235
|
+
};
|
|
236
|
+
return {
|
|
237
|
+
...options,
|
|
238
|
+
routingInterceptors: [
|
|
239
|
+
corsHeadersInterceptor,
|
|
240
|
+
preflightInterceptor,
|
|
241
|
+
...toArray(options.routingInterceptors)
|
|
242
|
+
]
|
|
243
|
+
};
|
|
241
244
|
}
|
|
242
245
|
}
|
|
243
246
|
|
|
244
|
-
class
|
|
245
|
-
|
|
246
|
-
CONTEXT_SYMBOL = Symbol("ORPC_RETHROW_HANDLER_PLUGIN_CONTEXT");
|
|
247
|
-
constructor(options) {
|
|
248
|
-
this.filter = options.filter;
|
|
249
|
-
}
|
|
247
|
+
class RequestHeadersHandlerPlugin {
|
|
248
|
+
name = "~request-headers";
|
|
250
249
|
init(options) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
options.interceptors.unshift(async (options2) => {
|
|
268
|
-
const pluginContext = options2.context[this.CONTEXT_SYMBOL];
|
|
269
|
-
if (!pluginContext) {
|
|
270
|
-
throw new TypeError("[RethrowHandlerPlugin] Rethrow handler context has been corrupted or modified by another plugin or interceptor");
|
|
271
|
-
}
|
|
272
|
-
try {
|
|
273
|
-
return await options2.next();
|
|
274
|
-
} catch (error) {
|
|
275
|
-
if (value(this.filter, error, options2)) {
|
|
276
|
-
pluginContext.error = { value: error };
|
|
277
|
-
return { matched: false, response: void 0 };
|
|
278
|
-
}
|
|
279
|
-
throw error;
|
|
280
|
-
}
|
|
281
|
-
});
|
|
250
|
+
return {
|
|
251
|
+
...options,
|
|
252
|
+
interceptors: [
|
|
253
|
+
(interceptorOptions) => {
|
|
254
|
+
const reqHeaders = interceptorOptions.context.reqHeaders ?? toFetchHeaders(interceptorOptions.request.headers);
|
|
255
|
+
return interceptorOptions.next({
|
|
256
|
+
...interceptorOptions,
|
|
257
|
+
context: {
|
|
258
|
+
...interceptorOptions.context,
|
|
259
|
+
reqHeaders
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
...toArray(options.interceptors)
|
|
264
|
+
]
|
|
265
|
+
};
|
|
282
266
|
}
|
|
283
267
|
}
|
|
284
268
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
constructor(options = {}) {
|
|
292
|
-
this.headerName = options.headerName ?? "x-csrf-token";
|
|
293
|
-
this.headerValue = options.headerValue ?? "orpc";
|
|
294
|
-
this.exclude = options.exclude ?? false;
|
|
295
|
-
this.error = options.error ?? new ORPCError("CSRF_TOKEN_MISMATCH", {
|
|
296
|
-
status: 403,
|
|
297
|
-
message: "Invalid CSRF token"
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
order = 8e6;
|
|
269
|
+
class ResponseHeadersHandlerPlugin {
|
|
270
|
+
name = "~response-headers";
|
|
271
|
+
/**
|
|
272
|
+
* Interceptors should run after batch interceptors so headers are applied to each sub-response.
|
|
273
|
+
*/
|
|
274
|
+
before = ["~batch"];
|
|
301
275
|
init(options) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
276
|
+
return {
|
|
277
|
+
...options,
|
|
278
|
+
routingInterceptors: [
|
|
279
|
+
async (interceptorOptions) => {
|
|
280
|
+
const resHeaders = new Headers(interceptorOptions.context.resHeaders);
|
|
281
|
+
const result = await interceptorOptions.next({
|
|
282
|
+
...interceptorOptions,
|
|
283
|
+
context: {
|
|
284
|
+
...interceptorOptions.context,
|
|
285
|
+
resHeaders
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
if (!result.response) {
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
...result,
|
|
293
|
+
response: {
|
|
294
|
+
...result.response,
|
|
295
|
+
headers: mergeStandardHeaders(result.response.headers, toStandardHeaders(resHeaders))
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
},
|
|
299
|
+
...toArray(options.routingInterceptors)
|
|
300
|
+
]
|
|
301
|
+
};
|
|
325
302
|
}
|
|
326
303
|
}
|
|
327
304
|
|
|
328
|
-
export { BatchHandlerPlugin,
|
|
305
|
+
export { BatchHandlerPlugin, CORSHandlerPlugin, RequestHeadersHandlerPlugin, ResponseHeadersHandlerPlugin };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { AnyORPCError } from '@orpc/client';
|
|
2
|
+
import { OrderablePlugin, Interceptor, Promisable } from '@orpc/shared';
|
|
3
|
+
import { StandardLazyRequest, StandardResponse } from '@standardserver/core';
|
|
4
|
+
import { C as Context, c as ProcedureClientInterceptor, A as AnyProcedure } from './server.BL22TloH.mjs';
|
|
5
|
+
import { Schema, ErrorMap } from '@orpc/contract';
|
|
6
|
+
|
|
7
|
+
interface StandardHandlerPlugin<T extends Context> extends OrderablePlugin {
|
|
8
|
+
/**
|
|
9
|
+
* Initializes the plugin and returns new handler options.
|
|
10
|
+
* Called once per plugin instance during composition.
|
|
11
|
+
*
|
|
12
|
+
* This method allows plugins to wrap, extend, or transform handler options
|
|
13
|
+
* such as interceptors, or configuration.
|
|
14
|
+
*
|
|
15
|
+
* @param options - The current handler options from previous plugins or base configuration
|
|
16
|
+
* @returns Transformed handler options with plugin's modifications applied
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* init(options) {
|
|
21
|
+
* return {
|
|
22
|
+
* ...options,
|
|
23
|
+
* interceptors: [...(options.interceptors || []), myInterceptor]
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
init?(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
|
|
29
|
+
}
|
|
30
|
+
declare class CompositeStandardHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
|
|
31
|
+
readonly name = "~composite";
|
|
32
|
+
protected readonly plugins: StandardHandlerPlugin<T>[];
|
|
33
|
+
constructor(plugins?: StandardHandlerPlugin<T>[]);
|
|
34
|
+
init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface StandardHandlerHandleOptions<T extends Context> {
|
|
38
|
+
prefix?: `/${string}` | undefined;
|
|
39
|
+
context: T;
|
|
40
|
+
}
|
|
41
|
+
type StandardHandlerHandleResult = {
|
|
42
|
+
matched: true;
|
|
43
|
+
response: StandardResponse;
|
|
44
|
+
} | {
|
|
45
|
+
matched: false;
|
|
46
|
+
response?: undefined;
|
|
47
|
+
};
|
|
48
|
+
interface StandardHandlerInterceptorOptions<T extends Context> extends StandardHandlerCodecResolvedProcedure, StandardHandlerHandleOptions<T> {
|
|
49
|
+
request: StandardLazyRequest;
|
|
50
|
+
}
|
|
51
|
+
type StandardHandlerInterceptor<T extends Context> = Interceptor<StandardHandlerInterceptorOptions<T>, Promise<StandardResponse>>;
|
|
52
|
+
interface StandardHandlerRoutingInterceptorOptions<T extends Context> extends StandardHandlerHandleOptions<T> {
|
|
53
|
+
request: StandardLazyRequest;
|
|
54
|
+
}
|
|
55
|
+
type StandardHandlerRoutingInterceptor<T extends Context> = Interceptor<StandardHandlerRoutingInterceptorOptions<T>, Promise<StandardHandlerHandleResult>>;
|
|
56
|
+
interface StandardHandlerOptions<TContext extends Context> {
|
|
57
|
+
/**
|
|
58
|
+
* Fired on every request before routing, useful when you want
|
|
59
|
+
* to intercept all requests regardless of whether they match a procedure or not.
|
|
60
|
+
*
|
|
61
|
+
* @examples
|
|
62
|
+
* - batch plugins - separate one request into multiple and call multiple next
|
|
63
|
+
* - openapi spec plugin - to intercept a request and early response
|
|
64
|
+
*/
|
|
65
|
+
routingInterceptors?: StandardHandlerRoutingInterceptor<TContext>[];
|
|
66
|
+
/**
|
|
67
|
+
* interceptor run after routing and before error handler,
|
|
68
|
+
* useful for error handling, logging, metrics, etc.
|
|
69
|
+
*/
|
|
70
|
+
interceptors?: StandardHandlerInterceptor<TContext>[];
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* ClientInterceptor equivalent with createRouterClient.interceptors / createProcedure.interceptors
|
|
74
|
+
* useful for error handling, logging, metrics, etc. (not counting encoding/decoding)
|
|
75
|
+
*/
|
|
76
|
+
clientInterceptors?: ProcedureClientInterceptor<TContext, Schema<unknown>, ErrorMap, any>[];
|
|
77
|
+
plugins?: StandardHandlerPlugin<TContext>[];
|
|
78
|
+
}
|
|
79
|
+
declare class StandardHandler<T extends Context> {
|
|
80
|
+
private readonly codec;
|
|
81
|
+
private readonly routingInterceptors;
|
|
82
|
+
private readonly interceptors;
|
|
83
|
+
private readonly clientInterceptors;
|
|
84
|
+
constructor(codec: StandardHandlerCodec<T>, options: StandardHandlerOptions<T>);
|
|
85
|
+
handle(request: StandardLazyRequest, { context, prefix }: StandardHandlerHandleOptions<T>): Promise<StandardHandlerHandleResult>;
|
|
86
|
+
}
|
|
87
|
+
declare class OtelHandlerPlugin implements StandardHandlerPlugin<any> {
|
|
88
|
+
name: string;
|
|
89
|
+
init(options: StandardHandlerOptions<any>): StandardHandlerOptions<any>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface StandardHandlerCodecResolvedProcedure {
|
|
93
|
+
path: string[];
|
|
94
|
+
procedure: AnyProcedure;
|
|
95
|
+
decodeInput: () => Promise<unknown>;
|
|
96
|
+
}
|
|
97
|
+
interface StandardHandlerCodec<T extends Context> {
|
|
98
|
+
resolveProcedure(request: StandardLazyRequest, options: StandardHandlerHandleOptions<T>): Promisable<StandardHandlerCodecResolvedProcedure | undefined>;
|
|
99
|
+
encodeOutput(output: unknown, procedure: AnyProcedure, path: string[], options: StandardHandlerHandleOptions<T>): Promisable<StandardResponse>;
|
|
100
|
+
encodeError(error: AnyORPCError, procedure: AnyProcedure, path: string[], options: StandardHandlerHandleOptions<T>): Promisable<StandardResponse>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { CompositeStandardHandlerPlugin as C, OtelHandlerPlugin as O, StandardHandler as S };
|
|
104
|
+
export type { StandardHandlerOptions as a, StandardHandlerHandleOptions as b, StandardHandlerCodec as c, StandardHandlerCodecResolvedProcedure as d, StandardHandlerPlugin as e, StandardHandlerRoutingInterceptorOptions as f, StandardHandlerHandleResult as g, StandardHandlerInterceptor as h, StandardHandlerInterceptorOptions as i, StandardHandlerRoutingInterceptor as j };
|