Dynamic and Adaptable Model Validator Using Zod, Interoperable with OpenAPI
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.
| Package | Description |
|---|---|
@adaptate/core |
Schema transformation engine — make fields required based on config |
@adaptate/utils |
OpenAPI ↔ Zod conversion, YAML spec loading with $ref resolution |
pnpm add @adaptate/core
# or
npm install @adaptate/coreFor OpenAPI utilities:
pnpm add @adaptate/utils
# or
npm install @adaptate/utilsPeer dependency: zod@^3.23.8 || ^4.0.0
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.
There are two common ways to create a fully optional schema:
1. Using .deepPartial() (recommended)
import { z } from 'zod';
import { transformSchema, type Config } 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 },
} satisfies Config<z.infer<typeof schema>>;
const updatedSchema = transformSchema(schema, config);
updatedSchema.parse({ name: 'Davin', age: 30, address: { city: 'Pettit' } }); // passes
updatedSchema.parse({ name: 'Davin', age: 30, address: { street: 'Main St' } }); // throws2. 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(),
});
// ... same config and usage as aboveMake 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(); // throws — parentContactNumber required because age < 18Convert 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.
Compact & Powerful: The core transformation logic is intentionally compact — fewer than 100 lines of code. Complex nested transformations (deep objects, arrays with wildcards, conditional requirements) are handled elegantly through recursion. This keeps the API surface small while delivering sophisticated behavior with minimal cognitive overhead.
For a deeper dive, see DESIGN.md which covers:
- Detailed code walkthroughs
- Runtime complexity analysis (O(N) time, O(D) space)
- Performance characteristics and trade-offs
- Comparison with alternative approaches
This is a pnpm monorepo orchestrated with Turborepo.
Package manager: Use pnpm only when working in this repository (pnpm install). This repo’s install/build safety posture is defined for pnpm (.npmrc, pnpm-workspace.yaml: lifecycle restrictions, store integrity, release-age gates, etc.). npm install at the root is unsupported — npm ignores or mishandles several of those controls and may resolve dependencies differently than CI, weakening protections against supply-chain attacks that the config is meant to mitigate. Root package.json pins packageManager. The Installation section above (pnpm add / npm install for @adaptate/*) applies to consumers installing published packages from the npm registry.
Requirements: Node.js ≥ 20, pnpm 9.12.3
pnpm install # Install dependencies
pnpm build # Full pipeline: check-types → test → build
pnpm test # Run Vitest in watch mode
npx vitest run # Single test run
npx turbo run check-types # TypeScript type checking├── .devcontainer/ # optional Dev Container (Node 24 + pnpm)
├── 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 and skills/ for development guidelines and operational procedures. Optional dev container: .devcontainer/README.md, .devcontainer/devcontainer.json.
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. The PR (#21) marks the first use of AI coding agents (Grok) in the project. Cursor Cloud Agent prepared the repo for agentic development workflows.
MIT