Adaptate
Dynamic and Adaptable Model Validator Using Zod, Interoperable with OpenAPI

Overview
adaptate is a dynamic and adaptable model validator that leverages Zod for schema validation and is interoperable with OpenAPI. Define a single optional Zod schema for your data model, then use configuration objects to declare which fields each consumer requires — at runtime.
Packages
@adaptate/core | Schema transformation engine — make fields required based on config |
@adaptate/utils | OpenAPI ↔ Zod conversion, YAML spec loading with $ref resolution |
Installation
pnpm add @adaptate/core
npm install @adaptate/core
For OpenAPI utilities:
pnpm add @adaptate/utils
npm install @adaptate/utils
Peer dependency: zod@^3.23.8 || ^4.0.0
The Problem
In component-oriented applications, different components consume different subsets of the same data model. One component needs name and age, another needs address.city, and a third needs everything. The data often comes from different API endpoints with varying completeness.
Without runtime validation per consumer, you either:
- Make everything optional (no safety)
- Make everything required (breaks partial views)
- Maintain separate schemas per component (duplication nightmare)
Adaptate solves this: define one schema with all fields optional, then use a config object to declare what each consumer requires.
Usage
Make Fields Required by Configuration
There are two common ways to create a fully optional schema:
1. Using .deepPartial() (recommended)
import { z } from 'zod';
import { transformSchema } from '@adaptate/core';
const schema = z.object({
name: z.string(),
age: z.number(),
address: z.object({
street: z.string(),
city: z.string(),
}),
}).deepPartial();
const config = {
name: true,
age: true,
address: { city: true },
};
const updatedSchema = transformSchema(schema, config);
updatedSchema.parse({ name: 'Davin', age: 30, address: { city: 'Pettit' } });
updatedSchema.parse({ name: 'Davin', age: 30, address: { street: 'Main St' } });
2. Manual .optional() (still works)
import { z } from 'zod';
import { transformSchema } from '@adaptate/core';
const schema = z.object({
name: z.string().optional(),
age: z.number().optional(),
address: z.object({
street: z.string().optional(),
city: z.string().optional(),
}).optional(),
});
Conditional Requirements
Make fields required based on runtime data
import { z } from 'zod';
import { makeConditionalSchemaTransformer } from '@adaptate/core';
const schema = z.object({
parentContactNumber: z.number().optional(),
age: z.number().optional(),
});
const config = {
parentContactNumber: { requiredIf: (data: any) => data.age < 18 },
age: true,
};
const data = { age: 17 };
const transformer = makeConditionalSchemaTransformer(data)(schema, config);
transformer.run();
OpenAPI ↔ Zod Conversion
Convert OpenAPI schemas to Zod and back (now fully feature-complete)
Load and dereference an OpenAPI spec:
import { getDereferencedOpenAPIDocument } from '@adaptate/utils';
const doc = await getDereferencedOpenAPIDocument({
location: 'filesystem',
callSiteURL: import.meta.url,
relativePathToSpecFile: './api-spec.yml',
});
Convert OpenAPI schema to Zod:
import { openAPISchemaToZod } from '@adaptate/utils';
const zodSchema = openAPISchemaToZod({
type: 'object',
required: ['age'],
properties: {
name: { type: 'string', minLength: 2 },
age: { type: 'integer', minimum: 0 },
},
});
Convert Zod to OpenAPI schema:
import { z } from 'zod';
import { zodToOpenAPISchema } from '@adaptate/utils';
const openAPISchema = zodToOpenAPISchema(
z.object({ name: z.string().min(2), age: z.number().int() })
);
See @adaptate/utils README for full documentation.
Development
This is a pnpm monorepo orchestrated with Turborepo.
Requirements: Node.js ≥ 20, pnpm 9.12.3
pnpm install
pnpm build
pnpm test
npx vitest run
npx turbo run check-types
Project Structure
├── packages/
│ ├── core/ # @adaptate/core — schema transformation
│ └── utils/ # @adaptate/utils — OpenAPI utilities
├── skills/ # Tool-agnostic agent SOPs
├── AGENTS.md # Agent guidelines
├── CODING_STYLE.md # Coding conventions
└── turbo.json # Turborepo task graph
See AGENTS.md for full development guidelines and skills/ for operational procedures.
Credits
This library recreates and generalizes a pattern originally observed at Oneflow AB, where the same data model was consumed by different components with varying required fields depending on context.
Development note: Initial prototype was created with ChatGPT Canvas. All important caveats and refinements were manually corrected by the author. This PR (#21) marks the first use of AI coding agents (Grok) in the project.
License
MIT