Expressions
Intersection
Like its TypeScript counterpart, an intersection combines two existing Type
s to create a new Type
that enforces the constraints of both.
Union
All unions are automatically discriminated to optimize check time and error message clarity.
A union that could apply different morphs to the same data throws a ParseError!
Learn the set theory behind this restriction
If you're relatively new to set-based types, that error might be daunting, but if you take a second to think through the example, it becomes clear why this isn't allowed. The logic of bad
is essentially:
- If the input is an object where
box
is astring
, parse and return it as a number - If the input is an object where
box
is astring
, return it as a string
There is no way to deterministically return an output for this type without sacrificing the commutativity of the union operator.
sameError
may look more innocuous, but has the same problem for an input like { a: "1", b: "2" }
.
- Left branch would only parse
a
, resulting in{ a: 1, b: "2" }
- Right branch would only parse
b
, resulting in{ a: "1", b: 2 }
Brand
Add a type-only symbol to an existing type so that the only values that satisfy is are those that have been directly validated.
Brands can be a great way to represent constraints that fall outside the scope TypeScript, but remember they don't change anything about what is enforced at runtime!
For more information on branding in general, check out this excellent article from Josh Goldberg.
Narrow
Narrow expressions allow you to add custom validation logic and error messages. You can read more about them in their intro section.
If the return type of a narrow is a type predicate, that will be reflected in the inferred Type
.
Morph
Morphs allow you to transform your data after it is validated. You can read more about them in their intro section.
To
If a morph returns an ArkErrors
instance, validation will fail with that result instead of it being treated as a value. This is especially useful for using other Types as morphs to validate output or chain transformations.
To make this easier, there's a special to
operator that can pipe to a parsed definition without having to wrap it in type
to make it a function:
Unit
While embedded literal syntax is usually ideal for defining exact primitive values, ===
and type.unit
can be helpful for referencing a non-serialiazable value like a symbol
from your type.
Enumerated
type.enumerated
defines a Type based on a list of allowed values. It is semantically equivalent to type.unit
if provided a single value.
valueOf
type.valueOf
defines a Type from a TypeScript enum
or enum-like object.
`enum` should be avoided in modern TypeScript
Over time, TS has shifted away from features that affect the .js
it ultimately outputs, including enum
.
With the introduction of the --erasableSyntaxOnly
option to facilitate type-stripping, enum
is no longer considered a best practice.
type.valueOf
exists primarily to facilitate integration with legacy code that relies on enum
, but if you have the option, prefer transparently defining value sets via ["tupleLiterals"] as const
, { objectLiterals: true } as const
, or directly via type.enumerated
.
It is almost semantically identical to type.enumerated(...Object.values(o))
. The only exception occurs when an object has an entry with a numeric value and entry with that value as a key mapping back to the original:
Notice EquivalentObject
doesn't include "numeric"
because it inverts a numeric value entry.
We recommend type.enumerated
as the more transparent option for converting value references to a Type. However, if the described inverted entry pairs can't exist on your object, you can safely use type.valueOf
.
Meta
Metadata allows you to associate arbitrary metadata with your types.
Some metadata is consumed directly by ArkType, for example description
is referenced by default when building an error message.
Other properties are introspectable, but aren't used by default internally.
Cast
Sometimes, you may want to directly specify how a Type
should be inferred without affecting the runtime behavior. In these cases, you can use a cast expression.
Parenthetical
By default, ArkType's operators follow the same precedence as TypeScript's. Also like in TypeScript, this can be overridden by wrapping an expression in parentheses.
this
this
is a special keyword that can be used to create a recursive type referencing the root of the current definition.
Unlike its TypeScript counterpart, ArkType's this
is not limited to interfaces. It can also be used from within a tuple expression.
Referencing this
from within a scope will result in a ParseError. For similar behavior within a scoped definition, just reference the alias by name: