Errors without try/catch
safe() returns a result tuple whose error lists every way a call can fail; switch on its code, or let matchError() check you handled each one.
On this page
safe() wraps a call and resolves to [data, error] instead of rejecting. Check error first: once it's handled, data has the output's type, with no undefined left to check:
import { function safe<T, E = unknown>(promise: PromiseLike<T> & {
readonly "~error"?: E;
}): Promise<SafeResult<T, E>>
safe } from '@trpc12/trpc/client';
async function function title(id: string): Promise<string>title(id: stringid: string) {
const [const post: {
id: string;
title: string;
author: string | null;
createdAt: string;
} | undefined
post, const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 20 more ... | undefined
err] = await safe<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | ... 20 more ... | (TrpcError<...> & {
...;
})>(promise: PromiseLike<...> & {
...;
}): Promise<...>
safe(const client: TrpcClient<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}, {}, undefined, LinksTypes<...>>
client.post: ClientRouter<{
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
}, {}, undefined, LinksTypes<...>>
post.byId: {
query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, CallError<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}, JsonForm, never>>;
}
byId.function query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, CallError<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}, JsonForm, never>>
query({ id: stringid }));
if (const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 20 more ... | undefined
err) return const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 19 more ... | (TrpcError<...> & {
...;
})
err.Error.message: stringmessage;
return post.title: stringtitle;}Don't check data instead: an output can be undefined, null or 0 too, and the error would go unhandled. The result is also an object, { data, error }, for code that reads it without destructuring.
createSafeClient(client) returns a client whose every query and mutation is safe:
import { function createSafeClient<TClient extends object>(client: TClient): SafeClient<TClient>createSafeClient } from '@trpc12/trpc/client';
const const safeClient: SafeClient<TrpcClient<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}, {}, undefined, LinksTypes<...>>>
safeClient = createSafeClient<TrpcClient<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}, {}, undefined, LinksTypes<...>>>(client: TrpcClient<...>): SafeClient<...>
createSafeClient(const client: TrpcClient<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}, {}, undefined, LinksTypes<...>>
client);
const [const post: {
id: string;
title: string;
author: string | null;
createdAt: string;
} | undefined
post, const err: ClientError<TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>, JsonForm> | undefined
err] = await const safeClient: SafeClient<TrpcClient<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}, {}, undefined, LinksTypes<...>>>
safeClient.post: SafeClient<ClientRouter<{
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
}, {}, undefined, LinksTypes<...>>>
post.byId: SafeClient<{
query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, CallError<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}, JsonForm, never>>;
}>
byId.function query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): Promise<SafeResult<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, ClientError<TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>, JsonForm>>>
query({ id: stringid: '1' });What a call can fail with
error is a TrpcError, and its type lists every way the call can fail. Each member has a literal code, in one of two groups:
- Defined errors (
defined: true): the procedure's own. They're the errors it returns, fails with or declares on the server (Errors), its input'sBAD_REQUESTwithdata.issues, and errors links declare (Custom links). Each code has itsdatatype. - Built-in errors (
defined: false, theBuiltinErrortype): one member per built-in code, and they never havedata.
| Built-in code | When |
|---|---|
NETWORK_ERROR | The request got no response. |
CLIENT_CLOSED_REQUEST | The call's signal aborted it. |
BAD_RESPONSE | The response wasn't a tRPC one (a proxy's HTML page) or didn't decode. |
STREAM_INTERRUPTED | A stream stopped without an end or an error. |
CLIENT_ERROR | A link failed, or failed with a custom code it didn't declare. |
INTERNAL_SERVER_ERROR | The server crashed, or threw a custom code it didn't declare. The message is masked in production. |
Any other code (CONFLICT) | The server threw it without declaring it, a proxy answered with its status (401 is UNAUTHORIZED), or the server lacks the procedure (NOT_FOUND). |
Switching on the code
switch (err.code) narrows the error to the members with that code. A code can be in both groups: the server's NOT_FOUND for a missing post, and a built-in NOT_FOUND when the server doesn't have the procedure (a client deployed before its server). Until err.defined says which, data is the defined error's type or undefined:
async function function title(id: string): Promise<string>title(id: stringid: string) {
const [const post: {
id: string;
title: string;
author: string | null;
createdAt: string;
} | undefined
post, const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 20 more ... | undefined
err] = await safe<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | ... 20 more ... | (TrpcError<...> & {
...;
})>(promise: PromiseLike<...> & {
...;
}): Promise<...>
safe(const client: TrpcClient<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}, {}, undefined, LinksTypes<...>>
client.post: ClientRouter<{
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
}, {}, undefined, LinksTypes<...>>
post.byId: {
query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, CallError<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}, JsonForm, never>>;
}
byId.function query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, CallError<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}, JsonForm, never>>
query({ id: stringid }));
if (const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 20 more ... | undefined
err) {
switch (const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 19 more ... | (TrpcError<...> & {
...;
})
err.TrpcError<TCode extends string = string, TData = unknown>.code: "NOT_FOUND" | "BAD_REQUEST" | "UNAUTHORIZED" | "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "NETWORK_ERROR" | "BAD_RESPONSE" | "CLIENT_ERROR" | "STREAM_INTERRUPTED"code) {
case 'NOT_FOUND':
const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
})
err.data; return const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
})
err.defined: booleandefined ? `No post ${const err: TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}
err.TrpcError<"NOT_FOUND", { id: string; }>.data: {
id: string;
}
data.id: stringid}` : 'Please reload';
case 'NETWORK_ERROR':
return 'You are offline';
default:
return const err: (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | (TrpcError<"METHOD_NOT_SUPPORTED", undefined> & {
readonly defined: false;
}) | ... 16 more ... | (TrpcError<...> & {
...;
})
err.Error.message: stringmessage;
}
}
return const post: {
id: string;
title: string;
author: string | null;
createdAt: string;
}
post.title: stringtitle;
}Handling every defined code
matchError(err, handlers) takes a handler per defined code, each getting that error with its data, and builtin for the built-in errors, which a switch on e.code narrows further. It returns what the handler returns:
import { function matchError<E extends AnyTrpcError, H extends ErrorHandlers<E> & { readonly [K in keyof H]: K extends keyof ErrorHandlers<E> ? unknown : never; }>(err: E, handlers: H): HandlerResult<H>matchError } from '@trpc12/trpc/client';
async function function title(id: string): Promise<string>title(id: stringid: string) {
const [const post: {
id: string;
title: string;
author: string | null;
createdAt: string;
} | undefined
post, const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 20 more ... | undefined
err] = await safe<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | ... 20 more ... | (TrpcError<...> & {
...;
})>(promise: PromiseLike<...> & {
...;
}): Promise<...>
safe(const client: TrpcClient<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}, {}, undefined, LinksTypes<...>>
client.post: ClientRouter<{
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
}, {}, undefined, LinksTypes<...>>
post.byId: {
query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, CallError<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}, JsonForm, never>>;
}
byId.function query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, CallError<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}, JsonForm, never>>
query({ id: stringid }));
if (const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 20 more ... | undefined
err) {
return matchError<(TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 19 more ... | (TrpcError<...> & {
...;
}), {
...;
}>(err: (TrpcError<...> & {
readonly defined: false;
}) | ... 25 more ... | (TrpcError<...> & {
...;
}), handlers: {
...;
}): string
matchError(const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 19 more ... | (TrpcError<...> & {
...;
})
err, {
type NOT_FOUND: (e: TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}) => string
NOT_FOUND: (e: TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}
e) => `No post ${e: TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}
e.TrpcError<"NOT_FOUND", { id: string; }>.data: {
id: string;
}
data.id: stringid}`,
type BAD_REQUEST: (e: TrpcError<"BAD_REQUEST", SchemaInputErrorData<ZodObject<{
id: ZodString;
}, $strip>>> & {
readonly defined: true;
}) => string
BAD_REQUEST: (e: TrpcError<...> & {
...;
}
e) => e: TrpcError<...> & {
...;
}
e.TrpcError<"BAD_REQUEST", SchemaInputErrorData<ZodObject<{ id: ZodString; }, $strip>>>.data: SchemaInputErrorData<ZodObject<{
id: ZodString;
}, $strip>>
data.SchemaInputErrorData<ZodObject<{ id: ZodString; }, $strip>>.issues: readonly ({
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly path: readonly [] | readonly ["id"];
readonly message: string;
} | {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
readonly maximum: string | number;
readonly inclusive?: boolean | undefined;
readonly exact?: boolean | undefined;
readonly path: readonly [] | readonly ["id"];
readonly message: string;
} | {
readonly code: "too_small";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
... 4 more ...;
readonly message: string;
} | ... 8 more ... | {
...;
})[]
issues[0]?.message: stringmessage ?? 'Invalid id',
builtin: (e: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 17 more ... | (TrpcError<...> & {
readonly defined: false;
})) => string
builtin: (e: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 17 more ... | (TrpcError<...> & {
readonly defined: false;
})
e) =>
e: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 17 more ... | (TrpcError<...> & {
readonly defined: false;
})
e.TrpcError<TCode extends string = string, TData = unknown>.code: "NOT_FOUND" | "BAD_REQUEST" | "UNAUTHORIZED" | "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "NETWORK_ERROR" | "BAD_RESPONSE" | "CLIENT_ERROR" | "STREAM_INTERRUPTED"code === 'NETWORK_ERROR' ? 'You are offline' : e: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 16 more ... | (TrpcError<...> & {
readonly defined: false;
})
e.Error.message: stringmessage,
});
}
return const post: {
id: string;
title: string;
author: string | null;
createdAt: string;
}
post.title: stringtitle;
}A handler missing for one of the procedure's codes, or left over for a code it no longer has, is a type error, so adding an error on the server shows every place that must handle it:
const [, const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 20 more ... | undefined
err] = await safe<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | ... 20 more ... | (TrpcError<...> & {
...;
})>(promise: PromiseLike<...> & {
...;
}): Promise<...>
safe(const client: TrpcClient<{
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
};
}, {}, undefined, LinksTypes<...>>
client.post: ClientRouter<{
byId: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}>;
list: Procedure<{
type: "query";
ctx: Context;
meta: {};
input: undefined;
output: Post[];
errors: never;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {};
input: {
title: string;
};
output: Post;
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
}, {}, undefined, LinksTypes<...>>
post.byId: {
query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, CallError<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}, JsonForm, never>>;
}
byId.function query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, CallError<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<ZodObject<{
id: ZodString;
}, $strip>>;
services: never;
}, JsonForm, never>>
query({ id: stringid: '1' }));
if (const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 20 more ... | undefined
err) {
matchError<(TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 19 more ... | (TrpcError<...> & {
...;
}), {
...;
} & ... 1 more ... & {}>(err: (TrpcError<...> & {
readonly defined: false;
}) | ... 25 more ... | (TrpcError<...> & {
...;
}), handlers: {
...;
} & ... 1 more ... & {}): unknown
matchError(const err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 19 more ... | (TrpcError<...> & {
...;
})
err, { type NOT_FOUND: (err: TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}) => unknown
NOT_FOUND: (e: TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}
e) => `No post ${e: TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}
e.TrpcError<"NOT_FOUND", { id: string; }>.data: {
id: string;
}
data.id: stringid}`,
builtin: (err: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 17 more ... | (TrpcError<...> & {
readonly defined: false;
})) => unknown
builtin: (e: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 17 more ... | (TrpcError<...> & {
readonly defined: false;
})
e) => e: (TrpcError<"NOT_FOUND", undefined> & {
readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
readonly defined: false;
}) | ... 17 more ... | (TrpcError<...> & {
readonly defined: false;
})
e.Error.message: stringmessage,
});
}A built-in error with a defined code, like the NOT_FOUND of a procedure the server lacks, goes to builtin.
Checking a caught value
isTrpcError(value) checks a value from a catch, and isDefinedError(error) narrows to the defined errors. In the Effect client the same union is the error channel, and an error's _tag is its code, so Effect.catchTag('NETWORK_ERROR', …) handles one member.
safe() doesn't catch a framework's control flow: Next's redirect() and notFound(), which in-process calls rethrow (Server Actions), still throw.
Credits
safe(), createSafeClient() and isDefinedError() follow oRPC's client error handling. The closed error union and matchError() follow result-rpc's errors as values.