Code Generation via OpenAPI Mixins
Vovk.ts can combine the existing Vovk.ts client with modules generated from one or more OpenAPI specifications. This lets you integrate third-party APIs into a Next.js/Vovk.ts application, or use it as a standalone codegen tool—Next.js is not required. This page covers configuration options related to code generation; note that the generate command does not require a config file.
The generated exports are referred to as “RPC modules” for consistency, even though they may not represent true RPC (the client method may not call a same-named server-side procedure).
Features
Comprehensible Syntax
Unlike many OpenAPI code generators, Vovk.ts preserves a consistent call signature for every method using a single argument object, making it easy to learn and remember:
import { PetstoreRPC } from 'vovk-client';
await PetstoreRPC.updatePet({
params: { id: '123' }, // URL params (if any)
query: { hello: 'world' }, // Query params (if any)
body: { name: 'Doggo' }, // Request body (if any)
disableClientValidation: true, // Optional: disable client-side validation
init: { headers: { 'X-Custom-Header': 'value' } }, // Optional: fetch init
apiRoot: 'https://api.example.com', // Optional: override API root URL
});Client-Side Validation and Schema Availability
RPC modules generated by Vovk.ts include built-in, optional client-side validation using Ajv . You can validate input data before sending a request to ensure it conforms to the expected schema. Disable validation by passing disableClientValidation: true.
import { UserRPC } from 'vovk-client';
await UserRPC.updateUser({
// ...will throw a validation error if input data is invalid
});In addition to runtime validation, the generated code also exports the Vovk.ts schema for broader use cases. The composed client and each chunk of the segmented client export a schema object that contains an organized, easy-to-navigate Vovk.ts schema.
import { schema } from 'vovk-client';
// import { schema } from 'vovk-client/schema';The schema is also accessible on every generated method.
import { UserRPC } from 'vovk-client';
UserRPC.updateUser.schema.validation.body; // JSON Schema for request bodyUsing Function Calling
Every RPC module generated by Vovk.ts can be mapped to AI tools, making them accessible through function calling APIs.
import { createLLMTools } from 'vovk';
import { PetstoreRPC } from 'vovk-client';
const { tools } = createLLMTools({
modules: {
PetstoreRPC,
},
});
console.log(tools);
// [{ execute: (llmInput) => {}, name: 'PetstoreRPC_updatePet', description: 'Update an existing pet by Id', parameters: { body: { ... } } }, ...]Python and Rust Clients (Experimental)
Vovk.ts templates also support generating Python and Rust clients with client-side validation and the same consistent options. See the Python and Rust pages for details.
Component-Agnostic Type Inference
A good practice in OpenAPI/codegen design is to use components/schemas to define input and output data. This enables properly named types for generated client functions. However, not every OpenAPI specification follows this pattern, and extracting every input/output into components/schemas can be impractical.
Without components/schemas, many code generators produce awkward type names (e.g., ApiUsersIdPostRequest, ApiUsersIdPost200Response). This often drives developers to avoid code generation and fall back to fetch or axios with manual casting.
Vovk.ts supports component-agnostic type inference. Even if the OpenAPI spec doesn’t define components/schemas, Vovk.ts can infer input and output types using simple utilities.
import { PetstoreRPC } from 'vovk-client';
import type { VovkBody, VovkQuery, VovkParams, VovkOutput } from 'vovk';
type Body = VovkBody<typeof PetstoreRPC.updatePet>;
type Query = VovkQuery<typeof PetstoreRPC.updatePet>;
type Params = VovkParams<typeof PetstoreRPC.updatePet>;
type Output = VovkOutput<typeof PetstoreRPC.updatePet>;In the Python client, types are exposed as TypedDicts.
from vovk_client import PetstoreRPC
body: PetstoreRPC.UpdateUserBody = {}
query: PetstoreRPC.UpdateUserQuery = {}
params: PetstoreRPC.UpdateUserParams = {}
output: PetstoreRPC.UpdateUserOutput = {}For the Rust client, types are generated as nested modules that contain structs.
use vovk_client::petstore_rpc;
use user_rpc::update_user_::{
body as Body,
body_::profile as Profile, // for nested data
query as Query,
params as Params,
output as Output,
};Bundle
The TypeScript artifacts can be bundled into an npm package using the bundle command. It also creates package.json and README.md files, where the README outlines each method with self-documenting code samples. See the “Hello World” example for details.
To create a bundle, ensure package.json and tsconfig.json are present at the project root.
Getting Started
Install Dependencies
If you’re using codegen as a standalone CLI (even without package.json), install vovk-cli globally or as a dev dependency.
npm install -g vovk-cli@draftOr install vovk-cli as a dev dependency and vovk and vovk-ajv as regular dependencies:
npm install -D vovk-cli@draftnpm install vovk@draft vovk-ajv@draftIf you’re in another Node.js project and want to use the composed client (where all generated API clients are combined into a single client), install vovk-client. It re-exports files generated by Vovk.ts at the default path node_modules/.vovk-client.
npm install vovk-client@draftCreate Config File
Create a config file as described on the config page to customize code generation results. Alternatively, use the vovk-cli init command:
npx vovk-cli@draft init --channel draftA basic config file looks like this:
/** @type {import('vovk').VovkConfig} */
const config = {
outputConfig: {
imports: {
validateOnClient: 'vovk-ajv',
},
},
};
export default config;Define OpenAPI Mixins
Define a mixin as a pseudo-segment in outputConfig.segments by setting the openAPIMixin property. It accepts:
source: an object with eitherurl(remote specs),path(local specs), orobject(inline specs). Theurlvariant may include afallbackfile path used if the remote URL is unreachable.getModuleName: a string or function to name generated RPC modules. The string can benestjs-operation-id(see NestJS) or any custom string.getMethodName: a string or function to generate method names. Supported strings:nestjs-operation-id(see NestJS),camel-case-operation-id(convertsoperationIdlikeget_userstogetUsers), orauto(generates fromoperationIdor from HTTP method + path ifoperationIdis unsuitable or missing).apiRoot(optional): the API root URL, overridable per call via theapiRootoption. Required if the OAS document has noserversproperty.
Petstore example with a remote URL and a local fallback:
/** @type {import('vovk').VovkConfig} */
const config = {
outputConfig: {
imports: {
validateOnClient: 'vovk-ajv',
},
segments: {
petstore: {
openAPIMixin: {
source: {
url: 'https://petstore3.swagger.io/api/v3/openapi.json',
fallback: './.openapi-cache/petstore.json',
},
getModuleName: 'PetstoreRPC',
getMethodName: 'auto',
apiRoot: 'https://petstore3.swagger.io/api/v3',
},
},
}
},
};
export default config;This generates a single PetstoreRPC module with methods for each operation defined in the OpenAPI spec.
import { PetstoreRPC } from 'vovk-client';
await PetstoreRPC.getPets({ query: { limit: 10 } });When getModuleName or getMethodName are functions, they receive:
operationObject: the Operation Object for the operation.method: the HTTP method (uppercase string).path: the operation path.openAPIObject: the entire OpenAPI document.
For a more advanced example, consider the GitHub REST API . The operationId in the GitHub OpenAPI spec has the form scope/operation (e.g., repos/remove-status-check-contexts, codespaces/list-for-authenticated-user). We can use the first part to generate module names and the second part to generate method names via lodash.
For example, issues/list-for-org becomes the GithubIssuesRPC module with a listForOrg method.
// @ts-check
import camelCase from 'lodash/camelCase.js';
import startCase from 'lodash/startCase.js';
/** @type {import('vovk').VovkConfig} */
const config = {
outputConfig: {
imports: {
validateOnClient: 'vovk-ajv',
},
segments: {
github: {
openAPIMixin: {
source: {
url: 'https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json',
fallback: './.openapi-cache/github.json',
},
getModuleName: ({ operationObject }) => {
const [operationNs] = operationObject.operationId?.split('/') ?? ['unknown'];
return `Github${startCase(camelCase(operationNs)).replace(/ /g, '')}RPC`;
},
getMethodName: ({ operationObject }) => {
const [, operationName] = operationObject.operationId?.split('/') ?? ['', 'ERROR'];
return camelCase(operationName);
},
},
},
},
},
};
export default config;You may also want to loosen ajv options, as third-party OAS documents can contain non-standard keywords that cause validation errors.
// @ts-check
/** @type {import('vovk').VovkConfig} */
const config = {
// ...
libs: {
/** @type {import('vovk-ajv').VovkAjvConfig} */
ajv: {
options: {
strict: false, // Petstore OAS includes "xml", which causes errors in strict mode
},
},
},
};
export default config;Customize Fetcher
You can customize the fetch function per mixin or use a single fetcher for all mixins. The fetcher prepares authorization headers, performs client-side validation, and makes/handles HTTP requests.
/** @type {import('vovk').VovkConfig} */
const config = {
outputConfig: {
// ...
segments: {
petstore: {
openAPIMixin: { /* ... */ },
imports: { fetcher: './src/lib/petstoreFetcher' },
},
}
},
};
export default config;Composed Client
mjs + cjs (default)
By default, the composed client uses mjs and cjs templates to generate both ESM and CJS clients. They are emitted to node_modules/.vovk-client and importable as the vovk-client package.
import { PetstoreRPC, GithubIssuesRPC, type Mixins } from 'vovk-client';
await PetstoreRPC.getPets({ query: { limit: 10 } });
await GithubIssuesRPC.listForOrg({ params: { org: 'finom' } });The Mixins namespace contains types generated from components/schemas across all mixed OpenAPI specifications, providing an alternative to component-agnostic inference.
import { PetstoreRPC, type Mixins } from 'vovk-client';
const pet: Mixins.Pet = { id: 1, name: 'Doggo' };
// Alternatively:
const pet2: VovkOutput<typeof PetstoreRPC.getPet> = { id: 1, name: 'Doggo' };ts
The ts template generates an uncompiled TypeScript client that can be emitted directly into your codebase.
/** @type {import('vovk').VovkConfig} */
const config = {
composedClient: {
fromTemplates: ['ts'], // use 'ts' instead of 'mjs' and 'cjs'
outDir: './src/lib/client', // emit to your codebase
prettifyClient: true, // prettify the output
},
};
export default config;import { PetstoreRPC, GithubIssuesRPC, type Mixins } from '../lib/client';
// ...Segmented Client
The segmented client splits code into multiple chunks, placing each mixin in a folder named after its segment (petstore, github, etc., from outputConfig.segments).
By default, output goes to src/client. You can change the folder via segmentedClient.outDir.
/** @type {import('vovk').VovkConfig} */
const config = {
segmentedClient: {
outDir: './src/lib/client', // emit to your codebase
prettifyClient: true, // prettify the output
},
};
export default config;import { PetstoreRPC, type Mixins as PetstoreMixins } from '@/lib/client/petstore';
import { GithubIssuesRPC, type Mixins as GithubMixins } from '@/lib/client/github';
// ...import { openapi as petstoreOpenAPI } from '@/lib/client/petstore/openapi';
import { openapi as githubOpenAPI } from '@/lib/client/github/openapi';
console.log(petstoreOpenAPI, githubOpenAPI);As a bonus, this approach also generates an alternative OAS document that includes Scalar -compatible code samples.