Skip to Content
Type Inference

Type Inference

These types infer the input and output of both RPC modules and controller procedures.

Client-side inference:

import type { VovkBody, VovkQuery, VovkParams, VovkInput, VovkOutput, VovkIteration, VovkReturnType, VovkYieldType } from 'vovk'; import { UserRPC, StreamRPC } from '@/client'; // infer input type Body = VovkBody<typeof UserRPC.updateUser>; type Query = VovkQuery<typeof UserRPC.updateUser>; type Params = VovkParams<typeof UserRPC.updateUser>; type Input = VovkInput<typeof UserRPC.updateUser>; // { params, query, body } // infer output type Output = VovkOutput<typeof UserRPC.updateUser>; type Iteration = VovkIteration<typeof StreamRPC.streamTokens>; // see below type Return = VovkReturnType<typeof UserRPC.updateUser>; type Yield = VovkYieldType<typeof StreamRPC.streamTokens>;

Server-side inference:

import type { VovkBody, VovkQuery, VovkParams, VovkInput, VovkOutput, VovkIteration, VovkReturnType, VovkYieldType } from 'vovk'; import type UserController from './user-controller'; import type StreamController from './stream-controller'; // infer input type Body = VovkBody<typeof UserController.updateUser>; type Query = VovkQuery<typeof UserController.updateUser>; type Params = VovkParams<typeof UserController.updateUser>; type Input = VovkInput<typeof UserController.updateUser>; // { params, query, body } // infer output type Output = VovkOutput<typeof UserController.updateUser>; type Iteration = VovkIteration<typeof StreamController.streamTokens>; // see below type Return = VovkReturnType<typeof UserController.updateUser>; type Yield = VovkYieldType<typeof StreamController.streamTokens>;

Input Inference

The types of body, query and params come from VovkRequest<TBody, TQuery, ?TParams>, the type of the procedure’s req argument. So both raw and validated methods set the input types.

A raw method with params as a type argument:

import { put, type VovkRequest } from 'vovk'; export default class UserController { @put('{param}') static async updateUser(req: VovkRequest<{ email: string }, { id: string }, { param: string }>) { // ... } }

A raw method with params as a separate argument:

import { put, type VovkRequest } from 'vovk'; export default class UserController { @put('{param}') static async updateUser(req: VovkRequest<{ email: string }, { id: string }>, params: { param: string }) { // ... } }

Validated procedures infer the input types from their schemas:

import { procedure, put } from 'vovk'; import { z } from 'zod'; export default class UserController { @put('{param}') static updateUser = procedure({ query: z.object({ id: z.string() }), params: z.object({ param: z.string() }), body: z.object({ email: z.string().email() }), }).handle((req, params) => { // ... }); }

All three give the same RPC method:

import { UserRPC } from '@/client'; await UserRPC.updateUser({ params: { param: 'value' }, query: { id: 'value' }, body: { email: 'value' }, });

And the same inferred input types:

import type { VovkBody, VovkQuery, VovkParams } from 'vovk'; import { UserRPC } from '@/client'; type Body = VovkBody<typeof UserRPC.updateUser>; // { email: string } type Query = VovkQuery<typeof UserRPC.updateUser>; // { id: string } type Params = VovkParams<typeof UserRPC.updateUser>; // { param: string }
import type { VovkBody, VovkQuery, VovkParams } from 'vovk'; import type UserController from './user-controller'; type Body = VovkBody<typeof UserController.updateUser>; // { email: string } type Query = VovkQuery<typeof UserController.updateUser>; // { id: string } type Params = VovkParams<typeof UserController.updateUser>; // { param: string }

For a procedure, the RPC method and fn take what the schemas accept (their input types), and the handler gets what the schemas return (their output types). The two differ when a schema has a default, a coercion or a transform. With body: z.object({ tags: z.string().transform((s) => s.split(',')) }), VovkBody of the RPC method is { tags: string }, and VovkBody of the controller method is { tags: string[] }.

Combined Input Type

VovkInput<T> puts all three input types (params, query, body) in one object.

import type { VovkInput } from 'vovk'; import type UserController from './user-controller'; type Input = VovkInput<typeof UserController.updateUser>; // { params: { param: string }; query: { id: string }; body: { email: string } }

It works the same with RPC modules:

import type { VovkInput } from 'vovk'; import { UserRPC } from '@/client'; type Input = VovkInput<typeof UserRPC.updateUser>; // { params: { param: string }; query: { id: string }; body: { email: string } }

Output/Iteration Inference

Only the procedure function sets output and iteration types.

For output:

import { procedure, get } from 'vovk'; import { z } from 'zod'; export default class UserController { @get() static updateUser = procedure({ output: z.object({ success: z.boolean() }), }).handle(async (req, params) => { return { success: true }; }); }

For JSON Lines responses:

import { procedure, get } from 'vovk'; import { z } from 'zod'; export default class StreamController { @get() static streamItems = procedure({ iteration: z.object({ item: z.boolean() }), }).handle(function *(req, params) { yield { item: true }; yield { item: true }; }); }
import type { VovkOutput, VovkIteration } from 'vovk'; import { UserRPC, StreamRPC } from '@/client'; type Output = VovkOutput<typeof UserRPC.updateUser>; // { success: boolean } type Iteration = VovkIteration<typeof StreamRPC.streamItems>; // { item: boolean }
import type { VovkOutput, VovkIteration } from 'vovk'; import type UserController from './user-controller'; import type StreamController from './stream-controller'; type Output = VovkOutput<typeof UserController.updateUser>; // { success: boolean } type Iteration = VovkIteration<typeof StreamController.streamItems>; // { item: boolean }

Return/Yield Inference

VovkReturnType<T> and VovkYieldType<T> infer what a method actually returns or yields, for methods without validation. They can’t be used for self-references in services: they cause “implicit any” TypeScript errors.

export default class UserController { @get() static updateUser = () => { return { success: true }; }; }
export default class StreamController { @get() static async *streamItems() { yield { item: true }; yield { item: true }; } }
import type { VovkReturnType, VovkYieldType } from 'vovk'; import { UserRPC, StreamRPC } from '@/client'; type Return = VovkReturnType<typeof UserRPC.updateUser>; // { success: boolean } type Yield = VovkYieldType<typeof StreamRPC.streamItems>; // { item: boolean }
import type { VovkReturnType, VovkYieldType } from 'vovk'; import type UserController from './user-controller'; import type StreamController from './stream-controller'; type Return = VovkReturnType<typeof UserController.updateUser>; // { success: boolean } type Yield = VovkYieldType<typeof StreamController.streamItems>; // { item: boolean }
Last updated on