Skip to content

Getting started ​

Install ​

sh
npm i juniper
sh
pnpm add juniper
sh
yarn add juniper

TIP

Juniper is ESM only, and requires Node.js 24 or later. From CommonJS, load it with a dynamic import: const { stringSchema } = await import('juniper');

Build a schema ​

Every schema has a functional constructor, like objectSchema() and stringSchema(). Nest them to describe your data, and get its TypeScript type with SchemaType:

ts
import { 
objectSchema
,
stringSchema
, type
SchemaType
} from 'juniper';
const
schema
=
objectSchema
({
properties
: {
foo
:
stringSchema
({
maxLength
: 10 }).
startsWith
('abc'),
bar
:
stringSchema
().
nullable
(),
anything
: true,
},
required
: ['foo'],
additionalProperties
: false,
}); type
Foo
=
SchemaType
<typeof
schema
>;
type Foo = {
    foo: `abc${string}`;
    bar?: string | null;
    anything?: unknown;
}

toJSON() renders the JSON Schema document:

ts
schema
.
toJSON
();
// { // type: 'object', // properties: { // foo: { type: 'string', maxLength: 10, pattern: '^abc' }, // bar: { type: ['string', 'null'] }, // anything: true, // }, // required: ['foo'], // additionalProperties: false, // }

Validate data ​

Juniper builds schemas; it does not validate data. Pass the output to any JSON Schema validator.

Juniper writes the format keyword as given, and does not check it. If a schema uses format, use a validator that supports the formats you need: juniper-validator does, and Ajv needs ajv-formats.

With juniper-validator ​

juniper-validator wraps Ajv, and implements Standard Schema, so it works with any library that accepts standard validators.

ts
import { 
makeValidator
} from 'juniper-validator';
const
validator
=
makeValidator
(
schema
);
if (
validator
.
is
(
input
)) {
console
.
log
(
input
.
foo
);
foo: `abc${string}`
}

With Ajv ​

Ajv's compile takes the schema's type as a generic, so the validator narrows its input too:

ts
import { 
Ajv2020
} from 'ajv/dist/2020.js';
const
validate
= new
Ajv2020
({
strict
: true }).
compile
<
SchemaType
<typeof
schema
>>(
schema
.
toJSON
()
); if (
validate
(
input
)) {
console
.
log
(
input
.
foo
);
foo: `abc${string}`
}

Mistakes are compile errors ​

Juniper is opinionated about schemas that can't be right. Each schema only exposes the keywords that apply to it:

ts
import { 
arraySchema
} from 'juniper';
arraySchema
().maxLength(10);
Property 'maxLength' does not exist on type 'ArraySchema<any, [], never, unknown, false, 0>'.

Keys in required must exist in properties, so a typo doesn't quietly make a schema reject everything:

ts
import { 
objectSchema
,
stringSchema
} from 'juniper';
objectSchema
({
properties
: {
fooBar
:
stringSchema
() },
required
: ['foobar'],
Type '"foobar"' is not assignable to type '"fooBar"'.
});

And schemas that can never match, like an object that must also be a string, are rejected:

ts
import { 
objectSchema
,
stringSchema
} from 'juniper';
objectSchema
().
allOf
([stringSchema()]);
Argument of type 'StringSchema<string, false>[]' is not assignable to parameter of type 'Schema<Record<string, unknown> | null>'. Type 'StringSchema<string, false>[]' is missing the following properties from type 'Schema<Record<string, unknown> | null>': toJSON, "~standard"

WARNING

Most of these checks happen only in TypeScript. In JavaScript the same code may run, and emit a nonsensical schema.

Constructors and immutability ​

Every schema is a class, with three equivalent ways to create it:

ts
import { 
StringSchema
,
stringSchema
} from 'juniper';
const
schema1
= new
StringSchema
({
maxLength
: 10 });
const
schema2
=
StringSchema
.
create
({
maxLength
: 10 });
const
schema3
=
stringSchema
({
maxLength
: 10 });
schema3
instanceof
StringSchema
; // true

Every option can also be set with a method of the same name. Schemas are immutable: each method returns a new schema and leaves the original alone, so a shared base schema is safe to build on.

ts
import { 
numberSchema
} from 'juniper';
const
integer
=
numberSchema
({
type
: 'integer' });
const
multipleOfFive
=
integer
.
multipleOf
(5);
integer
.
toJSON
(); // { type: 'integer' }
multipleOfFive
.
toJSON
(); // { type: 'integer', multipleOf: 5 }

Next steps ​