npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ai-assert-schema

v1.4.0

Published

Assert schemas for compatibility against your AI models

Readme

ai-assert-schema

Why?

AI providers like OpenAI only support a subset of JSON Schema for structured outputs and tool calling. If you use unsupported features, you may get invalid data not matching your schema or an error at runtime. This library validates your schemas against the constraints of your chosen AI model, so you can catch issues early and transparently.

Examples

Two typical examples of unsupported JSON schema features are optional properties and discriminated unions.

[!TIP] The following examples use Zod for schema definitions, but the same concepts apply to other JSON Schema libraries or raw JSON Schema objects.

Optional vs Nullable

Using z.optional() removes the property from the required array, which is not supported by OpenAI. Use z.nullable() instead, which keeps the property required but allows null as a valid type.

z.object({
  optional: z.string().optional(),
  nullable: z.string().nullable(),
});
{
  "properties": {
    "optional": { "type": "string" },
    "nullable": { "type": ["string", "null"] }
  },
  "required": ["nullable"] // 'optional' is not required, 'nullable' is required
}

Discriminated Union vs Union

Discriminated unions created with z.discriminatedUnion() convert to oneOf in JSON Schema, which is not supported by OpenAI. Use z.union() instead, which converts to anyOf and is supported.

const Dog = z.object({ type: z.literal('dog'), bark: z.boolean() });
const Cat = z.object({ type: z.literal('cat'), meow: z.boolean() });

z.object({
  discriminatedUnion: z.discriminatedUnion('type', [Dog, Cat]),
  union: z.union([Dog, Cat]),
});
{
  "properties": {
    "discriminatedUnion": {
      // 'oneOf' is not supported by OpenAI 
      "oneOf": [{ "type": "object", ... }, { "type": "object", ... }]
    },
    "union": {
      // 'anyOf' is supported by OpenAI
      "anyOf": [{ "type": "object", ... }, { "type": "object", ... }]
    }
  }
}

Installation

npm install ai-assert-schema

Usage

This library works with any Standard JSON Schema library (Zod, ArkType, Valibot) or raw JSON Schema objects. It can be used at run-time or test-time in your unit tests.

import { assertSchema } from 'ai-assert-schema';
import { z } from 'zod';

const Dog = z.object({ type: z.literal('dog'), bark: z.boolean() });
const Cat = z.object({ type: z.literal('cat'), meow: z.boolean() });

const validSchema = z.object({
  // Nullable is allowed
  name: z.string().nullable(),
  // z.union() produces anyOf
  animal: z.union([Dog, Cat]),
});

const invalidSchema = z.object({
  // Optional is not allowed
  name: z.string().optional(),
  // z.discriminatedUnion() produces oneOf
  animal: z.discriminatedUnion('type', [Dog, Cat]),
});

// Returns the schema if valid
assertSchema({ schema: validSchema, model: 'openai/gpt-4o-mini' });
assertSchema({ schema: validSchema, model: { provider: 'openai', modelId: 'gpt-4o-mini' } });

// Throws an error if invalid
assertSchema({ schema: invalidSchema, model: 'openai/gpt-4o-mini' });
assertSchema({ schema: invalidSchema, model: { provider: 'openai', modelId: 'gpt-4o-mini' } });

Assert at Run-time

Wrap your schema and model with assertSchema({ schema, model }). If the schema is valid, it will be returned unchanged. Otherwise, an error will be thrown before making the API call.

[!TIP] This example uses the AI SDK to pass the model, but you can also provide a string or plain object.

import { openai } from '@ai-sdk/openai';
import { generateText, Output } from 'ai';
import { assertSchema } from 'ai-assert-schema';
import { z } from 'zod';

const model = openai('gpt-4o-mini');

const result = await generateText({
  model,
  prompt: 'Describe a dog that barks',
  output: Output.object({
    schema: assertSchema({ schema, model }),
  }),
});

Assert at Test-time

Validate schemas in your test suite to catch issues during CI:

import { expect, test } from 'vitest';
import { assertSchema } from 'ai-assert-schema';

// Import your schema and model to test
import { yourSchema, yourModel } from './your-schema-file';

test('schema should be compatible with the model', () => {
  expect(() =>
    assertSchema({ schema: yourSchema, model: yourModel })
  ).not.toThrow();
});

Validate without Throwing

Use assertSchema.validate() to check schema compatibility without throwing an error:

import { assertSchema } from 'ai-assert-schema';

const result = assertSchema.validate({
  schema: mySchema,
  model: 'openai/gpt-4o',
});

if (!result.success) {
  console.warn('Schema has compatibility issues:');
  for (const issue of result.issues) {
    console.warn(`  - ${issue.message}`);
  }
}

Inline Constraints

Pass constraints directly instead of relying on the provider registry:

import { assertSchema } from 'ai-assert-schema';
import { openaiConstraints } from 'ai-assert-schema/constraints/openai';

// Use OpenAI constraints with a custom or unknown provider
assertSchema({
  schema: mySchema,
  model: 'my-custom-provider/model',
  constraints: openaiConstraints,
});

Providers

Currently supported providers: OpenAI, Anthropic, Google.

Register Custom Providers

Register custom providers using assertSchema.registry:

import { assertSchema } from 'ai-assert-schema';

// Register constraints for a custom provider
assertSchema.registry.register({
  // Matching 'my-provider/*' models
  pattern: /^my-provider\/.+$/,
  constraints: {
    provider: 'my-provider',
    unsupported: [
      { feature: 'anyOf', message: 'anyOf is not supported' },
      { feature: 'oneOf', message: 'oneOf is not supported' },
    ],
  },
});

// Now assertSchema will use these constraints for matching models
assertSchema({
  schema: mySchema,
  model: 'my-provider/my-model',
});

Provider Aliases

You can also register aliases that reference built-in providers ('openai', 'anthropic', or 'google') instead of providing full constraints:

import { assertSchema } from 'ai-assert-schema';

// Register an alias that uses OpenAI constraints
assertSchema.registry.register({
  pattern: /^my-openai-compatible\/.+$/,
  provider: 'openai',  // Uses built-in OpenAI constraints
});

// Register an alias that uses Anthropic constraints
assertSchema.registry.register({
  pattern: /^my-anthropic-compatible\/.+$/,
  provider: 'anthropic',  // Uses built-in Anthropic constraints
});

Pattern Matching

Patterns can be strings or regular expressions. The registry will always match exact strings first, then iterate over all regex patterns with later registrations taking precedence over earlier ones.

import { assertSchema } from 'ai-assert-schema';

assertSchema.registry.register({
  // Exact match for 'anthropic/claude-4'
  pattern: 'anthropic/claude-4',
  constraints: {},
});

assertSchema.registry.register({
  // Matching all 'anthropic/*' models
  pattern: /anthropic\/.+$/,
  constraints: {},
});

// Matches exact string first
assertSchema({
  schema: mySchema,
  model: 'anthropic/claude-4',
});

OpenAI

The built-in registry resolves OpenAI models using the following patterns:

  • 'openai/*'
  • 'openai.chat/*'
  • 'openai.responses/*'

Constraints

OpenAI's Structured Outputs have specific JSON Schema constraints. See the full constraint implementation in src/constraints/openai/openai.ts.

[!WARNING] The constraints were implemented following the official documentation. If you find any discrepancies with actual behavior, please open an issue.

Unsupported JSON Schema features:

  • oneOf
  • allOf
  • not
  • anyOf at root level
  • if/then/else conditionals
  • dependentRequired, dependentSchemas
  • patternProperties

Required constraints:

  • All properties must be required
  • Must use additionalProperties: false
Register OpenAI-Compatible Providers
import { assertSchema } from 'ai-assert-schema';

assertSchema.registry.register({
  // Match your custom provider
  pattern: 'my-provider/openai-compatible',
  // Use built-in OpenAI constraints
  provider: 'openai',
});

Azure OpenAI

Azure OpenAI uses the same constraints as OpenAI. The built-in registry resolves Azure models using the following patterns:

  • 'azure/*openai*'
  • 'azure.chat/*openai*'
  • 'azure.responses/*openai*'

If your deployment names do not follow this pattern, you can register custom patterns using assertSchema.registry.register():

import { assertSchema } from 'ai-assert-schema';

assertSchema.registry.register({
  // Match your custom Azure deployment name
  pattern: 'azure/my-deployment-name',
  // Use built-in OpenAI constraints
  provider: 'openai',
});

Anthropic

The built-in registry resolves Anthropic models using the following patterns:

  • 'anthropic/*'
  • 'anthropic.messages/*'

Constraints

Anthropic's Structured Outputs have specific JSON Schema constraints. See the full constraint implementation in src/constraints/anthropic/anthropic.ts.

[!WARNING] The constraints were implemented following the official documentation. If you find any discrepancies with actual behavior, please open an issue.

Unsupported JSON Schema features:

  • Recursive schemas
  • Numerical constraints (minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf)
  • String constraints (minLength, maxLength)
  • Array constraints (maxItems, uniqueItems, contains, minItems > 1)

Required constraints:

  • Must use additionalProperties: false
Register Anthropic-Compatible Providers
import { assertSchema } from 'ai-assert-schema';

assertSchema.registry.register({
  // Match your custom provider
  pattern: 'my-provider/claude-compatible',
  // Use built-in Anthropic constraints
  provider: 'anthropic',
});

Google

The built-in registry resolves Google models using the following patterns:

  • 'google.generative-ai/*'
  • 'google.vertex.chat/*'
  • 'google.vertex/*'

Constraints

Google Gemini's Structured Outputs (Gemini 2.0+) have specific JSON Schema constraints. See the full constraint implementation in src/constraints/google/google.ts.

[!WARNING] The constraints were implemented following the official documentation. If you find any discrepancies with actual behavior, please open an issue.

Unsupported JSON Schema features:

  • oneOf
  • allOf
  • not
  • if/then/else conditionals
  • pattern, minLength, maxLength (string constraints)
  • exclusiveMinimum, exclusiveMaximum, multipleOf (numerical constraints)
  • dependentRequired, dependentSchemas
  • patternProperties, propertyNames
  • uniqueItems, contains, additionalItems (array constraints)
  • Recursive schemas
Register Google-Compatible Providers
import { assertSchema } from 'ai-assert-schema';

assertSchema.registry.register({
  // Match your custom provider
  pattern: 'my-provider/google-compatible',
  // Use built-in Google constraints
  provider: 'google',
});

Contributing

Contributions are welcome!

  • Add new providers: Submit a PR with constraints for other AI providers
  • Fix constraints: If you find incorrect constraints, please open an issue or PR
  • Provider implementations: See src/constraints/openai/ for examples

API

assertSchema(options)

function assertSchema<SCHEMA extends SchemaInput>(
  options: AssertSchemaOptions<SCHEMA>
): SCHEMA

Parameters:

  • model - Model identifier as 'provider/model-id' string or { provider, modelId } object
  • schema - Your schema (Zod, Standard Schema, or JSON Schema object)
  • constraints - (optional) Custom constraints to use instead of looking up from registry

Returns: The input schema unchanged (for chaining)

Throws: SchemaAssertionError when the schema contains unsupported features

assertSchema.validate(options)

Same parameters as assertSchema, but returns a result object instead of throwing:

type ValidationResult =
  | { success: true; provider: string; modelId: string; jsonSchema: JSONSchema }
  | { success: false; provider: string; modelId: string; jsonSchema: JSONSchema; issues: ValidationIssue[] }

assertSchema.registry

The provider registry for registering custom constraints:

  • register({ pattern, provider }) - Register a pattern using built-in provider constraints ('openai' or 'anthropic')
  • register({ pattern, constraints }) - Register a pattern with custom constraints
  • resolve(model) - Resolve constraints for a model
  • getAll() - Get all registered patterns with their resolved constraints

License

MIT