Skip to content

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
entries: Array<string>

An array of glob patterns for finding folders within rootDir

ts
exclude?: RegExp

A regular expression for excluding files from processing

ts
requestDeclaration: string

The name of the request type declaration (such as Request)

ts
responseDeclaration: string

The name of the response type declaration (such as Response)

ts
targetFolder: string

The folder where validation schema files are generated

ts
tsconfigPath: string

The path to tsconfig for finding TypeScript aliases and resolving paths

ts
formatContent: (params: {
  schema: TypeBuilderOutputFile['schema'];
  sourcePath: string;
  targetPath: string;
}) => string

A function for formatting the validation schema file content

ts
getDegraded?: (degraded: Array<string>) => void

A callback that receives the list of types that fell back to any. Called before the error is thrown when bailOnDegraded: true

ts
bailOnDegraded?: boolean

Defaults to false: fallbacks do not interrupt generation. When true, the plugin throws an error listing the fallbacks after calling getDegraded. To make the entire generation run fail, also enable ci: true in FileGenerator

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

ts
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
USER: tsconfig.json was created with content:
json
{
  "include": ["**/*.ts"],
  "compilerOptions": {
    "target": "ES2023",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true
  }
}
USER: models/order.ts was created with content:
ts
export type TypeOrder = {
  id: string;
  status: 'draft' | 'confirmed';
};
USER: api/orders.ts was created with content:
ts
import type { TypeOrder } from '../models/order';

export type TypeRequest = { status?: TypeOrder['status'] };
export type TypeResponse = { orders: Array<TypeOrder> };
ARFA Generator shows logs:
txt
[ARFA-GEN] [write] validators/api/orders.ts
[ARFA-GEN] finished in 0ms
ARFA Generator generated files:
ts
// 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

ts
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
USER: tsconfig.json was created with content:
json
{
  "include": ["**/*.ts"],
  "compilerOptions": {
    "target": "ES2023",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true
  }
}
USER: features/orders/api/listOrders.ts was created with content:
ts
export type TypeRequest = { status?: 'draft' | 'confirmed' };
export type TypeResponse = { orders: Array<{ id: string; status: string }> };
USER: features/profile/api/getProfile.ts was created with content:
ts
export type TypeRequest = { profileId: string };
export type TypeResponse = { name: string };
ARFA Generator shows logs:
txt
[ARFA-GEN] [write] validators/features/orders/api/listOrders.ts
[ARFA-GEN] [write] validators/features/profile/api/getProfile.ts
[ARFA-GEN] finished in 0ms
ARFA Generator generated files:
ts
// this file is generated

export const listOrders = {
  "request": {
    "status": [
      "optional",
      [
        "union",
        [
          "literal",
          "draft"
        ],
        [
          "literal",
          "confirmed"
        ]
      ]
    ]
  },
  "response": {
    "orders": [
      "array",
      {
        "id": "string",
        "status": "string"
      }
    ]
  }
} as const;

MIT License