PluginValidators
Generates validation schemas from TypeScript contracts using the api-contract-check library. The plugin reads input API files, finds request and response types, builds serializable validation schemas, and writes them to targetFolder. When an API file is deleted, its corresponding validation schema is deleted automatically.
The plugin works incrementally in watch mode by maintaining a reverse dependency map.
The target path is constructed as targetFolder + relativePathFromRootDir. If the source is located at src/api/orders.ts and targetFolder is set to src/validators, the result is written to src/validators/api/orders.ts. For convenience, you can also use PluginReexport in the validators folder.
Remember to enable runtime validation as described in Payload validation.
ts | An array of glob patterns for finding folders within |
ts | A regular expression for excluding files from processing |
ts | The name of the request type declaration (such as |
ts | The name of the response type declaration (such as |
ts | The folder where validation schema files are generated |
ts | The path to tsconfig for finding TypeScript aliases and resolving paths |
ts | A function for formatting the validation schema file content |
ts | A callback that receives the list of types that fell back to |
ts | Defaults to |
Example: API contracts in a shared folder
Validation schemas are generated in validators/api for files in the api folder. Changing a shared type updates the validation schemas that depend on it
new PluginValidators([
{
entries: ['api'],
exclude: /(^_|\.test\.ts$|package\.json$)/,
targetFolder: path.resolve(rootDir, 'validators'),
tsconfigPath: path.resolve(rootDir, 'tsconfig.json'),
requestDeclaration: 'TypeRequest',
responseDeclaration: 'TypeResponse',
formatContent({ schema, targetPath }) {
return `export const ${path.parse(targetPath).name} = ${JSON.stringify(schema, null, 2)} as const;\n`;
},
},
]),
Run Sandbox
{
"include": ["**/*.ts"],
"compilerOptions": {
"target": "ES2023",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true
}
}
export type TypeOrder = {
id: string;
status: 'draft' | 'confirmed';
};
import type { TypeOrder } from '../models/order';
export type TypeRequest = { status?: TypeOrder['status'] };
export type TypeResponse = { orders: Array<TypeOrder> };
[ARFA-GEN] [write] validators/api/orders.ts
[ARFA-GEN] finished in 0ms
// this file is generated
export const orders = {
"request": {
"status": [
"optional",
[
"union",
[
"literal",
"draft"
],
[
"literal",
"confirmed"
]
]
]
},
"response": {
"orders": [
"array",
{
"id": "string",
"status": [
"union",
[
"literal",
"draft"
],
[
"literal",
"confirmed"
]
]
}
]
}
} as const;
Example: API contracts within feature modules
The plugin finds features/*/api folders and preserves the module structure in validators/features
new PluginValidators([
{
entries: ['features/*/api'],
exclude: /(^_|\.test\.ts$|package\.json$)/,
targetFolder: path.resolve(rootDir, 'validators'),
tsconfigPath: path.resolve(rootDir, 'tsconfig.json'),
requestDeclaration: 'TypeRequest',
responseDeclaration: 'TypeResponse',
formatContent({ schema, targetPath }) {
return `export const ${path.parse(targetPath).name} = ${JSON.stringify(schema, null, 2)} as const;\n`;
},
},
]),
Run Sandbox
{
"include": ["**/*.ts"],
"compilerOptions": {
"target": "ES2023",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true
}
}
export type TypeRequest = { status?: 'draft' | 'confirmed' };
export type TypeResponse = { orders: Array<{ id: string; status: string }> };
export type TypeRequest = { profileId: string };
export type TypeResponse = { name: string };
[ARFA-GEN] [write] validators/features/orders/api/listOrders.ts
[ARFA-GEN] [write] validators/features/profile/api/getProfile.ts
[ARFA-GEN] finished in 0ms
// this file is generated
export const listOrders = {
"request": {
"status": [
"optional",
[
"union",
[
"literal",
"draft"
],
[
"literal",
"confirmed"
]
]
]
},
"response": {
"orders": [
"array",
{
"id": "string",
"status": "string"
}
]
}
} as const;