@orpc/server 2.0.0-beta.43 → 2.0.0-beta.45

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