egoki
v0.9.1
Published
A composable JavaScript schema library for constructing, validating, and merging structured values.
Maintainers
Readme
Egoki
From Basque (/eˈɣ̞o.ki/ → eh-GOH-kee), meaning “suitable, appropriate, fitting”
A composable JavaScript schema library for constructing, validating, and merging structured values.
Features
- Compose schemas with an immutable fluent API.
- Extend compatible schemas recursively without mutating either schema.
- Inspect schema types and navigate configured schema structures.
- Validate unrestricted defined values, strings, numbers, booleans, null, functions, regular expressions, class and built-in instances, arrays, plain objects, and explicit unions of schemas.
- Configure required and optional values, defaults, allowed values, and synchronous refinements.
- Recursively validate, default, and merge nested arrays and objects.
- Merge values according to the schema that describes them.
- Collect multiple validation issues in a single
ValidationError. - Uses a single dependency (Hermēneíā), also maintained by the same author.
Installation
npm install egokiUsage
Egoki schemas are built with Schema factory methods and configured with chainable methods. Schema operations never modify the supplied schema or runtime values. Value-producing operations return values that satisfy the schema, or throw instead of returning an invalid value.
Creating schemas
import Schema from 'egoki';
const userSchema = Schema.object({
name: Schema.string(),
age: Schema.number().optional(),
active: Schema.boolean().default(true),
result: Schema.null().optional(),
onSave: Schema.function().optional(),
pattern: Schema.regexp().optional(),
createdAt: Schema.instance(Date),
identifier: Schema.union([
Schema.string(),
Schema.number(),
]),
});Schemas are immutable. Builder methods return a new schema instead of changing the schema they are called on.
const requiredName = Schema.string();
const optionalName = requiredName.optional();
requiredName === optionalName;
// ↳ falseAny schemas
Use Schema.any() when any defined JavaScript value is valid. It uses replacement merging by default; .adaptive() recursively merges plain objects while replacing arrays and every other runtime value.
const options = Schema.object()
.additionalProperties(
Schema.any().adaptive(),
);See Any Values for validation, adaptive merging, additional properties, and inspection.
Null schemas
Use Schema.null() when null is the only valid defined value. undefined remains the distinct omitted value and is rejected unless the schema is optional or a default supplies null.
const resultSchema = Schema.null().optional();
resultSchema.test(null);
// ↳ true
resultSchema.test(undefined);
// ↳ true
resultSchema.test(false);
// ↳ falseSee Null Values for defaults, merging, composition, and nested usage.
Function schemas
Use Schema.function() for JavaScript function values. It accepts any value for which typeof value === 'function', including arrow functions, async functions, generator functions, and classes.
const handlerSchema = Schema.function();
const handler = value => value;
handlerSchema.test(handler);
// ↳ trueA function passed to default() is the default value itself; Egoki does not invoke it as a factory.
See Functions for validation, defaults, merging, and scope.
Regular-expression schemas
Use Schema.regexp() for native JavaScript regular-expression values. Validation uses instanceof RegExp and does not execute the expression or modify lastIndex.
const patternSchema = Schema.regexp();
const pattern = /example/v;
patternSchema.test(pattern);
// ↳ trueRegular expressions use replacement merging, and their identity is preserved through validation, defaults, and merging.
See Regular Expressions for defaults, merging, composition, and scope.
Instance schemas
Use Schema.instance(Constructor) for values accepted by JavaScript's instanceof operator:
const dateSchema = Schema.instance(Date);
dateSchema.test(new Date());
// ↳ true
dateSchema.test('2026-08-26');
// ↳ falseSubclass instances and custom Symbol.hasInstance behavior follow native JavaScript semantics. Egoki does not invoke the constructor or inspect instance properties.
See Instances for constructor requirements, defaults, composition, and error behavior.
Union schemas
Use Schema.union() when a value may satisfy any of several explicit schemas.
const identifierSchema = Schema.union([
Schema.string(),
Schema.number(),
]);
identifierSchema.test('user-42');
// ↳ true
identifierSchema.test(42);
// ↳ trueUnion alternatives are ordered and use inclusive OR semantics. The union itself owns required/optional behavior and root defaults; nested defaults inside a selected alternative still apply. Failed unions retain each alternative's structured issues without promoting them to unrelated top-level failures.
See Unions for validation, defaulting, merging, and extension behavior.
Inspecting schemas
Every schema exposes a read-only type property, and Schema.isSchema() can optionally require a specific schema type.
const schema = Schema.object({
user: Schema.object({
name: Schema.string(),
}),
});
schema.type;
// ↳ 'object'
Schema.isSchema(schema, 'object');
// ↳ trueUse get() to retrieve an explicitly declared schema at a path. Use getSchemas() when a path may resolve through union alternatives or schema-valued additional properties. Array paths are the precise form; simple dot-separated strings are also supported.
const nameSchema = schema.get(['user', 'name']);
// ↳ StringSchema
const possible = Schema.union([
Schema.object({
value: Schema.string(),
}),
Schema.object({
value: Schema.number(),
}),
]).getSchemas('value');
// ↳ [StringSchema, NumberSchema]See Schema Inspection for path syntax, arrays, unions, and additional properties.
Validating values
Use validate() when the invalid value itself should produce an exception, or test() when a boolean result is enough.
userSchema.validate({
name: 'Ada',
age: 36,
active: true,
identifier: 'user-42',
});
// ↳ {name: 'Ada', age: 36, active: true, identifier: 'user-42'}userSchema.test({
name: 42,
active: true,
identifier: 'user-42',
});
// ↳ falseA failed validation throws ValidationError. It contains every issue discovered during the operation, including the path and invalid value. Issues may also expose optional, structurally frozen details metadata describing the failed condition.
import Schema, {ValidationError} from 'egoki';
try {
userSchema.validate({
name: 42,
active: true,
identifier: 'user-42',
});
} catch (error) {
if (error instanceof ValidationError) {
console.log(error.issues);
}
}Use .refine() for synchronous application-specific constraints after built-in schema validation:
const port = Schema.number().refine(
value => Number.isInteger(value) && value > 0,
'Expected a positive integer port',
);Use .message() to replace schema-wide messages or select a stable validation reason and optional application-defined code:
const count = Schema.number()
.message('Invalid count')
.message({
message: 'The argument `count` requires a number',
reason: 'type',
});See Validation for validation behavior and structured issues.
See Refinements for predicate rules, custom issue metadata, and composition.
See Validation Messages for selectors, precedence, and composition.
Defaults
Use default() to provide a value when the runtime value is undefined. Defaults are applied explicitly with applyDefaults() and do not make a required schema optional during direct validation.
const schema = Schema.object({
name: Schema.string().default('Anonymous'),
});
schema.applyDefaults({});
// ↳ {name: 'Anonymous'}Defaults can be applied recursively to nested schemas and additional properties configured with a schema.
See Defaults for configuring and applying default values.
Merging values
merge() combines a target and source according to the schema's merge strategy.
const schema = Schema.object({
name: Schema.string(),
settings: Schema.object({
language: Schema.string(),
}),
});
schema.merge(
{
name: 'Ada',
settings: {language: 'en'},
},
{
settings: {language: 'pt'},
},
);
// ↳ {
// name: 'Ada',
// settings: {language: 'pt'},
// }Arrays use replacement by default, but can be configured to append, prepend, or merge items by a key. Keyed arrays require every item to contain the configured key as a defined own property and require keys to be unique within every validated array and merge operand.
const schema = Schema.array(
Schema.number(),
).append();
schema.merge([1, 2], [3, 4]);
// ↳ [1, 2, 3, 4]See Merging for the available strategies and recursive behavior.
Resolving values
resolve() combines defaulting, merging, and validation into one operation. It first applies defaults to the target, merges the source, and validates the resulting value.
const schema = Schema.object({
name: Schema.string().default('Anonymous'),
});
schema.resolve({}, {});
// ↳ {name: 'Anonymous'}See Resolution for applying defaults, merging values, and validating the result.
Extending schemas
Use extend() to compose two schemas of the same root type. The result is a new schema and neither input is modified.
Compatible nested schemas are extended recursively. When nested schema types are incompatible, the extension schema replaces the existing nested schema. The root schema type itself must match.
const base = Schema.object({
options: Schema.object({
enabled: Schema.boolean(),
}),
});
const extension = Schema.object({
options: Schema.object({
timeout: Schema.number(),
}),
variants: Schema.array(Schema.object()),
});
const combined = base.extend(extension);extend() composes schema definitions only. It does not use runtime merge strategies; merge() remains the operation for combining runtime values.
See Composition for recursive behavior and option precedence.
API
Schema
The default and named Schema exports provide the factory and utility methods used to create schemas. ValidationError and RefinementError are also available as named exports.
| Method | Parameters | Returns | Description |
| - | - | - | - |
| Schema.any() | — | AnySchema | Creates a schema accepting any defined value. |
| Schema.string() | — | StringSchema | Creates a string schema. |
| Schema.number() | — | NumberSchema | Creates a number schema. |
| Schema.boolean() | — | BooleanSchema | Creates a boolean schema. |
| Schema.null() | — | NullSchema | Creates a schema accepting only null. |
| Schema.function() | — | FunctionSchema | Creates a function schema. |
| Schema.regexp() | — | RegExpSchema | Creates a regular-expression schema. |
| Schema.instance(Constructor) | Function | InstanceSchema | Creates a schema using a constructor's instanceof semantics. |
| Schema.array(items?) | Schema | ArraySchema | Creates an array schema, optionally with an item schema. |
| Schema.object(properties?) | Record<string, Schema> | ObjectSchema | Creates an object schema, optionally with declared property schemas. |
| Schema.union(schemas) | Schema[] | UnionSchema | Creates a schema accepting at least one alternative. |
| Schema.isSchema(value, type?) | unknown, string | boolean | Determines whether a value is a schema instance, optionally of a specific type. |
Common schema properties and methods
All schemas expose the following public API from Schema.
| Property | Type | Description |
| - | - | - |
| .type | string | The schema's concrete public type. |
| Method | Parameters | Returns | Description |
| - | - | - | - |
| .required(required?) | boolean | this | Configures whether a value is required. |
| .optional() | — | this | Makes a value optional. |
| .message(options) | object \| string | this | Overrides validation messages reported by the schema. |
| .refine(predicate, options?) | (value) => boolean, object \| string | this | Appends synchronous application-specific validation. |
| .default(value) | unknown | this | Configures a default value when none is defined. |
| .clone() | — | this | Creates a distinct schema with equivalent behavior. |
| .replace() | — | this | Replaces source values with target values during merging. |
| .extend(schema) | Schema | this | Extends a schema of the same root type without mutating either schema. |
| .get(path?) | string \| Array<string \| number> | Schema \| undefined | Returns the explicitly configured schema at a path. |
| .getSchemas(path?) | string \| Array<string \| number> | Schema[] | Returns configured schemas that may apply at a path. |
| .validate(value) | unknown | unknown | Validates a value and throws on failure. |
| .test(value) | unknown | boolean | Tests whether a value satisfies the schema. |
| .applyDefaults(value) | unknown | unknown | Applies configured defaults and returns a value satisfying the schema. |
| .merge(target, ...sources) | unknown, ...unknown[] | unknown | Merges sources from left to right and validates only the final result. |
| .resolve(target, ...sources) | unknown, ...unknown[] | unknown | Resolves sources transactionally with defaults before and after merging, then validates once. |
Merge strategy builders are available only on the schemas that support the corresponding strategy.
AnySchema
AnySchema accepts every defined JavaScript value. It uses replacement merging by default and exposes .adaptive() for runtime-aware recursive plain-object merging.
| Method | Parameters | Returns | Description |
| - | - | - | - |
| .adaptive() | — | this | Recursively merges plain objects and replaces all other defined values. |
See Any Values for details.
StringSchema, NumberSchema, and BooleanSchema
These schemas validate JavaScript strings, numbers, and booleans respectively.
| Method | Parameters | Returns | Description |
| - | - | - | - |
| .enum(values) | unknown[] | this | Restricts string, number, or boolean runtime values to an allowed set. |
enum() is available only on string, number, and boolean schemas. Any, null, function, regular-expression, instance, array, object, and union schemas do not expose it.
Non-empty values
Strings and arrays expose .nonEmpty() (or .nonEmpty(true)) to reject zero-length values. .nonEmpty(false) disables only this constraint; length settings remain independent. Optional schemas still accept omitted values.
The check does not trim strings and counts array holes towards length. Failures use the dedicated non-empty reason. See Non-Empty Values.
Length builders
Strings and arrays expose .minLength(limit) and .maxLength(limit) to configure inclusive bounds. .length(limit) sets both bounds to the same value; .length(minimum, maximum) sets both explicitly. Each supplied limit accepts a non-negative safe integer or false to clear its bound. .length(false) clears both.
Schema.string().minLength(3).maxLength(20);
Schema.array(Schema.number()).length(2);
Schema.string().length(3).maxLength(6); // Lengths 3–6.
Schema.array().length(5, false); // At least 5 items, no maximum.Checks use JavaScript .length, including UTF-16 code units for strings and holes for arrays. See Length Constraints for metadata, defaults, and composition.
String builders
StringSchema exposes .trimmed(), .trimmedStart(), and .trimmedEnd() to require strings that already satisfy JavaScript's corresponding trimming check. They validate without changing values. Directional builders set one end; .trimmed() sets both. Omitted arguments or true enable the selected checks; false disables them. Explicit undefined is invalid.
Schema.string().trimmed().nonEmpty();
Schema.string().trimmedStart(); // Trailing whitespace remains allowed.
Schema.string().trimmedEnd(); // Leading whitespace remains allowed.Empty strings and internal whitespace pass trimming checks. Each failure produces one issue: trimmed-start, trimmed-end, or trimmed, based on the failing enabled checks. Its details.trimmed describes both effective flags. See String Constraints for messages, defaults, and composition.
StringSchema supports .pattern(regexp) as a first-class constraint before application refinements. Each call replaces the previous pattern; .pattern(false) clears it.
Schema.string().pattern(/^[a-z]+$/iv);Patterns use native matching without implicit anchoring. Egoki snapshots source and flags and starts each check at index zero without modifying the supplied expression. See String Constraints for stateful patterns, issue details, and composition.
Numeric builders
NumberSchema additionally exposes these first-class constraints. They run before application refinements, without coercing runtime values.
| Method | Parameters | Returns | Description |
| - | - | - | - |
| .min(value, options?) | number \| false, {exclusive?: boolean} | this | Sets or clears the lower bound. |
| .max(value, options?) | number \| false, {exclusive?: boolean} | this | Sets or clears the upper bound. |
| .range(minimum, maximum, options?) | number \| false endpoints, {exclusiveMinimum?: boolean, exclusiveMaximum?: boolean} | this | Appends an accepted interval. |
| .ranges(ranges) | Descriptor array or false | this | Replaces all intervals; [] or false clears them. |
| .finite(isFinite?) | boolean | this | Requires a finite number unless explicitly disabled. |
| .integer(isInteger?) | boolean | this | Requires an integer unless explicitly disabled. |
| .safeInteger(isSafeInteger?) | boolean | this | Requires a safe integer unless explicitly disabled. |
Bounds are inclusive by default. Pass {exclusive: true} to exclude the boundary or false to clear it. Numeric flags are enabled by no-argument calls and disabled with false. See Numeric Constraints for infinity handling, issue details, and composition.
Ranges accept values in any configured interval while preserving independent bounds and flags. Replacement descriptors use {minimum, maximum, exclusiveMinimum?, exclusiveMaximum?}. During extension, an explicitly configured range collection replaces the inherited collection in full.
NullSchema
NullSchema accepts only null as a defined runtime value. It extends the common Schema API directly, uses replacement merging, and does not expose enum().
See Null Values for details.
FunctionSchema
FunctionSchema validates JavaScript function values. It extends the common Schema API directly, uses replacement merging, and does not expose enum() or callable-contract builders.
See Functions for details.
RegExpSchema
RegExpSchema validates native JavaScript regular-expression values using instanceof RegExp. It extends the common Schema API directly, uses replacement merging, and does not execute expressions or expose enum().
See Regular Expressions for details.
InstanceSchema
InstanceSchema validates with a configured constructor's native instanceof semantics. It uses replacement merging, preserves instance identity, and does not inspect instance properties or expose enum().
See Instances for details.
UnionSchema
UnionSchema accepts a runtime value when at least one configured alternative schema accepts it. It uses replacement merging and can be nested anywhere another schema is accepted.
See Unions for alternative ordering, defaults, validation errors, and extension behavior.
ArraySchema
ArraySchema validates arrays and can optionally validate each item with another schema.
| Method | Parameters | Returns | Description |
| - | - | - | - |
| .items(schema) | Schema | this | Configures the item schema. |
| .append() | — | this | Appends source items after target items during merging. |
| .prepend() | — | this | Prepends source items before target items during merging. |
| .keyedBy(key) | string | this | Matches array items by a defined, unique own property and enforces that identity during validation. |
ObjectSchema
ObjectSchema validates plain objects and can define schemas for declared and additional properties.
| Method | Parameters | Returns | Description |
| - | - | - | - |
| .properties(properties) | Record<string, Schema> | this | Configures declared property schemas. |
| .additionalProperties(schema) | Schema \| boolean | this | Configures how undeclared properties are handled. |
| .reserve(properties) | string \| string[] | this | Protects exact property definitions and policies from later configuration and extension. |
| .forbid(properties, options?) | string \| string[] \| RegExp, string \| object | this | Rejects matching enumerable string properties. |
| .disallow(properties, options?) | string \| string[] \| RegExp, string \| object | this | Alias for .forbid(). |
| .allow(properties) | string \| string[] \| RegExp | this | Reverses earlier matching property prohibitions. |
| .deep() | — | this | Deep merges source values with target values during merging. |
ValidationError
ValidationError extends TypeError and is thrown when validation fails.
| Property | Type | Description |
| - | - | - |
| summary | string | Concise validation-result summary. |
| message | string | Summary followed by formatted issue details. |
| issues | object[] | All validation issues, in discovery order. |
| issue | object | The first validation issue. |
See Validation for the structure of validation issues.
RefinementError
RefinementError extends TypeError and is thrown when a refinement predicate returns a value other than a primitive boolean. Exceptions thrown by predicate code propagate unchanged rather than being wrapped.
See Refinements for the predicate contract and error boundaries.
Documentation
- Any Values — unrestricted defined values and adaptive merging.
- Null Values — validating the exact
nullvalue and distinguishing omission. - Functions — function validation, defaults, merging, and scope.
- Regular Expressions — native regular-expression validation, defaults, and merging.
- Instances — constructor-based validation, identity, and composition.
- Validation — validation behavior, errors, and structured issues.
- String Constraints — regular-expression patterns, matching state, and composition.
- Length Constraints — minimum, maximum, and exact lengths for strings and arrays.
- Non-Empty Values — independent non-empty validation for strings and arrays.
- Numeric Constraints — numeric bounds, ranges, finite numbers, integers, and safe integers.
- Validation Messages — overriding issue messages by schema, reason, and application code.
- Object Property Policies — forbidding and re-allowing reserved object properties.
- Reserved Schema Properties — protecting object schema definitions and policies during composition.
- Refinements — synchronous custom validation, issue paths, and predicate behavior.
- Defaults — configuring and applying default values.
- Merging — merge strategies and recursive merging.
- Resolution — applying defaults, merging values, and validating the result as one operation.
- Composition — composing and extending schemas.
- Unions — accepting values through one or more alternative schemas.
- Inspection — inspecting schema types and retrieving schemas by path.
