TypeScript RPC Customization
You can customize the generated TypeScript client by replacing imports of lower-level libraries. Do this via the outputConfig.imports object in the config file, which can specify a fetcher, validateOnClient, or createRPC.
By default (when vovk-ajv is used for client-side validation), an index.mjs file generating the UserRPC module might look like:
import { createRPC } from 'vovk';
import { schema } from './schema.cjs';
export const UserRPC = createRPC(schema, '', 'UserRPC', import('vovk'), {
validateOnClient: import('vovk-ajv'),
apiRoot: 'http://localhost:3000/api',
});When outputConfig.imports is modified:
/** @type {import('vovk').VovkConfig} */
const config = {
outputConfig: {
imports: {
fetcher: './src/lib/fetcher',
validateOnClient: './src/lib/validateOnClient',
createRPC: './src/lib/createRPC',
},
}
};
export default config;The generated index.mjs uses these imports and resolves relative paths:
import { createRPC } from '../../src/lib/createRPC';
export const UserRPC = createRPC(schema, '', 'UserRPC', import('../../src/lib/fetcher'), {
validateOnClient: import('../../src/lib/validateOnClient'),
apiRoot: 'http://localhost:3000/api',
});fetcher and validateOnClient can also be set per segment. This enables different options or auth mechanisms per segment (including OpenAPI Mixins) and different validation libraries where needed.
/** @type {import('vovk').VovkConfig} */
const config = {
outputConfig: {
imports: { // applied to all segments
fetcher: './src/lib/fetcher',
validateOnClient: './src/lib/validateOnClient',
},
segments: {
admin: {
imports: { // applied only to "admin" segment
fetcher: './src/lib/adminFetcher',
validateOnClient: './src/lib/adminValidateOnClient',
},
},
}
}
};
export default config;fetcher
The fetcher prepares handlers, performs client-side validation, issues HTTP requests, differentiates content types (JSON, JSON Lines, or other Response types), and returns data in the appropriate format.
- For
application/json, it returns the parsed JSON. - For
application/jsonlorapplication/jsonlines, it returns a disposable async iterable. - For other content types, it returns the
Responseobject as-is, letting you access text or binary data.
It can also process custom per-call options passed to RPC methods.
import { UserRPC } from 'vovk-client';
const user = await UserRPC.updateUser({
// ...
successMessage: 'Successfully updated the user',
someOtherCustomFlag: true,
});createFetcher
The file at imports.fetcher must export a fetcher variable. To simplify creating a custom fetcher, use createFetcher from vovk.
import { createFetcher } from 'vovk';
export const fetcher = createFetcher<{
successMessage?: string; // "Successfully created a new user"
useAuth?: boolean; // if true, Authorization header will be set
someOtherCustomFlag?: boolean; // any custom flag that you want to pass to the RPC method
}>({
prepareRequestInit: async (init, { useAuth, someOtherCustomFlag }) => {
// ...
return {
...init,
headers: {
...init.headers,
...(useAuth ? { Authorization: 'Bearer token' } : {}),
},
};
},
transformResponse: async (data, { someOtherCustomFlag }) => {
// ...
return {
...data,
};
},
onSuccess: async (data, { successMessage }) => {
if (successMessage) {
alert(successMessage);
}
},
onError: async (error) => {
alert(error.message);
},
});With this setup, all RPC modules accept the desired options:
import { UserRPC } from 'vovk-client';
await UserRPC.updateUser({
params: { param: 'value' },
query: { id: 'value' },
body: { email: 'value' },
successMessage: 'Successfully updated the user',
useAuth: true,
someOtherCustomFlag: true,
});createFetcher accepts an object with:
prepareRequestInit(init: RequestInit, options: T)
Prepares RequestInit before making the request. Use it to set auth headers or Next.js-specific next options. Receives the prepared init and custom call options, and must return a RequestInit object (usually based on init). Useful for logging or other pre-request logic.
transformResponse(data: unknown, options: T, info: { response: Response, init: RequestInit, schema: VovkHandlerSchema })
Transforms the response before returning it to the caller. The data type depends on the content type: JSON, a disposable async iterator , or a Response. Return the transformed value. The info argument provides the original Response, the RequestInit used, and the VovkHandlerSchema (e.g., schema.operationObject).
onError(error: HttpException, options: T)
Called when a request fails. Use it for error messages, logging, or custom handling.
onSuccess(data: unknown, options: T)
Called on success. Use it for success messages, logging, or post-processing.
validateOnClient
validateOnClient defines how client-side validation is performed for RPC method input (params, query, body). Create it via createValidateOnClient from vovk. It accepts a validate function that receives the input data, the validation schema, and metadata, and returns validated data or throws on failure. Validation runs only when both input and schema are provided.
import { validateData } from 'validation-library';
import { createValidateOnClient, HttpException, HttpStatus } from 'vovk';
export const validateOnClient = createValidateOnClient({
validate: async (input, schema, meta) => {
const isValid = validateData(input, schema);
if (!isValid) {
throw new HttpException(HttpStatus.NULL, 'Validation failed', {
// ... optional cause
});
}
return input;
},
});See client-side validation for details.
createRPC (Advanced)
createRPC lets you replace the default RPC client completely. It is configured globally (not per segment, unlike fetcher or validateOnClient). This is an advanced option with limited use cases; feel free to ask on GitHub Discussions .