Your First Type
If you already know TypeScript, congratulations- you just learned most of ArkType’s syntax 🎉
Define
import { type } from "arktype"
const const user: Type<{
name: string;
platform: "android" | "ios";
versions?: (string | number)[];
}>
user = type({
name: "string",
platform: "'android' | 'ios'",
"versions?": "(number | string)[]"
})
// extract the type if needed
type type User = {
name: string;
platform: "android" | "ios";
versions?: (string | number)[];
}
User = typeof const user: Type<{
name: string;
platform: "android" | "ios";
versions?: (string | number)[];
}>
user.infer
If you make a mistake, don’t worry- every definition gets the autocomplete and validation you’re used to from your editor, all within TypeScript’s type system.
Compose
Suppose we want to move platform
and version
from our original type to a new device
property.
const const user: Type<{
name: string;
device: {
platform: "android" | "ios";
versions?: (string | number)[];
};
}>
user = const type: TypeParser
<{
readonly name: "string";
readonly device: {
readonly platform: "'android' | 'ios'";
readonly "versions?": "(number | string)[]";
};
}, Type<{
name: string;
device: {
platform: "android" | "ios";
versions?: (string | number)[];
};
}, {}>>(def: validateObjectLiteral<...>) => Type<...> (+2 overloads)
type({
name: "string",
// nested definitions don't need to be wrapped
device: {
platform: "'android' | 'ios'",
"versions?": "(number | string)[]"
}
})
To decouple device
from user
, just move it to its own type and reference it.
const const device: Type<{
platform: "android" | "ios";
versions?: (string | number)[];
}>
device = const type: TypeParser
<{
readonly platform: "'android' | 'ios'";
readonly "versions?": "(number | string)[]";
}, Type<{
platform: "android" | "ios";
versions?: (string | number)[];
}, {}>>(def: validateObjectLiteral<{
readonly platform: "'android' | 'ios'";
readonly "versions?": "(number | string)[]";
}, {}, bindThis<...>>) => Type<...> (+2 overloads)
type({
platform: "'android' | 'ios'",
"versions?": "(number | string)[]"
})
const const user: Type<{
name: string;
device: {
platform: "android" | "ios";
versions?: (string | number)[];
};
}>
user = const type: TypeParser
<{
readonly name: "string";
readonly device: Type<{
platform: "android" | "ios";
versions?: (string | number)[];
}, {}>;
}, Type<{
name: string;
device: {
platform: "android" | "ios";
versions?: (string | number)[];
};
}, {}>>(def: validateObjectLiteral<...>) => Type<...> (+2 overloads)
type({
name: "string",
device: const device: Type<{
platform: "android" | "ios";
versions?: (string | number)[];
}>
device
})
Validate
At runtime, we can pass unknown
data to our type and get back either a validated User
or an array of clear, customizable errors with a root summary
.
const const out: {
name: string;
device: {
platform: "android" | "ios";
versions?: (string | number)[];
};
} | ArkErrors
out = user({
name: "Alan Turing",
device: {
platform: "enigma",
versions: [0, "1", 0n]
}
})
if (const out: {
name: string;
device: {
platform: "android" | "ios";
versions?: (string | number)[];
};
} | ArkErrors
out instanceof type.errors) {
// hover out.summary to see validation errors
console.error(const out: RuntimeErrors
out.RuntimeErrors.summary: string
device.platform must be "android" or "ios" (was "enigma")
device.versions[2] must be a number or a string (was bigint)summary)
} else {
// hover out to see your validated data
console.log(`Hello, ${const out: {
name: string;
device: {
platform: "android" | "ios";
versions?: (string | number)[];
};
}
out.name}`)
}
And that’s it! You now know how to to define a Type
use it to check your data at runtime.
Next, we’ll take a look at how ArkType extends TypeScript’s type system to handle runtime constraints like maxLength
and pattern
.