@orpc/server 2.0.0-beta.46 → 2.0.0-beta.48

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 (169) hide show
  1. package/README.md +6 -4
  2. package/dist/adapters/aws-lambda/handler.d.mts +25 -0
  3. package/dist/adapters/aws-lambda/handler.mjs +21 -0
  4. package/dist/adapters/aws-lambda/index.d.mts +14 -49
  5. package/dist/adapters/aws-lambda/index.mjs +2 -39
  6. package/dist/adapters/aws-lambda/rpc-handler.d.mts +18 -0
  7. package/dist/adapters/aws-lambda/rpc-handler.mjs +10 -0
  8. package/dist/adapters/crossws/handler.d.mts +41 -0
  9. package/dist/adapters/crossws/handler.mjs +38 -0
  10. package/dist/adapters/crossws/index.d.mts +2 -63
  11. package/dist/adapters/crossws/index.mjs +2 -68
  12. package/dist/adapters/crossws/rpc-handler.d.mts +15 -0
  13. package/dist/adapters/crossws/rpc-handler.mjs +10 -0
  14. package/dist/adapters/fastify/handler.d.mts +21 -0
  15. package/dist/adapters/fastify/handler.mjs +20 -0
  16. package/dist/adapters/fastify/index.d.mts +2 -42
  17. package/dist/adapters/fastify/index.mjs +2 -39
  18. package/dist/adapters/fastify/rpc-handler.d.mts +14 -0
  19. package/dist/adapters/fastify/rpc-handler.mjs +10 -0
  20. package/dist/adapters/fetch/handler.d.mts +23 -0
  21. package/dist/adapters/fetch/handler.mjs +22 -0
  22. package/dist/adapters/fetch/index.d.mts +6 -46
  23. package/dist/adapters/fetch/index.mjs +4 -41
  24. package/dist/adapters/fetch/rpc-handler.d.mts +16 -0
  25. package/dist/adapters/fetch/rpc-handler.mjs +10 -0
  26. package/dist/adapters/message-port/handler.d.mts +57 -0
  27. package/dist/adapters/message-port/handler.mjs +62 -0
  28. package/dist/adapters/message-port/index.d.mts +2 -77
  29. package/dist/adapters/message-port/index.mjs +2 -87
  30. package/dist/adapters/message-port/rpc-handler.d.mts +15 -0
  31. package/dist/adapters/message-port/rpc-handler.mjs +10 -0
  32. package/dist/adapters/node/handler.d.mts +21 -0
  33. package/dist/adapters/node/handler.mjs +20 -0
  34. package/dist/adapters/node/index.d.mts +6 -43
  35. package/dist/adapters/node/index.mjs +4 -40
  36. package/dist/adapters/node/rpc-handler.d.mts +15 -0
  37. package/dist/adapters/node/rpc-handler.mjs +10 -0
  38. package/dist/adapters/standard/codec.d.mts +16 -0
  39. package/dist/adapters/standard/codec.mjs +1 -0
  40. package/dist/adapters/standard/handler.d.mts +68 -0
  41. package/dist/adapters/standard/handler.mjs +185 -0
  42. package/dist/adapters/standard/index.d.mts +6 -95
  43. package/dist/adapters/standard/index.mjs +5 -8
  44. package/dist/adapters/standard/plugin.d.mts +32 -0
  45. package/dist/adapters/standard/plugin.mjs +16 -0
  46. package/dist/adapters/standard/rpc-handler-codec.d.mts +42 -0
  47. package/dist/adapters/standard/rpc-handler-codec.mjs +51 -0
  48. package/dist/adapters/standard/rpc-matcher.d.mts +53 -0
  49. package/dist/adapters/standard/rpc-matcher.mjs +140 -0
  50. package/dist/adapters/standard/utils.d.mts +10 -0
  51. package/dist/adapters/standard/utils.mjs +6 -0
  52. package/dist/adapters/standard-peer/index.d.mts +1 -11
  53. package/dist/adapters/standard-peer/index.mjs +1 -10
  54. package/dist/adapters/standard-peer/utils.d.mts +4 -0
  55. package/dist/adapters/standard-peer/utils.mjs +11 -0
  56. package/dist/adapters/websocket/handler.d.mts +55 -0
  57. package/dist/adapters/websocket/handler.mjs +55 -0
  58. package/dist/adapters/websocket/index.d.mts +2 -77
  59. package/dist/adapters/websocket/index.mjs +2 -87
  60. package/dist/adapters/websocket/rpc-handler.d.mts +15 -0
  61. package/dist/adapters/websocket/rpc-handler.mjs +10 -0
  62. package/dist/builder-variants.d.mts +267 -0
  63. package/dist/builder-variants.mjs +1 -0
  64. package/dist/builder.d.mts +141 -0
  65. package/dist/builder.mjs +145 -0
  66. package/dist/constants.d.mts +2 -0
  67. package/dist/constants.mjs +2 -0
  68. package/dist/context.d.mts +13 -0
  69. package/dist/context.mjs +1 -0
  70. package/dist/extensions/callable.d.mts +16 -9
  71. package/dist/extensions/callable.mjs +5 -9
  72. package/dist/helpers/base64url.d.mts +31 -0
  73. package/dist/helpers/base64url.mjs +25 -0
  74. package/dist/helpers/cookie.d.mts +50 -0
  75. package/dist/helpers/cookie.mjs +35 -0
  76. package/dist/helpers/encryption.d.mts +28 -0
  77. package/dist/helpers/encryption.mjs +62 -0
  78. package/dist/helpers/index.d.mts +4 -167
  79. package/dist/helpers/index.mjs +4 -200
  80. package/dist/helpers/signing.d.mts +50 -0
  81. package/dist/helpers/signing.mjs +43 -0
  82. package/dist/implementer-procedure.d.mts +36 -0
  83. package/dist/implementer-procedure.mjs +38 -0
  84. package/dist/implementer-router.d.mts +78 -0
  85. package/dist/implementer-router.mjs +77 -0
  86. package/dist/implementer.d.mts +22 -0
  87. package/dist/implementer.mjs +30 -0
  88. package/dist/index.d.mts +57 -476
  89. package/dist/index.mjs +25 -458
  90. package/dist/lazy.d.mts +20 -0
  91. package/dist/lazy.mjs +22 -0
  92. package/dist/middleware-decorated.d.mts +27 -0
  93. package/dist/middleware-decorated.mjs +65 -0
  94. package/dist/middleware.d.mts +70 -0
  95. package/dist/middleware.mjs +1 -0
  96. package/dist/plugins/batch.d.mts +104 -0
  97. package/dist/plugins/batch.mjs +373 -0
  98. package/dist/plugins/cors.d.mts +67 -0
  99. package/dist/plugins/cors.mjs +134 -0
  100. package/dist/plugins/get-method-csrf-protection.d.mts +23 -0
  101. package/dist/plugins/get-method-csrf-protection.mjs +45 -0
  102. package/dist/plugins/head-method.d.mts +23 -0
  103. package/dist/plugins/head-method.mjs +95 -0
  104. package/dist/plugins/index.d.mts +39 -432
  105. package/dist/plugins/index.mjs +23 -967
  106. package/dist/plugins/method-override.d.mts +38 -0
  107. package/dist/plugins/method-override.mjs +50 -0
  108. package/dist/plugins/request-compression.d.mts +16 -0
  109. package/dist/plugins/request-compression.mjs +67 -0
  110. package/dist/plugins/request-headers.d.mts +22 -0
  111. package/dist/plugins/request-headers.mjs +21 -0
  112. package/dist/plugins/request-limit.d.mts +30 -0
  113. package/dist/plugins/request-limit.mjs +53 -0
  114. package/dist/plugins/response-compression.d.mts +59 -0
  115. package/dist/plugins/response-compression.mjs +172 -0
  116. package/dist/plugins/response-headers.d.mts +30 -0
  117. package/dist/plugins/response-headers.mjs +34 -0
  118. package/dist/plugins/rethrow.d.mts +33 -0
  119. package/dist/plugins/rethrow.mjs +46 -0
  120. package/dist/plugins/signal-sync.d.mts +13 -0
  121. package/dist/plugins/signal-sync.mjs +84 -0
  122. package/dist/plugins/timeout.d.mts +37 -0
  123. package/dist/plugins/timeout.mjs +56 -0
  124. package/dist/procedure-client.d.mts +41 -0
  125. package/dist/procedure-client.mjs +157 -0
  126. package/dist/procedure-decorated.d.mts +20 -0
  127. package/dist/procedure-decorated.mjs +41 -0
  128. package/dist/procedure-utils.d.mts +52 -0
  129. package/dist/procedure-utils.mjs +33 -0
  130. package/dist/procedure.d.mts +93 -0
  131. package/dist/procedure.mjs +20 -0
  132. package/dist/router-client.d.mts +27 -0
  133. package/dist/router-client.mjs +35 -0
  134. package/dist/router-hidden.d.mts +5 -0
  135. package/dist/router-hidden.mjs +12 -0
  136. package/dist/router-utils.d.mts +59 -0
  137. package/dist/router-utils.mjs +191 -0
  138. package/dist/router.d.mts +61 -0
  139. package/dist/router.mjs +1 -0
  140. package/package.json +11 -11
  141. package/dist/adapters/aws-lambda/index.d.ts +0 -51
  142. package/dist/adapters/crossws/index.d.ts +0 -63
  143. package/dist/adapters/fastify/index.d.ts +0 -42
  144. package/dist/adapters/fetch/index.d.ts +0 -47
  145. package/dist/adapters/message-port/index.d.ts +0 -77
  146. package/dist/adapters/node/index.d.ts +0 -44
  147. package/dist/adapters/standard/index.d.ts +0 -95
  148. package/dist/adapters/standard-peer/index.d.ts +0 -11
  149. package/dist/adapters/websocket/index.d.ts +0 -77
  150. package/dist/extensions/callable.d.ts +0 -10
  151. package/dist/helpers/index.d.ts +0 -167
  152. package/dist/index.d.ts +0 -476
  153. package/dist/plugins/index.d.ts +0 -432
  154. package/dist/shared/server.1ExWFMze.mjs +0 -69
  155. package/dist/shared/server.9B1jEm5a.d.mts +0 -105
  156. package/dist/shared/server.BB464y-6.mjs +0 -207
  157. package/dist/shared/server.BC5BIu02.d.mts +0 -73
  158. package/dist/shared/server.BSYGskId.d.ts +0 -14
  159. package/dist/shared/server.BvG3boBj.d.mts +0 -14
  160. package/dist/shared/server.CTV_mC2P.d.mts +0 -34
  161. package/dist/shared/server.CYbIpHbH.mjs +0 -239
  162. package/dist/shared/server.CgBF3Vj1.d.ts +0 -34
  163. package/dist/shared/server.Chq5FpXf.mjs +0 -353
  164. package/dist/shared/server.DFPxP2v3.d.mts +0 -166
  165. package/dist/shared/server.DFPxP2v3.d.ts +0 -166
  166. package/dist/shared/server.DOg7ke6y.d.ts +0 -105
  167. package/dist/shared/server.Dz7j9BEh.d.ts +0 -73
  168. package/dist/shared/server.W91HSRkE.mjs +0 -9
  169. package/dist/shared/server.sBQSgqK3.mjs +0 -41
@@ -1,967 +1,23 @@
1
- import { ORPCError } from '@orpc/client';
2
- import { DEFAULT_BATCH_MAX_SIZE, BATCH_HEADER } from '@orpc/client/plugins';
3
- export { BATCH_HEADER, DEFAULT_BATCH_MAX_SIZE } from '@orpc/client/plugins';
4
- import { toArray, value, promiseWithResolvers, isAsyncIteratorObject, isCompressibleContentType, isNoTransformCacheControl, parseAcceptEncodingQualities, isAcceptableEncoding, varyByAcceptEncoding, stringifyJSON, anyAbortSignal, override, AsyncIteratorClass, runWithSignal, AbortError, wrapAsyncIterator, wrapReadableStream } from '@orpc/shared';
5
- import { flattenStandardHeader, parseStandardUrl, generateContentDisposition, mergeStandardHeaders } from '@standard-server/core';
6
- import { ServerPeer, isPeerMessage, isClientPeerSendMessage, encodePeerMessage } from '@standard-server/peer';
7
- import { toFetchHeaders, toStandardBody, toStandardHeaders } from '@standard-server/fetch';
8
- export { R as RequestLimitHandlerPlugin } from '../shared/server.1ExWFMze.mjs';
9
-
10
- const BATCH_CONTENT_TYPE = "application/vnd.orpc.batch";
11
- const textEncoder = new TextEncoder();
12
- function encodeLengthPrefix(length) {
13
- const prefix = new Uint8Array(4);
14
- new DataView(prefix.buffer).setUint32(0, length, false);
15
- return prefix;
16
- }
17
- async function encodeFrame(message) {
18
- const encoded = await encodePeerMessage(message);
19
- const bytes = typeof encoded === "string" ? textEncoder.encode(encoded) : encoded;
20
- return [encodeLengthPrefix(bytes.byteLength), bytes];
21
- }
22
- function isBodyHeader(name) {
23
- return name.startsWith("content-") || name === "standard-server";
24
- }
25
- function isClientPeerSendMessages(maybe) {
26
- return Array.isArray(maybe) && maybe.every((m) => isPeerMessage(m) && isClientPeerSendMessage(m));
27
- }
28
- function internalError(err) {
29
- Promise.reject(err);
30
- return { status: 500, headers: {}, body: "Internal server error" };
31
- }
32
- function liftSubresponseHeaders(batchHeaders, subresponseHeaders) {
33
- const [first = {}, ...rest] = subresponseHeaders;
34
- const shared = {};
35
- for (const [name, value2] of Object.entries(first)) {
36
- if (value2 === void 0 || isBodyHeader(name)) {
37
- continue;
38
- }
39
- const flattened = flattenStandardHeader(value2);
40
- if (rest.every((other) => flattenStandardHeader(other[name]) === flattened)) {
41
- shared[name] = value2;
42
- }
43
- }
44
- const headers = { ...shared, ...batchHeaders };
45
- const cookies = new Set([batchHeaders, ...subresponseHeaders].flatMap((other) => toArray(other["set-cookie"])));
46
- if (cookies.size > 0) {
47
- headers["set-cookie"] = [...cookies];
48
- }
49
- const varies = /* @__PURE__ */ new Map();
50
- for (const value2 of [batchHeaders, ...subresponseHeaders].flatMap((other) => toArray(other.vary))) {
51
- for (const field of value2.split(",")) {
52
- const name = field.trim();
53
- const key = name.toLowerCase();
54
- if (name && !varies.has(key)) {
55
- varies.set(key, name);
56
- }
57
- }
58
- }
59
- if (varies.size > 0) {
60
- headers.vary = varies.has("*") ? "*" : [...varies.values()].join(", ");
61
- }
62
- return headers;
63
- }
64
- class BatchHandlerPlugin {
65
- name = "~batch";
66
- /**
67
- * Run batch interceptors inside the tracing interceptors
68
- * so each subrequest gets its own span instead of sharing one batch-level span.
69
- */
70
- after = ["~tracing"];
71
- maxSize;
72
- mapSubrequest;
73
- mapSubresponse;
74
- successStatus;
75
- headers;
76
- keepAliveEnabled;
77
- keepAliveInterval;
78
- constructor(options = {}) {
79
- this.maxSize = options.maxSize ?? DEFAULT_BATCH_MAX_SIZE;
80
- this.mapSubrequest = options.mapSubrequest ?? ((subRequest, { request: batchRequest }) => ({
81
- ...subRequest,
82
- headers: {
83
- ...subRequest.headers,
84
- /**
85
- * The batch request headers win over the subrequest ones. They are the only ones the
86
- * transport actually saw, so headers the browser injects on its own, such as `cookie`
87
- * or `origin`, cannot be overridden by a subrequest, which is just request payload.
88
- */
89
- ...batchRequest.headers,
90
- [BATCH_HEADER]: void 0
91
- // useful in case batch plugin is used multiple times
92
- }
93
- }));
94
- this.mapSubresponse = options.mapSubresponse ?? ((subresponse, { headers: batchHeaders }) => {
95
- const headers = {};
96
- for (const [name, value2] of Object.entries(subresponse.headers)) {
97
- if (name !== "set-cookie" && flattenStandardHeader(value2) !== flattenStandardHeader(batchHeaders[name])) {
98
- headers[name] = value2;
99
- }
100
- }
101
- return { ...subresponse, headers };
102
- });
103
- this.successStatus = options.successStatus ?? 207;
104
- this.headers = options.headers ?? {};
105
- this.keepAliveEnabled = options.keepAlive?.enabled ?? true;
106
- this.keepAliveInterval = options.keepAlive?.interval ?? 15e3;
107
- }
108
- init(options) {
109
- const routingInterceptor = async (interceptorOptions) => {
110
- if (flattenStandardHeader(interceptorOptions.request.headers[BATCH_HEADER]) === void 0) {
111
- return interceptorOptions.next();
112
- }
113
- let messages;
114
- try {
115
- if (interceptorOptions.request.method === "GET") {
116
- const [, search] = parseStandardUrl(interceptorOptions.request.url);
117
- const params = new URLSearchParams(search);
118
- const data = params.getAll("data").at(-1);
119
- if (!data) {
120
- return {
121
- matched: true,
122
- response: { status: 400, headers: {}, body: "Missing data parameter for batch request" }
123
- };
124
- }
125
- const mightBeMessages = JSON.parse(data);
126
- if (!isClientPeerSendMessages(mightBeMessages)) {
127
- return {
128
- matched: true,
129
- response: { status: 400, headers: {}, body: "Invalid batch request data parameter" }
130
- };
131
- }
132
- messages = mightBeMessages;
133
- } else {
134
- const mightBeMessages = await interceptorOptions.request.resolveBody();
135
- if (!isClientPeerSendMessages(mightBeMessages)) {
136
- return {
137
- matched: true,
138
- response: { status: 400, headers: {}, body: "Invalid batch request body" }
139
- };
140
- }
141
- messages = mightBeMessages;
142
- }
143
- } catch (error) {
144
- return {
145
- matched: true,
146
- response: {
147
- status: 400,
148
- headers: {},
149
- body: error instanceof ORPCError ? error.message : "Invalid batch request"
150
- }
151
- };
152
- }
153
- const outerMethod = interceptorOptions.request.method;
154
- if ((outerMethod === "GET" || outerMethod === "QUERY") && messages.some((message) => message.kind === "request" && message.json.method !== outerMethod)) {
155
- return {
156
- matched: true,
157
- response: { status: 400, headers: {}, body: `${outerMethod} batch requests only accept ${outerMethod} sub-requests` }
158
- };
159
- }
160
- const maxSize = await value(this.maxSize, interceptorOptions);
161
- if (messages.length > maxSize) {
162
- return {
163
- matched: true,
164
- response: { status: 413, headers: {}, body: "Batch request size exceeds the maximum allowed size" }
165
- };
166
- }
167
- const status = await value(this.successStatus, interceptorOptions);
168
- const headers = await value(this.headers, interceptorOptions);
169
- const bufferedMessages = [];
170
- let streamController;
171
- let keepAliveTimer;
172
- const ready = messages.map(() => promiseWithResolvers());
173
- const lifting = Promise.all(ready.map(({ promise }) => promise)).then((all) => {
174
- const heads = all.filter((head) => head !== void 0);
175
- return {
176
- batchHeaders: liftSubresponseHeaders(headers, heads.map((head) => head.headers)),
177
- streams: heads.some((head) => head.stream)
178
- };
179
- });
180
- const clearKeepAlive = () => {
181
- if (keepAliveTimer !== void 0) {
182
- clearInterval(keepAliveTimer);
183
- keepAliveTimer = void 0;
184
- }
185
- };
186
- const scheduleKeepAlive = () => {
187
- if (!this.keepAliveEnabled) {
188
- return;
189
- }
190
- clearKeepAlive();
191
- keepAliveTimer = setInterval(() => {
192
- try {
193
- streamController.enqueue(encodeLengthPrefix(0));
194
- } catch {
195
- clearKeepAlive();
196
- }
197
- }, this.keepAliveInterval);
198
- };
199
- const enqueueMessage = async (message) => {
200
- for (const chunk of await encodeFrame(message)) {
201
- streamController.enqueue(chunk);
202
- }
203
- scheduleKeepAlive();
204
- };
205
- const peer = new ServerPeer(async (message) => {
206
- if (streamController === void 0) {
207
- bufferedMessages.push(message);
208
- } else {
209
- await enqueueMessage(message);
210
- }
211
- });
212
- const handleIndividualRequest = async (request, markReady) => {
213
- let response;
214
- try {
215
- request = this.mapSubrequest(request, interceptorOptions);
216
- const result = await interceptorOptions.next({ ...interceptorOptions, request });
217
- response = result.matched ? result.response : { status: 404, headers: {}, body: "No procedure matched" };
218
- } catch (err) {
219
- response = internalError(err);
220
- }
221
- markReady({
222
- headers: response.headers,
223
- stream: response.body instanceof ReadableStream || isAsyncIteratorObject(response.body)
224
- });
225
- const { batchHeaders: batchHeaders2 } = await lifting;
226
- try {
227
- return this.mapSubresponse(response, { status, headers: batchHeaders2 }, interceptorOptions);
228
- } catch (err) {
229
- return internalError(err);
230
- }
231
- };
232
- const runSubrequests = async () => {
233
- const promise = Promise.all(messages.map((msg, i) => {
234
- const { resolve } = ready[i];
235
- return peer.message(msg, (request) => handleIndividualRequest(request, resolve)).finally(() => resolve(void 0));
236
- }));
237
- const signal = interceptorOptions.request.signal;
238
- const closePeer = () => peer.close(signal?.reason);
239
- if (signal?.aborted) {
240
- closePeer();
241
- }
242
- signal?.addEventListener("abort", closePeer);
243
- try {
244
- await promise;
245
- } finally {
246
- signal?.removeEventListener("abort", closePeer);
247
- }
248
- };
249
- const subrequests = runSubrequests();
250
- const { batchHeaders, streams } = await lifting;
251
- if (!streams) {
252
- await subrequests;
253
- await peer.close();
254
- if (bufferedMessages.some((msg) => msg.binary !== void 0)) {
255
- const chunks = (await Promise.all(bufferedMessages.map(encodeFrame))).flat();
256
- return {
257
- matched: true,
258
- response: {
259
- status,
260
- headers: batchHeaders,
261
- body: new Blob(chunks, { type: BATCH_CONTENT_TYPE })
262
- }
263
- };
264
- }
265
- return {
266
- matched: true,
267
- response: { status, headers: batchHeaders, body: bufferedMessages }
268
- };
269
- }
270
- const body = new ReadableStream({
271
- start(controller) {
272
- streamController = controller;
273
- scheduleKeepAlive();
274
- },
275
- async cancel(reason) {
276
- clearKeepAlive();
277
- await peer.close(reason);
278
- }
279
- });
280
- const flushed = Promise.all(bufferedMessages.splice(0).map(enqueueMessage));
281
- Promise.all([flushed, subrequests]).then(async () => {
282
- clearKeepAlive();
283
- streamController.close();
284
- await peer.close();
285
- }).catch(async (error) => {
286
- clearKeepAlive();
287
- streamController.error(error);
288
- await peer.close(error);
289
- });
290
- return {
291
- matched: true,
292
- response: {
293
- status,
294
- headers: { ...batchHeaders, "content-type": BATCH_CONTENT_TYPE },
295
- body
296
- }
297
- };
298
- };
299
- return {
300
- ...options,
301
- routingInterceptors: [routingInterceptor, ...toArray(options.routingInterceptors)]
302
- };
303
- }
304
- }
305
-
306
- class CORSHandlerPlugin {
307
- options;
308
- name = "~cors";
309
- /**
310
- * - Do not create spans for CORS preflight requests.
311
- * - Run CORS interceptors before batch interceptors so headers are applied to
312
- * the actual response rather than sub-responses.
313
- */
314
- after = ["~tracing", "~batch"];
315
- constructor(options = {}) {
316
- const defaults = {
317
- origin: "*",
318
- allowMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH", "QUERY"]
319
- };
320
- this.options = {
321
- ...defaults,
322
- ...options
323
- };
324
- }
325
- init(options) {
326
- const corsHeadersInterceptor = async (interceptorOptions) => {
327
- const result = await interceptorOptions.next();
328
- if (!result.matched) {
329
- return result;
330
- }
331
- const resHeaders = { ...result.response.headers };
332
- const origin = flattenStandardHeader(interceptorOptions.request.headers.origin);
333
- const allowOrigin = await this.resolveOrigin(this.options.origin, origin, interceptorOptions);
334
- const timingAllowOrigin = await this.resolveOrigin(this.options.timingOrigin, origin, interceptorOptions);
335
- if (allowOrigin.value !== void 0) {
336
- resHeaders["access-control-allow-origin"] = allowOrigin.value;
337
- }
338
- if (timingAllowOrigin.value !== void 0) {
339
- resHeaders["timing-allow-origin"] = timingAllowOrigin.value;
340
- }
341
- if (allowOrigin.varies || timingAllowOrigin.varies) {
342
- const existingVary = flattenStandardHeader(resHeaders.vary);
343
- if (!existingVary?.split(",").some((v) => v.trim().toLowerCase() === "origin")) {
344
- resHeaders.vary = existingVary ? `${existingVary}, Origin` : "Origin";
345
- }
346
- }
347
- if (this.options.credentials) {
348
- resHeaders["access-control-allow-credentials"] = "true";
349
- }
350
- if (this.options.exposeHeaders?.length) {
351
- resHeaders["access-control-expose-headers"] = flattenStandardHeader(this.options.exposeHeaders);
352
- }
353
- return { ...result, response: { ...result.response, headers: resHeaders } };
354
- };
355
- const preflightInterceptor = async (interceptorOptions) => {
356
- if (interceptorOptions.request.method === "OPTIONS") {
357
- const resHeaders = {};
358
- if (this.options.maxAge !== void 0) {
359
- resHeaders["access-control-max-age"] = this.options.maxAge.toString();
360
- }
361
- if (this.options.allowMethods?.length) {
362
- resHeaders["access-control-allow-methods"] = flattenStandardHeader(this.options.allowMethods);
363
- }
364
- const allowHeaders = this.options.allowHeaders ?? interceptorOptions.request.headers["access-control-request-headers"];
365
- if (typeof allowHeaders === "string" || allowHeaders?.length) {
366
- resHeaders["access-control-allow-headers"] = flattenStandardHeader(allowHeaders);
367
- }
368
- return {
369
- matched: true,
370
- response: {
371
- status: 204,
372
- headers: resHeaders,
373
- body: void 0
374
- }
375
- };
376
- }
377
- return interceptorOptions.next();
378
- };
379
- return {
380
- ...options,
381
- routingInterceptors: [
382
- corsHeadersInterceptor,
383
- preflightInterceptor,
384
- ...toArray(options.routingInterceptors)
385
- ]
386
- };
387
- }
388
- async resolveOrigin(option, origin, interceptorOptions) {
389
- if (typeof option === "function") {
390
- if (origin === void 0) {
391
- return { value: void 0, varies: true };
392
- }
393
- const allowed2 = toArray(await option(origin, interceptorOptions));
394
- if (allowed2.includes("*")) {
395
- return { value: "*", varies: true };
396
- }
397
- return { value: allowed2.includes(origin) ? origin : void 0, varies: true };
398
- }
399
- const allowed = toArray(await option);
400
- if (allowed.includes("*")) {
401
- return { value: "*", varies: false };
402
- }
403
- if (origin !== void 0 && allowed.includes(origin)) {
404
- return { value: origin, varies: true };
405
- }
406
- return { value: void 0, varies: allowed.length > 0 };
407
- }
408
- }
409
-
410
- class GetMethodCsrfProtectionHandlerPlugin {
411
- name = "~get-method-csrf-protection";
412
- init(options) {
413
- const interceptor = (interceptorOptions) => {
414
- if (this.isAllowed(interceptorOptions)) {
415
- return interceptorOptions.next();
416
- }
417
- throw new ORPCError("FORBIDDEN", { message: "Request blocked by CSRF protection." });
418
- };
419
- return {
420
- ...options,
421
- interceptors: [interceptor, ...toArray(options.interceptors)]
422
- };
423
- }
424
- isAllowed({ request }) {
425
- if (request.method !== "GET") {
426
- return true;
427
- }
428
- const site = flattenStandardHeader(request.headers["sec-fetch-site"])?.toLowerCase();
429
- if (site === void 0 || site === "same-origin" || site === "same-site") {
430
- return true;
431
- }
432
- const mode = flattenStandardHeader(request.headers["sec-fetch-mode"])?.toLowerCase();
433
- const dest = flattenStandardHeader(request.headers["sec-fetch-dest"])?.toLowerCase();
434
- if (mode === void 0 || dest === void 0) {
435
- return false;
436
- }
437
- return mode !== "navigate" || dest !== "document";
438
- }
439
- }
440
-
441
- class MethodOverrideHandlerPlugin {
442
- name = "~method-override";
443
- /**
444
- * Should override batch sub-request methods, not the original batch request.
445
- */
446
- before = ["~batch"];
447
- param;
448
- methods;
449
- constructor(options = {}) {
450
- this.param = options.param ?? "method";
451
- this.methods = new Set((options.methods ?? ["PUT", "PATCH", "DELETE"]).map((method) => method.toUpperCase()));
452
- }
453
- init(options) {
454
- const routingInterceptor = async ({ next, ...interceptorOptions }) => {
455
- const { request } = interceptorOptions;
456
- if (request.method !== "POST") {
457
- return next();
458
- }
459
- const [pathname, search, hash] = parseStandardUrl(request.url);
460
- const params = new URLSearchParams(search);
461
- const raw = params.getAll(this.param).at(-1);
462
- if (raw === void 0) {
463
- return next();
464
- }
465
- const method = raw.toUpperCase();
466
- if (!this.methods.has(method)) {
467
- return next();
468
- }
469
- params.delete(this.param);
470
- const url = `${pathname}${params.size ? `?${params}` : ""}${hash ?? ""}`;
471
- return next({
472
- ...interceptorOptions,
473
- request: { ...request, method, url }
474
- });
475
- };
476
- return {
477
- ...options,
478
- routingInterceptors: [routingInterceptor, ...toArray(options.routingInterceptors)]
479
- };
480
- }
481
- }
482
-
483
- const MAX_CONTENT_ENCODINGS = 5;
484
- class RequestCompressionHandlerPlugin {
485
- name = "~request-compression";
486
- /**
487
- * Should decompress the original batch request body instead of sub-requests.
488
- */
489
- after = ["~batch"];
490
- init(options) {
491
- const routingInterceptor = async ({ next, ...interceptorOptions }) => {
492
- const encodings = parseContentEncodings(
493
- flattenStandardHeader(interceptorOptions.request.headers["content-encoding"])
494
- );
495
- if (encodings.length === 0 || !encodings.every(isSupportedEncoding)) {
496
- return next();
497
- }
498
- const decompressedHeaders = {
499
- ...interceptorOptions.request.headers,
500
- "content-length": void 0,
501
- "content-encoding": void 0
502
- };
503
- return next({
504
- ...interceptorOptions,
505
- request: {
506
- ...interceptorOptions.request,
507
- headers: decompressedHeaders,
508
- async resolveBody(hint) {
509
- const stream = await interceptorOptions.request.resolveBody("octet-stream");
510
- if (!(stream instanceof ReadableStream)) {
511
- return stream;
512
- }
513
- if (encodings.length > MAX_CONTENT_ENCODINGS) {
514
- throw new ORPCError("UNSUPPORTED_MEDIA_TYPE", { message: "Too many content encodings." });
515
- }
516
- let decompressedStream = stream;
517
- for (let i = encodings.length - 1; i >= 0; i--) {
518
- decompressedStream = decompressedStream.pipeThrough(
519
- new DecompressionStream(encodings[i])
520
- );
521
- }
522
- const response = new Response(decompressedStream, {
523
- headers: toFetchHeaders(decompressedHeaders)
524
- });
525
- return toStandardBody(response, { hint });
526
- }
527
- }
528
- });
529
- };
530
- return {
531
- ...options,
532
- routingInterceptors: [
533
- routingInterceptor,
534
- ...toArray(options.routingInterceptors)
535
- ]
536
- };
537
- }
538
- }
539
- const SUPPORTED_ENCODINGS = ["gzip", "deflate", "deflate-raw"];
540
- function isSupportedEncoding(encoding) {
541
- return SUPPORTED_ENCODINGS.includes(encoding);
542
- }
543
- function parseContentEncodings(header) {
544
- if (header === void 0) {
545
- return [];
546
- }
547
- return header.split(",").map((part) => part.trim().toLowerCase());
548
- }
549
-
550
- class RequestHeadersHandlerPlugin {
551
- name = "~request-headers";
552
- init(options) {
553
- return {
554
- ...options,
555
- interceptors: [
556
- (interceptorOptions) => {
557
- const reqHeaders = interceptorOptions.context.reqHeaders ?? toFetchHeaders(interceptorOptions.request.headers);
558
- return interceptorOptions.next({
559
- ...interceptorOptions,
560
- context: {
561
- ...interceptorOptions.context,
562
- reqHeaders
563
- }
564
- });
565
- },
566
- ...toArray(options.interceptors)
567
- ]
568
- };
569
- }
570
- }
571
-
572
- const AVG_BYTES_PER_CHAR = 1.2;
573
- class ResponseCompressionHandlerPlugin {
574
- name = "~response-compression";
575
- /**
576
- * Compression should be done after batching, to compress the final response.
577
- * Compression should also be done after response headers are set, to access final headers like Content-Type and Cache-Control.
578
- */
579
- after = ["~batch", "~response-headers"];
580
- encodings;
581
- threshold;
582
- isCompressibleContentType;
583
- constructor(options = {}) {
584
- this.encodings = options.encodings ?? ["gzip", "deflate"];
585
- this.threshold = options.threshold ?? 1024;
586
- this.isCompressibleContentType = options.isCompressibleContentType ?? isCompressibleContentType;
587
- }
588
- init(options) {
589
- const routingInterceptor = async ({ next, ...interceptorOptions }) => {
590
- const result = await next();
591
- if (!result.matched) {
592
- return result;
593
- }
594
- const response = result.response;
595
- const encoding = negotiateResponseCompressionEncoding(response, interceptorOptions.request.headers, this.encodings);
596
- if (encoding === void 0) {
597
- return result;
598
- }
599
- const body = response.body;
600
- const headers = response.headers;
601
- if (body instanceof ReadableStream) {
602
- const contentLength = Number(flattenStandardHeader(headers["content-length"]));
603
- if ((!Number.isFinite(contentLength) || contentLength >= this.threshold) && this.isCompressibleContentType(flattenStandardHeader(headers["content-type"]), interceptorOptions)) {
604
- return {
605
- ...result,
606
- response: {
607
- ...response,
608
- body: body.pipeThrough(new CompressionStream(encoding)),
609
- headers: {
610
- ...toCompressedHeaders(headers, encoding),
611
- "standard-server": "octet-stream"
612
- }
613
- }
614
- };
615
- }
616
- } else if (body instanceof Blob) {
617
- const contentType = headers["content-type"] ?? body.type;
618
- if ((!Number.isFinite(body.size) || body.size >= this.threshold) && this.isCompressibleContentType(flattenStandardHeader(contentType), interceptorOptions)) {
619
- const contentDisposition = headers["content-disposition"] ?? generateContentDisposition(
620
- body instanceof File ? body.name : "blob"
621
- );
622
- return {
623
- ...result,
624
- response: {
625
- ...response,
626
- body: body.stream().pipeThrough(new CompressionStream(encoding)),
627
- headers: {
628
- ...toCompressedHeaders(headers, encoding),
629
- "standard-server": "file",
630
- "content-type": contentType,
631
- "content-disposition": contentDisposition
632
- }
633
- }
634
- };
635
- }
636
- } else if (body instanceof FormData) {
637
- const PART_OVERHEAD = 64;
638
- let contentLength = 0;
639
- for (const [key, value] of body) {
640
- contentLength += PART_OVERHEAD + key.length;
641
- if (value instanceof Blob) {
642
- if (!Number.isFinite(value.size)) {
643
- if (!this.isCompressibleContentType(value.type, interceptorOptions)) {
644
- contentLength = -Infinity;
645
- break;
646
- }
647
- contentLength = Infinity;
648
- } else {
649
- contentLength += this.isCompressibleContentType(value.type, interceptorOptions) ? value.size : -value.size;
650
- }
651
- } else {
652
- contentLength += value.length * AVG_BYTES_PER_CHAR;
653
- }
654
- }
655
- if (contentLength >= this.threshold) {
656
- const res = new Response(body);
657
- const compressedStream = res.body.pipeThrough(new CompressionStream(encoding));
658
- return {
659
- ...result,
660
- response: {
661
- ...response,
662
- body: compressedStream,
663
- headers: {
664
- ...toCompressedHeaders(headers, encoding),
665
- "standard-server": [],
666
- "content-type": res.headers.get("content-type")
667
- }
668
- }
669
- };
670
- }
671
- } else if (body instanceof URLSearchParams) {
672
- const string = body.toString();
673
- if (string.length * AVG_BYTES_PER_CHAR >= this.threshold) {
674
- return {
675
- ...result,
676
- response: {
677
- ...response,
678
- body: new Blob([string]).stream().pipeThrough(new CompressionStream(encoding)),
679
- headers: {
680
- ...toCompressedHeaders(headers, encoding),
681
- "standard-server": [],
682
- "content-type": "application/x-www-form-urlencoded"
683
- }
684
- }
685
- };
686
- }
687
- } else if (body !== void 0 && !isAsyncIteratorObject(body)) {
688
- const string = stringifyJSON(body);
689
- if (string.length * AVG_BYTES_PER_CHAR >= this.threshold) {
690
- return {
691
- ...result,
692
- response: {
693
- ...response,
694
- body: new Blob([string]).stream().pipeThrough(new CompressionStream(encoding)),
695
- headers: {
696
- ...toCompressedHeaders(headers, encoding),
697
- "standard-server": [],
698
- "content-type": "application/json"
699
- }
700
- }
701
- };
702
- }
703
- }
704
- return result;
705
- };
706
- return {
707
- ...options,
708
- routingInterceptors: [
709
- routingInterceptor,
710
- ...toArray(options.routingInterceptors)
711
- ]
712
- };
713
- }
714
- }
715
- function negotiateResponseCompressionEncoding(response, requestHeaders, encodings) {
716
- if (flattenStandardHeader(response.headers["content-encoding"]) !== void 0) {
717
- return void 0;
718
- }
719
- if (response.status === 206 || flattenStandardHeader(response.headers["content-range"]) !== void 0) {
720
- return void 0;
721
- }
722
- if (isNoTransformCacheControl(flattenStandardHeader(response.headers["cache-control"]))) {
723
- return void 0;
724
- }
725
- const acceptEncodings = parseAcceptEncodingQualities(flattenStandardHeader(requestHeaders["accept-encoding"]));
726
- return encodings.find((encoding) => isAcceptableEncoding(acceptEncodings, encoding));
727
- }
728
- function toCompressedHeaders(headers, encoding) {
729
- return {
730
- ...headers,
731
- "content-length": [],
732
- "content-encoding": encoding,
733
- "vary": varyByAcceptEncoding(flattenStandardHeader(headers.vary)),
734
- /**
735
- * A strong tag shared with the identity bytes would let `If-Range` splice them after compressed ones.
736
- * A weak tag still revalidates through `If-None-Match`, and the compressed body serves no ranges.
737
- *
738
- * @see https://www.rfc-editor.org/rfc/rfc9110.html#name-etag
739
- */
740
- "etag": weakenEtag(flattenStandardHeader(headers.etag)),
741
- "accept-ranges": []
742
- };
743
- }
744
- function weakenEtag(etag) {
745
- return etag === void 0 || etag.startsWith("W/") ? etag : `W/${etag}`;
746
- }
747
-
748
- class ResponseHeadersHandlerPlugin {
749
- name = "~response-headers";
750
- /**
751
- * Interceptors should run after batch interceptors so headers are applied to each sub-response.
752
- */
753
- before = ["~batch"];
754
- init(options) {
755
- return {
756
- ...options,
757
- routingInterceptors: [
758
- async (interceptorOptions) => {
759
- const resHeaders = new Headers(interceptorOptions.context.resHeaders);
760
- const result = await interceptorOptions.next({
761
- ...interceptorOptions,
762
- context: {
763
- ...interceptorOptions.context,
764
- resHeaders
765
- }
766
- });
767
- if (!result.response) {
768
- return result;
769
- }
770
- return {
771
- ...result,
772
- response: {
773
- ...result.response,
774
- headers: mergeStandardHeaders(result.response.headers, toStandardHeaders(resHeaders))
775
- }
776
- };
777
- },
778
- ...toArray(options.routingInterceptors)
779
- ]
780
- };
781
- }
782
- }
783
-
784
- class RethrowHandlerPlugin {
785
- name = "~rethrow";
786
- filter;
787
- CONTEXT_SYMBOL = Symbol("ORPC_RETHROW_HANDLER_PLUGIN_CONTEXT");
788
- constructor(options) {
789
- this.filter = options.filter;
790
- }
791
- init(options) {
792
- const routingInterceptor = async (options2) => {
793
- const pluginContext = {};
794
- const result = await options2.next({
795
- ...options2,
796
- context: {
797
- ...options2.context,
798
- [this.CONTEXT_SYMBOL]: pluginContext
799
- }
800
- });
801
- if (pluginContext.error) {
802
- throw pluginContext.error.value;
803
- }
804
- return result;
805
- };
806
- const interceptor = async (options2) => {
807
- const pluginContext = options2.context[this.CONTEXT_SYMBOL];
808
- if (!pluginContext) {
809
- throw new TypeError("[RethrowHandlerPlugin] Rethrow handler context has been corrupted or modified by another plugin or interceptor");
810
- }
811
- try {
812
- return await options2.next();
813
- } catch (error) {
814
- if (this.filter(error, options2)) {
815
- pluginContext.error = { value: error };
816
- }
817
- throw error;
818
- }
819
- };
820
- return {
821
- ...options,
822
- routingInterceptors: [
823
- ...toArray(options.routingInterceptors),
824
- routingInterceptor
825
- // rethrow as early as possible and keep it close to `interceptor` to avoid a corrupted or modified context
826
- ],
827
- interceptors: [
828
- interceptor,
829
- // catch as many errors as possible
830
- ...toArray(options.interceptors)
831
- ]
832
- };
833
- }
834
- }
835
-
836
- class SignalSyncHandlerPlugin {
837
- name = "~signal-sync";
838
- init(options) {
839
- const clientInterceptor = async (interceptorOptions) => {
840
- const controller = new AbortController();
841
- const signal = anyAbortSignal([interceptorOptions.signal, controller.signal]);
842
- const cancel = () => controller.abort(new AbortError("The response body was cancelled"));
843
- const output = await interceptorOptions.next({ ...interceptorOptions, signal });
844
- if (isAsyncIteratorObject(output)) {
845
- return override(output, syncAsyncIterator(output, signal, cancel));
846
- }
847
- if (output instanceof ReadableStream) {
848
- return override(output, syncReadableStream(output, signal, cancel));
849
- }
850
- return output;
851
- };
852
- return { ...options, clientInterceptors: [clientInterceptor, ...toArray(options.clientInterceptors)] };
853
- }
854
- }
855
- function listenAbort(signal, onAbort) {
856
- if (signal.aborted) {
857
- onAbort();
858
- } else {
859
- signal.addEventListener("abort", onAbort, { once: true });
860
- }
861
- return () => signal.removeEventListener("abort", onAbort);
862
- }
863
- function syncAsyncIterator(iterator, signal, cancel) {
864
- let cancelling;
865
- const unlisten = listenAbort(signal, () => {
866
- cancelling = (async () => iterator.return?.())();
867
- cancelling.catch(() => {
868
- });
869
- });
870
- return new AsyncIteratorClass(
871
- () => runWithSignal(signal, () => iterator.next()),
872
- async ({ kind }) => {
873
- if (kind === "cancelled") {
874
- cancel();
875
- await cancelling;
876
- }
877
- unlisten();
878
- }
879
- );
880
- }
881
- function syncReadableStream(stream, signal, cancel) {
882
- const reader = stream.getReader();
883
- let cancelling;
884
- let unlisten;
885
- return new ReadableStream({
886
- start(controller) {
887
- unlisten = listenAbort(signal, () => {
888
- controller.error(signal.reason);
889
- cancelling = reader.cancel(signal.reason);
890
- cancelling.catch(() => {
891
- });
892
- });
893
- },
894
- async pull(controller) {
895
- try {
896
- const { done, value } = await reader.read();
897
- if (signal.aborted) {
898
- return;
899
- }
900
- if (done) {
901
- unlisten();
902
- controller.close();
903
- } else {
904
- controller.enqueue(value);
905
- }
906
- } catch (error) {
907
- unlisten();
908
- throw error;
909
- }
910
- },
911
- async cancel() {
912
- cancel();
913
- await cancelling;
914
- }
915
- }, { highWaterMark: 0 });
916
- }
917
-
918
- class TimeoutHandlerPlugin {
919
- timeout;
920
- streamingTimeout;
921
- name = "~timeout";
922
- constructor(options) {
923
- this.timeout = options.timeout;
924
- this.streamingTimeout = options.streamingTimeout;
925
- }
926
- init(options) {
927
- const interceptor = async (interceptorOptions) => {
928
- const timeoutMs = value(this.timeout, interceptorOptions);
929
- const streamingTimeoutMs = value(this.streamingTimeout, interceptorOptions);
930
- const hasTimeout = timeoutMs !== null && timeoutMs !== void 0;
931
- const hasStreamingTimeout = streamingTimeoutMs !== null && streamingTimeoutMs !== void 0;
932
- if (!hasTimeout && !hasStreamingTimeout) {
933
- return interceptorOptions.next();
934
- }
935
- const controller = new AbortController();
936
- const abortWithTimeout = (ms) => {
937
- controller.abort(new AbortError(`Request timed out after ${ms}ms`));
938
- };
939
- const timeoutId = hasTimeout ? setTimeout(abortWithTimeout, timeoutMs, timeoutMs) : void 0;
940
- const signal = anyAbortSignal([interceptorOptions.request.signal, controller.signal]);
941
- try {
942
- const response = await interceptorOptions.next({
943
- ...interceptorOptions,
944
- request: { ...interceptorOptions.request, signal }
945
- });
946
- if (hasStreamingTimeout) {
947
- const body = response.body;
948
- const isIterator = isAsyncIteratorObject(body);
949
- if (isIterator || body instanceof ReadableStream) {
950
- const streamingTimeoutId = setTimeout(abortWithTimeout, streamingTimeoutMs, streamingTimeoutMs);
951
- const onFinish = () => clearTimeout(streamingTimeoutId);
952
- return {
953
- ...response,
954
- body: isIterator ? override(body, wrapAsyncIterator(body, { onFinish })) : override(body, wrapReadableStream(body, { onFinish }))
955
- };
956
- }
957
- }
958
- return response;
959
- } finally {
960
- clearTimeout(timeoutId);
961
- }
962
- };
963
- return { ...options, interceptors: [interceptor, ...toArray(options.interceptors)] };
964
- }
965
- }
966
-
967
- export { BATCH_CONTENT_TYPE, BatchHandlerPlugin, CORSHandlerPlugin, CORSHandlerPlugin as CORSPlugin, GetMethodCsrfProtectionHandlerPlugin, MethodOverrideHandlerPlugin, RequestCompressionHandlerPlugin, RequestHeadersHandlerPlugin, RequestHeadersHandlerPlugin as RequestHeadersPlugin, ResponseCompressionHandlerPlugin, ResponseHeadersHandlerPlugin, ResponseHeadersHandlerPlugin as ResponseHeadersPlugin, RethrowHandlerPlugin, SignalSyncHandlerPlugin, TimeoutHandlerPlugin, negotiateResponseCompressionEncoding, toCompressedHeaders };
1
+ export * from "./batch.mjs";
2
+ export * from "./cors.mjs";
3
+ export {
4
+
5
+ CORSHandlerPlugin as CORSPlugin } from "./cors.mjs";
6
+ export * from "./get-method-csrf-protection.mjs";
7
+ export * from "./head-method.mjs";
8
+ export * from "./method-override.mjs";
9
+ export * from "./request-compression.mjs";
10
+ export * from "./request-headers.mjs";
11
+ export {
12
+
13
+ RequestHeadersHandlerPlugin as RequestHeadersPlugin } from "./request-headers.mjs";
14
+ export * from "./request-limit.mjs";
15
+ export * from "./response-compression.mjs";
16
+ export * from "./response-headers.mjs";
17
+ export {
18
+
19
+ ResponseHeadersHandlerPlugin as ResponseHeadersPlugin } from "./response-headers.mjs";
20
+ export * from "./rethrow.mjs";
21
+ export * from "./signal-sync.mjs";
22
+ export * from "./timeout.mjs";
23
+ export { BATCH_HEADER, DEFAULT_BATCH_MAX_SIZE } from "@orpc/client/plugins";