Syntax Reference
A detailed description of all available syntax
Last updated
A detailed description of all available syntax
Last updated
The following reference contains an overview of all the syntax available. When a schema is parsed, an AST tree is produced and exposed via the ruleset
field on a validator instance. The shape of every node type is documented under the "rule structure" sections. Most casual users can ignore this information, but it does come in handy for .
Stability Warning: In regards to the "Rule Structure" sections found on this page, please keep in mind that there are no guarantees over the stability of the shape of these interfaces. While breaking changes to these interfaces are somewhat unlikely, it is entirely possible that TypeScript will add new syntax features that, if Moat Maker were to properly support, would require changing the shape of particular AST nodes in non-backward-compatible ways. The SemVer number will still be updated appropriately if breaking changes do come up, but if you can help it, it's advisable to avoid relying on this ruleset feature altogether.
A simple rule allows you to match an input value against a primitive type. The available primitive types are: string
, number
, bigint
, boolean
, symbol
, null
, and undefined
. You can also match against object
to ensure your value isn't a primitive value at all.
should be avoided, but if a validator ever does receive one, it'll correctly treat it like an object, which means new Boolean(true)
does not match validator`true`
.
Rule Structure
Rule Structure
Just like with the syntax, you are now allowed to set the value
property to NaN
, Infinity
, or -Infinity
.
Both any
and unknown
perform the same action in Moat Maker, which is "do not validate me". It is recommended to just stick to using the unknown
type to align with the preference of using unknown
over any
in TypeScript, but don't treat this suggestion as law. If your validator schema is pretty much a copy-paste of a TypeScript interface, then, by all means, use the any
type if your TypeScript interface definition uses it, to help keep the relationship between the two clearer.
Rule Structure
Property matching syntax can be used to describe which properties you expect the value being validated to have. The values need not be an object — primitives can match as well when their properties line up with your schema. The only values that can never match a property rule are undefined
and null.
Just like in TypeScript, the value being validated may have extra properties that aren't defined by your schema. A property can be marked as optional by adding a question mark before the colon. If any of the properties are getters, their functions will be triggered as needed to verify that they return a value of the correct type.
Individual properties can be separated from each other via commas, semicolons, or new lines. It's recommended to choose the same separator that you use when defining object types in TypeScript.
To add special characters to a property key, you can quote the key. To use a dynamic value (like a symbol) as a key, you can bracket the key and interpolate your value into the brackets.
Note that the order in which properties are evaluated should be considered as undefined behavior. The exact details of the evaluation order could change at any point in time.
Rule Structure
A FrozenMap
is a data structure implemented internally that's intended to mimic the behavior of a normal map except that it does not provide methods to mutate its contents. If new methods come out for the normal Map
class, you can expect it to take a bit for FrozenMap
to be updated to contain those methods as well.
An array rule will verify two things.
The provided value is an array (or it inherits from the Array
class).
The contents of the array matches the supplied pattern.
Rule Structure
Tuple syntax is an alternative to array syntax, and is used when the types of the elements in the array depend upon their position, for example, the [number, string]
rule states that the first element must be a number, and the second must be a string. It also states that the provided array must contain exactly two items, no more, no less.
Similar to TypeScript, entries at the end of the tuple can be marked as optional by adding a ?
(e.g. [number, boolean?, string?]
Allows you to provide an array with 1, 2, or 3 items), and an unknown number of additional items can be received via the rest syntax ...
(e.g. [boolean, ...number[]]
allows you to provide an array with at least one item). Optional entries must appear after required entries, and if rest syntax is used, it must be used at the end.
Just like in TypeScript, you can choose to provide names/labels for tuple entries as follows:
Rule Structure
Union syntax can be used when you need to provide multiple possible types a value can conform to.
Rule Structure
The variants
property must be non-empty.
By using the &
syntax, you can require that two different rules must be matched.
You'll find this especially useful in scenarios where you are...
Composing validators together, and wish to tack on extra rules, e.g. validator`${anotherValidatorInstance} & { x: number }`
etc
Rule Structure
The variants
property must be non-empty.
Rule Structure
Parentheses
Parentheses work as you would expect them to work.
Comments
Both line comments and block comments are supported.
Comments will work across interpolated regions, causing the interpolated content to be ignored. (Remember that the interpolated value will still be evaluated, it's just that Moat Maker ignores the received value).
A variety of different types of values can be interpolated for different effects. If you interpolate...
a primitive, such as a number, string, symbol, etc: These are compared for equality. -0 is considered equal to +0. NaN is also supported, and an interpolated NaN is considered equal to a NaN being validated.
Regular Expressions: A value being validated must be a string that conforms to the pattern described in the interpolated regular expression.
The TypeScript type InterpolatedValue
is exported, and represents a value that is allowed to be interpolated into a validator.
Rule Structure
A primitive literal rule lets you match against an exact primitive value. Literal syntax is supported for string, numbers, bigints, and booleans (undefined and null are categorized as "simple rules"). All kinds of numeric literals are supported to align with , including scientific notation (e.g. 2.3e7
), hexadecimal/octal/binary literals, the use of underscores as a separator (e.g. 123_456
), and infinity. Similar to TypeScript, NaN
and Infinity
are not supported literals, but you can still match against NaN
by interpolating it in (e.g. validator`${NaN}`
).
Warning: As always, avoid comparing decimal numbers or numbers with large magnitudes (numbers outside of the to range). Computers tend to make minor calculation errors that can prevent such numbers from being equal. Instead of doing an equality check, consider checking if the value is inside a range. You can use the recipe to help out.
Index signatures can be used to enforce a rule on all non-inherited properties, regardless of whether or not they're . The type of an index signature must either be string
, number
, or symbol
.
The hope is for JavaScript to come out with a native frozen-map implementation, at which point this project will switch to using those, instead of an internal implementation of them. There is currently a in the works to do just this.
If the array is a sparse array, the holes are treated as undefined
. If you wish to actively prevent sparse arrays instead, you can use the recipe.
If the array is a sparse array, the holes are treated as undefined
. If you wish to actively prevent sparse arrays instead, you can use the recipe.
Wishing to mix with built-in rule syntax, e.g. validator`number[] & ${expectNonEmptyArray}`
Special syntax to match against the contents of an iterable is provided as a convenience. This isn't natively supported by TypeScript, but was added because it helps deal with the fact that generic syntax can't be supported in Moat Maker the same way it can in TypeScript. Visit the dedicated to learn more about using the iterable-matching syntax, as well as other generic-replacement tools.
Classes/functions: When a class is interpolated, Moat Maker will check that the value being validated is an instance of this class, or an instance of a derived class. Remember that in JavaScript, a class is a function, so there's no practical way to tell them apart. The internal algorithm differs from a normal instanceof
check in a couple of ways: 1. The symbol.hasInstance
property will be ignored to align more closely with TypeScript's behavior, and 2. when a primitive class is interpolated, brand-checking will be performed, which basically means if you have an instance from one realm (like your browser's parent frame) and a class from another (like an iframe), you can compare the two, and it will work as expected. instanceof
does not support cross-realm checks like this. If you wish to check if a value is a direct instance of a class (and not of a derived class), you can use the recipe.
Certain object types returned by this library, including , , and : Refer to their individual documentation entries to learn about how they behave when they are interpolated into a validator.
Any other object not covered by an above bullet point: These will throw an error. If you wish to compare objects by identity, you can use the recipe.