@orpc/server 2.0.0-beta.47 → 2.0.0-beta.49

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 (178) hide show
  1. package/README.md +6 -7
  2. package/dist/adapters/aws-lambda/handler.d.ts +25 -0
  3. package/dist/adapters/aws-lambda/handler.js +24 -0
  4. package/dist/adapters/aws-lambda/index.d.ts +10 -45
  5. package/dist/adapters/aws-lambda/index.js +2 -0
  6. package/dist/adapters/aws-lambda/rpc-handler.d.ts +19 -0
  7. package/dist/adapters/aws-lambda/rpc-handler.js +18 -0
  8. package/dist/adapters/crossws/handler.d.ts +45 -0
  9. package/dist/adapters/crossws/handler.js +60 -0
  10. package/dist/adapters/crossws/index.d.ts +2 -63
  11. package/dist/adapters/crossws/index.js +2 -0
  12. package/dist/adapters/crossws/rpc-handler.d.ts +16 -0
  13. package/dist/adapters/crossws/rpc-handler.js +15 -0
  14. package/dist/adapters/fastify/handler.d.ts +21 -0
  15. package/dist/adapters/fastify/handler.js +20 -0
  16. package/dist/adapters/fastify/index.d.ts +2 -42
  17. package/dist/adapters/fastify/index.js +2 -0
  18. package/dist/adapters/fastify/rpc-handler.d.ts +15 -0
  19. package/dist/adapters/fastify/rpc-handler.js +14 -0
  20. package/dist/adapters/fetch/handler.d.ts +23 -0
  21. package/dist/adapters/fetch/handler.js +22 -0
  22. package/dist/adapters/fetch/index.d.ts +5 -45
  23. package/dist/adapters/fetch/index.js +7 -0
  24. package/dist/adapters/fetch/rpc-handler.d.ts +17 -0
  25. package/dist/adapters/fetch/rpc-handler.js +16 -0
  26. package/dist/adapters/message-port/{index.d.mts → handler.d.ts} +17 -30
  27. package/dist/adapters/message-port/handler.js +86 -0
  28. package/dist/adapters/message-port/index.d.ts +2 -77
  29. package/dist/adapters/message-port/index.js +2 -0
  30. package/dist/adapters/message-port/rpc-handler.d.ts +16 -0
  31. package/dist/adapters/message-port/rpc-handler.js +15 -0
  32. package/dist/adapters/node/handler.d.ts +21 -0
  33. package/dist/adapters/node/handler.js +20 -0
  34. package/dist/adapters/node/index.d.ts +5 -42
  35. package/dist/adapters/node/index.js +7 -0
  36. package/dist/adapters/node/rpc-handler.d.ts +16 -0
  37. package/dist/adapters/node/rpc-handler.js +15 -0
  38. package/dist/adapters/standard/codec.d.ts +16 -0
  39. package/dist/adapters/standard/codec.js +1 -0
  40. package/dist/adapters/standard/handler.d.ts +68 -0
  41. package/dist/adapters/standard/handler.js +205 -0
  42. package/dist/adapters/standard/index.d.ts +6 -95
  43. package/dist/adapters/standard/index.js +5 -0
  44. package/dist/adapters/standard/plugin.d.ts +32 -0
  45. package/dist/adapters/standard/plugin.js +16 -0
  46. package/dist/adapters/standard/rpc-handler-codec.d.ts +46 -0
  47. package/dist/adapters/standard/rpc-handler-codec.js +51 -0
  48. package/dist/adapters/standard/rpc-matcher.d.ts +53 -0
  49. package/dist/adapters/standard/rpc-matcher.js +152 -0
  50. package/dist/adapters/standard/utils.d.ts +10 -0
  51. package/dist/adapters/standard/utils.js +6 -0
  52. package/dist/adapters/standard-peer/index.d.ts +1 -11
  53. package/dist/adapters/standard-peer/index.js +1 -0
  54. package/dist/adapters/standard-peer/utils.d.ts +4 -0
  55. package/dist/adapters/standard-peer/utils.js +7 -0
  56. package/dist/adapters/websocket/{index.d.mts → handler.d.ts} +11 -29
  57. package/dist/adapters/websocket/handler.js +85 -0
  58. package/dist/adapters/websocket/index.d.ts +2 -77
  59. package/dist/adapters/websocket/index.js +2 -0
  60. package/dist/adapters/websocket/rpc-handler.d.ts +16 -0
  61. package/dist/adapters/websocket/rpc-handler.js +15 -0
  62. package/dist/builder-variants.d.ts +226 -0
  63. package/dist/builder-variants.js +1 -0
  64. package/dist/builder.d.ts +127 -0
  65. package/dist/builder.js +226 -0
  66. package/dist/constants.d.ts +2 -0
  67. package/dist/constants.js +2 -0
  68. package/dist/context.d.ts +6 -0
  69. package/dist/context.js +1 -0
  70. package/dist/extensions/callable.d.ts +6 -5
  71. package/dist/extensions/callable.js +7 -0
  72. package/dist/helpers/base64url.d.ts +31 -0
  73. package/dist/helpers/base64url.js +53 -0
  74. package/dist/helpers/cookie.d.ts +51 -0
  75. package/dist/helpers/cookie.js +69 -0
  76. package/dist/helpers/encryption.d.ts +28 -0
  77. package/dist/helpers/encryption.js +80 -0
  78. package/dist/helpers/index.d.ts +4 -167
  79. package/dist/helpers/index.js +4 -0
  80. package/dist/helpers/signing.d.ts +50 -0
  81. package/dist/helpers/signing.js +88 -0
  82. package/dist/implementer-procedure.d.ts +16 -0
  83. package/dist/implementer-procedure.js +44 -0
  84. package/dist/implementer-router.d.ts +40 -0
  85. package/dist/implementer-router.js +117 -0
  86. package/dist/implementer.d.ts +16 -0
  87. package/dist/implementer.js +37 -0
  88. package/dist/index.d.ts +45 -460
  89. package/dist/index.js +48 -0
  90. package/dist/lazy.d.ts +20 -0
  91. package/dist/lazy.js +31 -0
  92. package/dist/middleware-decorated.d.ts +10 -0
  93. package/dist/middleware-decorated.js +71 -0
  94. package/dist/middleware.d.ts +54 -0
  95. package/dist/middleware.js +1 -0
  96. package/dist/plugins/batch.d.ts +102 -0
  97. package/dist/plugins/batch.js +405 -0
  98. package/dist/plugins/cors.d.ts +73 -0
  99. package/dist/plugins/cors.js +112 -0
  100. package/dist/plugins/get-method-csrf-protection.d.ts +23 -0
  101. package/dist/plugins/get-method-csrf-protection.js +64 -0
  102. package/dist/plugins/head-method.d.ts +23 -0
  103. package/dist/plugins/head-method.js +104 -0
  104. package/dist/plugins/index.d.ts +29 -422
  105. package/dist/plugins/index.js +29 -0
  106. package/dist/plugins/method-override.d.ts +38 -0
  107. package/dist/plugins/method-override.js +50 -0
  108. package/dist/plugins/request-compression.d.ts +16 -0
  109. package/dist/plugins/request-compression.js +82 -0
  110. package/dist/plugins/request-headers.d.ts +22 -0
  111. package/dist/plugins/request-headers.js +30 -0
  112. package/dist/{shared/server.CgBF3Vj1.d.ts → plugins/request-limit.d.ts} +4 -8
  113. package/dist/plugins/request-limit.js +68 -0
  114. package/dist/plugins/response-compression.d.ts +59 -0
  115. package/dist/plugins/response-compression.js +209 -0
  116. package/dist/plugins/response-headers.d.ts +30 -0
  117. package/dist/plugins/response-headers.js +47 -0
  118. package/dist/plugins/rethrow.d.ts +33 -0
  119. package/dist/plugins/rethrow.js +59 -0
  120. package/dist/plugins/signal-sync.d.ts +13 -0
  121. package/dist/plugins/signal-sync.js +91 -0
  122. package/dist/plugins/timeout.d.ts +37 -0
  123. package/dist/plugins/timeout.js +59 -0
  124. package/dist/procedure-client.d.ts +19 -0
  125. package/dist/procedure-client.js +175 -0
  126. package/dist/procedure-decorated.d.ts +10 -0
  127. package/dist/procedure-decorated.js +44 -0
  128. package/dist/procedure-utils.d.ts +41 -0
  129. package/dist/procedure-utils.js +49 -0
  130. package/dist/procedure.d.ts +72 -0
  131. package/dist/procedure.js +24 -0
  132. package/dist/router-client.d.ts +23 -0
  133. package/dist/router-client.js +43 -0
  134. package/dist/router-hidden.d.ts +5 -0
  135. package/dist/router-hidden.js +14 -0
  136. package/dist/router-utils.d.ts +48 -0
  137. package/dist/router-utils.js +202 -0
  138. package/dist/{shared/server.Dz7j9BEh.d.ts → router.d.ts} +14 -15
  139. package/dist/router.js +1 -0
  140. package/package.json +84 -86
  141. package/dist/adapters/aws-lambda/index.d.mts +0 -51
  142. package/dist/adapters/aws-lambda/index.mjs +0 -39
  143. package/dist/adapters/crossws/index.d.mts +0 -63
  144. package/dist/adapters/crossws/index.mjs +0 -68
  145. package/dist/adapters/fastify/index.d.mts +0 -42
  146. package/dist/adapters/fastify/index.mjs +0 -39
  147. package/dist/adapters/fetch/index.d.mts +0 -47
  148. package/dist/adapters/fetch/index.mjs +0 -42
  149. package/dist/adapters/message-port/index.mjs +0 -87
  150. package/dist/adapters/node/index.d.mts +0 -44
  151. package/dist/adapters/node/index.mjs +0 -41
  152. package/dist/adapters/standard/index.d.mts +0 -95
  153. package/dist/adapters/standard/index.mjs +0 -8
  154. package/dist/adapters/standard-peer/index.d.mts +0 -11
  155. package/dist/adapters/standard-peer/index.mjs +0 -10
  156. package/dist/adapters/websocket/index.mjs +0 -87
  157. package/dist/extensions/callable.d.mts +0 -10
  158. package/dist/extensions/callable.mjs +0 -11
  159. package/dist/helpers/index.d.mts +0 -167
  160. package/dist/helpers/index.mjs +0 -200
  161. package/dist/index.d.mts +0 -476
  162. package/dist/index.mjs +0 -463
  163. package/dist/plugins/index.d.mts +0 -432
  164. package/dist/plugins/index.mjs +0 -965
  165. package/dist/shared/server.1ExWFMze.mjs +0 -69
  166. package/dist/shared/server.9B1jEm5a.d.mts +0 -105
  167. package/dist/shared/server.BB464y-6.mjs +0 -207
  168. package/dist/shared/server.BC5BIu02.d.mts +0 -73
  169. package/dist/shared/server.BSYGskId.d.ts +0 -14
  170. package/dist/shared/server.BvG3boBj.d.mts +0 -14
  171. package/dist/shared/server.CTV_mC2P.d.mts +0 -34
  172. package/dist/shared/server.CYbIpHbH.mjs +0 -239
  173. package/dist/shared/server.Chq5FpXf.mjs +0 -353
  174. package/dist/shared/server.DFPxP2v3.d.mts +0 -166
  175. package/dist/shared/server.DFPxP2v3.d.ts +0 -166
  176. package/dist/shared/server.DOg7ke6y.d.ts +0 -105
  177. package/dist/shared/server.W91HSRkE.mjs +0 -9
  178. package/dist/shared/server.sBQSgqK3.mjs +0 -41
@@ -0,0 +1,33 @@
1
+ import type { ThrowableError } from '@orpc/shared';
2
+ import type { StandardHandlerInterceptorOptions, StandardHandlerOptions, StandardHandlerPlugin } from '../adapters/standard/index.ts';
3
+ import type { Context } from '../context.ts';
4
+ export interface RethrowHandlerPluginOptions<T extends Context> {
5
+ /**
6
+ * Decide which errors should be rethrown.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const rethrowPlugin = new RethrowHandlerPlugin({
11
+ * filter: (error) => {
12
+ * // Rethrow all non-ORPCError errors
13
+ * return !(error instanceof ORPCError)
14
+ * }
15
+ * })
16
+ * ```
17
+ */
18
+ filter: (error: ThrowableError, options: StandardHandlerInterceptorOptions<T>) => boolean;
19
+ }
20
+ /**
21
+ * The plugin can bypass oRPC's built-in error handling
22
+ * and rethrow matching errors directly to your framework's error handling mechanism
23
+ * (e.g., NestJS exception filters, Express error middleware).
24
+ *
25
+ * @see {@link https://orpc.dev/docs/plugins/rethrow | Rethrow Handler Plugin}
26
+ */
27
+ export declare class RethrowHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
28
+ name: string;
29
+ private readonly filter;
30
+ private readonly CONTEXT_SYMBOL;
31
+ constructor(options: RethrowHandlerPluginOptions<T>);
32
+ init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
33
+ }
@@ -0,0 +1,59 @@
1
+ import { toArray } from '@orpc/shared';
2
+ /**
3
+ * The plugin can bypass oRPC's built-in error handling
4
+ * and rethrow matching errors directly to your framework's error handling mechanism
5
+ * (e.g., NestJS exception filters, Express error middleware).
6
+ *
7
+ * @see {@link https://orpc.dev/docs/plugins/rethrow | Rethrow Handler Plugin}
8
+ */
9
+ export class RethrowHandlerPlugin {
10
+ name = '~rethrow';
11
+ filter;
12
+ CONTEXT_SYMBOL = Symbol('ORPC_RETHROW_HANDLER_PLUGIN_CONTEXT');
13
+ constructor(options) {
14
+ this.filter = options.filter;
15
+ }
16
+ init(options) {
17
+ const routingInterceptor = async (options) => {
18
+ const pluginContext = {};
19
+ const result = await options.next({
20
+ ...options,
21
+ context: {
22
+ ...options.context,
23
+ [this.CONTEXT_SYMBOL]: pluginContext,
24
+ },
25
+ });
26
+ if (pluginContext.error) {
27
+ throw pluginContext.error.value;
28
+ }
29
+ return result;
30
+ };
31
+ const interceptor = async (options) => {
32
+ const pluginContext = options.context[this.CONTEXT_SYMBOL];
33
+ if (!pluginContext) {
34
+ throw new TypeError('[RethrowHandlerPlugin] Rethrow handler context has been corrupted or modified by another plugin or interceptor');
35
+ }
36
+ try {
37
+ // await is important here to catch both sync and async errors
38
+ return await options.next();
39
+ }
40
+ catch (error) {
41
+ if (this.filter(error, options)) {
42
+ pluginContext.error = { value: error };
43
+ }
44
+ throw error;
45
+ }
46
+ };
47
+ return {
48
+ ...options,
49
+ routingInterceptors: [
50
+ ...toArray(options.routingInterceptors),
51
+ routingInterceptor, // rethrow as early as possible and keep it close to `interceptor` to avoid a corrupted or modified context
52
+ ],
53
+ interceptors: [
54
+ interceptor, // catch as many errors as possible
55
+ ...toArray(options.interceptors),
56
+ ],
57
+ };
58
+ }
59
+ }
@@ -0,0 +1,13 @@
1
+ import type { StandardHandlerOptions, StandardHandlerPlugin } from '../adapters/standard/index.ts';
2
+ import type { Context } from '../context.ts';
3
+ /**
4
+ * Keeps the request signal and a streaming output (async iterator object or readable stream) in sync:
5
+ * cancelling the output aborts the signal with an `AbortError`,
6
+ * and aborting the signal ends the output with the abort reason and cancels it.
7
+ *
8
+ * @see {@link https://orpc.dev/docs/plugins/signal-sync | Signal Sync Plugin}
9
+ */
10
+ export declare class SignalSyncHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
11
+ name: string;
12
+ init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
13
+ }
@@ -0,0 +1,91 @@
1
+ import { AbortError, anyAbortSignal, AsyncIteratorClass, isAsyncIteratorObject, override, runWithSignal, toArray, } from '@orpc/shared';
2
+ /**
3
+ * Keeps the request signal and a streaming output (async iterator object or readable stream) in sync:
4
+ * cancelling the output aborts the signal with an `AbortError`,
5
+ * and aborting the signal ends the output with the abort reason and cancels it.
6
+ *
7
+ * @see {@link https://orpc.dev/docs/plugins/signal-sync | Signal Sync Plugin}
8
+ */
9
+ export class SignalSyncHandlerPlugin {
10
+ name = '~signal-sync';
11
+ init(options) {
12
+ // oxlint-disable-next-line unicorn/consistent-function-scoping
13
+ const clientInterceptor = async (interceptorOptions) => {
14
+ const controller = new AbortController();
15
+ const signal = anyAbortSignal([interceptorOptions.signal, controller.signal]);
16
+ const cancel = () => controller.abort(new AbortError('The response body was cancelled'));
17
+ const output = await interceptorOptions.next({ ...interceptorOptions, signal });
18
+ if (isAsyncIteratorObject(output)) {
19
+ return override(output, syncAsyncIterator(output, signal, cancel));
20
+ }
21
+ if (output instanceof ReadableStream) {
22
+ return override(output, syncReadableStream(output, signal, cancel));
23
+ }
24
+ return output;
25
+ };
26
+ return {
27
+ ...options,
28
+ clientInterceptors: [clientInterceptor, ...toArray(options.clientInterceptors)],
29
+ };
30
+ }
31
+ }
32
+ function listenAbort(signal, onAbort) {
33
+ if (signal.aborted) {
34
+ onAbort();
35
+ }
36
+ else {
37
+ signal.addEventListener('abort', onAbort, { once: true });
38
+ }
39
+ return () => signal.removeEventListener('abort', onAbort);
40
+ }
41
+ function syncAsyncIterator(iterator, signal, cancel) {
42
+ let cancelling;
43
+ const unlisten = listenAbort(signal, () => {
44
+ cancelling = (async () => iterator.return?.())();
45
+ cancelling.catch(() => { }); // only awaited when the consumer cancels
46
+ });
47
+ return new AsyncIteratorClass(() => runWithSignal(signal, () => iterator.next()), async ({ kind }) => {
48
+ if (kind === 'cancelled') {
49
+ cancel();
50
+ await cancelling;
51
+ }
52
+ unlisten();
53
+ });
54
+ }
55
+ function syncReadableStream(stream, signal, cancel) {
56
+ const reader = stream.getReader();
57
+ let cancelling;
58
+ let unlisten;
59
+ return new ReadableStream({
60
+ start(controller) {
61
+ unlisten = listenAbort(signal, () => {
62
+ controller.error(signal.reason);
63
+ cancelling = reader.cancel(signal.reason);
64
+ cancelling.catch(() => { }); // only awaited when the consumer cancels
65
+ });
66
+ },
67
+ async pull(controller) {
68
+ try {
69
+ const { done, value } = await reader.read();
70
+ if (signal.aborted) {
71
+ return;
72
+ }
73
+ if (done) {
74
+ unlisten();
75
+ controller.close();
76
+ }
77
+ else {
78
+ controller.enqueue(value);
79
+ }
80
+ }
81
+ catch (error) {
82
+ unlisten();
83
+ throw error;
84
+ }
85
+ },
86
+ async cancel() {
87
+ cancel();
88
+ await cancelling;
89
+ },
90
+ }, { highWaterMark: 0 });
91
+ }
@@ -0,0 +1,37 @@
1
+ import type { Value } from '@orpc/shared';
2
+ import type { StandardHandlerInterceptorOptions, StandardHandlerOptions, StandardHandlerPlugin } from '../adapters/standard/index.ts';
3
+ import type { Context } from '../context.ts';
4
+ export interface TimeoutHandlerPluginOptions<T extends Context> {
5
+ /**
6
+ * Timeout in milliseconds before the request signal is aborted.
7
+ * This only covers producing the response, use `streamingTimeout`
8
+ * to limit streaming response bodies.
9
+ * Use `null` or `undefined` to disable the timeout.
10
+ */
11
+ timeout: Value<number | null | undefined, [options: StandardHandlerInterceptorOptions<T>]>;
12
+ /**
13
+ * Timeout in milliseconds for the full duration of a streaming response body
14
+ * (async iterator object or readable stream), measured from when the response is produced.
15
+ * When exceeded, the request signal is aborted and the body ends
16
+ * once its producer honors the signal.
17
+ * Usually higher than `timeout`.
18
+ * Use `null` or `undefined` to disable the timeout.
19
+ *
20
+ * @default undefined (streaming responses run without limit)
21
+ */
22
+ streamingTimeout?: Value<number | null | undefined, [options: StandardHandlerInterceptorOptions<T>]> | undefined;
23
+ }
24
+ /**
25
+ * The Timeout Handler Plugin aborts the request signal with an `AbortError`
26
+ * when handling exceeds a configured timeout. It only aborts the signal,
27
+ * the procedure must honor it to stop early and produce the response.
28
+ *
29
+ * @see {@link https://orpc.dev/docs/plugins/timeout | Timeout Plugin}
30
+ */
31
+ export declare class TimeoutHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
32
+ private readonly timeout;
33
+ private readonly streamingTimeout;
34
+ name: string;
35
+ constructor(options: NoInfer<TimeoutHandlerPluginOptions<T>>);
36
+ init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
37
+ }
@@ -0,0 +1,59 @@
1
+ import { AbortError, anyAbortSignal, isAsyncIteratorObject, override, toArray, value, wrapAsyncIterator, wrapReadableStream, } from '@orpc/shared';
2
+ /**
3
+ * The Timeout Handler Plugin aborts the request signal with an `AbortError`
4
+ * when handling exceeds a configured timeout. It only aborts the signal,
5
+ * the procedure must honor it to stop early and produce the response.
6
+ *
7
+ * @see {@link https://orpc.dev/docs/plugins/timeout | Timeout Plugin}
8
+ */
9
+ export class TimeoutHandlerPlugin {
10
+ timeout;
11
+ streamingTimeout;
12
+ name = '~timeout';
13
+ constructor(options) {
14
+ this.timeout = options.timeout;
15
+ this.streamingTimeout = options.streamingTimeout;
16
+ }
17
+ init(options) {
18
+ const interceptor = async (interceptorOptions) => {
19
+ const timeoutMs = value(this.timeout, interceptorOptions);
20
+ const streamingTimeoutMs = value(this.streamingTimeout, interceptorOptions);
21
+ const hasTimeout = timeoutMs !== null && timeoutMs !== undefined;
22
+ const hasStreamingTimeout = streamingTimeoutMs !== null && streamingTimeoutMs !== undefined;
23
+ if (!hasTimeout && !hasStreamingTimeout) {
24
+ return interceptorOptions.next();
25
+ }
26
+ const controller = new AbortController();
27
+ const abortWithTimeout = (ms) => {
28
+ controller.abort(new AbortError(`Request timed out after ${ms}ms`));
29
+ };
30
+ const timeoutId = hasTimeout ? setTimeout(abortWithTimeout, timeoutMs, timeoutMs) : undefined;
31
+ const signal = anyAbortSignal([interceptorOptions.request.signal, controller.signal]);
32
+ try {
33
+ const response = await interceptorOptions.next({
34
+ ...interceptorOptions,
35
+ request: { ...interceptorOptions.request, signal },
36
+ });
37
+ if (hasStreamingTimeout) {
38
+ const body = response.body;
39
+ const isIterator = isAsyncIteratorObject(body);
40
+ if (isIterator || body instanceof ReadableStream) {
41
+ const streamingTimeoutId = setTimeout(abortWithTimeout, streamingTimeoutMs, streamingTimeoutMs);
42
+ const onFinish = () => clearTimeout(streamingTimeoutId);
43
+ return {
44
+ ...response,
45
+ body: isIterator
46
+ ? override(body, wrapAsyncIterator(body, { onFinish }))
47
+ : override(body, wrapReadableStream(body, { onFinish })),
48
+ };
49
+ }
50
+ }
51
+ return response;
52
+ }
53
+ finally {
54
+ clearTimeout(timeoutId);
55
+ }
56
+ };
57
+ return { ...options, interceptors: [interceptor, ...toArray(options.interceptors)] };
58
+ }
59
+ }
@@ -0,0 +1,19 @@
1
+ import type { Client, ClientContext } from '@orpc/client';
2
+ import type { AnySchema, ErrorMap, InferSchemaInput, InferSchemaOutput, ORPCErrorConstructorMap, ORPCErrorFromErrorMap } from '@orpc/contract';
3
+ import type { Interceptor, MaybeOptionalOptions, Promisable, PromiseWithError, ThrowableError, Value } from '@orpc/shared';
4
+ import type { Context } from './context.ts';
5
+ import type { Lazyable } from './lazy.ts';
6
+ import type { Procedure, ProcedureHandlerOptions } from './procedure.ts';
7
+ export type ProcedureClient<TClientContext extends ClientContext, TInputSchema extends AnySchema, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap> = Client<TClientContext, InferSchemaInput<TInputSchema>, InferSchemaOutput<TOutputSchema>, ORPCErrorFromErrorMap<TErrorMap> | ThrowableError>;
8
+ export interface ProcedureClientInterceptorOptions<TInitialContext extends Context, TErrorMap extends ErrorMap> extends ProcedureHandlerOptions<TInitialContext, unknown, ORPCErrorConstructorMap<TErrorMap>> {
9
+ }
10
+ export type ProcedureClientInterceptor<TInitialContext extends Context, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap> = Interceptor<ProcedureClientInterceptorOptions<TInitialContext, TErrorMap>, PromiseWithError<InferSchemaOutput<TOutputSchema>, ORPCErrorFromErrorMap<TErrorMap> | ThrowableError>>;
11
+ export type ProcedureClientOptions<TInitialContext extends Context, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap, TClientContext extends ClientContext> = {
12
+ path?: string[] | undefined;
13
+ interceptors?: ProcedureClientInterceptor<TInitialContext, TOutputSchema, TErrorMap>[] | undefined;
14
+ } & (object extends TInitialContext ? {
15
+ context?: Value<Promisable<TInitialContext>, [clientContext: TClientContext]> | undefined;
16
+ } : {
17
+ context: Value<Promisable<TInitialContext>, [clientContext: TClientContext]>;
18
+ });
19
+ export declare function createProcedureClient<TInitialContext extends Context, TInputSchema extends AnySchema, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap, TClientContext extends ClientContext = object>(lazyableProcedure: Lazyable<Procedure<TInitialContext, any, TInputSchema, TOutputSchema, TErrorMap>>, ...rest: MaybeOptionalOptions<ProcedureClientOptions<TInitialContext, TOutputSchema, TErrorMap, TClientContext>>): ProcedureClient<TClientContext, TInputSchema, TOutputSchema, TErrorMap>;
@@ -0,0 +1,175 @@
1
+ import { ORPCError, wrapAsyncIteratorPreservingEventMeta } from '@orpc/client';
2
+ import { createORPCErrorConstructorMap, reconcileORPCError, ValidationError } from '@orpc/contract';
3
+ import { getTracer, intercept, isAsyncIteratorObject, mergeTwoLevels, override, resolveMaybeOptionalOptions, runWithSpan, toArray, traceAsyncIterator, traceReadableStream, value, } from '@orpc/shared';
4
+ import { unlazy } from './lazy.js';
5
+ export function createProcedureClient(lazyableProcedure, ...rest) {
6
+ const options = resolveMaybeOptionalOptions(rest);
7
+ return async (...[input, callerOptions]) => {
8
+ const path = toArray(options.path);
9
+ const { default: procedure } = await unlazy(lazyableProcedure);
10
+ // callerOptions.context can be undefined when all field is optional
11
+ const clientContext = callerOptions?.context ?? {};
12
+ // options.context can be undefined when all field is optional
13
+ const context = (await value(options.context, clientContext)) ??
14
+ {};
15
+ const errors = createORPCErrorConstructorMap(procedure['~orpc'].errorMap);
16
+ const reconcileError = async (e) => {
17
+ if (e instanceof ORPCError) {
18
+ return await reconcileORPCError(procedure['~orpc'].errorMap, e);
19
+ }
20
+ return e;
21
+ };
22
+ try {
23
+ const output = await runWithSpan('call_procedure', (span) => {
24
+ span?.setAttribute('procedure.path', path);
25
+ return intercept(options.interceptors, {
26
+ context,
27
+ // input can be optional if it is undefinable
28
+ input: input,
29
+ errors,
30
+ path,
31
+ procedure: procedure,
32
+ signal: callerOptions?.signal,
33
+ lastEventId: callerOptions?.lastEventId,
34
+ }, (interceptorOptions) => executeProcedureInternal(interceptorOptions.procedure, interceptorOptions));
35
+ });
36
+ if (isAsyncIteratorObject(output)) {
37
+ /**
38
+ * traceAsyncIterator/wrapAsyncIteratorPreservingEventMeta return AsyncIteratorClass
39
+ * which is backwards compatible with AsyncIteratorObject.
40
+ *
41
+ * @warning
42
+ * If remove this return, can be breaking change
43
+ * because AsyncIteratorClass convert `.throw` to `.return` (rarely used)
44
+ *
45
+ * @warning
46
+ * Remember use `override` for AsyncIteratorObject to remain other special properties
47
+ */
48
+ return override(output, wrapAsyncIteratorPreservingEventMeta(getTracer()
49
+ ? traceAsyncIterator('consume_async_iterator_object_output', output)
50
+ : output, { mapError: reconcileError }));
51
+ }
52
+ else if (getTracer() && output instanceof ReadableStream) {
53
+ /**
54
+ * @warning
55
+ * Remember use `override` for ReadableStream to remain other special properties
56
+ */
57
+ return override(output, traceReadableStream('consume_octet_stream_output', output));
58
+ }
59
+ return output;
60
+ }
61
+ catch (e) {
62
+ throw await reconcileError(e);
63
+ }
64
+ };
65
+ }
66
+ async function validateInput(i, schema, input) {
67
+ return runWithSpan(`validate_input.${i}`, async (span) => {
68
+ span?.setAttribute('input_schema.index', i);
69
+ const result = await schema['~standard'].validate(input);
70
+ if (result.issues) {
71
+ throw new ORPCError('BAD_REQUEST', {
72
+ message: 'Input validation failed',
73
+ data: {
74
+ issues: result.issues,
75
+ },
76
+ cause: new ValidationError({
77
+ message: 'Input validation failed',
78
+ issues: result.issues,
79
+ invalidData: input,
80
+ }),
81
+ });
82
+ }
83
+ return result.value;
84
+ });
85
+ }
86
+ async function validateOutput(i, schema, output) {
87
+ return runWithSpan(`validate_output.${i}`, async (span) => {
88
+ span?.setAttribute('output_schema.index', i);
89
+ const result = await schema['~standard'].validate(output);
90
+ if (result.issues) {
91
+ throw new ORPCError('INTERNAL_SERVER_ERROR', {
92
+ message: 'Output validation failed',
93
+ cause: new ValidationError({
94
+ message: 'Output validation failed',
95
+ issues: result.issues,
96
+ invalidData: output,
97
+ }),
98
+ });
99
+ }
100
+ return result.value;
101
+ });
102
+ }
103
+ const middlewareDone = (...rest) => {
104
+ const options = resolveMaybeOptionalOptions(rest);
105
+ return {
106
+ output: options.output,
107
+ // context can be undefined when all field is optional
108
+ context: options.context ?? {},
109
+ };
110
+ };
111
+ async function executeProcedureInternal(procedure, options) {
112
+ const inputSchemas = toArray(procedure['~orpc'].inputSchemas);
113
+ const outputSchemas = toArray(procedure['~orpc'].outputSchemas);
114
+ const orderedMiddlewares = procedure['~orpc'].orderedMiddlewares;
115
+ const next = async (midIndex, context, input, signal) => {
116
+ let currentInput = input;
117
+ let middlewareInput = input;
118
+ const startInputIndex = midIndex === 0 ? 0 : (orderedMiddlewares[midIndex - 1].inputSchemasLengthAtUse ?? 0);
119
+ const endInputIndex = midIndex === orderedMiddlewares.length
120
+ ? inputSchemas.length
121
+ : (orderedMiddlewares[midIndex].inputSchemasLengthAtUse ?? 0);
122
+ /**
123
+ * Each schema after the first validates the original input with the results so far applied, then
124
+ * its result is merged in, so no schema loses a field an earlier one stripped. Values that are not
125
+ * plain objects stay piped, which keeps schemas like `asyncIteratorObject` wrapping each other.
126
+ */
127
+ if (!procedure['~orpc'].disableInputValidation) {
128
+ for (let i = startInputIndex; i < endInputIndex; i++) {
129
+ const validated = await validateInput(i, inputSchemas[i], i !== 0 ? mergeTwoLevels(options.input, currentInput) : currentInput);
130
+ currentInput = i !== 0 ? mergeTwoLevels(currentInput, validated) : validated;
131
+ }
132
+ middlewareInput =
133
+ endInputIndex > 0 && endInputIndex < inputSchemas.length
134
+ ? mergeTwoLevels(options.input, currentInput)
135
+ : currentInput;
136
+ }
137
+ let currentOutput;
138
+ let currentContext = context;
139
+ if (midIndex < orderedMiddlewares.length) {
140
+ const { middleware } = orderedMiddlewares[midIndex];
141
+ const result = await runWithSpan(`middleware.${middleware.name}`, async (span) => {
142
+ span?.setAttribute('middleware.index', midIndex);
143
+ return await middleware({
144
+ ...options,
145
+ context,
146
+ signal,
147
+ next: (...rest) => {
148
+ const nextOptions = resolveMaybeOptionalOptions(rest);
149
+ // context can be undefined when all field is optional
150
+ const nextContext = nextOptions.context ?? {};
151
+ return next(midIndex + 1, { ...context, ...nextContext }, currentInput, nextOptions.signal ?? signal);
152
+ },
153
+ lastEventId: options.lastEventId,
154
+ }, middlewareInput, middlewareDone);
155
+ });
156
+ currentOutput = result.output;
157
+ currentContext = { ...context, ...result.context };
158
+ }
159
+ else {
160
+ currentOutput = await runWithSpan('handler', () => procedure['~orpc'].handler({ ...options, context, input: currentInput, signal }, currentInput));
161
+ }
162
+ const startOutputIndex = midIndex === 0 ? 0 : (orderedMiddlewares[midIndex - 1].outputSchemasLengthAtUse ?? 0);
163
+ const endOutputIndex = midIndex === orderedMiddlewares.length
164
+ ? outputSchemas.length
165
+ : (orderedMiddlewares[midIndex].outputSchemasLengthAtUse ?? 0);
166
+ if (!procedure['~orpc'].disableOutputValidation) {
167
+ for (let i = endOutputIndex - 1; i >= startOutputIndex; i--) {
168
+ currentOutput = await validateOutput(i, outputSchemas[i], currentOutput);
169
+ }
170
+ }
171
+ return { output: currentOutput, context: currentContext };
172
+ };
173
+ const { output } = await next(0, options.context, options.input, options.signal);
174
+ return output;
175
+ }
@@ -0,0 +1,10 @@
1
+ import type { AnySchema, ErrorMap, InferSchemaInput, InferSchemaOutput, MergedErrorMap, MetaPlugin } from '@orpc/contract';
2
+ import type { IntersectPick } from '@orpc/shared';
3
+ import type { Context, MergedContext, MergedInitialContext } from './context.ts';
4
+ import type { Middleware } from './middleware.ts';
5
+ import { Procedure } from './procedure.ts';
6
+ export declare class DecoratedProcedure<TInitialContext extends Context, TInjectedContext extends Context, TInputSchema extends AnySchema, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap> extends Procedure<TInitialContext, TInjectedContext, TInputSchema, TOutputSchema, TErrorMap> {
7
+ meta(...plugins: MetaPlugin<TInputSchema, TOutputSchema, TErrorMap>[]): DecoratedProcedure<TInitialContext, TInjectedContext, TInputSchema, TOutputSchema, TErrorMap>;
8
+ errors<T extends ErrorMap>(errors: T): DecoratedProcedure<TInitialContext, TInjectedContext, TInputSchema, TOutputSchema, MergedErrorMap<TErrorMap, T>>;
9
+ use<$OutContext extends IntersectPick<MergedContext<TInitialContext, TInjectedContext>, $OutContext>, $InContext extends Context = MergedContext<TInitialContext, TInjectedContext>, $ErrorMap extends ErrorMap = TErrorMap>(middleware: Middleware<$InContext | MergedContext<TInitialContext, TInjectedContext>, $OutContext, InferSchemaOutput<TInputSchema>, InferSchemaInput<TOutputSchema>, $ErrorMap>): DecoratedProcedure<MergedInitialContext<TInitialContext, TInjectedContext, $InContext>, MergedContext<TInjectedContext, $OutContext>, TInputSchema, TOutputSchema, MergedErrorMap<$ErrorMap, TErrorMap>>;
10
+ }
@@ -0,0 +1,44 @@
1
+ import { getHiddenMetaPlugins, mergeErrorMap, resolveMetaPlugins } from '@orpc/contract';
2
+ import { Procedure } from './procedure.js';
3
+ export class DecoratedProcedure extends Procedure {
4
+ meta(...plugins) {
5
+ const [meta, metaPlugins] = resolveMetaPlugins(this['~orpc'].meta, this['~orpc'].metaPlugins, plugins);
6
+ return new DecoratedProcedure({
7
+ ...this['~orpc'],
8
+ meta,
9
+ metaPlugins,
10
+ });
11
+ }
12
+ errors(errors) {
13
+ let procedure = new DecoratedProcedure({
14
+ ...this['~orpc'],
15
+ errorMap: mergeErrorMap(this['~orpc'].errorMap, errors),
16
+ });
17
+ const plugins = getHiddenMetaPlugins(errors);
18
+ if (plugins) {
19
+ procedure = procedure.meta(...plugins);
20
+ }
21
+ return procedure;
22
+ }
23
+ use(middleware) {
24
+ // Since middleware executes before the handler, we use `IntersectPick` to ensure
25
+ // that the middleware's output context ($OutContext) is compatible with the
26
+ // context requirements of the handler, which may have already been defined.
27
+ let procedure = new DecoratedProcedure({
28
+ ...this['~orpc'],
29
+ errorMap: mergeErrorMap(middleware['~orpc']?.errorMap, this['~orpc'].errorMap),
30
+ orderedMiddlewares: [
31
+ ...this['~orpc'].orderedMiddlewares,
32
+ {
33
+ middleware,
34
+ inputSchemasLengthAtUse: this['~orpc'].inputSchemas?.length,
35
+ outputSchemasLengthAtUse: this['~orpc'].outputSchemas?.length,
36
+ },
37
+ ],
38
+ });
39
+ if (middleware['~orpc']?.metaPlugins) {
40
+ procedure = procedure.meta(...middleware['~orpc']?.metaPlugins);
41
+ }
42
+ return procedure;
43
+ }
44
+ }
@@ -0,0 +1,41 @@
1
+ import type { ClientOptions } from '@orpc/client';
2
+ import type { AnySchema, ErrorMap, InferSchemaInput, InferSchemaOutput, ORPCErrorFromErrorMap, ProcedureContract } from '@orpc/contract';
3
+ import type { PromiseWithError, ThrowableError } from '@orpc/shared';
4
+ import type { Context } from './context.ts';
5
+ import type { Lazyable } from './lazy.ts';
6
+ import { Lazy } from './lazy.ts';
7
+ import type { ProcedureClientOptions } from './procedure-client.ts';
8
+ import type { AnyProcedure } from './procedure.ts';
9
+ import { Procedure } from './procedure.ts';
10
+ export declare function createGuardedProcedureLazy(lazy: Lazy<unknown>): Lazy<AnyProcedure>;
11
+ /**
12
+ * Create a new procedure that ensure the contract is applied to the procedure.
13
+ *
14
+ * .input/.output/.handler mismatches are already caught by the `implementer.router`,
15
+ * but .errors (in case implemented procedure error map is more than enough) and .meta cannot be
16
+ * validated that way - so they must be overridden here to ensure the client always sees what the contract defines.
17
+ */
18
+ export declare function createContractProcedure<TInitialContext extends Context, TInjectedContext extends Context, TInputSchema extends AnySchema, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap>(procedure: Procedure<TInitialContext, TInjectedContext, TInputSchema, TOutputSchema, any>, contract: ProcedureContract<any, any, TErrorMap>): Procedure<TInitialContext, TInjectedContext, TInputSchema, TOutputSchema, TErrorMap>;
19
+ export type CallOptions<TInitialContext extends Context, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap> = ProcedureClientOptions<TInitialContext, TOutputSchema, TErrorMap, object> & Omit<ClientOptions<object>, 'context'>;
20
+ export type CallRest<TInitialContext extends Context, TInputSchema extends AnySchema, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap> = object extends CallOptions<TInitialContext, TOutputSchema, TErrorMap> ? undefined extends InferSchemaInput<TInputSchema> ? [
21
+ input?: InferSchemaInput<TInputSchema>,
22
+ options?: CallOptions<TInitialContext, TOutputSchema, TErrorMap>
23
+ ] : [
24
+ input: InferSchemaInput<TInputSchema>,
25
+ options?: CallOptions<TInitialContext, TOutputSchema, TErrorMap>
26
+ ] : [
27
+ input: InferSchemaInput<TInputSchema>,
28
+ options: CallOptions<TInitialContext, TOutputSchema, TErrorMap>
29
+ ];
30
+ /**
31
+ * Quickly call a procedure without creating a client.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * const output = await call(getting, 'input')
36
+ * const output = await call(getting, 'input', { context: { db: 'postgres' } })
37
+ * ```
38
+ *
39
+ * @see {@link https://orpc.dev/docs/client/server-side#one-off-calls | Server-Side Clients - One-Off Calls}
40
+ */
41
+ export declare function call<TInitialContext extends Context, TInputSchema extends AnySchema, TOutputSchema extends AnySchema, TErrorMap extends ErrorMap>(lazyableProcedure: Lazyable<Procedure<TInitialContext, any, TInputSchema, TOutputSchema, TErrorMap>>, ...[input, options,]: CallRest<TInitialContext, TInputSchema, TOutputSchema, TErrorMap>): PromiseWithError<InferSchemaOutput<TOutputSchema>, ORPCErrorFromErrorMap<TErrorMap> | ThrowableError>;
@@ -0,0 +1,49 @@
1
+ import { Lazy, unlazy } from './lazy.js';
2
+ import { createProcedureClient } from './procedure-client.js';
3
+ import { Procedure } from './procedure.js';
4
+ export function createGuardedProcedureLazy(lazy) {
5
+ const guarded = new Lazy({
6
+ ...lazy['~orpc'],
7
+ async loader() {
8
+ const { default: maybeProcedure } = await unlazy(lazy);
9
+ if (!(maybeProcedure instanceof Procedure)) {
10
+ throw new TypeError(`
11
+ Expected a lazy<procedure> but got lazy<unknown>.
12
+ This should be caught by TypeScript compilation.
13
+ Please report this issue if you think this is a bug.
14
+ `);
15
+ }
16
+ return { default: maybeProcedure };
17
+ },
18
+ });
19
+ return guarded;
20
+ }
21
+ /**
22
+ * Create a new procedure that ensure the contract is applied to the procedure.
23
+ *
24
+ * .input/.output/.handler mismatches are already caught by the `implementer.router`,
25
+ * but .errors (in case implemented procedure error map is more than enough) and .meta cannot be
26
+ * validated that way - so they must be overridden here to ensure the client always sees what the contract defines.
27
+ */
28
+ export function createContractProcedure(procedure, contract) {
29
+ return new Procedure({
30
+ ...procedure['~orpc'],
31
+ errorMap: contract['~orpc'].errorMap,
32
+ meta: contract['~orpc'].meta,
33
+ metaPlugins: contract['~orpc'].metaPlugins,
34
+ });
35
+ }
36
+ /**
37
+ * Quickly call a procedure without creating a client.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const output = await call(getting, 'input')
42
+ * const output = await call(getting, 'input', { context: { db: 'postgres' } })
43
+ * ```
44
+ *
45
+ * @see {@link https://orpc.dev/docs/client/server-side#one-off-calls | Server-Side Clients - One-Off Calls}
46
+ */
47
+ export function call(lazyableProcedure, ...[input = undefined, options = {},]) {
48
+ return createProcedureClient(lazyableProcedure, options)(input, options);
49
+ }