mrkeyoor.com_
Sat 08 Aug 21:02 UTC
npmUtilsupdated 08 Aug 2026

@exodus/schemasafe

@exodus/schemasafe compiles JSON Schema into fast JavaScript validators or combined JSON parsers and validators. It supports drafts 04, 06, 07, 2019-09, and 2020-12, rejects schemas with unknown or incoherent keywords by default, can enforce stronger coverage and complexity rules, and can emit self-contained validator modules for content-security-policy environments. It has no runtime dependencies and includes optional error locations, external references, custom formats, default insertion, extra-property removal, and an experimental schema linter.

Verdict

A sharp choice for small, auditable, fail-closed JSON Schema validation, especially when parser mode prevents unvalidated objects from entering the program. Pick Ajv for ecosystem breadth or Zod for TypeScript-first models, and plan around schemasafe's slow release cadence and incomplete typings.

API stability4/5The public API has three entry points, validator, parser, and lint, plus stable generated-function methods such as toModule() and toJSON(). Version 1.3.0 has been current since August 2023, so application-facing churn is low. Option semantics are substantial, however, and experimental lint messages may change in non-major releases according to the project's own linter documentation.
Docs5/5The README gives complete examples for validators, parser mode, custom formats, external schemas, errors, and module generation. Focused documents explain every option, strong mode, parser safety, error shapes, draft support, performance, code generation, auditability, and the experimental linter. The project is unusually candid about untrusted-schema denial of service and incomplete TypeScript declarations.
Maintenance2/5The repository is not archived and its latest push was in May 2025, but npm version 1.3.0 was published in August 2023. GitHub reports 19 open issues and pull requests together on a 178-star project. The code may be intentionally compact and stable, yet the release gap is a real concern for a validator expected to track specifications, runtimes, and security reports.
Ecosystem4/5The npm endpoint reports 4,784,590 downloads for the measured week, and the package supports five major JSON Schema drafts plus external references and a strict subset of OpenAPI discriminator. Zero dependencies and generated standalone modules make it easy to embed. Its extension and TypeScript ecosystem is much smaller than Ajv's, so popularity here does not equal plug-in breadth.

Use it if

  • You validate security-sensitive JSON and want compilation to fail when schema keywords are unknown, unused, unreachable, or incoherent
  • You want a parser that never exposes a successfully parsed but unvalidated JSON object to application code
  • You need zero runtime dependencies and self-contained generated validators that can be produced during a build for strict CSP
  • You want strong mode to require schema declarations, validation coverage, string checks, and basic regular-expression or uniqueItems complexity bounds
Skip it if

Setup reality

npm install @exodus/schemasafe gives a CommonJS package with no dependencies and an included declaration file. There is no peer setup, but the important choice is validator() versus parser(). validator() compiles in default mode and accepts an already parsed value. parser() accepts raw JSON text and uses strong mode by default, which can reject schemas that other validators accept: it requires $schema, requires every object property and array item path to be validated, requires string checks using format, pattern, or contentSchema, and enables complexity checks. Start by compiling schemas during application startup or build, not per request. Schema compilation errors always throw synchronously; invalid data returns false or a parser result with valid: false. Error details are off by default, and allErrors does nothing useful unless includeErrors is also true. Runtime compilation depends on generated JavaScript execution, so a strict Content-Security-Policy should use toModule() in a trusted build and import the output. Treat schemas as trusted configuration even though code generation JSON-escapes schema content: the security document says untrusted schemas can still cause denial of service. useDefaults and removeAdditional mutate validated output and deliberately refuse ambiguous schemas. Formats are asserted by default, differing from the newer JSON Schema specification's annotation default unless mode: 'spec' is selected. The TypeScript declaration openly calls itself experimental and incomplete, so do not expect a schema to infer a precise output type. Add separate input-size limits; strong mode checks for missing bounds around certain expensive patterns but does not judge whether a supplied maximum is sensible.

Patterns

Compile and run a basic validatorcompile-validator

const { validator } = require('@exodus/schemasafe');

const validate = validator({
  type: 'object',
  required: ['name'],
  properties: { name: { type: 'string' } },
  additionalProperties: false,
});

console.log(validate({ name: 'Ada' })); // true
console.log(validate({})); // false

validator() uses default mode, not strong mode, and accepts a value that has already been parsed or constructed.

Parse raw JSON only when it validatesparse-and-validate

const { parser } = require('@exodus/schemasafe');

const parseUser = parser({
  $schema: 'https://json-schema.org/draft/2020-12/schema',
  type: 'object',
  required: ['name'],
  properties: {
    name: { type: 'string', minLength: 1, maxLength: 100 },
  },
  additionalProperties: false,
});

const result = parseUser('{"name":"Ada"}');
if (result.valid) console.log(result.value);

parser() runs strong mode by default and returns no value when either JSON parsing or schema validation fails.

Compile an object validator in strong modeenable-strong-mode

const validate = validator({
  $schema: 'https://json-schema.org/draft/2020-12/schema',
  type: 'object',
  required: ['email'],
  properties: {
    email: { type: 'string', format: 'email', maxLength: 254 },
  },
  additionalProperties: false,
}, { mode: 'strong' });

Strong mode refuses uncovered properties and unchecked strings, so a permissive schema may fail during compilation before any data is tested.

Inspect the first validation errorinclude-validation-errors

const validate = validator(schema, { includeErrors: true });

if (!validate({ hello: 100 })) {
  console.log(validate.errors);
  // [{ keywordLocation: '#/properties/hello/type', instanceLocation: '#/hello' }]
}

Errors live on the compiled function and are overwritten by later calls; copy them immediately in concurrent or shared-validator code.

Return more than the first errorcollect-all-errors

const validate = validator(schema, {
  includeErrors: true,
  allErrors: true,
});

validate(candidate);
for (const error of validate.errors ?? []) {
  console.log(error.instanceLocation, error.keywordLocation);
}

allErrors requires includeErrors. Some expensive checks are skipped after the same property has already failed, limiting error collection as a denial-of-service precaution.

Validate a custom string formatdefine-custom-format

const validate = validator(
  { type: 'string', format: 'hex-color' },
  { formats: { 'hex-color': /^#[0-9a-f]{6}$/i } },
);

console.log(validate('#ff8800')); // true

Custom format functions and regular expressions are trusted code, not schema data; strong mode disables weak RegExp-object formats, so use a function there if needed.

Resolve an external $refresolve-external-schema

const address = {
  $id: 'https://example.test/address',
  type: 'object',
  properties: { city: { type: 'string' } },
};
const validate = validator(
  { $ref: 'https://example.test/address' },
  { schemas: [address] },
);

External schemas can be an array with top-level $id values, a Map, or an object keyed by the referenced name.

Insert schema defaults during validationapply-default-values

const validate = validator({
  type: 'object',
  properties: { retries: { type: 'integer', default: 3 } },
  additionalProperties: false,
}, { useDefaults: true });

const config = {};
validate(config);
console.log(config.retries); // 3

This mutates the input object. Compilation throws when the schema cannot apply defaults without ambiguous behavior.

Strip properties forbidden by the schemaremove-extra-properties

const validate = validator({
  type: 'object',
  properties: { name: { type: 'string' } },
  additionalProperties: false,
}, { removeAdditional: true });

const value = { name: 'Ada', admin: true };
validate(value);
console.log(value); // { name: 'Ada' }

removeAdditional mutates data and supports additionalProperties: false and additionalItems: false; uncertain schema paths fail compilation.

Generate validator source during a buildgenerate-standalone-module

const validate = validator(schema, { mode: 'strong' });
const moduleSource = validate.toModule();

// Write moduleSource to a generated file in your build, then import that file at runtime.

Pre-generating avoids runtime function construction under strict CSP. Treat schemas and custom format functions as trusted build inputs.

Collect schema compilation problemslint-schema

const { lint } = require('@exodus/schemasafe');

const errors = lint(schema, { mode: 'strong' });
for (const error of errors) {
  console.error(error.keywordLocation, error.message);
}

Linter mode is experimental, and its exact messages or details may change in non-major releases.

Follow newer format-annotation behaviorselect-spec-mode

const validate = validator(schema, { mode: 'spec' });

For draft 2019-09 and newer, spec mode disables format assertions by default and relaxes unused or unreachable keyword checks; default and strong modes assert formats.

Alternatives

PackageRegistryPick it when
ajvnpmChoose it for the largest JSON Schema ecosystem, broader extension support, standalone generation, and mature TypeScript integrations
jsonschemanpmChoose it when a straightforward non-code-generating validator is preferable to maximum throughput
zodnpmChoose it when TypeScript-first schema authoring and inferred application types matter more than JSON Schema portability