1/**
2 * Defines schema-backed contracts for individual RPC procedures.
3 *
4 * An `Rpc` describes one remote procedure by recording its tag, payload schema,
5 * success schema, error schema, defect schema, middleware, and annotations.
6 * Clients and servers read the same declaration, so the procedure contract is
7 * independent of the transport used to call it. This module includes
8 * constructors, type helpers for deriving client and handler shapes, exit
9 * schemas, and handler wrappers for special execution modes.
10 *
11 * @stability unstable
12 * @since 4.0.0
13 */
14import type * as Cause from "../Cause.ts"
15import * as Context from "../Context.ts"
16import type { Deferred } from "../Deferred.ts"
17import type { Effect } from "../Effect.ts"
18import type { Exit as Exit_ } from "../Exit.ts"
19import type { Headers } from "../http/Headers.ts"
20import * as Option from "../Option.ts"
21import { type Pipeable, pipeArguments } from "../Pipeable.ts"
22import * as Predicate from "../Predicate.ts"
23import * as PrimaryKey from "../PrimaryKey.ts"
24import type * as Queue from "../Queue.ts"
25import * as Schema from "../Schema.ts"
26import type { Stream } from "../Stream.ts"
27import type * as Struct from "../Struct.ts"
28import type { NoInfer } from "../Types.ts"
29import type { RequestId } from "./RpcMessage.ts"
30import type * as RpcMiddleware from "./RpcMiddleware.ts"
31import * as RpcSchema from "./RpcSchema.ts"
32
33const TypeId = "~effect/rpc/Rpc"
34
35/**
36 * Returns `true` when the value is an `Rpc` definition.
37 *
38 * @stability unstable
39 * @category guards
40 * @since 4.0.0
41 */
42export const isRpc = (u: unknown): u is Rpc<any, any, any> => Predicate.hasProperty(u, TypeId)
43
44/**
45 * Schema for RPC defects.
46 *
47 * **Details**
48 *
49 * Defect schemas decode and encode without services and can be constructed from
50 * `null`, `undefined`, or an object value.
51 *
52 * @stability unstable
53 * @category schemas
54 * @since 4.0.0
55 */
56export interface DefectSchema extends Schema.Top {
57 make(input: null, options?: Schema.MakeOptions): unknown
58 make(input: undefined, options?: Schema.MakeOptions): unknown
59 make(input: {}, options?: Schema.MakeOptions): unknown
60 readonly DecodingServices: never
61 readonly EncodingServices: never
62}
63
64/**
65 * Represents a typed RPC definition.
66 *
67 * **Details**
68 *
69 * An RPC is identified by a tag and carries payload, success, error, defect,
70 * middleware, and annotation metadata used by RPC clients and servers.
71 *
72 * @stability unstable
73 * @category models
74 * @since 4.0.0
75 */
76export interface Rpc<
77 in out Tag extends string,
78 out Payload extends Schema.Top = Schema.Void,
79 out Success extends Schema.Top = Schema.Void,
80 out Error extends Schema.Top = Schema.Never,
81 out Middleware extends RpcMiddleware.AnyService = never,
82 out Requires = never
83> extends Pipeable {
84 new(_: never): {}
85
86 readonly [TypeId]: typeof TypeId
87 readonly _tag: Tag
88 readonly key: string
89 readonly payloadSchema: Payload
90 readonly successSchema: Success
91 readonly errorSchema: Error
92 readonly defectSchema: Schema.Top
93 readonly annotations: Context.Context<never>
94 readonly middlewares: ReadonlySet<Middleware>
95 readonly "~requires": Requires
96
97 /**
98 * Set the schema for the success response of the rpc.
99 */
100 setSuccess<S extends Schema.Top>(schema: S): Rpc<
101 Tag,
102 Payload,
103 S,
104 Error,
105 Middleware,
106 Requires
107 >
108
109 /**
110 * Set the schema for the error response of the rpc.
111 */
112 setError<E extends Schema.Top>(schema: E): Rpc<
113 Tag,
114 Payload,
115 Success,
116 E,
117 Middleware,
118 Requires
119 >
120
121 /**
122 * Set the schema for the payload of the rpc.
123 */
124 setPayload<P extends Schema.Top | Schema.Struct.Fields>(
125 schema: P
126 ): Rpc<
127 Tag,
128 P extends Schema.Struct.Fields ? Schema.Struct<P> : P,
129 Success,
130 Error,
131 Middleware,
132 Requires
133 >
134
135 /**
136 * Add an `RpcMiddleware` to this procedure.
137 */
138 middleware<M extends RpcMiddleware.AnyService>(middleware: M): Rpc<
139 Tag,
140 Payload,
141 Success,
142 Error,
143 Middleware | M,
144 RpcMiddleware.ApplyServices<M["Identifier"], Requires>
145 >
146
147 /**
148 * Set the schema for the error response of the rpc.
149 */
150 prefix<const Prefix extends string>(prefix: Prefix): Rpc<
151 `${Prefix}${Tag}`,
152 Payload,
153 Success,
154 Error,
155 Middleware,
156 Requires
157 >
158
159 /**
160 * Add an annotation on the rpc.
161 */
162 annotate<I, S>(
163 tag: Context.Key<I, S>,
164 value: NoInfer<S>
165 ): Rpc<Tag, Payload, Success, Error, Middleware, Requires>
166
167 /**
168 * Merge the annotations of the rpc with the provided annotations.
169 */
170 annotateMerge<I>(
171 annotations: Context.Context<I>
172 ): Rpc<Tag, Payload, Success, Error, Middleware, Requires>
173}
174
175/**
176 * Represents server-side metadata for the client associated with an RPC request.
177 *
178 * **When to use**
179 *
180 * Use to inspect or annotate the connected client while handling an RPC request
181 * on the server.
182 *
183 * **Details**
184 *
185 * It stores the client id and request annotations that handlers can read or
186 * extend.
187 *
188 * @stability unstable
189 * @category models
190 * @since 4.0.0
191 */
192export class ServerClient {
193 readonly id: number
194 annotations: Context.Context<never>
195 constructor(id: number) {
196 this.id = id
197 this.annotations = Context.empty()
198 }
199 annotate<I, S>(
200 tag: Context.Key<I, S>,
201 value: NoInfer<S>
202 ): ServerClient {
203 this.annotations = Context.add(this.annotations, tag, value)
204 return this
205 }
206}
207
208/**
209 * Represents the server-side implementation of an RPC.
210 *
211 * **Details**
212 *
213 * The handler receives the decoded request plus client, request id, headers,
214 * and RPC metadata, and returns either an effectful result or a stream result.
215 *
216 * @stability unstable
217 * @category models
218 * @since 4.0.0
219 */
220export interface Handler<Tag extends string> {
221 readonly _: unique symbol
222 readonly tag: Tag
223 readonly handler: (request: any, options: {
224 readonly client: ServerClient
225 readonly requestId: RequestId
226 readonly headers: Headers
227 readonly rpc: Any
228 }) => Effect<{} | Deferred<any, any>, any> | Stream<any, any>
229 readonly context: Context.Context<never>
230}
231
232/**
233 * An erased RPC definition that preserves the common runtime metadata shared by
234 * all RPCs.
235 *
236 * @stability unstable
237 * @category models
238 * @since 4.0.0
239 */
240export interface Any extends Pipeable {
241 readonly [TypeId]: typeof TypeId
242 readonly _tag: string
243 readonly key: string
244 readonly annotations: Context.Context<never>
245}
246
247/**
248 * An erased RPC definition with all schema, middleware, annotation, and service
249 * metadata available.
250 *
251 * @stability unstable
252 * @category models
253 * @since 4.0.0
254 */
255export interface AnyWithProps extends Pipeable {
256 readonly [TypeId]: typeof TypeId
257 readonly _tag: string
258 readonly key: string
259 readonly payloadSchema: Schema.Top
260 readonly successSchema: Schema.Top
261 readonly errorSchema: Schema.Top
262 readonly defectSchema: Schema.Top
263 readonly annotations: Context.Context<never>
264 readonly middlewares: ReadonlySet<RpcMiddleware.AnyServiceWithProps>
265 readonly "~requires": any
266}
267
268/**
269 * Extracts the tag string from an `Rpc`.
270 *
271 * @stability unstable
272 * @category utility types
273 * @since 4.0.0
274 */
275export type Tag<R> = R extends Rpc<
276 infer _Tag,
277 infer _Payload,
278 infer _Success,
279 infer _Error,
280 infer _Middleware,
281 infer _Requires
282> ? _Tag
283 : never
284
285/**
286 * Extracts the success schema from an `Rpc`.
287 *
288 * @stability unstable
289 * @category utility types
290 * @since 4.0.0
291 */
292export type SuccessSchema<R> = R extends Rpc<
293 infer _Tag,
294 infer _Payload,
295 infer _Success,
296 infer _Error,
297 infer _Middleware,
298 infer _Requires
299> ? _Success
300 : never
301
302/**
303 * Extracts the decoded success value type from an `Rpc`.
304 *
305 * @stability unstable
306 * @category utility types
307 * @since 4.0.0
308 */
309export type Success<R> = SuccessSchema<R>["Type"]
310
311/**
312 * Extracts the encoded success value type from an `Rpc`.
313 *
314 * @stability unstable
315 * @category utility types
316 * @since 4.0.0
317 */
318export type SuccessEncoded<R> = R extends Rpc<
319 infer _Tag,
320 infer _Payload,
321 infer _Success,
322 infer _Error,
323 infer _Middleware,
324 infer _Requires
325> ? _Success["Encoded"]
326 : never
327
328/**
329 * Extracts the success schema used in an RPC exit.
330 *
331 * **Details**
332 *
333 * For streaming RPCs, this is the stream element schema; otherwise it is the
334 * RPC success schema.
335 *
336 * @stability unstable
337 * @category utility types
338 * @since 4.0.0
339 */
340export type SuccessExitSchema<R> = SuccessSchema<R> extends RpcSchema.Stream<infer _A, infer _E> ? _A : SuccessSchema<R>
341
342/**
343 * Extracts the decoded success value carried by an RPC exit.
344 *
345 * **Details**
346 *
347 * For streaming RPCs, the immediate exit success is `void` because stream
348 * elements are delivered separately.
349 *
350 * @stability unstable
351 * @category utility types
352 * @since 4.0.0
353 */
354export type SuccessExit<R> = Success<R> extends infer T ? T extends Stream<infer _A, infer _E, infer _Env> ? void : T
355 : never
356
357/**
358 * Extracts the decoded stream element type from a streaming RPC, or `never` for
359 * non-streaming RPCs.
360 *
361 * @stability unstable
362 * @category utility types
363 * @since 4.0.0
364 */
365export type SuccessChunk<R> = Success<R> extends Stream<infer _A, infer _E, infer _Env> ? _A : never
366
367/**
368 * Extracts the RPC error schema, including error schemas contributed by
369 * middleware.
370 *
371 * @stability unstable
372 * @category utility types
373 * @since 4.0.0
374 */
375export type ErrorSchema<R> = R extends Rpc<
376 infer _Tag,
377 infer _Payload,
378 infer _Success,
379 infer _Error,
380 infer _Middleware,
381 infer _Requires
382> ? _Error | _Middleware["error"]
383 : never
384
385/**
386 * Extracts the decoded error value type from an `Rpc`, including middleware
387 * errors.
388 *
389 * @stability unstable
390 * @category utility types
391 * @since 4.0.0
392 */
393export type Error<R> = Schema.Schema.Type<ErrorSchema<R>>
394
395/**
396 * Extracts the error schema used in an RPC exit.
397 *
398 * **Details**
399 *
400 * For streaming RPCs, this includes both the stream error schema and the RPC
401 * error schema; otherwise it is the RPC error schema.
402 *
403 * @stability unstable
404 * @category utility types
405 * @since 4.0.0
406 */
407export type ErrorExitSchema<R> = SuccessSchema<R> extends RpcSchema.Stream<infer _A, infer _E> ? _E | ErrorSchema<R>
408 : ErrorSchema<R>
409
410/**
411 * Extracts the decoded error type used by an RPC exit.
412 *
413 * **Details**
414 *
415 * For streaming RPCs, this includes both stream errors and RPC errors.
416 *
417 * @stability unstable
418 * @category utility types
419 * @since 4.0.0
420 */
421export type ErrorExit<R> = Success<R> extends Stream<infer _A, infer _E, infer _Env> ? _E | Error<R> : Error<R>
422
423/**
424 * The `Exit` type produced for an RPC, using the RPC's exit success and exit
425 * error types.
426 *
427 * @stability unstable
428 * @category utility types
429 * @since 4.0.0
430 */
431export type Exit<R> = Exit_<SuccessExit<R>, ErrorExit<R>>
432
433/**
434 * Extracts the payload constructor input type accepted by the RPC payload
435 * schema.
436 *
437 * @stability unstable
438 * @category utility types
439 * @since 4.0.0
440 */
441export type PayloadConstructor<R> = R extends Rpc<
442 infer _Tag,
443 infer _Payload,
444 infer _Success,
445 infer _Error,
446 infer _Middleware,
447 infer _Requires
448> ? _Payload["~type.make.in"]
449 : never
450
451/**
452 * Extracts the decoded payload type from an `Rpc`.
453 *
454 * @stability unstable
455 * @category utility types
456 * @since 4.0.0
457 */
458export type Payload<R> = R extends Rpc<
459 infer _Tag,
460 infer _Payload,
461 infer _Success,
462 infer _Error,
463 infer _Middleware,
464 infer _Requires
465> ? _Payload["Type"]
466 : never
467
468/**
469 * Extracts all schema services required to encode or decode an RPC's payload,
470 * success, error, and middleware error schemas.
471 *
472 * @stability unstable
473 * @category utility types
474 * @since 4.0.0
475 */
476export type Services<R> = R extends Rpc<
477 infer _Tag,
478 infer _Payload,
479 infer _Success,
480 infer _Error,
481 infer _Middleware,
482 infer _Requires
483> ?
484 | _Payload["DecodingServices"]
485 | _Payload["EncodingServices"]
486 | _Success["DecodingServices"]
487 | _Success["EncodingServices"]
488 | _Error["DecodingServices"]
489 | _Error["EncodingServices"]
490 | _Middleware["error"]["DecodingServices"]
491 | _Middleware["error"]["EncodingServices"]
492 : never
493
494/**
495 * Extracts the schema services required on the client side of an RPC.
496 *
497 * **Details**
498 *
499 * This includes payload encoding services and success, error, and middleware
500 * error decoding services.
501 *
502 * @stability unstable
503 * @category utility types
504 * @since 4.0.0
505 */
506export type ServicesClient<R> = R extends Rpc<
507 infer _Tag,
508 infer _Payload,
509 infer _Success,
510 infer _Error,
511 infer _Middleware,
512 infer _Requires
513> ?
514 | _Payload["EncodingServices"]
515 | _Success["DecodingServices"]
516 | _Error["DecodingServices"]
517 | _Middleware["error"]["DecodingServices"]
518 : never
519
520/**
521 * Extracts the schema services required on the server side of an RPC.
522 *
523 * **Details**
524 *
525 * This includes payload decoding services and success, error, and middleware
526 * error encoding services.
527 *
528 * @stability unstable
529 * @category utility types
530 * @since 4.0.0
531 */
532export type ServicesServer<R> = R extends Rpc<
533 infer _Tag,
534 infer _Payload,
535 infer _Success,
536 infer _Error,
537 infer _Middleware,
538 infer _Requires
539> ?
540 | _Payload["DecodingServices"]
541 | _Success["EncodingServices"]
542 | _Error["EncodingServices"]
543 | _Middleware["error"]["EncodingServices"]
544 : never
545
546/**
547 * Extracts the service identifiers for middleware attached to an `Rpc`.
548 *
549 * @stability unstable
550 * @category utility types
551 * @since 4.0.0
552 */
553export type Middleware<R> = R extends Rpc<
554 infer _Tag,
555 infer _Payload,
556 infer _Success,
557 infer _Error,
558 infer _Middleware,
559 infer _Requires
560> ? Context.Service.Identifier<_Middleware>
561 : never
562
563/**
564 * Extracts client-side middleware service requirements for middleware marked as
565 * required on the client.
566 *
567 * @stability unstable
568 * @category utility types
569 * @since 4.0.0
570 */
571export type MiddlewareClient<R> = R extends Rpc<
572 infer _Tag,
573 infer _Payload,
574 infer _Success,
575 infer _Error,
576 infer _Middleware,
577 infer _Requires
578> ? _Middleware extends { readonly requiredForClient: true } ? RpcMiddleware.ForClient<_Middleware["Identifier"]>
579 : never
580 : never
581
582/**
583 * Returns an RPC type with an additional error schema unioned into its error
584 * channel.
585 *
586 * @stability unstable
587 * @category utility types
588 * @since 4.0.0
589 */
590export type AddError<R extends Any, Error extends Schema.Top> = R extends Rpc<
591 infer _Tag,
592 infer _Payload,
593 infer _Success,
594 infer _Error,
595 infer _Middleware,
596 infer _Requires
597> ? Rpc<
598 _Tag,
599 _Payload,
600 _Success,
601 _Error | Error,
602 _Middleware,
603 _Requires
604 > :
605 never
606
607/**
608 * Returns an RPC type with additional middleware and the corresponding
609 * middleware service requirements applied.
610 *
611 * @stability unstable
612 * @category utility types
613 * @since 4.0.0
614 */
615export type AddMiddleware<R extends Any, Middleware extends RpcMiddleware.AnyService> = R extends Rpc<
616 infer _Tag,
617 infer _Payload,
618 infer _Success,
619 infer _Error,
620 infer _Middleware,
621 infer _Requires
622> ? Rpc<
623 _Tag,
624 _Payload,
625 _Success,
626 _Error,
627 _Middleware | Middleware,
628 RpcMiddleware.ApplyServices<Middleware["Identifier"], _Requires>
629 > :
630 never
631
632/**
633 * Converts an RPC definition into the corresponding `Handler` type.
634 *
635 * @stability unstable
636 * @category utility types
637 * @since 4.0.0
638 */
639export type ToHandler<R extends Any> = R extends Rpc<
640 infer _Tag,
641 infer _Payload,
642 infer _Success,
643 infer _Error,
644 infer _Middleware,
645 infer _Requires
646> ? Handler<_Tag> :
647 never
648
649/**
650 * The function signature for implementing an RPC handler.
651 *
652 * **Details**
653 *
654 * The function receives the decoded payload and request metadata, and returns
655 * the RPC result shape, optionally wrapped with `Wrapper` options.
656 *
657 * @stability unstable
658 * @category utility types
659 * @since 4.0.0
660 */
661export type ToHandlerFn<Current extends Any, R = any> = (
662 payload: Payload<Current>,
663 options: {
664 readonly client: ServerClient
665 readonly requestId: RequestId
666 readonly headers: Headers
667 readonly rpc: Current
668 }
669) => WrapperOr<ResultFrom<Current, R>>
670
671/**
672 * Returns `true` when the RPC with the specified tag has a streaming success
673 * schema, or `never` otherwise.
674 *
675 * @stability unstable
676 * @category utility types
677 * @since 4.0.0
678 */
679export type IsStream<R extends Any, Tag extends string> = R extends Rpc<
680 Tag,
681 infer _Payload,
682 RpcSchema.Stream<infer _A, infer _E>,
683 infer _Error,
684 infer _Middleware,
685 infer _Requires
686> ? true :
687 never
688
689/**
690 * Extracts RPCs whose tags match the given tag or union of tags.
691 *
692 * @stability unstable
693 * @category utility types
694 * @since 4.0.0
695 */
696export type ExtractTag<R extends Any, Tag extends string> = R extends Rpc<
697 infer _Tag extends Tag,
698 infer _Payload,
699 infer _Success,
700 infer _Error,
701 infer _Middleware,
702 infer _Requires
703> ? R :
704 never
705
706/**
707 * Extracts the services provided by middleware on the RPC with the specified
708 * tag.
709 *
710 * @stability unstable
711 * @category utility types
712 * @since 4.0.0
713 */
714export type ExtractProvides<R extends Any, Tag extends string> = R extends Rpc<
715 Tag,
716 infer _Payload,
717 infer _Success,
718 infer _Error,
719 infer _Middleware,
720 infer _Requires
721> ? RpcMiddleware.Provides<_Middleware["Identifier"]> :
722 never
723
724/**
725 * Extracts the service requirements of the RPC with the specified tag.
726 *
727 * @stability unstable
728 * @category utility types
729 * @since 4.0.0
730 */
731export type ExtractRequires<R extends Any, Tag extends string> = R extends Rpc<
732 Tag,
733 infer _Payload,
734 infer _Success,
735 infer _Error,
736 infer _Middleware,
737 infer _Requires
738> ? _Requires :
739 never
740
741/**
742 * Removes the services provided by middleware for the specified RPC tag from an
743 * environment type.
744 *
745 * @stability unstable
746 * @category utility types
747 * @since 4.0.0
748 */
749export type ExcludeProvides<Env, R extends Any, Tag extends string> = Exclude<
750 Env,
751 ExtractProvides<R, Tag>
752>
753
754/**
755 * Computes the allowed handler result type for an RPC.
756 *
757 * **Details**
758 *
759 * Streaming RPCs may return a stream or an effect that produces a queue. Other
760 * RPCs return an effect that succeeds with the success value or a deferred
761 * success value.
762 *
763 * @stability unstable
764 * @category utility types
765 * @since 4.0.0
766 */
767export type ResultFrom<R extends Any, Services> = R extends Rpc<
768 infer _Tag,
769 infer _Payload,
770 infer _Success,
771 infer _Error,
772 infer _Middleware,
773 infer _Requires
774> ? [_Success] extends [RpcSchema.Stream<infer _SA, infer _SE>] ?
775 | Stream<
776 _SA["Type"],
777 _SE["Type"] | _Error["Type"],
778 Services
779 >
780 | Effect<
781 Queue.Dequeue<_SA["Type"], _SE["Type"] | _Error["Type"] | Cause.Done>,
782 _SE["Type"] | Schema.Schema.Type<_Error>,
783 Services
784 > :
785 Effect<
786 _Success["Type"] | Deferred<_Success["Type"], _Error["Type"]>,
787 _Error["Type"],
788 Services
789 > :
790 never
791
792/**
793 * Returns an RPC type with the specified string prefix added to its tag while
794 * preserving its payload, success, error, middleware, and requirements.
795 *
796 * @stability unstable
797 * @category utility types
798 * @since 4.0.0
799 */
800export type Prefixed<Rpcs extends Any, Prefix extends string> = Rpcs extends Rpc<
801 infer _Tag,
802 infer _Payload,
803 infer _Success,
804 infer _Error,
805 infer _Middleware,
806 infer _Requires
807> ? Rpc<
808 `${Prefix}${_Tag}`,
809 _Payload,
810 _Success,
811 _Error,
812 _Middleware,
813 _Requires
814 >
815 : never
816
817const Proto = {
818 [TypeId]: TypeId,
819 pipe() {
820 return pipeArguments(this, arguments)
821 },
822 setSuccess(
823 this: AnyWithProps,
824 successSchema: Schema.Top
825 ) {
826 return makeProto({
827 _tag: this._tag,
828 payloadSchema: this.payloadSchema,
829 successSchema,
830 errorSchema: this.errorSchema,
831 defectSchema: this.defectSchema,
832 annotations: this.annotations,
833 middlewares: this.middlewares
834 })
835 },
836 setError(this: AnyWithProps, errorSchema: Schema.Top) {
837 return makeProto({
838 _tag: this._tag,
839 payloadSchema: this.payloadSchema,
840 successSchema: this.successSchema,
841 errorSchema,
842 defectSchema: this.defectSchema,
843 annotations: this.annotations,
844 middlewares: this.middlewares
845 })
846 },
847 setPayload(this: AnyWithProps, payloadSchema: Schema.Struct<Schema.Struct.Fields> | Schema.Struct.Fields) {
848 return makeProto({
849 _tag: this._tag,
850 payloadSchema: Schema.isSchema(payloadSchema) ? payloadSchema as any : Schema.Struct(payloadSchema as any),
851 successSchema: this.successSchema,
852 errorSchema: this.errorSchema,
853 defectSchema: this.defectSchema,
854 annotations: this.annotations,
855 middlewares: this.middlewares
856 })
857 },
858 middleware(this: AnyWithProps, middleware: RpcMiddleware.AnyService) {
859 return makeProto({
860 _tag: this._tag,
861 payloadSchema: this.payloadSchema,
862 successSchema: this.successSchema,
863 errorSchema: this.errorSchema,
864 defectSchema: this.defectSchema,
865 annotations: this.annotations,
866 middlewares: new Set([...this.middlewares, middleware])
867 })
868 },
869 prefix(this: AnyWithProps, prefix: string) {
870 return makeProto({
871 _tag: `${prefix}${this._tag}`,
872 payloadSchema: this.payloadSchema,
873 successSchema: this.successSchema,
874 errorSchema: this.errorSchema,
875 defectSchema: this.defectSchema,
876 annotations: this.annotations,
877 middlewares: this.middlewares
878 })
879 },
880 annotate(this: AnyWithProps, tag: Context.Key<any, any>, value: any) {
881 return makeProto({
882 _tag: this._tag,
883 payloadSchema: this.payloadSchema,
884 successSchema: this.successSchema,
885 errorSchema: this.errorSchema,
886 defectSchema: this.defectSchema,
887 middlewares: this.middlewares,
888 annotations: Context.add(this.annotations, tag, value)
889 })
890 },
891 annotateMerge(this: AnyWithProps, context: Context.Context<any>) {
892 return makeProto({
893 _tag: this._tag,
894 payloadSchema: this.payloadSchema,
895 successSchema: this.successSchema,
896 errorSchema: this.errorSchema,
897 defectSchema: this.defectSchema,
898 middlewares: this.middlewares,
899 annotations: Context.merge(this.annotations, context)
900 })
901 }
902}
903
904const makeProto = <
905 const Tag extends string,
906 Payload extends Schema.Top,
907 Success extends Schema.Top,
908 Error extends Schema.Top,
909 Middleware extends RpcMiddleware.AnyService,
910 Requires
911>(options: {
912 readonly _tag: Tag
913 readonly payloadSchema: Payload
914 readonly successSchema: Success
915 readonly errorSchema: Error
916 readonly defectSchema: Schema.Constraint
917 readonly annotations: Context.Context<never>
918 readonly middlewares: ReadonlySet<Middleware>
919}): Rpc<Tag, Payload, Success, Error, Middleware, Requires> => {
920 function Rpc() {}
921 Object.setPrototypeOf(Rpc, Proto)
922 Object.assign(Rpc, options)
923 Rpc.key = `effect/rpc/Rpc/${options._tag}`
924 return Rpc as any
925}
926
927/**
928 * Creates an RPC definition with the supplied tag and optional schemas.
929 *
930 * **Details**
931 *
932 * Payload options can be either a schema or struct fields. `stream: true` wraps
933 * the success and error schemas in a stream schema and sets the normal error
934 * schema to `Schema.Never`. `primaryKey` creates a payload class with a
935 * primary key derived from the payload value.
936 *
937 * @stability unstable
938 * @category constructors
939 * @since 4.0.0
940 */
941export const make = <
942 const Tag extends string,
943 Payload extends Schema.Top | Schema.Struct.Fields = Schema.Void,
944 Success extends Schema.Top = Schema.Void,
945 Error extends Schema.Top = Schema.Never,
946 const Stream extends boolean = false
947>(tag: Tag, options?: {
948 readonly payload?: Payload
949 readonly success?: Success
950 readonly error?: Error
951 readonly defect?: DefectSchema
952 readonly stream?: Stream
953 readonly primaryKey?: [Payload] extends [Schema.Struct.Fields] ? ((
954 payload: Payload extends Schema.Struct.Fields ? Struct.Simplify<Schema.Struct<Payload>["Type"]> : Payload["Type"]
955 ) => string) :
956 never
957}): Rpc<
958 Tag,
959 Payload extends Schema.Struct.Fields ? Schema.Struct<Payload> : Payload,
960 Stream extends true ? RpcSchema.Stream<Success, Error> : Success,
961 Stream extends true ? typeof Schema.Never : Error
962> => {
963 const successSchema = options?.success ?? Schema.Void
964 const errorSchema = options?.error ?? Schema.Never
965 const defectSchema = options?.defect ?? Schema.Defect()
966 let payloadSchema: any
967 if (options?.primaryKey) {
968 payloadSchema = class Payload extends Schema.Class<Payload>(`effect/rpc/Rpc/${tag}`)(options.payload as any) {
969 [PrimaryKey.symbol](): string {
970 return options.primaryKey!(this as any)
971 }
972 }
973 } else {
974 payloadSchema = Schema.isSchema(options?.payload)
975 ? options?.payload as any
976 : options?.payload
977 ? Schema.Struct(options?.payload as any)
978 : Schema.Void
979 }
980 return makeProto({
981 _tag: tag,
982 payloadSchema,
983 successSchema: options?.stream ?
984 RpcSchema.Stream(successSchema, errorSchema) :
985 successSchema,
986 errorSchema: options?.stream ? Schema.Never : errorSchema,
987 defectSchema,
988 annotations: Context.empty(),
989 middlewares: new Set<never>()
990 }) as any
991}
992
993/**
994 * Creates a custom `Rpc` constructor that can transform the output schemas.
995 *
996 * **Example** (Defining a paginated RPC constructor)
997 *
998 * ```ts import.meta.vitest
999 * import { Schema } from "effect"
1000 * import { Rpc } from "effect/rpc"
1001 *
1002 * // Create a custom Rpc wrapper definition by transforming the success and error
1003 * // schemas.
1004 * export interface RpcWithPagination extends Rpc.Custom {
1005 * readonly out: Rpc.Custom.Out<
1006 * Paginated<this["success"]>,
1007 * this["error"]
1008 * >
1009 * }
1010 *
1011 * // The type definition for the transformed success schema.
1012 * export interface Paginated<S extends Schema.Constraint> extends
1013 * Schema.Struct<{
1014 * readonly offset: Schema.Number
1015 * readonly total: Schema.Number
1016 * readonly results: Schema.$Array<S>
1017 * }>
1018 * {}
1019 *
1020 * // You can then implement the schema transformation using `Rpc.custom`
1021 * export const makePaginated = Rpc.custom<RpcWithPagination>((schemas) => ({
1022 * ...schemas,
1023 * success: Schema.Struct({
1024 * offset: Schema.Number,
1025 * total: Schema.Number,
1026 * results: Schema.Array(schemas.success)
1027 * })
1028 * }))
1029 *
1030 * // You can then use the custom constructor in the same way `Rpc.make` is used.
1031 * export const listAllRpc = makePaginated("listAll", {
1032 * success: Schema.String
1033 * })
1034 *
1035 * const result = [listAllRpc._tag, Schema.isSchema(listAllRpc.successSchema)] // => ["listAll", true]
1036 * ```
1037 *
1038 * @stability unstable
1039 * @category constructors
1040 * @since 4.0.0
1041 */
1042export const custom = <Def extends Custom>(
1043 f: (options: Custom.OutDefault) => (Def & Custom.OutDefault)["out"]
1044) =>
1045<
1046 const Tag extends string,
1047 Payload extends Schema.Top | Schema.Struct.Fields = Schema.Void,
1048 Success extends Schema.Top = Schema.Void,
1049 Error extends Schema.Top = Schema.Never,
1050 const Stream extends boolean = false,
1051 Out extends Custom.OutDefault = Custom.Kind<Def, Success, Error>
1052>(tag: Tag, options?: {
1053 readonly payload?: Payload
1054 readonly success?: Success
1055 readonly error?: Error
1056 readonly defect?: DefectSchema
1057 readonly stream?: Stream
1058 readonly primaryKey?: [Payload] extends [Schema.Struct.Fields] ? ((
1059 payload: Payload extends Schema.Struct.Fields ? Struct.Simplify<Schema.Struct<Payload>["Type"]> : Payload["Type"]
1060 ) => string) :
1061 never
1062}): Rpc<
1063 Tag,
1064 Payload extends Schema.Struct.Fields ? Schema.Struct<Payload> : Payload,
1065 Stream extends true ? RpcSchema.Stream<Out["success"], Out["error"]> : Out["success"],
1066 Stream extends true ? typeof Schema.Never : Out["error"]
1067> => {
1068 const success = options?.success ?? Schema.Void
1069 const error = options?.error ?? Schema.Never
1070 const defect = options?.defect ?? Schema.Defect()
1071 const out = f({
1072 success,
1073 error,
1074 defect
1075 })
1076 return make(tag, {
1077 ...out,
1078 primaryKey: options?.primaryKey,
1079 payload: options?.payload,
1080 stream: options?.stream
1081 }) as any
1082}
1083
1084/**
1085 * Defines the type-level contract for an RPC custom constructor.
1086 *
1087 * **Details**
1088 *
1089 * A custom constructor receives the original success, error, and defect schemas
1090 * and returns transformed output schemas through `out`.
1091 *
1092 * @stability unstable
1093 * @category constructors
1094 * @since 4.0.0
1095 */
1096export interface Custom {
1097 readonly out: Custom.OutDefault
1098 readonly success: Schema.Top
1099 readonly error: Schema.Top
1100 readonly defect: DefectSchema
1101}
1102
1103/**
1104 * Helper types for defining RPC custom constructors.
1105 *
1106 * @stability unstable
1107 * @since 4.0.0
1108 */
1109export declare namespace Custom {
1110 /**
1111 * The transformed schemas produced by a custom RPC constructor.
1112 *
1113 * @stability unstable
1114 * @category constructors
1115 * @since 4.0.0
1116 */
1117 export interface Out<
1118 Success extends Schema.Constraint,
1119 Error extends Schema.Constraint
1120 > {
1121 readonly success: Success
1122 readonly error: Error
1123 readonly defect: DefectSchema
1124 }
1125
1126 /**
1127 * The default custom-constructor output shape for arbitrary success and error
1128 * schemas.
1129 *
1130 * @stability unstable
1131 * @category constructors
1132 * @since 4.0.0
1133 */
1134 export type OutDefault = Out<Schema.Top, Schema.Top>
1135
1136 /**
1137 * Applies a custom constructor definition to concrete success and error
1138 * schemas and returns its transformed output schema type.
1139 *
1140 * @stability unstable
1141 * @category constructors
1142 * @since 4.0.0
1143 */
1144 export type Kind<
1145 Def extends Custom,
1146 Success extends Schema.Constraint,
1147 Error extends Schema.Constraint
1148 > = (Def & {
1149 readonly success: Success
1150 readonly error: Error
1151 })["out"]
1152}
1153
1154const exitSchemaCache = new WeakMap<Any, Schema.Exit<Schema.Top, Schema.Top, DefectSchema>>()
1155
1156/**
1157 * Builds the `Schema.Exit` used to encode and decode RPC results.
1158 *
1159 * **Details**
1160 *
1161 * The failure side includes the RPC error schema, middleware error schemas, and
1162 * stream error schema for streaming RPCs. Streaming RPCs use `Schema.Void` for
1163 * the exit success value. The schema is cached per RPC definition.
1164 *
1165 * @stability unstable
1166 * @category constructors
1167 * @since 4.0.0
1168 */
1169export const exitSchema = <R extends Any>(
1170 self: R
1171): Schema.Exit<
1172 SuccessExitSchema<R>,
1173 ErrorExitSchema<R>,
1174 DefectSchema
1175> => {
1176 if (exitSchemaCache.has(self)) {
1177 return exitSchemaCache.get(self) as any
1178 }
1179 const rpc = self as any as AnyWithProps
1180 const failures = new Set<Schema.Top>([rpc.errorSchema])
1181 const streamSchemas = RpcSchema.getStreamSchemas(rpc.successSchema)
1182 if (Option.isSome(streamSchemas)) {
1183 failures.add(streamSchemas.value.error)
1184 }
1185 for (const middleware of rpc.middlewares) {
1186 failures.add(middleware.error)
1187 }
1188 const schema = Schema.Exit(
1189 Option.isSome(streamSchemas) ? Schema.Void : rpc.successSchema,
1190 Schema.Union([...failures]),
1191 rpc.defectSchema
1192 )
1193 exitSchemaCache.set(self, schema as any)
1194 return schema as any
1195}
1196
1197const WrapperTypeId = "~effect/rpc/Rpc/Wrapper"
1198
1199/**
1200 * Wraps a handler result with execution options for the RPC server.
1201 *
1202 * **Details**
1203 *
1204 * `fork` requests concurrent execution, and `uninterruptible` requests
1205 * uninterruptible execution.
1206 *
1207 * @stability unstable
1208 * @category wrapping
1209 * @since 4.0.0
1210 */
1211export interface Wrapper<A> {
1212 readonly [WrapperTypeId]: typeof WrapperTypeId
1213 readonly value: A
1214 readonly fork: boolean
1215 readonly uninterruptible: boolean
1216}
1217
1218/**
1219 * A value that may be returned directly or wrapped with RPC server execution
1220 * options.
1221 *
1222 * @stability unstable
1223 * @category wrapping
1224 * @since 4.0.0
1225 */
1226export type WrapperOr<A> = A | Wrapper<A>
1227
1228/**
1229 * Returns `true` when the value is an RPC `Wrapper`.
1230 *
1231 * @stability unstable
1232 * @category guards
1233 * @since 4.0.0
1234 */
1235export const isWrapper = (u: object): u is Wrapper<any> => WrapperTypeId in u
1236
1237/**
1238 * Wraps a handler result with RPC server execution options.
1239 *
1240 * **Details**
1241 *
1242 * When the value is already wrapped, unspecified options are inherited from the
1243 * existing wrapper.
1244 *
1245 * @stability unstable
1246 * @category wrapping
1247 * @since 4.0.0
1248 */
1249export const wrap = (options: {
1250 readonly fork?: boolean | undefined
1251 readonly uninterruptible?: boolean | undefined
1252}) =>
1253<A extends object>(value: A): Wrapper<A> =>
1254 isWrapper(value) ?
1255 {
1256 [WrapperTypeId]: WrapperTypeId,
1257 value: value.value,
1258 fork: options.fork ?? value.fork,
1259 uninterruptible: options.uninterruptible ?? value.uninterruptible
1260 } :
1261 {
1262 [WrapperTypeId]: WrapperTypeId,
1263 value,
1264 fork: options.fork ?? false,
1265 uninterruptible: options.uninterruptible ?? false
1266 }
1267
1268/**
1269 * Returns the wrapped response value when the input is an RPC `Wrapper`, or the
1270 * input itself when it is already unwrapped.
1271 *
1272 * @stability unstable
1273 * @category wrapping
1274 * @since 4.0.0
1275 */
1276export const unwrap = <A extends object>(value: WrapperOr<A>): A => isWrapper(value) ? value.value : value
1277
1278/**
1279 * Maps the value inside an RPC wrapper, preserving wrapper options such as
1280 * `fork` and `uninterruptible`; unwrapped values are mapped directly.
1281 *
1282 * @stability unstable
1283 * @category wrapping
1284 * @since 4.0.0
1285 */
1286export const wrapMap = <A extends object, B extends object>(self: WrapperOr<A>, f: (value: A) => B): WrapperOr<B> => {
1287 if (isWrapper(self)) {
1288 return wrap(self)(f(self.value))
1289 }
1290 return f(self)
1291}
1292
1293/**
1294 * Wraps a response Effect or Stream so the RPC server executes it concurrently
1295 * regardless of the server concurrency setting.
1296 *
1297 * @stability unstable
1298 * @category wrapping
1299 * @since 4.0.0
1300 */
1301export const fork: <A extends object>(value: A) => Wrapper<A> = wrap({ fork: true })
1302
1303/**
1304 * Wraps a response Effect or Stream so the RPC server runs it in an uninterruptible region.
1305 *
1306 * @stability unstable
1307 * @category wrapping
1308 * @since 4.0.0
1309 */
1310export const uninterruptible: <A extends object>(value: A) => Wrapper<A> = wrap({ uninterruptible: true })