Scopes
Scopes are the foundation of ArkType, and one of the most powerful features for users wanting full control over configuration and to make their own keywords available fluidly within string definition syntax.
A scope is just like a scope in code- a resolution space where you can define types, generics, or other scopes. The type
export is a actually just a method on our default Scope
!
Defining a Scope
To define a scope, you may either import { scope } from "arktype"
or use type.scope
on the default type
export.
A scope is specified as an object literal mapping names to definitions.
coolScope
is an object with reusable methods like type
and generic
. You can use it to create additional Type
s that can reference your aliases- id
, user
and usersById
.
To use the scoped types directly, you must .export()
your Scope
to a Module
. A Module
is just an object mapping aliases to Type
s. They can be used for validation or in any other context a Type
can be used.
.export()
is also useful in combination with the spread operator for extending your Scope
s. Recall that a Type
can be referenced as a definition. This means that spreading a Module
into the definition you pass to scope
includes all of that Module's aliases in your new Scope
.
If you don't plan to reuse your Scope
to create additional types, it is common to export it inline:
type.module
is available as sugar for this pattern:
Cyclic Types
Scopes make it easy to create recursive Type
s. Just reference the alias like you would any other:
Cyclic types are inferred to arbitrary depth. At runtime, they can safely validate cyclic data.
Some `any`s are not what they seem!
By default, TypeScript represents anonymous cycles as ...
. However, if you have noErrorTruncation
enabled, they are visually displayed as any
😬
Luckily, despite its appearance, the type otherwise behaves as you'd expect- TypeScript will provide completions and will complain as normal if you access a non-existent property.
visibility
Intermediate aliases can be useful for composing Scoped definitions from aliases. Sometimes, you may not want to expose those aliases externally as Type
s when your Scope
is export
ed.
This can be done using private aliases:
import()
Private aliases are especially useful for building scopes without polluting them with every alias you might want to reference internally. To facilitate this, Scopes have an import()
method that behaves identically to export()
but converts all exported aliases to private
.
submodules
If you've used keywords like string.email
or number.integer
, you may wonder if aliases can be grouped in your own Scopes. Recall from the introduction to Scopes that type
is actually just a method on ArkType's default Scope
, meaning all of its functionality is available externally, including alias groups called Submodules.
Submodules are groups of aliases with a shared prefix. To define one, just assign the value of the prefix to a Module
with the names you want:
Submodules are parsed bottom-up. This means subaliases can be referenced directly in the root scope, but root aliases can't be referenced from the submodule, even if it's inlined.
nested
Submodules can be nested to arbitrary depth:
rooted
The Submodules from our previous examples group Type
s together, but cannot be referenced as Type
s themselves the way string
and number
can. To define a Rooted Submodule, just use an alias called root
:
thunks
When users are first learning about Scopes, one of the most common mistakes is to reference an alias in a nested type
call:
This error occurs because although the id
alias would be resolvable in the current Scope directly, type
only allows references to builtin keywords. In this case, the type
wrapper is redundant and the fix is to simply remove it:
However, even if it is possible to define your scope without invoking type
by composing aliases and tuple expressions, the fluent methods available on Type
can define complex types that can be cumbersome to express otherwise. In these situations, you can use a thunk definition to access the type
method on the Scope you're currently defining:
Though thunk definitions are really only useful when defining a Scope, they can be used anywhere a Type
definition is expected: