@metreeca/core - v0.9.22
    Preparing search index...

    Module trace

    Composable value validators.

    Provides composable validators for declaring value constraints. Checks nest to mirror the shape of the data and apply as a single expression, so no traversal or branching need be written by hand. A run reports every violation the value incurs at once, as a Trace keyed to mirror the value itself, diagnosing a failure in full rather than one error at a time.

    Constraining Numbers

    Restrict numbers to integral values:

    import { integer } from '@metreeca/core/trace';

    integer(); // no fractional part

    Constraining Strings

    Restrict strings by length, pattern, or whitespace:

    import { length, normalised, pattern } from '@metreeca/core/trace';

    length(1, 100); // between 1 and 100 characters
    pattern(/^\p{Lu}/u); // matching the pattern
    normalised(); // no leading, trailing, or repeated whitespace
    normalised(true); // as above, but admitting newlines

    Constraining Literals

    Restrict numbers and strings alike, each compared by its own natural ordering:

    import { domain, gt, gte, lt, lte } from '@metreeca/core/trace';

    gt(0); // greater than 0, comparing numbers by magnitude
    gte(0); // greater than or equal to 0
    lt("m"); // less than "m", comparing strings lexicographically
    lte("m"); // less than or equal to "m"

    domain(["draft", "review", "final"]); // one of the admitted values

    Constraining Arrays

    Restrict the array and its items in one call:

    import { all, array, length, size, values } from '@metreeca/core/trace';

    array(length(1, 100)); // every element within bounds
    array([length(1, 10), length(1, 100)]); // positional tuple template, matched in length

    array(length(1, 100), all(size(1, 10), values(["en"]))); // elements, plus cardinality and membership

    Constraining Objects

    Restrict the object and its entries in one call:

    import { test, all, entry, keys, length, object, pass, pattern, size } from '@metreeca/core/trace';
    import { key } from '@metreeca/core';

    object(entry([undefined, length(0, 100)])); // every entry, constrained by value
    object(entry([pattern(/^[^_]/u)])); // every entry, constrained by key
    object({ label: length(1, 100), notes: length(0, 1000) }); // closed: unnamed properties rejected
    object({ label: length(1, 100), [key]: pass }); // open: unnamed properties admitted unconstrained

    object({ id: length(1, 50) }, // properties, plus constraints spanning them
    all(
    size(1, 10), // between 1 and 10 properties
    keys(["id"]), // required property names
    test(record => !("id" in record && "code" in record) || ["mutually exclusive"])
    )
    );

    Constraining Other Values

    Adapt an arbitrary predicate to cover whatever the built-in vocabulary doesn't, reporting either a fixed message or one computed from the rejected value; reject outright to close a branch reached only by values already known to be illegal:

    import { test, fail } from '@metreeca/core/trace';

    test(value => value.length % 2 === 0 || ["expected an even number of characters"]);
    test(value => value % 3 === 0 || [`expected a multiple of 3, found <${value}>`]);

    fail(["unexpected value"]); // reject anything, reporting a fixed message
    fail(value => [`unexpected <${value}>`]); // as above, but computing the message

    Combining Validators

    Assemble validators into compound checks, bottoming out in pass, the constant admitting anything:

    import {
    all, any, fail, gte, integer, length, nullable, one, optional, pass, pattern, required, type
    } from '@metreeca/core/trace';
    import { isString } from '@metreeca/core';

    pass; // accept anything: neutral to all(), absorbing to any()

    all(integer(), gte(0)); // every validator must pass
    any(length(3, 3), length(5, 5)); // at least one must pass
    one(pattern(/^\d+$/u), pattern(/^[a-z]+$/u)); // exactly one must pass

    required(length(1, 100)); // rejected when absent
    optional(length(1, 100)); // unconstrained when absent
    nullable(length(1, 100)); // unconstrained when null
    optional(nullable(length(1, 100))); // unconstrained when either

    type(isString, length(1, 100)); // strings within bounds, anything else rejected as a non-string

    type(isString, // as above, wording the rejection explicitly
    length(1, 100),
    fail(value => [`expected a string, found <${typeof value}>`])
    );

    Validators key the violations they report by the part of the value incurring them, so a nested check already yields a navigable report; name a violation of your own by prefixing the message handed to fail or test with a facet of its own, in braces.

    Every validator slot is Modal, so a check may be switched off inline with a guard expression, without rebuilding the surrounding composition conditionally:

    all(
    strict && integer(),
    bounded && gte(0)
    );

    Composing Complex Validators

    Nest the above into a deep check, end to end: literal, array, and object constraints combined under a single validator, applied to a value, and reported to callers as a TraceError:

    import {
    all, array, fail, gte, length, normalised, object, optional, pass, size, TraceError, type
    } from '@metreeca/core/trace';
    import { isNumber, isString, key } from '@metreeca/core';

    const validateProduct = object({

    label: type(isString, all(length(1, 100), normalised())),
    price: type(isNumber, gte(0)),
    notes: optional(type(isString, normalised(true))),

    tags: array( // multi-valued, reporting non-qualifying elements explicitly
    type(isString, length(1, 25), fail(["expected a string"])),
    size(0, 10)
    ),

    [key]: pass // admit unnamed properties

    });

    const trace = validateProduct(product);

    if ( trace !== undefined ) { throw new TraceError("malformed product", trace); }

    A failing run reports every violated constraint at once, keyed by property and by element position, each message naming the constraint that incurred it:

    [
    {
    "label": ["{length} expected string length less than or equal to <100>"],
    "tags": [{ "2": ["{length} expected string length less than or equal to <25>"] }]
    }
    ]

    Variables

    pass

    A validator accepting any value.

    Type Aliases

    Trace

    Validation trace.

    Validator

    Value validator.

    Optional validator.

    Keyed

    Named entries with an optional wildcard entry.

    Classes

    TraceError

    Error carrying a structured validation Trace.

    Functions

    integer

    Constrains numbers to integral values.

    length

    Constrains string length.

    pattern

    Constrains strings to a pattern.

    normalised

    Constrains strings to normalised whitespace.

    gt

    Constrains numbers and strings to an exclusive lower bound.

    gte

    Constrains numbers and strings to an inclusive lower bound.

    lt

    Constrains numbers and strings to an exclusive upper bound.

    lte

    Constrains numbers and strings to an inclusive upper bound.

    domain

    Constrains numbers and strings to an enumerated domain.

    array

    Constrains an array.

    object

    Constrains an object.

    entry

    Constrains an entry.

    size

    Constrains the size of an array or object.

    keys

    Requires keys to be present in an array or object.

    values

    Requires values to be present in an array or object.

    test

    Constrains a value with a custom predicate that words its own violation.

    fail

    Rejects every value.

    required

    Requires a value to be present.

    optional

    Makes a validator tolerant of absent values.

    nullable

    Makes a validator tolerant of null values.

    all

    Requires every validator to pass.

    any

    Requires at least one validator to pass.

    one

    Requires exactly one validator to pass.

    type

    Constrains a value by type.