ExperimentalContracts and OpenAPI might change or go away in any release. Stability
Generating a client with Hey API
Generate a typed SDK of the REST routes from the OpenAPI document, with Hey API.
On this page
Hey API (@hey-api/openapi-ts) turns an OpenAPI document into a TypeScript SDK, with plugins for validators, TanStack Query, mocks and more. Point it at the document experimental_generateOpenApi writes, and apps that can't import the router's type get typed functions for its REST routes: another team's app, a public API's users, another repository. An app that can import the router's type is better served by the tRPC client, which needs no generation step.
The document
This router serves the blog over REST: a query with a declared NOT_FOUND error, a mutation behind a bearer token that takes a file, and a subscription. The document declares the bearer scheme that authed routes require (Security and tags):
/**
* The blog of `./server.ts` over REST, documented for clients generated from
* OpenAPI. `pnpm --filter @samples/docs generate:api` writes its document to
* `../openapi.json`, and Hey API writes `./api/` from that.
*/
import {
function experimental_generateOpenApi(router: object, opts: GenerateOpenApiOptions): OpenApiDocumentexperimental_generateOpenApi,
function experimental_openapi(): ProcedureBuilder<RootDef<{}, OpenApiMeta>>experimental_openapi,
} from '@trpc12/trpc/openapi';
import { function error<const TCode extends string, TData = undefined>(opts: Omit<TrpcErrorOptions<TCode, TData>, "defined"> & ReservedCodeCheck<TCode>): TrpcError<TCode, TData>error, function initTrpc<TConfig extends RootConfig = {}>(opts?: RootOptions<TConfig["meta"] extends object ? TConfig["meta"] : {}>): TrpcRoot<TConfig["ctx"] extends object ? TConfig["ctx"] : {}, TConfig["meta"] extends object ? TConfig["meta"] : {}>initTrpc, function tracked<TData>(id: string, data: TData): TrackedEnvelope<TData>tracked } from '@trpc12/trpc/server';
import { import zz } from 'zod';
import { const db: {
post: {
find: (id: string) => Post | undefined;
list: () => Post[];
create: (input: {
title: string;
author?: string | null;
}) => Post;
};
}
db, const PostSchema: z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
author: z.ZodNullable<z.ZodString>;
createdAt: z.ZodDate;
}, z.core.$strip>
PostSchema, type Context } from './server.ts';
const const t: TrpcRoot<Context, {
route?: Route | undefined;
}, ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>
t = initTrpc<{
ctx: Context;
}>(opts?: RootOptions<{}>): TrpcRoot<Context, {}, RootDef<Context, {}>>
initTrpc<{ ctx: Contextctx: Context }>().TrpcRoot<Context, {}, RootDef<Context, {}>>.concat<RootDef<{}, OpenApiMeta>>(extension: ProcedureBuilder<RootDef<{}, OpenApiMeta>>): TrpcRoot<Context, {
route?: Route | undefined;
}, ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>
concat(function experimental_openapi(): ProcedureBuilder<RootDef<{}, OpenApiMeta>>experimental_openapi());
const const authed: ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
ctx: Context;
ctxAdded: {
user: User;
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>>
authed = const t: TrpcRoot<Context, {
route?: Route | undefined;
}, ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>
t.TrpcRoot<Context, { route?: Route | undefined; }, ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>.procedure: ProcedureBuilder<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>procedure
.ProcedureBuilder<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>.use<{
user: User;
}, TrpcError<"UNAUTHORIZED", undefined>, never, Unset, {}>(fn: MiddlewareFunction<{
user: User | null;
}, undefined, {
route?: Route | undefined;
}, {
user: User;
}, TrpcError<"UNAUTHORIZED", undefined>, never, Unset>): ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
ctx: Context;
ctxAdded: {
user: User;
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>> (+1 overload)
use((opts: MiddlewareOpts<{
user: User | null;
}, undefined, {
route?: Route | undefined;
}>
opts) =>
opts: MiddlewareOpts<{
user: User | null;
}, undefined, {
route?: Route | undefined;
}>
opts.MiddlewareOpts<{ user: User | null; }, undefined, { route?: Route | undefined; }>.ctx: {
user: User | null;
}
ctx.user: User | nulluser
? opts: MiddlewareOpts<{
user: User | null;
}, undefined, {
route?: Route | undefined;
}>
opts.MiddlewareOpts<{ user: User | null; }, undefined, { route?: Route | undefined; }>.next: NextFn
<{
user: User;
}>(opts: {
ctx: {
user: User;
};
}) => Promise<MiddlewareResult<{
user: User;
}, Unset>> (+2 overloads)
next({ ctx: {
user: User;
}
ctx: { user: Useruser: opts: MiddlewareOpts<{
user: User | null;
}, undefined, {
route?: Route | undefined;
}>
opts.MiddlewareOpts<{ user: User | null; }, undefined, { route?: Route | undefined; }>.ctx: {
user: User | null;
}
ctx.user: Useruser } })
: error<"UNAUTHORIZED", undefined>(opts: Omit<TrpcErrorOptions<"UNAUTHORIZED", undefined>, "defined">): TrpcError<"UNAUTHORIZED", undefined>error({ code: "UNAUTHORIZED"code: 'UNAUTHORIZED' }),
)
.ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { ctx: Context; ctxAdded: { user: User; }; inputOut: Unset; errors: TrpcError<...>; shorts: never; }>>.meta<{
readonly route: {
readonly spec: (op: OperationSpec) => {
security: {
bearer: never[];
}[];
};
};
}>(meta: {
readonly route: {
readonly spec: (op: OperationSpec) => {
security: {
bearer: never[];
}[];
};
};
} & UndeclaredMeta<{
route?: Route | undefined;
}, {
readonly route: {
readonly spec: (op: OperationSpec) => {
security: {
bearer: never[];
}[];
};
};
}>): ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>>
meta({ route: {
readonly spec: (op: OperationSpec) => {
security: {
bearer: never[];
}[];
};
}
route: { spec: (op: OperationSpec) => {
security: {
bearer: never[];
}[];
}
spec: (op: OperationSpecop) => ({ ...op: OperationSpecop, security: {
bearer: never[];
}[]
security: [{ bearer: never[]bearer: [] }] }) } });
export const const restRouter: {
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {
route?: Route | undefined;
};
input: {
id: string;
};
output: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {
route?: Route | undefined;
};
input: {
title: string;
cover?: z.core.File | undefined;
};
output: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
onAdd: Procedure<...>;
};
}
restRouter = {
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {
route?: Route | undefined;
};
input: {
id: string;
};
output: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {
route?: Route | undefined;
};
input: {
title: string;
cover?: z.core.File | undefined;
};
output: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
onAdd: Procedure<...>;
}
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {
route?: Route | undefined;
};
input: {
id: string;
};
output: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>;
services: never;
}>
byId: const t: TrpcRoot<Context, {
route?: Route | undefined;
}, ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>
t.TrpcRoot<Context, { route?: Route | undefined; }, ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>.procedure: ProcedureBuilder<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>procedure
.ProcedureBuilder<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>.meta<{
readonly route: {
readonly method: "GET";
readonly path: "/posts/{id}";
};
}>(meta: {
readonly route: {
readonly method: "GET";
readonly path: "/posts/{id}";
};
} & UndeclaredMeta<{
route?: Route | undefined;
}, {
readonly route: {
readonly method: "GET";
readonly path: "/posts/{id}";
};
}>): ProcedureBuilder<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>
meta({ route: {
readonly method: "GET";
readonly path: "/posts/{id}";
}
route: { method: "GET"method: 'GET', path: "/posts/{id}"path: '/posts/{id}' } })
.ProcedureBuilder<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>.errors<{
readonly NOT_FOUND: {
readonly data: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>;
};
}>(map: {
readonly NOT_FOUND: {
readonly data: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>;
};
} & ReservedKeyCheck): ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
}>>
errors({ type NOT_FOUND: {
readonly data: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>;
}
NOT_FOUND: { data: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>
data: import zz.function object<{
id: z.ZodString;
}>(shape?: {
id: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>
object({ id: z.ZodStringid: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }) } })
.ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { declared: { NOT_FOUND: TrpcError<"NOT_FOUND", { id: string; }>; }; errors: TrpcError<...>; }>>.input<z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>> (+1 overload)
input(import zz.function object<{
id: z.ZodString;
}>(shape?: {
id: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>
object({ id: z.ZodStringid: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.ProcedureBuilder<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { declared: { NOT_FOUND: TrpcError<"NOT_FOUND", { id: string; }>; }; errors: TrpcError<...>; }>, ZodObject<...>>>.output<z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
author: z.ZodNullable<z.ZodString>;
createdAt: z.ZodDate;
}, z.core.$strip>>(schema: z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
author: z.ZodNullable<z.ZodString>;
createdAt: z.ZodDate;
}, z.core.$strip>, opts?: {
validate?: true;
}): ProcedureBuilder<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
};
errors: TrpcError<...>;
}>, z.ZodObject<...>>, {
...;
}>> (+1 overload)
output(const PostSchema: z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
author: z.ZodNullable<z.ZodString>;
createdAt: z.ZodDate;
}, z.core.$strip>
PostSchema)
.ProcedureBuilder<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { declared: { NOT_FOUND: TrpcError<"NOT_FOUND", { id: string; }>; }; errors: TrpcError<...>; }>, ZodObject<...>>, { ...; }>>.query<Post | TrpcError<"NOT_FOUND", {
id: string;
}>>(resolver: (opts: ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>, {
outputIn: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
outputOut: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
requires: never;
}>>) => Post | ... 1 more ... | TrpcError<...>): Procedure<...>
query(
(opts: ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>, {
outputIn: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
outputOut: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
requires: never;
}>>
opts) =>
const db: {
post: {
find: (id: string) => Post | undefined;
list: () => Post[];
create: (input: {
title: string;
author?: string | null;
}) => Post;
};
}
db.post: {
find: (id: string) => Post | undefined;
list: () => Post[];
create: (input: {
title: string;
author?: string | null;
}) => Post;
}
post.find: (id: string) => Post | undefinedfind(opts: ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>, {
outputIn: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
outputOut: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
requires: never;
}>>
opts.ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { declared: { NOT_FOUND: TrpcError<"NOT_FOUND", { id: string; }>; }; errors: TrpcError<...>; }>, ZodObject<...>>, { ...; }>>.input: {
id: string;
}
input.id: stringid) ??
error<"NOT_FOUND", {
id: string;
}>(opts: Omit<TrpcErrorOptions<"NOT_FOUND", {
id: string;
}>, "defined">): TrpcError<"NOT_FOUND", {
id: string;
}>
error({ code: "NOT_FOUND"code: 'NOT_FOUND', data?: {
id: string;
} | undefined
data: { id: stringid: opts: ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>, {
outputIn: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
outputOut: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
requires: never;
}>>
opts.ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { declared: { NOT_FOUND: TrpcError<"NOT_FOUND", { id: string; }>; }; errors: TrpcError<...>; }>, ZodObject<...>>, { ...; }>>.input: {
id: string;
}
input.id: stringid } }),
),
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {
route?: Route | undefined;
};
input: {
title: string;
cover?: z.core.File | undefined;
};
output: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>
create: const authed: ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
ctx: Context;
ctxAdded: {
user: User;
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>>
authed
.ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { ctx: Context; ctxAdded: { user: User; }; inputOut: Unset; errors: TrpcError<...>; shorts: never; }>>.meta<{
readonly route: {
readonly method: "POST";
readonly path: "/posts";
readonly successStatus: 201;
};
}>(meta: {
readonly route: {
readonly method: "POST";
readonly path: "/posts";
readonly successStatus: 201;
};
} & UndeclaredMeta<{
route?: Route | undefined;
}, {
readonly route: {
readonly method: "POST";
readonly path: "/posts";
readonly successStatus: 201;
};
}>): ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
ctx: Context;
ctxAdded: {
user: User;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>>
meta({ route: {
readonly method: "POST";
readonly path: "/posts";
readonly successStatus: 201;
}
route: { method: "POST"method: 'POST', path: "/posts"path: '/posts', successStatus: 201successStatus: 201 } })
.ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { ctx: Context; ctxAdded: { user: User; }; inputOut: Unset; errors: TrpcError<...>; shorts: never; }>>.input<z.ZodObject<{
title: z.ZodString;
cover: z.ZodOptional<z.ZodFile>;
}, z.core.$strip>>(schema: z.ZodObject<{
title: z.ZodString;
cover: z.ZodOptional<z.ZodFile>;
}, z.core.$strip>): ProcedureBuilder<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
ctx: Context;
ctxAdded: {
user: User;
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>, z.ZodObject<{
title: z.ZodString;
cover: z.ZodOptional<z.ZodFile>;
}, z.core.$strip>>> (+1 overload)
input(import zz.function object<{
title: z.ZodString;
cover: z.ZodOptional<z.ZodFile>;
}>(shape?: {
title: z.ZodString;
cover: z.ZodOptional<z.ZodFile>;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
title: z.ZodString;
cover: z.ZodOptional<z.ZodFile>;
}, z.core.$strip>
object({ title: z.ZodStringtitle: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string()._ZodString<$ZodStringInternals<string>>.min(minLength: number, params?: string | z.core.$ZodCheckMinLengthParams): z.ZodStringmin(1), cover: z.ZodOptional<z.ZodFile>cover: import zz.function file(params?: string | z.core.$ZodFileParams): z.ZodFilefile().ZodType<any, any, $ZodFileInternals>.optional(): z.ZodOptional<z.ZodFile>optional() }))
.ProcedureBuilder<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { ctx: Context; ctxAdded: { user: User; }; inputOut: Unset; errors: TrpcError<...>; shorts: never; }>, ZodObject<...>>>.output<z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
author: z.ZodNullable<z.ZodString>;
createdAt: z.ZodDate;
}, z.core.$strip>>(schema: z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
author: z.ZodNullable<z.ZodString>;
createdAt: z.ZodDate;
}, z.core.$strip>, opts?: {
validate?: true;
}): ProcedureBuilder<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
ctx: Context;
ctxAdded: {
user: User;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, z.ZodObject<...>>, {
...;
}>> (+1 overload)
output(const PostSchema: z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
author: z.ZodNullable<z.ZodString>;
createdAt: z.ZodDate;
}, z.core.$strip>
PostSchema)
.ProcedureBuilder<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { ctx: Context; ctxAdded: { user: User; }; inputOut: Unset; errors: TrpcError<...>; shorts: never; }>, ZodObject<...>>, { ...; }>>.mutation<Post>(resolver: (opts: ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
ctx: Context;
ctxAdded: {
user: User;
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>, z.ZodObject<{
title: z.ZodString;
cover: z.ZodOptional<z.ZodFile>;
}, z.core.$strip>>, {
outputIn: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
outputOut: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
requires: never;
}>>) => Post | Returns<...>): Procedure<...>
mutation((opts: ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
ctx: Context;
ctxAdded: {
user: User;
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>, z.ZodObject<{
title: z.ZodString;
cover: z.ZodOptional<z.ZodFile>;
}, z.core.$strip>>, {
outputIn: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
outputOut: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
requires: never;
}>>
opts) =>
const db: {
post: {
find: (id: string) => Post | undefined;
list: () => Post[];
create: (input: {
title: string;
author?: string | null;
}) => Post;
};
}
db.post: {
find: (id: string) => Post | undefined;
list: () => Post[];
create: (input: {
title: string;
author?: string | null;
}) => Post;
}
post.create: (input: {
title: string;
author?: string | null;
}) => Post
create({ title: stringtitle: opts: ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
ctx: Context;
ctxAdded: {
user: User;
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>, z.ZodObject<{
title: z.ZodString;
cover: z.ZodOptional<z.ZodFile>;
}, z.core.$strip>>, {
outputIn: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
outputOut: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
requires: never;
}>>
opts.ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { ctx: Context; ctxAdded: { user: User; }; inputOut: Unset; errors: TrpcError<...>; shorts: never; }>, ZodObject<...>>, { ...; }>>.input: {
title: string;
cover?: z.core.File | undefined;
}
input.title: stringtitle, author?: string | null | undefinedauthor: opts: ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
ctx: Context;
ctxAdded: {
user: User;
};
inputOut: Unset;
errors: TrpcError<"UNAUTHORIZED", undefined>;
shorts: never;
}>, z.ZodObject<{
title: z.ZodString;
cover: z.ZodOptional<z.ZodFile>;
}, z.core.$strip>>, {
outputIn: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
outputOut: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
requires: never;
}>>
opts.ResolverOpts<With<InputDef<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { ctx: Context; ctxAdded: { user: User; }; inputOut: Unset; errors: TrpcError<...>; shorts: never; }>, ZodObject<...>>, { ...; }>>.ctx: {
user: User;
}
ctx.user: Useruser.User.name: stringname }),
),
onAdd: Procedure<{
type: "subscription";
ctx: Context;
meta: {
route?: Route | undefined;
};
input: undefined;
output: TrackedEnvelope<{
id: string;
title: string;
author: string | null;
createdAt: Date;
}>;
errors: never;
services: never;
}>
onAdd: const t: TrpcRoot<Context, {
route?: Route | undefined;
}, ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>
t.TrpcRoot<Context, { route?: Route | undefined; }, ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>.procedure: ProcedureBuilder<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>procedure
.ProcedureBuilder<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>.meta<{
readonly route: {
readonly path: "/posts/events";
};
}>(meta: {
readonly route: {
readonly path: "/posts/events";
};
} & UndeclaredMeta<{
route?: Route | undefined;
}, {
readonly route: {
readonly path: "/posts/events";
};
}>): ProcedureBuilder<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>
meta({ route: {
readonly path: "/posts/events";
}
route: { path: "/posts/events"path: '/posts/events' } })
.ProcedureBuilder<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>>.output<z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
author: z.ZodNullable<z.ZodString>;
createdAt: z.ZodDate;
}, z.core.$strip>>(schema: z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
author: z.ZodNullable<z.ZodString>;
createdAt: z.ZodDate;
}, z.core.$strip>, opts?: {
validate?: true;
}): ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
outputIn: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
outputOut: {
...;
};
requires: never;
}>> (+1 overload)
output(const PostSchema: z.ZodObject<{
id: z.ZodString;
title: z.ZodString;
author: z.ZodNullable<z.ZodString>;
createdAt: z.ZodDate;
}, z.core.$strip>
PostSchema)
.ProcedureBuilder<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, { outputIn: { id: string; title: string; author: string | null; createdAt: Date; }; outputOut: { ...; }; requires: never; }>>.subscription<AsyncGenerator<TrackedEnvelope<Post>, void, unknown>>(resolver: (opts: SubscriptionResolverOpts<With<ExtendedDef<RootDef<Context, {}>, RootDef<{}, OpenApiMeta>>, {
outputIn: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
outputOut: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
requires: never;
}>>) => Yields<{
id: string;
title: string;
author: string | null;
createdAt: Date;
} | TrackedEnvelope<...>, AnyTrpcError> | AsyncGenerator<...>): Procedure<...>
subscription(async function* () {
for (const const post: Postpost of const db: {
post: {
find: (id: string) => Post | undefined;
list: () => Post[];
create: (input: {
title: string;
author?: string | null;
}) => Post;
};
}
db.post: {
find: (id: string) => Post | undefined;
list: () => Post[];
create: (input: {
title: string;
author?: string | null;
}) => Post;
}
post.list: () => Post[]list()) yield tracked<Post>(id: string, data: Post): TrackedEnvelope<Post>tracked(const post: Postpost.Post.id: stringid, const post: Postpost);
}),
},
};
export const const document: OpenApiDocumentdocument = function experimental_generateOpenApi(router: object, opts: GenerateOpenApiOptions): OpenApiDocumentexperimental_generateOpenApi(const restRouter: {
post: {
byId: Procedure<{
type: "query";
ctx: Context;
meta: {
route?: Route | undefined;
};
input: {
id: string;
};
output: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | TrpcError<"NOT_FOUND", {
id: string;
}> | SchemaValidationError<z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>;
services: never;
}>;
create: Procedure<{
type: "mutation";
ctx: Context;
meta: {
route?: Route | undefined;
};
input: {
title: string;
cover?: z.core.File | undefined;
};
output: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
errors: TrpcError<...> | SchemaValidationError<...>;
services: never;
}>;
onAdd: Procedure<...>;
};
}
restRouter, {
GenerateOpenApiOptions.info: OpenApiInfoinfo: { OpenApiInfo.title: stringtitle: 'Blog', OpenApiInfo.version: stringversion: '1.0.0' },
GenerateOpenApiOptions.servers?: readonly OpenApiServer[] | undefinedservers: [{ OpenApiServer.url: stringurl: 'http://localhost:3000' }],
GenerateOpenApiOptions.components?: {
readonly schemas?: Readonly<Record<string, JsonSchema>>;
readonly securitySchemes?: Readonly<Record<string, OpenApiSecurityScheme>>;
} | undefined
components: {
securitySchemes?: Readonly<Record<string, OpenApiSecurityScheme>> | undefinedsecuritySchemes: { bearer: {
type: "http";
scheme: string;
}
bearer: { type: "http"type: 'http', scheme: stringscheme: 'bearer' } },
},
});A script writes it where Hey API reads it:
await function writeFile(file: PathLike | FileHandle, data: string | NodeJS.ArrayBufferView | Iterable<string | NodeJS.ArrayBufferView> | AsyncIterable<string | NodeJS.ArrayBufferView>, options?: (ObjectEncodingOptions & {
mode?: Mode | undefined;
flag?: OpenMode | undefined;
flush?: boolean | undefined;
} & EventEmitter<T extends EventMap<T> = any>.Abortable) | BufferEncoding | null): Promise<void>
writeFile('openapi.json', var JSON: JSONJSON.JSON.stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string (+1 overload)stringify(const document: OpenApiDocumentdocument, null, 2));Generating the SDK
// @filename: src/openapi-ts.config.ts
import { function defineConfig(config: LazyOrAsync<ReadonlyArray<UserConfig>>): Promise<ReadonlyArray<UserConfig>> (+1 overload)defineConfig } from '@hey-api/openapi-ts';
export default function defineConfig(config: LazyOrAsync<UserConfig>): Promise<UserConfig> (+1 overload)defineConfig({
input: MaybeArray<UserInput | ApiRegistryShorthands | AnyString | JsonSchema>input: './openapi.json',
output: MaybeArray<string | UserOutput>output: 'src/api',
plugins?: readonly (keyof PluginConfigMap | (Plugin.Name<"@hey-api/client-fetch"> & Plugin.Hooks & UserIndexExportOption & {
baseUrl?: string | number | boolean;
bundle?: boolean;
runtimeConfigPath?: string;
strictBaseUrl?: boolean;
} & {
throwOnError?: boolean;
} & {
name: "@hey-api/client-fetch";
}) | (Plugin.Name<"@hey-api/typescript"> & Plugin.Hooks & UserCommentsOption & UserIndexExportOption & HeyApiTypeScriptResolvers & {
...;
} & {
...;
}) | ... 26 more ... | (Plugin.Name<...> & ... 5 more ... & {
...;
}))[] | undefined
plugins: [
'@hey-api/client-fetch',
'@hey-api/typescript',
{ name: "@hey-api/sdk"name: '@hey-api/sdk', transformer?: boolean | PluginTransformerNames | {
response?: PluginTransformerNames | boolean;
} | undefined
transformer: true },
{ name: "@hey-api/transformers"name: '@hey-api/transformers', dates?: boolean | "date" | "temporal" | undefineddates: true },
],
});npx @hey-api/openapi-tsHey API 0.99 needs TypeScript 5 or 6: it uses TypeScript's JavaScript API, which TypeScript 7 doesn't have. A project on TypeScript 7, which experimental_generateOpenApiFromTypes needs, can run it with TypeScript 6 alongside: pnpm dlx --package=@hey-api/openapi-ts --package=typescript@6 openapi-ts.
What the document gives Hey API:
- Each
operationIdis the procedure's path, sopost.byIdispostById().route.operationIdrenames one. - Each declared error is a response whose
codeis a constant. Thedefaultresponse leaves out the codes the operation documents, soerror.defined && error.code === 'NOT_FOUND'narrows to the declared error and itsdata(The document). - Operations with
securitysend the credentials from the client'sauthoption. - A body with a file is
multipart/form-data, so the SDK sends aFormData(Inputs). Dates aredate-timestrings, whichtransformerswithdates: trueturns back intoDates.
Calling the API
import { const client: Clientclient } from './api/client.gen';
import { const postById: <ThrowOnError extends boolean = false>(options: Options<PostByIdData, ThrowOnError>) => RequestResult<PostByIdResponses, PostByIdErrors, ThrowOnError>postById, const postCreate: <ThrowOnError extends boolean = false>(options: Options<PostCreateData, ThrowOnError>) => RequestResult<PostCreateResponses, PostCreateErrors, ThrowOnError>postCreate } from './api';
const client: Clientclient.setConfig: (config: Config<ClientOptions>) => Config<ClientOptions>setConfig({
Config<ClientOptions>.baseUrl?: string | undefinedbaseUrl: 'https://api.example.com',
Config.auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthTokenauth: () => var localStorage: StoragelocalStorage.Storage.getItem(key: string): string | nullgetItem('token') ?? var undefinedundefined,
});
const const result: ({
data: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
error: undefined;
} | {
data: undefined;
error: {
defined: true;
code: "BAD_REQUEST";
message: string;
data: {
issues: Array<{
message: string;
path?: Array<string | number>;
}>;
};
} | {
defined: true;
code: "NOT_FOUND";
message: string;
data: {
id: string;
};
} | TrpcUntypedError | {
defined: true;
code: "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "UNAUTHORIZED" | "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";
message: string;
data?: unknown;
};
}) & {
...;
}
result = await postById<false>(options: Options<PostByIdData, false, unknown>): Promise<({
data: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
error: undefined;
} | {
data: undefined;
error: {
defined: true;
code: "BAD_REQUEST";
message: string;
data: {
issues: Array<{
message: string;
path?: Array<string | number>;
}>;
};
} | {
defined: true;
code: "NOT_FOUND";
message: string;
data: {
id: string;
};
} | TrpcUntypedError | {
...;
};
}) & {
...;
}>
postById({ path: {
id: string;
}
path: { id: stringid: '1' } });
if (const result: ({
data: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
error: undefined;
} | {
data: undefined;
error: {
defined: true;
code: "BAD_REQUEST";
message: string;
data: {
issues: Array<{
message: string;
path?: Array<string | number>;
}>;
};
} | {
defined: true;
code: "NOT_FOUND";
message: string;
data: {
id: string;
};
} | TrpcUntypedError | {
defined: true;
code: "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "UNAUTHORIZED" | "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";
message: string;
data?: unknown;
};
}) & {
...;
}
result.error: {
defined: true;
code: "BAD_REQUEST";
message: string;
data: {
issues: Array<{
message: string;
path?: Array<string | number>;
}>;
};
} | {
defined: true;
code: "NOT_FOUND";
message: string;
data: {
id: string;
};
} | TrpcUntypedError | {
defined: true;
code: "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "UNAUTHORIZED" | "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";
message: string;
data?: unknown;
} | undefined
error) {
const const error: {
defined: true;
code: "BAD_REQUEST";
message: string;
data: {
issues: Array<{
message: string;
path?: Array<string | number>;
}>;
};
} | {
defined: true;
code: "NOT_FOUND";
message: string;
data: {
id: string;
};
} | TrpcUntypedError | {
defined: true;
code: "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "UNAUTHORIZED" | "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";
message: string;
data?: unknown;
}
error = const result: {
data: undefined;
error: {
defined: true;
code: "BAD_REQUEST";
message: string;
data: {
issues: Array<{
message: string;
path?: Array<string | number>;
}>;
};
} | {
defined: true;
code: "NOT_FOUND";
message: string;
data: {
id: string;
};
} | TrpcUntypedError | {
defined: true;
code: "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "UNAUTHORIZED" | "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";
message: string;
data?: unknown;
};
} & {
...;
}
result.error: {
defined: true;
code: "BAD_REQUEST";
message: string;
data: {
issues: Array<{
message: string;
path?: Array<string | number>;
}>;
};
} | {
defined: true;
code: "NOT_FOUND";
message: string;
data: {
id: string;
};
} | TrpcUntypedError | {
defined: true;
code: "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "UNAUTHORIZED" | "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";
message: string;
data?: unknown;
}
error;
if (const error: {
defined: true;
code: "BAD_REQUEST";
message: string;
data: {
issues: Array<{
message: string;
path?: Array<string | number>;
}>;
};
} | {
defined: true;
code: "NOT_FOUND";
message: string;
data: {
id: string;
};
} | TrpcUntypedError | {
defined: true;
code: "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "UNAUTHORIZED" | "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";
message: string;
data?: unknown;
}
error.defined: booleandefined && const error: {
defined: true;
code: "BAD_REQUEST";
message: string;
data: {
issues: Array<{
message: string;
path?: Array<string | number>;
}>;
};
} | {
defined: true;
code: "NOT_FOUND";
message: string;
data: {
id: string;
};
} | {
defined: true;
code: "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "UNAUTHORIZED" | "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";
message: string;
data?: unknown;
}
error.code: "BAD_REQUEST" | "NOT_FOUND" | "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "UNAUTHORIZED" | "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"code === 'NOT_FOUND') {
var console: Consoleconsole.Console.log(...data: any[]): voidlog(`No post ${const error: {
defined: true;
code: "NOT_FOUND";
message: string;
data: {
id: string;
};
}
error.data: {
id: string;
}
data.id: stringid}`);
} else {
var console: Consoleconsole.Console.log(...data: any[]): voidlog(const error: {
defined: true;
code: "BAD_REQUEST";
message: string;
data: {
issues: Array<{
message: string;
path?: Array<string | number>;
}>;
};
} | TrpcUntypedError | {
defined: true;
code: "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "UNAUTHORIZED" | "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";
message: string;
data?: unknown;
}
error.message: stringmessage);
}
} else {
var console: Consoleconsole.Console.log(...data: any[]): voidlog(const result: {
data: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
error: undefined;
} & {
...;
}
result.data: {
id: string;
title: string;
author: string | null;
createdAt: Date;
}
data.title: stringtitle, const result: {
data: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
error: undefined;
} & {
...;
}
result.data: {
id: string;
title: string;
author: string | null;
createdAt: Date;
}
data.createdAt: DatecreatedAt.Date.getFullYear(): numbergetFullYear());
}
const const cover: Filecover = new var File: new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag) => FileFile(['…'], 'cover.png', { BlobPropertyBag.type?: string | undefinedtype: 'image/png' });
await postCreate<false>(options: Options<PostCreateData, false, unknown>): Promise<({
data: {
id: string;
title: string;
author: string | null;
createdAt: Date;
};
error: undefined;
} | {
data: undefined;
error: TrpcUntypedError | {
defined: true;
code: "BAD_REQUEST";
message: string;
data: {
issues: Array<{
message: string;
path?: Array<string | number>;
}>;
};
} | {
defined: true;
code: "PARSE_ERROR" | "UNSUPPORTED_PROTOCOL" | "UNAUTHORIZED" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "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";
message: string;
data?: unknown;
};
}) & {
...;
}>
postCreate({ body: {
title: string;
cover?: Blob | File;
}
body: { title: stringtitle: 'Hello', cover?: Blob | File | undefinedcover } });Subscriptions
A subscription is a stream of server-sent events. Its function returns { stream }, and Hey API reconnects after a dropped connection with Last-Event-ID, so tracked() events resume. Hey API yields the data of every event, though: tRPC ends a stream with a done event, whose data is empty, and sends a failure as an error event with the error as data. onSseEvent runs just before each item is yielded, so it tells them apart:
import { const postOnAdd: <ThrowOnError extends boolean = false>(options?: Options<PostOnAddData, ThrowOnError, PostOnAddResponse>) => Promise<ServerSentEventsResult<PostOnAddResponses>>postOnAdd } from './api';
let let event: string | undefinedevent: string | undefined;
const const events: ServerSentEventsResult<PostOnAddResponses>events = await postOnAdd<false>(options?: Options<PostOnAddData, false, {
id: string;
title: string;
author: string | null;
createdAt: Date;
}> | undefined): Promise<ServerSentEventsResult<PostOnAddResponses>>
postOnAdd({
onSseEvent?: ((event: StreamEvent<{
id: string;
title: string;
author: string | null;
createdAt: Date;
}>) => void) | undefined
onSseEvent: (e: StreamEvent<{
id: string;
title: string;
author: string | null;
createdAt: Date;
}>
e) => {
let event: string | undefinedevent = e: StreamEvent<{
id: string;
title: string;
author: string | null;
createdAt: Date;
}>
e.StreamEvent<TData = unknown>.event?: string | undefinedevent;
},
});
for await (const const post: {
id: string;
title: string;
author: string | null;
createdAt: Date;
}
post of const events: ServerSentEventsResult<PostOnAddResponses>events.stream: AsyncGenerator<{
id: string;
title: string;
author: string | null;
createdAt: Date;
}, void, unknown>
stream) {
if (let event: string | undefinedevent === 'done') break;
if (let event: string | undefinedevent === 'error')
throw new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error('Subscription failed', { ErrorOptions.cause?: unknowncause: const post: {
id: string;
title: string;
author: string | null;
createdAt: Date;
}
post });
var console: Consoleconsole.Console.log(...data: any[]): voidlog(const post: {
id: string;
title: string;
author: string | null;
createdAt: Date;
}
post.title: stringtitle);
}