Primate LogoPrimate

Schemas

Import Pema's callable default export:

import p from "pema";

p(schema) normalizes schema shorthand. Existing Pema schemas remain schemas; primitive literals become literal schemas; constructors validate instances; one-member arrays describe arrays, other arrays describe tuples; and object shapes become object schemas.

const Settings = p({
  theme: p.union("light", "dark"),
  retries: p.u8,
});

const Names = p([p.string]);
const Pair = p([p.string, p.number]);
const Enabled = p(true);

Prefer p({...}) for object schemas. The equivalent p.object(...) factory is available for completeness, alongside explicit factories such as p.tuple(...) and p.literal(...) when they make intent clearer.

Parse and infer

Every ordinary schema has parse(value). It returns the parsed value or throws a ParseError. The compile-time output is available through schema.infer:

const Port = p.u16;
type Port = typeof Port.infer; // number

const port = Port.parse(8080);

Object schemas additionally expose input, which accounts for fields supplied by defaults and optional fields:

const Config = p({
  host: p.string.default("localhost"),
  port: p.u16,
});

type ConfigInput = typeof Config.input;
// { host?: string; port: number }

Public type helpers include Schema, NormalizeSchema, NormalizeSchemaObject, Parsed and Serialized. Prefer typeof schema.infer for an application's parsed value unless a generic API needs the lower-level types.

Optional and default values

optional() accepts undefined and omits an undefined object property from the parsed output. default(value) substitutes a value, or calls a function, when the input is undefined.

const Preferences = p({
  nickname: p.string.optional(),
  locale: p.string.default("en"),
  created: p.date.now(),
});

These produce OptionalType and DefaultType wrappers. For Stores, a wrapper is persistent only when its inner schema is Storable.

Derived values

derive() runs after successful parsing and changes the inferred output:

const FullName = p({
  first: p.string,
  last: p.string,
}).derive(({ first, last }) => `${first} ${last}`);

Derived schemas are validation-only and are not direct Store fields. Use p.async() for asynchronous object derivation.

Strict and loose parsing

Pema is strict by default. Use p.loose when a type supports coercing web input:

p.u16.parse("8080");       // throws
p.loose.u16.parse("8080"); // 8080
p.loose.boolean.parse("true"); // true

Every factory is present under p.strict and p.loose, but coercion only changes behavior for types that define it. strict does not reject undeclared object keys; use object closed() for that policy.

Primitive checks

Primitive schemas support check(predicate, message):

const Even = p.u32.check(value => value % 2 === 0, "Expected an even number");

The predicate runs after type parsing and coercion. A false result produces an invalid_format issue.

Schema metadata

Ordinary schemas expose toJSON(). Primate uses serialized schema metadata for framework integration, but it is not a lossless representation of every validator, transform, predicate, derived function or object policy. Do not use it as a general-purpose schema persistence format.

Previous
Intro
Next
Strings