Skip to content

Picking a method

Where does the data come from?

SourceMethodReturns
Outside (HTTP, queue, file)entities.instantiate(id, data)T (throws on invalid)
Inside (your code, test fixtures)entities.materialize(s, data)T (validates by default)
Doesn't matter - you want errors-without-throwentities.validate(id, data)ValidationErrors
Doesn't matter - you want a yes/noentities.is(id, data)boolean

The trust boundary axis

instantiate is for data crossing into your system from outside - HTTP request bodies, queue messages, file imports, IPC payloads. materialize is for data you produced - test fixtures, form scaffolding, default-filled instances. See instantiate vs materialize for the decode-step and enablePartial mechanics.

Decision recipes

HTTP request handler

/**
 * Picking a method: HTTP handler — instantiate at the trust boundary
 *
 * `instantiate` is for data crossing from outside: HTTP request bodies,
 * queue messages, file imports. Failure is the caller's contract violation.
 * The error is theirs to handle — return 400, log and re-queue, etc.
 *
 * Uses the canonical bookstore order fixture to simulate a valid HTTP payload,
 * then a tampered payload to show the error path.
 */

import {
  InstantiationError
} from '../../../src/index.js';
import type { Order } from '../bookstore/index.js';
import {
  aboxFixtures, bookstoreEntities, OrderSchema
} from '../bookstore/index.js';

// Simulate parsing a raw request body. In a real handler this would be
// `await req.json()` — unknown shape from the wire.
const rawPayload: unknown = { ...aboxFixtures.order };

let order: null | Order = null;
let statusCode = 200;

try {
  order = bookstoreEntities.instantiate(OrderSchema.$id, rawPayload);
} catch (error) {
  if (error instanceof InstantiationError) {
    statusCode = 400;
  } else {
    throw error;
  }
}

// Valid payload — instantiate succeeds.
console.assert(statusCode === 200);
console.assert(order !== null);

if (order !== null) {
  console.assert(order.customerId === aboxFixtures.order.customerId);
}

console.log('valid payload → status', statusCode, '| orderId:', order?.orderId);

// Tampered payload — missing required `orderId`.
const {
  'orderId': _omit, ...payloadWithoutId
} = aboxFixtures.order;

void _omit;

let caughtBad = false;
let badStatus = 200;

try {
  bookstoreEntities.instantiate(OrderSchema, payloadWithoutId);
} catch (error) {
  if (error instanceof InstantiationError) {
    caughtBad = true;
    badStatus = 400;
  }
}

console.assert(caughtBad);
console.log('missing orderId → status', badStatus);

// Instance form reuse — bookstoreEntities resolves all transitive $refs
// (CustomerId, OrderLine, Money, etc.), so the same registry handles every
// subsequent call without re-compiling.
const staticOrder = bookstoreEntities.instantiate(OrderSchema, aboxFixtures.order);

console.assert(staticOrder.orderId === aboxFixtures.order.orderId);
console.log('reuse instantiate → orderId:', staticOrder.orderId);
Output
Press Execute to run this example against the real library.

Test fixture

/**
 * Picking a method: test fixture — materialize for trusted construction
 *
 * `materialize` is for data you produce: test fixtures, form scaffolding,
 * default-filled instances. Failure is your own bug — the schema contract
 * has not been met. The method validates by default and throws
 * `MaterializationError` if validation fails.
 *
 * Pass `{ enablePartial: true }` to allow missing required-without-default
 * fields during lenient construction (form scaffolding use case).
 */

import {
  aboxFixtures, bookstoreEntities, OrderSchema
} from '../bookstore/index.js';

// Full fixture — all required fields present. materialize succeeds.
// materialize takes a schema object as its first argument; the result
// is typed via the registry's Order (the partial input carries plain literals,
// so the materialize generic falls back to an untyped result).
const order = bookstoreEntities.materialize(
  OrderSchema,
  {
    ...aboxFixtures.order,
    'orderLines': [...aboxFixtures.order.orderLines]
  }
);

console.assert(order.customerId === aboxFixtures.order.customerId);
console.log('materialize succeeded → orderId:', order.orderId, '| customerId:', order.customerId);
Output
Press Execute to run this example against the real library.

Lenient partial construction

/**
 * Picking a method: lenient partial construction with enablePartial
 *
 * `materialize` with `{ enablePartial: true }` allows missing required-
 * without-default fields. Use this for form scaffolding where only a
 * subset of fields is known at construction time — for example, starting
 * an order with only a customerId and filling items later.
 *
 * Without `enablePartial`, a missing required field would throw
 * `MaterializationError`.
 */

import {
  aboxFixtures, bookstoreEntities, OrderSchema
} from '../bookstore/index.js';

// Partial construction — items and total are required in OrderSchema
// but are omitted here. enablePartial suppresses the missing-field error.
// materialize takes a schema object as its first argument.
const scaffold = bookstoreEntities.materialize(
  OrderSchema,
  { 'customerId': aboxFixtures.customer.customerId },
  { 'enablePartial': true }
);

console.assert(scaffold.customerId === aboxFixtures.customer.customerId);
console.log('partial scaffold → customerId:', scaffold.customerId, '| orderId:', scaffold.orderId);
Output
Press Execute to run this example against the real library.

Logging / analytics (no throw needed)

/**
 * Picking a method: validate — structured errors without a throw
 *
 * `validate` returns a `ValidationErrors` collection. Use it when you
 * want to inspect or log errors without catching exceptions — logging
 * pipelines, analytics, audit trails, progressive form validation.
 *
 * An empty collection (`errors.ok === true`) means valid.
 * A non-empty collection carries structured error items with `keyword`,
 * `path`, and `message` fields.
 */

import {
  aboxFixtures, bookstoreEntities, OrderSchema
} from '../bookstore/index.js';

// Valid order — empty collection, ok === true.
const errors = bookstoreEntities.validate(OrderSchema.$id, aboxFixtures.order);

console.assert(errors.length === 0);
console.log('valid order → error count:', errors.length);

// Invalid order — missing required `orderId`.
const invalid = {
  'customerId': aboxFixtures.customer.customerId,
  'orderLines': aboxFixtures.order.orderLines,
  'orderTotal': aboxFixtures.order.orderTotal,
  'placedAt': aboxFixtures.order.placedAt,
  'shippingAddress': aboxFixtures.order.shippingAddress
};
const invalidErrors = bookstoreEntities.validate(OrderSchema.$id, invalid);

// `orderId` is required — at least one error reported.
console.assert(invalidErrors.length > 0);
console.log('missing orderId → error count:', invalidErrors.length, '| first keyword:', invalidErrors.items[0]?.keyword);
Output
Press Execute to run this example against the real library.

When to use is

is is a TypeScript type guard. Use it when you need to narrow a union type or check unknown input without triggering a throw:

/**
 * Picking a method: is — boolean type guard
 *
 * `is` is a TypeScript type guard. Use it when you need to narrow a union
 * type or check unknown input without triggering a throw. The method
 * returns `true` if the data satisfies the schema, `false` otherwise.
 *
 * `is` does not apply defaults or coerce values — it is a pure predicate.
 */

import type { Order } from '../bookstore/index.js';
import {
  aboxFixtures, bookstoreEntities, OrderSchema
} from '../bookstore/index.js';

const incoming: unknown = { ...aboxFixtures.order };

// Passing the schema $id selects the type-guard overload, which narrows
// `incoming` to the registry's Order type within the block.
if (bookstoreEntities.is(OrderSchema.$id, incoming)) {
  // Within this block, `incoming` is narrowed to `Order`.
  const order: Order = incoming;

  console.assert(order.orderId === aboxFixtures.order.orderId);
  console.log('is(Order) → true | orderId:', order.orderId);
}

// Invalid shape — is returns false.
const notAnOrder: unknown = { 'customerId': 'foo' };

console.assert(!bookstoreEntities.is(OrderSchema.$id, notAnOrder));
console.log('is(Order) on bare {customerId} →', bookstoreEntities.is(OrderSchema.$id, notAnOrder));
Output
Press Execute to run this example against the real library.

See also

Released under the MIT License.