Picking a method
Where does the data come from?
| Source | Method | Returns |
|---|---|---|
| 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-throw | entities.validate(id, data) | ValidationErrors |
| Doesn't matter - you want a yes/no | entities.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.Related
instantiate- trust-boundary coercion entry pointmaterialize- construction helper for trusted datavalidate- structured errors without a throwis- boolean type guard- instantiate vs materialize - decision table with Transform decoder detail
See also
- Argument conventions - static counterparts and
SchemaRef - Bookstore domain - schemas used in examples
- Error views - what to do with
ValidationErrorsaftervalidate