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:
-
validate(data, ajvOptions?)
Validates the inputdata
against this schema. Returns a Promise resolving to:{valid: boolean;errors: ErrorObject[] | null | undefined;}If
valid
isfalse
,errors
will contain an array of detailed validation issues. -
toSchema(name?)
Generates a valid JSON Schema (as a plain JavaScript object) from this fragment. The optionalname
parameter sets a$id
and atitle
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 numbersconst 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 patternconst 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 schemaconst 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 sampleasync 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 typetype 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.