@orpc/server 1.14.6 → 2.0.0-beta.10

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.
Files changed (78) hide show
  1. package/README.md +78 -137
  2. package/dist/adapters/crossws/index.d.mts +42 -21
  3. package/dist/adapters/crossws/index.d.ts +42 -21
  4. package/dist/adapters/crossws/index.mjs +37 -18
  5. package/dist/adapters/fetch/index.d.mts +83 -67
  6. package/dist/adapters/fetch/index.d.ts +83 -67
  7. package/dist/adapters/fetch/index.mjs +131 -106
  8. package/dist/adapters/message-port/index.d.mts +51 -34
  9. package/dist/adapters/message-port/index.d.ts +51 -34
  10. package/dist/adapters/message-port/index.mjs +73 -38
  11. package/dist/adapters/node/index.d.mts +82 -60
  12. package/dist/adapters/node/index.d.ts +82 -60
  13. package/dist/adapters/node/index.mjs +127 -98
  14. package/dist/adapters/standard/index.d.mts +16 -18
  15. package/dist/adapters/standard/index.d.ts +16 -18
  16. package/dist/adapters/standard/index.mjs +5 -5
  17. package/dist/adapters/standard-peer/index.d.mts +12 -14
  18. package/dist/adapters/standard-peer/index.d.ts +12 -14
  19. package/dist/adapters/standard-peer/index.mjs +2 -21
  20. package/dist/adapters/websocket/index.d.mts +50 -36
  21. package/dist/adapters/websocket/index.d.ts +50 -36
  22. package/dist/adapters/websocket/index.mjs +55 -42
  23. package/dist/extensions/callable.d.mts +10 -0
  24. package/dist/extensions/callable.d.ts +10 -0
  25. package/dist/extensions/callable.mjs +11 -0
  26. package/dist/helpers/index.d.mts +2 -2
  27. package/dist/helpers/index.d.ts +2 -2
  28. package/dist/helpers/index.mjs +1 -1
  29. package/dist/index.d.mts +163 -770
  30. package/dist/index.d.ts +163 -770
  31. package/dist/index.mjs +296 -403
  32. package/dist/plugins/index.d.mts +120 -127
  33. package/dist/plugins/index.d.ts +120 -127
  34. package/dist/plugins/index.mjs +252 -223
  35. package/dist/shared/server.BL22TloH.d.mts +184 -0
  36. package/dist/shared/server.BL22TloH.d.ts +184 -0
  37. package/dist/shared/server.BsNNjG5J.d.mts +61 -0
  38. package/dist/shared/server.BwHnWUuN.mjs +222 -0
  39. package/dist/shared/server.CX4vUnDk.mjs +11 -0
  40. package/dist/shared/server.Cj5lgPuG.d.mts +66 -0
  41. package/dist/shared/server.CjOb6ItT.mjs +41 -0
  42. package/dist/shared/server.CrlKQucM.mjs +233 -0
  43. package/dist/shared/server.DBANIheG.d.ts +104 -0
  44. package/dist/shared/server.DJIkub_t.d.mts +104 -0
  45. package/dist/shared/server.D_QauotT.mjs +30 -0
  46. package/dist/shared/server.GDpX6Df8.mjs +271 -0
  47. package/dist/shared/server.Pa0F03f_.d.ts +61 -0
  48. package/dist/shared/{server.DZ5BIITo.mjs → server.W91HSRkE.mjs} +2 -2
  49. package/dist/shared/server.d_2NS3g5.d.ts +66 -0
  50. package/package.json +26 -55
  51. package/dist/adapters/aws-lambda/index.d.mts +0 -46
  52. package/dist/adapters/aws-lambda/index.d.ts +0 -46
  53. package/dist/adapters/aws-lambda/index.mjs +0 -40
  54. package/dist/adapters/bun-ws/index.d.mts +0 -36
  55. package/dist/adapters/bun-ws/index.d.ts +0 -36
  56. package/dist/adapters/bun-ws/index.mjs +0 -47
  57. package/dist/adapters/fastify/index.d.mts +0 -53
  58. package/dist/adapters/fastify/index.d.ts +0 -53
  59. package/dist/adapters/fastify/index.mjs +0 -52
  60. package/dist/adapters/ws/index.d.mts +0 -31
  61. package/dist/adapters/ws/index.d.ts +0 -31
  62. package/dist/adapters/ws/index.mjs +0 -37
  63. package/dist/hibernation/index.d.mts +0 -44
  64. package/dist/hibernation/index.d.ts +0 -44
  65. package/dist/hibernation/index.mjs +0 -65
  66. package/dist/shared/server.7cEtMB30.d.ts +0 -74
  67. package/dist/shared/server.B8gYOD5g.d.mts +0 -12
  68. package/dist/shared/server.BqadksTP.d.mts +0 -74
  69. package/dist/shared/server.C8_sRzQB.d.mts +0 -42
  70. package/dist/shared/server.ChUyt5-i.d.mts +0 -32
  71. package/dist/shared/server.ChyoA9XY.d.ts +0 -42
  72. package/dist/shared/server.DEBcqOjg.mjs +0 -418
  73. package/dist/shared/server.EfTOZ2Q7.d.ts +0 -12
  74. package/dist/shared/server.TEVCLCFC.mjs +0 -39
  75. package/dist/shared/server.ZxHCEN1h.mjs +0 -226
  76. package/dist/shared/server.qKsRrdxW.d.mts +0 -193
  77. package/dist/shared/server.qKsRrdxW.d.ts +0 -193
  78. package/dist/shared/server.yoEB3Fx4.d.ts +0 -32
@@ -1,124 +1,173 @@
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';
5
- import { ORPCError } from '@orpc/client';
6
- export { S as StrictGetMethodPlugin } from '../shared/server.TEVCLCFC.mjs';
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
- mapRequestItem;
16
+ mapSubrequest;
12
17
  successStatus;
13
18
  headers;
14
- order = 5e6;
15
19
  constructor(options = {}) {
16
20
  this.maxSize = options.maxSize ?? 10;
17
- this.mapRequestItem = options.mapRequestItem ?? ((request, { request: batchRequest }) => ({
18
- ...request,
21
+ this.mapSubrequest = options.mapSubrequest ?? ((subRequest, { request: batchRequest }) => ({
22
+ ...subRequest,
19
23
  headers: {
20
24
  ...batchRequest.headers,
21
- ...request.headers
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
- 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();
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
- let isParsing = false;
39
+ const mode = batchHeader === "buffered" ? "buffered" : "streaming";
40
+ let messages;
35
41
  try {
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);
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 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
- };
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
- } catch (cause) {
107
- if (isParsing) {
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: { status: 400, headers: {}, body: "Invalid batch request, this could be caused by a malformed request body or a missing header" }
117
+ response: {
118
+ status,
119
+ headers,
120
+ body: new Blob(chunks, { type: "application/octet-stream" })
121
+ }
111
122
  };
112
123
  }
113
- throw cause;
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 CORSPlugin {
162
+ class CORSHandlerPlugin {
120
163
  options;
121
- order = 9e6;
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
- options.rootInterceptors ??= [];
134
- options.rootInterceptors.unshift(async (interceptorOptions) => {
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"] = flattenHeader(this.options.allowMethods);
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"] = flattenHeader(allowHeaders);
223
+ resHeaders["access-control-allow-headers"] = flattenStandardHeader(allowHeaders);
146
224
  }
147
225
  return {
148
226
  matched: true,
@@ -154,103 +232,85 @@ class CORSPlugin {
154
232
  };
155
233
  }
156
234
  return interceptorOptions.next();
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
- 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
- });
235
+ };
236
+ return {
237
+ ...options,
238
+ routingInterceptors: [
239
+ corsHeadersInterceptor,
240
+ preflightInterceptor,
241
+ ...toArray(options.routingInterceptors)
242
+ ]
243
+ };
189
244
  }
190
245
  }
191
246
 
192
- class RequestHeadersPlugin {
247
+ class RequestHeadersHandlerPlugin {
248
+ name = "~request-headers";
193
249
  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
- });
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
+ };
205
266
  }
206
267
  }
207
268
 
208
- class ResponseHeadersPlugin {
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"];
209
275
  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
- });
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
+ };
241
302
  }
242
303
  }
243
304
 
244
- class experimental_RethrowHandlerPlugin {
305
+ class RethrowHandlerPlugin {
306
+ name = "~rethrow";
245
307
  filter;
246
308
  CONTEXT_SYMBOL = Symbol("ORPC_RETHROW_HANDLER_PLUGIN_CONTEXT");
247
309
  constructor(options) {
248
310
  this.filter = options.filter;
249
311
  }
250
312
  init(options) {
251
- options.rootInterceptors ??= [];
252
- options.interceptors ??= [];
253
- options.rootInterceptors.push(async (options2) => {
313
+ const routingInterceptor = async (options2) => {
254
314
  const pluginContext = {};
255
315
  const result = await options2.next({
256
316
  ...options2,
@@ -263,8 +323,8 @@ class experimental_RethrowHandlerPlugin {
263
323
  throw pluginContext.error.value;
264
324
  }
265
325
  return result;
266
- });
267
- options.interceptors.unshift(async (options2) => {
326
+ };
327
+ const interceptor = async (options2) => {
268
328
  const pluginContext = options2.context[this.CONTEXT_SYMBOL];
269
329
  if (!pluginContext) {
270
330
  throw new TypeError("[RethrowHandlerPlugin] Rethrow handler context has been corrupted or modified by another plugin or interceptor");
@@ -272,57 +332,26 @@ class experimental_RethrowHandlerPlugin {
272
332
  try {
273
333
  return await options2.next();
274
334
  } catch (error) {
275
- if (value(this.filter, error, options2)) {
335
+ if (this.filter(error, options2)) {
276
336
  pluginContext.error = { value: error };
277
- return { matched: false, response: void 0 };
278
337
  }
279
338
  throw error;
280
339
  }
281
- });
282
- }
283
- }
284
-
285
- const SIMPLE_CSRF_PROTECTION_CONTEXT_SYMBOL = Symbol("SIMPLE_CSRF_PROTECTION_CONTEXT");
286
- class SimpleCsrfProtectionHandlerPlugin {
287
- headerName;
288
- headerValue;
289
- exclude;
290
- error;
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;
301
- init(options) {
302
- options.rootInterceptors ??= [];
303
- options.clientInterceptors ??= [];
304
- options.rootInterceptors.unshift(async (options2) => {
305
- const headerName = await value(this.headerName, options2);
306
- const headerValue = await value(this.headerValue, options2);
307
- return options2.next({
308
- ...options2,
309
- context: {
310
- ...options2.context,
311
- [SIMPLE_CSRF_PROTECTION_CONTEXT_SYMBOL]: options2.request.headers[headerName] === headerValue
312
- }
313
- });
314
- });
315
- options.clientInterceptors.unshift(async (options2) => {
316
- if (typeof options2.context[SIMPLE_CSRF_PROTECTION_CONTEXT_SYMBOL] !== "boolean") {
317
- throw new TypeError("[SimpleCsrfProtectionHandlerPlugin] CSRF protection context has been corrupted or modified by another plugin or interceptor");
318
- }
319
- const excluded = await value(this.exclude, options2);
320
- if (!excluded && !options2.context[SIMPLE_CSRF_PROTECTION_CONTEXT_SYMBOL]) {
321
- throw this.error;
322
- }
323
- return options2.next();
324
- });
340
+ };
341
+ return {
342
+ ...options,
343
+ routingInterceptors: [
344
+ ...toArray(options.routingInterceptors),
345
+ routingInterceptor
346
+ // rethrow as early as possible and keep it close to `interceptor` to avoid a corrupted or modified context
347
+ ],
348
+ interceptors: [
349
+ interceptor,
350
+ // catch as many errors as possible
351
+ ...toArray(options.interceptors)
352
+ ]
353
+ };
325
354
  }
326
355
  }
327
356
 
328
- export { BatchHandlerPlugin, CORSPlugin, RequestHeadersPlugin, ResponseHeadersPlugin, SimpleCsrfProtectionHandlerPlugin, experimental_RethrowHandlerPlugin };
357
+ export { BatchHandlerPlugin, CORSHandlerPlugin, RequestHeadersHandlerPlugin, ResponseHeadersHandlerPlugin, RethrowHandlerPlugin };