Created
January 16, 2026 08:28
-
-
Save dbrxnds/b052ba3df36115f650c537e7d5de3ffd to your computer and use it in GitHub Desktop.
Effect + tRPC ProcedureResult utility.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Cause, Data, Effect, Predicate, type Types } from "effect" | |
| declare const ProcedureResultTaggedErrorBrand: unique symbol | |
| export interface TaggedError< | |
| Tag extends string = string, | |
| Serialized extends { readonly _tag: Tag } = { readonly _tag: Tag }, | |
| > { | |
| readonly _tag: Tag | |
| readonly [ProcedureResultTaggedErrorBrand]: Serialized | |
| toJSON(): Serialized | |
| } | |
| export type FailureResult<E extends TaggedError> = { | |
| readonly _type: "Failure" | |
| } & E[typeof ProcedureResultTaggedErrorBrand] | |
| /** | |
| * Creates an error class that indicates it can safely be sent to the client via tRPC. | |
| * Works like `Data.TaggedError` but adds type branding for use with `toResult`. | |
| * | |
| * @example | |
| * ```ts | |
| * class MissingPermissionError extends ProcedureResult.TaggedError("MissingPermissionError")<{ | |
| * permission: string | |
| * }> {} | |
| * | |
| * // No fields: | |
| * class InternalServerError extends ProcedureResult.TaggedError("InternalServerError") {} | |
| * ``` | |
| */ | |
| /* eslint-disable @typescript-eslint/no-empty-object-type */ | |
| export const TaggedError = <Tag extends string>( | |
| tag: Tag, | |
| ): new <A extends Record<string, unknown> = {}>( | |
| ...args: Types.Equals<A, {}> extends true | |
| ? [] | |
| : [{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }] | |
| ) => Cause.YieldableError & { readonly _tag: Tag } & Readonly<A> & | |
| TaggedError<Tag, { readonly _tag: Tag } & Readonly<A>> => { | |
| return Data.TaggedError(tag) as never | |
| } | |
| export class InternalServerError extends TaggedError("InternalServerError") {} | |
| export type SuccessResult<A> = { readonly _type: "Success"; readonly _tag: "Success"; value: A } | |
| export type ProcedureResult<A, E extends TaggedError> = SuccessResult<A> | FailureResult<E> | |
| export type Success<T extends ProcedureResult<unknown, TaggedError>> = [T] extends [ | |
| ProcedureResult<infer A, TaggedError>, | |
| ] | |
| ? SuccessResult<A> | |
| : never | |
| export type Failure<T extends ProcedureResult<unknown, TaggedError>> = [T] extends [ | |
| ProcedureResult<object, infer E>, | |
| ] | |
| ? FailureResult<E> | |
| : never | |
| export function isSuccess<A, E extends TaggedError>( | |
| result: Nullish<ProcedureResult<A, E>>, | |
| ): result is SuccessResult<A> { | |
| return Predicate.isNotNullable(result) && result._type === "Success" | |
| } | |
| export function isFailure<A, E extends TaggedError>( | |
| result: Nullish<ProcedureResult<A, E>>, | |
| ): result is FailureResult<E> { | |
| return Predicate.isNotNullable(result) && result._type === "Failure" | |
| } | |
| export function match<A, E extends TaggedError, T1, T2>( | |
| result: ProcedureResult<A, E>, | |
| options: { | |
| onSuccess: (data: SuccessResult<A>["value"]) => T1 | |
| onFailure: (error: FailureResult<E>) => T2 | |
| }, | |
| ): T1 | T2 { | |
| return isSuccess(result) ? options.onSuccess(result.value) : options.onFailure(result) | |
| } | |
| /** | |
| * Converts an effect to a `ProcedureResult`. | |
| * Only `ProcedureResult.TaggedError` is supported as an error type. See `ProcedureResult.TaggedError` for more information. | |
| * | |
| * @example | |
| * ```ts | |
| * .pipe( | |
| * ...effects, | |
| * ProcedureResult.toResult, | |
| * ) | |
| * ``` | |
| * | |
| */ | |
| export function toResult<A, E extends TaggedError, R>( | |
| effect: Effect.Effect<A, E, R>, | |
| ): Effect.Effect<ProcedureResult<A, E | InternalServerError>, never, R> { | |
| return effect.pipe( | |
| Effect.matchCause({ | |
| onSuccess: (value) => ({ _type: "Success", _tag: "Success", value }), | |
| onFailure: (cause) => { | |
| const error = Cause.isFailType(cause) | |
| ? cause.error.toJSON() | |
| : new InternalServerError().toJSON() | |
| return { | |
| _type: "Failure", | |
| ...(typeof error === "object" && error), | |
| } as FailureResult<E | InternalServerError> | |
| }, | |
| }), | |
| ) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment