Create a schema that matches exactly one literal value.
Typical usage is in union branches and tagged object fields, for example
kind = schema.literal("user").
@eryx/schema Module
JSON
@module @eryx/schema
Runtime schema construction, validation, and parsing.
This module is the public entrypoint for value schemas. It is designed for runtime data boundaries: request bodies, config files, CLI input, persisted payloads, plugin data, and any other dynamic values that should be validated before your application logic depends on them.
Why schemas instead of ad-hoc checks?
Without schemas, validation logic tends to spread across call sites and diverge over time. With schemas, you define the contract once and reuse it:
- one place for validation rules,
- one consistent parse/validation behavior,
- one composable unit for nested and shared types.
parse vs validate
Every schema provides two runtime entrypoints:
parse(value)->(ok, parsedOrError)validate(value)->boolean
validate is for pass/fail checks.
parse is for pass/fail plus normalized output.
Use parse when:
- you depend on transformed output (for example string trimming/casing),
- you want actionable error messages,
- you are at an external boundary and need diagnostics.
Use validate when:
- you only need a boolean gate,
- you do not need transformed output or error details.
Quick Start
local schema = require("@eryx/schema")
local User = schema.table({
id = schema.number():int():positive(),
name = schema.string():trimWhitespace():minLen(1),
email = schema.optional(schema.string()),
})
local ok, parsedOrErr = User:parse({
id = 7,
name = " Ada ",
})
-- ok == true
-- parsedOrErr.name == "Ada"
Available Constructors
schema.literal(value)
Matches exactly one literal value (string/number/boolean/nil).
Good for:
- discriminators (for example
kind = "user"), - protocol constants,
- strict mode flags.
local Kind = schema.literal("user")
schema.boolean()
Validates booleans.
Good for:
- feature switches,
- toggle fields in payloads.
schema.number()
Validates numbers and supports chainable numeric constraints.
Common constraints:
- range checks:
:gt,:gte,:lt,:lte - sign checks:
:positive,:nonnegative,:negative,:nonpositive - divisibility:
:multipleOf - integer-only:
:int
local Port = schema.number():int():gte(1):lte(65535)
schema.string()
Validates strings and supports both validation and parse-time mutation.
Common validators:
- size:
:minLen,:maxLen,:length - pattern matching:
:match(Lua patterns),:regex - containment:
:startsWith,:endsWith,:includes - casing:
:isUpperCase,:isLowerCase
Common mutators:
:trimWhitespace():toUpperCase():toLowerCase()
Use mutators when you want canonicalized output from parse.
Use validators when you want strict input acceptance criteria.
schema.array(childSchema)
Validates list-like tables where each element must satisfy a child schema.
Common constraints:
:minLen:maxLen:length:nonempty
local Tags = schema.array(schema.string():trimWhitespace()):nonempty()
schema.optional(childSchema)
Wraps a schema so nil is accepted.
You can provide a default at parse time:
local RetryCount = schema.optional(schema.number():int()):default(3)
schema.anyOf(...)
Union schema. Parsing succeeds if any branch succeeds. Branches can be schemas or raw literals.
local Role = schema.anyOf("admin", "member", "guest")
schema.map(keySchema, valueSchema)
Validates key/value tables by validating both keys and values. Both key and value schemas can be full schemas or literals.
Good for:
- dynamic dictionaries,
- metadata bags with constrained key/value types.
Table Schemas
schema.table({...}) creates object-like schemas with named fields.
Default behavior is strict:
- unknown keys fail parse,
- required keys must be present,
- optional fields are represented with
schema.optional(...).
local Config = schema.table({
host = schema.string(),
port = schema.number():int(),
tls = schema.optional(schema.boolean()):default(false),
})
Struct Helpers (@eryx/schema/struct)
Table schemas are created here with schema.table(...).
Table transformation helpers (strict/strip/passthrough/catchall/extend/etc)
are provided by @eryx/schema/struct.
local Base = schema.table({ a = schema.number() })
local struct = require("@eryx/schema/struct")
local Strict = struct.strict(Base)
local Strip = struct.strip(Base)
local Pass = struct.passthrough(Base)
Common helpers from @eryx/schema/struct:
- unknown key policy:
strictstrippassthroughcatchall
- shape transforms:
partialpickomitrequired
- composition:
extend
- output immutability:
readonlyfreeze
When to use these helpers:
stripat untrusted boundaries when extra keys should be ignored.passthroughwhen preserving unknown extension keys is intentional.catchallwhen extension keys are allowed but must still be typed.partialfor patch/update payloads.pick/omitfor endpoint-specific projections.requiredwhen moving from partial input to strict internal shapes.
Full Integration Example
local schema = require("@eryx/schema")
local struct = require("@eryx/schema/struct")
local Payload = struct.strip(schema.table({
id = schema.number():int(),
role = schema.anyOf("admin", "member"),
tags = schema.optional(schema.array(schema.string())),
}))
local ok, result = Payload:parse({
id = 42,
role = "admin",
tags = { "ops" },
extra = "ignored",
})
Notes and Conventions
- Schema modifiers are immutable-style: chaining creates new schema objects.
- Error messages are path-oriented for easier debugging of nested failures.
- Favor small, reusable schema fragments and compose them.
- Keep schemas close to your domain modules for discoverability and drift prevention.
Summary
Functions
API Reference
Functions
schema.literal
Parameters
Returns
A literal schema.
schema.number
Create a number schema. By default, this schema accepts any runtime number and applies no extra constraints until modifiers are chained.
Parameters
Internal metadata snapshot used when deriving chained schemas.
Returns
A number schema.
schema.string
Create a string schema. By default, this accepts any runtime string and applies no additional constraints until modifiers are chained.
Parameters
Internal metadata snapshot used when deriving chained schemas.
Returns
A string schema.
schema.boolean
Create a schema that accepts boolean values. The returned schema is intentionally minimal and composes cleanly inside table, map, optional, and union schemas.
Returns
A boolean schema.
schema.array
Create an array schema for values validated by a child schema.
Child schemas can be primitive or composed; nested errors are path-prefixed
using array indices (for example #2).
Parameters
Child schema applied to each numeric-indexed element.
Internal metadata snapshot used for chained schema derivation.
Returns
An array schema.
schema.table
Create a table schema from field definitions. Each key maps to a child schema. By default, unknown keys are rejected unless policy is changed via struct helpers.
Parameters
Field map where each key points to a child schema.
Optional unknown-key/catchall/readonly configuration.
Returns
A table schema.
schema.optional
Create an optional schema around a child schema.
Use this to model fields that may be absent. For predictable downstream
shapes, pair with :default(...).
Parameters
Schema used when input is not nil.
Internal optional metadata, including default configuration.
Returns
An optional schema.
schema.anyOf
Create a union schema that succeeds when any child schema matches.
Raw literal values are promoted to literal schemas.
This allows concise declarations like schema.anyOf("a", "b", 3).
At least one branch is required.
Returns
A union schema.
schema.map
Create a map schema from key and value schemas (or literal shorthands).
Literal key/value inputs are promoted automatically, allowing concise forms
like schema.map("status", "ok") when strict constants are desired.
Parameters
Schema (or literal shorthand) used to validate map keys.
Schema (or literal shorthand) used to validate map values.
Returns
A map schema.