Skip to content

API Reference

This document details each publicly exposed function and type in Loom Schema. You will see how to create different schema fragments, compose them, and perform validation.


Core Concepts

Schema Fragments

Each function (e.g., string(), object(), array()) returns an object we call a schema fragment. A schema fragment has two main methods:

  1. validate(data, ajvOptions?)
    Validates the input data against this schema. Returns a Promise resolving to:

    {
    valid: boolean;
    errors: ErrorObject[] | null | undefined;
    }

    If valid is false, errors will contain an array of detailed validation issues.

  2. toSchema(name?)
    Generates a valid JSON Schema (as a plain JavaScript object) from this fragment. The optional name parameter sets a $id and a title in the resulting JSON Schema to help identify it.

TypeScript Integration

Most schema-building functions allow you to infer the TypeScript type for your schema using:

type MyType = Infer<typeof mySchemaFragment>;

This helps ensure your data usage remains consistent with the validation rules you’ve defined.


Base Schema Types

Every schema fragment in Loom Schema inherits a common set of base properties defined in the BaseSchemaOptions interface. These properties provide metadata, identification, and additional capabilities to your schemas. They are available regardless of the specific type (e.g. string, number, object, etc.) you create, ensuring a consistent foundation across all schema fragments.

Inherited Base Properties:

  • $schema: (optional) The JSON Schema version identifier (e.g. "http://json-schema.org/draft/2020-12/schema").
  • $id: (optional) A unique identifier for the schema, generated automatically if not provided.
  • $vocabulary: (optional) Specifies which vocabularies the schema uses.
  • $comment: (optional) A comment for documentation purposes.
  • $anchor: (optional) Defines a URI fragment identifier to reference this schema.
  • $recursiveAnchor: (optional) Indicates if recursive references are allowed.
  • $recursiveRef: (optional) A reference to a schema for recursive definitions.
  • title: (optional) A human-friendly title for the schema.
  • description: (optional) A detailed explanation of what the schema represents.
  • examples: (optional) An array of sample values conforming to the schema.
  • deprecated: (optional) A boolean flag indicating whether the schema is deprecated.
  • readOnly: (optional) A boolean flag indicating the schema is read-only.
  • writeOnly: (optional) A boolean flag indicating the schema is write-only.
  • default: (optional) A default value for the schema.
  • Composition Keywords:
    • allOf, anyOf, oneOf: Allow combining multiple schemas.
    • not: Excludes a specific schema.
    • if, then, else: Enable conditional schema definitions.
  • $ref: (optional) A reference to another schema.
  • $defs: (optional) An object containing subschemas.
  • enum: (optional) An array of possible constant values.
  • const: (optional) A constant value that the data must match.

These base properties are automatically mixed into every fragment you build (using helpers like string(), number(), object(), etc.), ensuring that your generated JSON Schemas carry all the necessary metadata and compositional abilities.

Schema-Building Functions

string(options?)

Creates a string schema fragment.

import { string } from "loom-schema";
const nameSchema = string({ minLength: 1, maxLength: 100 });

Options (selected common fields):

  • type?: "string" (internally set to "string")
  • format?: string — e.g., "email", "uri", etc.
  • minLength?: number
  • maxLength?: number
  • pattern?: string

number(options?)

Creates a number schema fragment.

import { number } from "loom-schema";
const scoreSchema = number({ minimum: 0, maximum: 100 });

Options (selected common fields):

  • type?: "number" (internally set to "number")
  • minimum?: number
  • maximum?: number
  • exclusiveMinimum?: number
  • exclusiveMaximum?: number
  • multipleOf?: number

integer(options?)

Creates an integer schema fragment (same as number, but type set to "integer").

import { integer } from "loom-schema";
const countSchema = integer({ minimum: 0, maximum: 10 });

Options (similar to number, except type is forced to "integer").


boolean(options?)

Creates a boolean schema fragment.

import { boolean } from "loom-schema";
const toggleSchema = boolean();

Options (selected common fields):

  • type?: "boolean" (internally set to "boolean")

nil(options?)

Creates a null schema fragment.

import { nil } from "loom-schema";
const nullOnlySchema = nil();

Options (selected common fields):

  • type?: "null" (internally set to "null")

array(options)

Creates an array schema fragment.

import { array, number } from "loom-schema";
// An array of numbers
const numberArraySchema = array({ items: number() });

Options:

  • type?: "array" (internally set to "array")
  • items?: JsonSchemaInput — A fragment or a plain object describing the array items.
  • minItems?: number
  • maxItems?: number
  • uniqueItems?: boolean
  • And other JSON Schema keywords relevant to arrays.

You can pass another Loom fragment to items (like number()) or use any other schema you have created.


object(options)

Creates an object schema fragment.

import { object, string, number } from "loom-schema";
const userSchema = object({
properties: {
name: string({ minLength: 1 }),
age: number({ minimum: 0 }),
},
required: ["name", "age"],
});

Options:

  • type?: "object" (internally set to "object")
  • properties?: Record<string, JsonSchemaInput>
  • required?: string[]
  • additionalProperties?: JsonSchemaInput | boolean
  • And other JSON Schema keywords relevant to objects.

If you simply pass key-value pairs (without an explicit properties field) and do not use reserved keywords, Loom automatically treats them as properties.


schema(options?)

Creates an object schema fragment with an easy way to add multiple possible keywords. It can handle advanced usage (like adding allOf, anyOf, oneOf, etc.) while giving you a convenient single entry point.

import { schema } from "loom-schema";
const advancedObject = schema({
type: "object",
properties: {
exampleKey: { type: "string" },
},
required: ["exampleKey"],
});

Most of the time, you will prefer using the dedicated object, string, number, etc. helpers. But schema() is a good fallback if you need full manual control.

  • schema accepts fragments just like any other fragment would, as-well.

Composition Functions

Sometimes, your data may need to satisfy multiple schemas at once or any of several schemas. Loom offers convenient wrappers for these cases.

allOf([...fragments], options?)

Creates a fragment that requires all of the given fragments to be satisfied.
Returns a single fragment that merges the constraints of each included fragment.

import { allOf, string, object } from "loom-schema";
// A string that must be at least 3 characters, and must match a pattern
const allOfSchema = allOf([
string({ minLength: 3 }),
string({ pattern: "^[a-zA-Z0-9]+$" }),
]);

anyOf([...fragments], options?)

Creates a fragment that requires at least one of the given fragments to be satisfied.
Useful for data that can be of multiple permitted shapes.

import { anyOf, string, number } from "loom-schema";
const multiTypeSchema = anyOf([string(), number()]);

oneOf([...fragments], options?)

Creates a fragment that requires exactly one of the given fragments to be satisfied.
This is stricter than anyOf, enforcing that only one fragment may match.

import { oneOf, object, string } from "loom-schema";
const exclusiveTypeSchema = oneOf([
string({ format: "email" }),
object({
properties: { localPart: string(), domain: string() },
required: ["localPart", "domain"],
}),
]);

conditional({ if, then, else }, options?)

Creates a fragment that conditionally applies different schemas based on an if condition.
If data matches the if schema, it must also match then. Otherwise, it must match else.

import { conditional, object, number, string } from "loom-schema";
const conditionalSchema = conditional({
if: object({ required: ["isSpecial"] }),
then: object({
properties: { isSpecial: number() },
required: ["isSpecial"],
}),
else: string({ format: "email" }),
});

Validation

Every fragment has a validate(data, ajvOptions?) method:

const result = await myFragment.validate(someData);
if (!result.valid) {
console.error(result.errors);
} else {
console.log("Data is valid!");
}
  • data: The data you want to validate.
  • ajvOptions: Optional settings to pass through to AJV, including:
    • customKeywords — An array of custom keyword definitions or names.
    • customVocabularies — Additional vocabularies to include in validation.
    • Most other standard AJV options like allErrors, strict, etc.

Generating JSON Schemas

Each fragment also has a toSchema(name?) method that outputs a valid JSON Schema. The optional name gets used as $id and title.

const rawSchema = myFragment.toSchema("MyAwesomeSchema");
console.log(JSON.stringify(rawSchema, null, 2));

This is especially useful when you need to store or share the generated JSON Schema, or integrate with other tooling that expects a standard JSON Schema.


Type Utilities

Infer<T>

A TypeScript helper that extracts the type from any Loom schema fragment. For instance:

import { object, string, Infer } from "loom-schema";
const userSchema = object({
properties: { name: string() },
required: ["name"],
});
// The TS type is { name: string }
type User = Infer<typeof userSchema>;

This ensures that when you use userSchema for validation, you also get a consistent TypeScript type for that data.


Example: Putting It All Together

import { object, string, number, array, anyOf, Infer } from "loom-schema";
// Define a user schema
const userSchema = object({
properties: {
username: string({ minLength: 3 }),
favoriteThings: array({ items: string() }),
age: anyOf([
number({ minimum: 13 }),
number({ multipleOf: 2, maximum: 12 }), // Possibly allow even ages under 13 (just a silly example)
]),
},
required: ["username", "age"],
});
// Validate a data sample
async function example() {
const sample = { username: "abc", age: 14, favoriteThings: ["pizza"] };
const result = await userSchema.validate(sample);
if (result.valid) {
console.log("Data is valid!");
} else {
console.error("Validation errors:", result.errors);
}
}
example();
// Infer the TS type
type User = Infer<typeof userSchema>; // { username: string; age: number; favoriteThings?: string[] }

Summary

Loom Schema empowers you to define modular, composable JSON Schemas in TypeScript or JavaScript. Each fragment can be validated independently or turned into a standard JSON Schema. Combining fragments is straightforward with functions like allOf, anyOf, oneOf, and conditional.

For more advanced usage, you can pass AJV-specific options to validate(...) or generate named schemas via toSchema("MyName"). If you are looking to unify your data models and validations, Loom Schema provides a flexible, type-safe solution.