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

@texaryn/schema-json

v0.9.0

Published

JSON Schema evaluation and validation adapter for Texaryn

Readme

@texaryn/schema-json

JSON Schema evaluation and validation adapter for Texaryn, backed by json-schema-library.

Status: pre-1.0. Public APIs may change before 1.0.

Install

pnpm add @texaryn/core @texaryn/schema-json

Quick start

import { createJsonSchemaAdapter } from '@texaryn/schema-json'

const schema = {
  $schema: 'https://json-schema.org/draft/2020-12/schema',
  type: 'object',
  properties: {
    name: {
      type: 'string',
      title: 'Name',
      minLength: 2,
    },
  },
  required: ['name'],
}

const adapter = await createJsonSchemaAdapter(schema)

const projection = adapter.project({ name: '' })
const validation = await adapter.validate({ name: '' })

console.log(projection.nodes.get('/name'))
console.log(validation.valid, validation.errors)

The returned object implements SchemaEvaluationPort from @texaryn/core.

JSON Schema dialect support

| Dialect | Support | | --------- | ------------- | | Draft 4 | Supported | | Draft 6 | Not supported | | Draft 7 | Supported | | 2019-09 | Supported | | 2020-12 | Supported |

Dialect support does not mean every keyword generates a form control or that Texaryn provides full specification compliance. See the JSON Schema support guide for the capability matrix, projection limits and validation behavior.

The adapter detects the dialect from $schema. If $schema is absent or unrecognized, the adapter uses draft-07 by default. You can override the fallback:

const adapter = await createJsonSchemaAdapter(schema, {
  defaultDialect: '2020-12',
})

Draft 4 and Draft 7 use the id keyword, definitions, boolean exclusiveMinimum and exclusiveMaximum, tuple-valued items, and ignore $ref siblings. Draft 4 does not recognize Draft 7 conditionals. Draft 6 is not supported.

The external resources returned by resolveResource must use the root schema's dialect. The adapter rejects external resources that declare another supported dialect. Custom dialects and user-defined vocabularies are not supported; the adapter uses built-in validation keywords even when a custom metaschema omits the validation vocabulary, and does not guarantee rejection of every unknown required vocabulary.

The detected dialect alone decides whether the schema's own format keywords are asserted. Draft 4 and Draft 7 assert them, and so does a schema whose $schema is absent or unrecognized while the fallback is draft-07; 2019-09 and 2020-12 never do, even when the format-assertion vocabulary is declared, and the adapter has no option that changes that. The format keywords inside a published metaschema reached through $ref are asserted in every dialect.

Projection

project(data) turns the schema plus current instance data into a framework-neutral SchemaProjection.

The projection contains one NodeProjection per JSON Pointer and includes information such as:

  • JSON Schema type
  • format
  • constraints
  • enum values
  • annotations
  • object children and required flags
  • whether a node is active for the current data

This allows Texaryn to render fields that are declared by the schema even when they have not been filled yet.

Dynamic schema behavior

The adapter currently handles the schema features required by Texaryn's runtime and renderer, including:

  • objects and primitive fields
  • arrays
  • enums
  • local $ref, including recursive references
  • opt-in dynamic scope projection for supported $dynamicRef and $recursiveRef paths
  • if / then / else
  • oneOf
  • anyOf
  • dependentSchemas
  • dependentRequired
  • Draft 4 and Draft 7 dependencies
  • annotations and field constraints

Inactive conditional fields remain represented in the projection with active: false, allowing the runtime and renderer to preserve a deterministic UI structure across branch changes. A branch the specification never evaluates (a then or else without if, a then under if: false, an else under if: true) contributes no fields unless a reference points into it or a schema inside it declares an $id, $anchor or $dynamicAnchor.

Recursive schemas

A local $ref may point back to a schema that contains it, such as { properties: { child: { $ref: '#' } } } or a definition that refers to itself. The projection expands such a schema once past the data: below the last location that holds data, a path never applies the same schemas twice. At {} the example above projects /child, and once /child holds an object it projects /child/child. Each level the user fills exposes the next.

boundaries on an object's NodeProjection says why the projection withheld something beneath it: recursion when a descendant would repeat the object's schemas, budget when a fixed per-projection limit withheld members. The limits are 16 objects and 512 nodes, counted only over locations that exist because of the recursion, two or more levels below the data; they never cut a member of a location that holds data. A node the projection reached only by expanding the recursion carries recursiveExpansion, and schema-defaults initialization never writes there.

boundaryTargets has the next withheld instance pointer for each boundary reason, with a generation scoped token. Pass selected tokens to project(data, { expandedBoundaryTokens }) to reveal those locations. The runtime also supplies boundaryGeneration; direct adapter callers can leave it at zero or increment it whenever replacing a container changes pointer ownership. Each token admits one target. The built in renderers show one action per boundary reason on an object and advance one target per click, so a large budget boundary does not create a button for every withheld field. Expansion changes the view only. It does not write form data, affect validation, or change initialization. React, React Bootstrap, React MUI, Vue, and Web Components provide these actions by default.

A schema that applies itself at one instance location without crossing into a property or item, such as { allOf: [{ $ref: '#' }] }, makes the evaluator recurse without end, so createJsonSchemaAdapter rejects it with SameLocationCycleError. Its positions field lists the schema positions of each cycle, and the message names one of them and the path through the cycle. The check reads the schema, not the data: a cycle behind an if is rejected even while the if does not hold, except the branches the specification never evaluates. It is conservative for dynamic references, treating a $dynamicRef to a $dynamicAnchor as reaching every $dynamicAnchor of that name and a $recursiveRef as reaching every schema that declares $recursiveAnchor: true, so a schema whose dynamic references could close such a cycle is rejected even when no evaluation selects it.

The JSON Schema support guide has the details, and ADR-007 records the decision.

Dynamic reference projection

Dynamic scope aware form projection is opt in with dynamicReferenceProjection: 'local'. It supports Draft 2020-12 $dynamicRef and Draft 2019-09 $recursiveRef: '#' under object properties or homogeneous array items, including references reached through allOf, anyOf, oneOf, conditionals and dependentSchemas.

External dynamic references use the host supplied resolveResource callback. The adapter does not fetch schemas. Without the option, the projector does not follow the active dynamic scope. The option rejects references below unsupported applicators and array keywords, root relative resource identifiers, and unsupported reference siblings. See ADR-010 for the exact boundaries.

const adapter = await createJsonSchemaAdapter(schema, {
  dynamicReferenceProjection: 'local',
  resolveResource: (uri) => schemaResources.get(uri),
})

Adapter creation also rejects a retained static $ref when json-schema-library resolves it to different declarations with different validation assertions in the validation tree and the normalised projection tree. The check covers the dialect's supported assertions, including boolean subschemas. The error is ProjectionValidationDivergenceError.

The adapter exposes projectSubmission(data) for supported schemas. It removes values declared only by inactive branches, keeps provisional fields, and preserves undeclared data so validation can still reject it. This opt-in treats an inactive declaration as form data to omit even when an enclosing object allows additional properties. createFormRuntime selects the mode with submission: 'projected'; it validates and submits the same independent snapshot, while the form's live data stays unchanged. Schemas with dynamic references or unevaluatedProperties or unevaluatedItems do not expose this capability yet. The private Hyperjump adapter also does not expose it. Runtime creation fails if projected submission is requested without the capability.

JSON Schema engine

The production adapter uses @texaryn/json-schema-library 11.6.6, a Texaryn maintained fork based on upstream 11.6.5. It fixes reference reduction state and propagates failed nested oneOf reductions through dependent schemas. The adapter vendors the resolved ESM runtime and license into its package, so installed consumers use the same engine build. The dependency range is ^11.6.6; the lockfile and build guard pin the packaged runtime to 11.6.6 until a later engine release is reviewed. The adapter does not modify the engine's internal reference registry.

Validation

const result = await adapter.validate(data)

if (!result.valid) {
  for (const error of result.errors) {
    console.log(error.instancePointer, error.keyword, error.message)
  }
}

Validation errors are normalized to Texaryn's ValidationError contract:

interface ValidationError {
  instancePointer: string
  keyword: string
  message?: string
  params: Record<string, unknown>
}

Required-property errors are located at the missing property pointer rather than only at the parent object.

instancePointer is an RFC 6901 pointer: a property named a/b is /a~1b, and a~b is /a~0b. json-schema-library joins property names into its own error pointers without escaping, so the adapter recovers the real keys from the validated data. Two locations whose keys read the same once joined, such as a/b holding c next to a holding b/c, are told apart by the value the error reports. When both hold equal values and only one fails, the error is located at the first of them in key order, until json-schema-library escapes at the source (sagold/json-schema-library#130). params is json-schema-library's raw error data, and its pointers are not escaped.

A data key named like an Object.prototype member (__proto__, constructor, toString) validates and projects like any other key. The one exception is a schema that combines patternProperties with additionalProperties: false: json-schema-library reads that schema's plain properties object, so it treats such an undeclared key as declared.

Scoped validation

This adapter also implements the optional validateAt() port method:

const result = await adapter.validateAt(data, '/profile')

The result contains validation errors at that pointer or below it.

API

import {
  createJsonSchemaAdapter,
  ProjectionValidationDivergenceError,
  SameLocationCycleError,
  type AdapterConfig,
  type Dialect,
} from '@texaryn/schema-json'

createJsonSchemaAdapter(schema, config?)

Creates a prepared adapter for one schema.

const adapter = await createJsonSchemaAdapter(schema, {
  defaultDialect: 'draft-07',
})

AdapterConfig

interface AdapterConfig {
  defaultDialect?: Dialect
  dynamicReferenceProjection?: 'local'
  resolveResource?: (uri: string) => unknown | undefined | Promise<unknown | undefined>
  maxExternalResources?: number
}

Dialect

The union of dialect identifiers the adapter recognizes:

type Dialect = 'draft-04' | 'draft-07' | '2019-09' | '2020-12'

SameLocationCycleError

Thrown by createJsonSchemaAdapter for a schema that applies itself at one instance location. A host that loads authored schemas can tell a broken schema from a failed load with instanceof:

try {
  await createJsonSchemaAdapter(schema)
} catch (error) {
  if (error instanceof SameLocationCycleError) console.log(error.positions, error.message)
  else throw error
}

ProjectionValidationDivergenceError

Thrown by createJsonSchemaAdapter when one retained static $ref resolves to different declarations with different validation assertions for validation and projection. Its reference and sourcePosition identify the reference site. validationPosition and projectionPosition identify the two resolved schema positions.

Architecture

The adapter is deliberately separate from @texaryn/core:

JSON Schema
    |
    v
@texaryn/schema-json
    |
    v
SchemaEvaluationPort
    |
    v
@texaryn/core

The core runtime does not know which JSON Schema library produced the projection or validation result.

That boundary allows additional implementations to satisfy the same port without changing the runtime or renderer.

Related packages

See the repository README for a complete example.

License

Apache-2.0