表达式

交集

类似于其 TypeScript 对标,交集将两个现有的 Type 组合起来,创建一个新的 Type,它强制执行两者的约束。

const  = ({
	// 一个带有 arktype.io 域的电子邮件地址
	intersected: "string.email & /@arktype\\.io$/"
})

并集

所有并集都会自动进行判别,以优化检查时间和错误消息的清晰度。

const  = ({
	key: "string | number"
})

一个可能对相同数据应用不同 morph 的并集会抛出 ParseError!

// 操作数重叠,但两者都不转换数据
const  = ("number > 0").or("number < 10")
// 操作数转换数据,但输入之间没有重叠
const  = ("string.numeric.parse").or({ box: "string" })
// 操作数重叠并转换数据,但转换方式相同
const  = ("string > 5", "=>", Number.parseFloat).or([
	"0 < string < 10",
	"=>",
	Number.parseFloat
])
// ParseError: 包含 morph 的无序并集与输入重叠的类型是不确定的
const  = ({ box: "string.numeric.parse" }).or({ box: "string" })
const  = ({ a: "string.numeric.parse" }).or({ b: "string.numeric.parse" })
了解此限制背后的集合论

如果您对基于集合的类型相对陌生,这个错误可能令人望而却步,但如果您花点时间思考这个例子,就会清楚为什么不允许这样做。bad 的逻辑本质上是:

  • 如果输入是一个对象,其中 boxstring,则解析并将其作为数字返回
  • 如果输入是一个对象,其中 boxstring,则将其作为字符串返回

没有办法为这种类型确定性地返回输出,而不牺牲并集操作符的交换性

sameError 可能看起来更无害,但对于像 { a: "1", b: "2" } 这样的输入,它有同样的问题。

  • 左分支只会解析 a,导致 { a: 1, b: "2" }
  • 右分支只会解析 b,导致 { a: "1", b: 2 }

品牌

向现有类型添加一个仅限类型的符号,这样只有直接验证的值才能满足它。

const  = ("(number % 2)#even")
type  = typeof even.infer

const :  = even.assert(2)
// TypeScript: Type 'number' is not assignable to type 'Brand<number, "even">'
const :  = 5

品牌是一种很好的方式来表示 TypeScript 范围之外的约束,但请记住,它们不会改变运行时强制执行的任何内容!

有关品牌的一般信息,请查看 Josh Goldberg这篇优秀文章

窄化

窄化表达式允许您添加自定义验证逻辑和错误消息。您可以在它们的介绍部分中阅读更多内容。

const  = ({
	password: "string",
	confirmPassword: "string"
}).narrow((data, ctx) => {
	if (data.password === data.confirmPassword) {
		return true
	}
	return ctx.reject({
		expected: "与密码相同",
		// 不在错误消息中显示密码!
		actual: "",
		path: ["confirmPassword"]
	})
})

// ArkErrors: confirmPassword must be identical to password
const  = Form({
	password: "arktype",
	confirmPassword: "artkype"
})

如果窄化的返回类型是一个类型谓词,那么它将反映在推断的 Type 中。

// 悬停查看谓词如何传播到外部 `Type`
const  = ("string").narrow(
	(data, ctx): data is `ark${string}` =>
		data.startsWith("ark") ?? ctx.reject("以 'ark' 开头的字符串")
)

Morph

Morph 允许您在验证后转换数据。您可以在它们的介绍部分中阅读更多内容。

// 悬停查看 morph 如何在类型级别表示
const  = ("string").pipe(str => str.trimStart())

To

如果 morph 返回一个 ArkErrors 实例,则验证将使用该结果失败,而不是将其视为值。这特别适用于将其他 Type 用作 morph 来验证输出或链式转换。

为了使这更容易,有一个特殊的 to 操作符,可以管道到解析定义,而无需将其包装在 type 中使其成为函数:

const  = ("string.numeric.parse |> number % 2")

const  = ("number % 2")
// 等价于 parseEvenTo
const  = ("string.numeric.parse").pipe()

单元

虽然嵌入的字面量语法 通常是定义确切原语值的理想选择,但 ===type.unit 可以帮助引用像 symbol 这样的非可序列化值。

const  = Symbol()

const  = type.unit()

枚举

type.enumerated 根据允许值的列表定义一个 Type。如果提供单个值,它等价于 type.unit

const  = Symbol()

const  = type.enumerated(1337, true, )

valueOf

type.valueOf 从 TypeScript enum 或类似 enum 的对象定义一个 Type。

现代 TypeScript 中应避免使用 `enum`

随着时间推移,TS 已远离影响最终输出的 .js 的特性,包括 enum

随着 --erasableSyntaxOnly 选项 的引入,以促进类型剥离,enum 不再被视为最佳实践。

type.valueOf 主要存在于与依赖 enum 的遗留代码集成,但如果您有选择,请优先通过 ["tupleLiterals"] as const{ objectLiterals: true } as const 或直接通过 type.enumerated 透明地定义值集。

enum TsEnum {
	numeric = 1
}

const  = type.valueOf(TsEnum) // Type<1>

它几乎等价于 type.enumerated(...Object.values(o))。唯一的例外是当对象有一个具有数字值的条目,以及一个将该值作为键映射回原始条目的条目时:

// 这是 TsEnum 在 JS 中编译的结构
const  = {
	numeric: 1,
	"1": "numeric"
} as 

// 即使推断也允许字符串 "numeric",但只允许数字 1
const  = type.valueOf()

注意 EquivalentObject 不包括 "numeric",因为它反转了数字值条目。

我们推荐 type.enumerated 作为将值引用转换为 Type 的更透明选项。然而,如果您的对象上不存在所述的反转条目对,您可以安全地使用 type.valueOf

元数据

元数据允许您将任意元数据与您的类型关联。

某些元数据直接被 ArkType 消费,例如 description 在构建错误消息时默认被引用。

其他属性是可内省的,但默认不被内部使用。

// 此验证器的错误消息现在将以“must be a special string”开头
const  = ("string").configure({
	description: "a special string"
})

// 为添加描述元数据提供的语法糖
const  = ("number").describe("a special number")

强制转换

有时,您可能希望直接指定 Type 应该如何被推断,而不影响运行时行为。在这些情况下,您可以使用强制转换表达式。

// 允许任何字符串,但建议 “foo” 和 “bar”
type  = "foo" | "bar" | (string & {})

const  = ({
	autocompletedString: "string" as type.<>
})

括号

默认情况下,ArkType 的操作符遵循与 TypeScript 相同的优先级。就像在 TypeScript 中一样,这可以通过将表达式包裹在括号中来覆盖。

// 悬停查看区别!
const  = ({
	stringOrArrayOfNumbers: "string | number[]",
	arrayOfStringsOrNumbers: "(string | number)[]"
})

this

this 是一个特殊关键字,可用于创建引用当前定义根部的递归类型。

const  = ({
	label: "string",
	"box?": "this"
})

const  = DisappointingGift({
	label: "foo",
	box: { label: "bar", box: {} }
})

if ( instanceof type.errors) {
	// ArkErrors: box.box.label must be a string (was missing)
	console.error(.summary)
} else {
	// 缩小推断到任意深度
	console.log(.box?.box?.
label: string | undefined
label
)
}

从作用域内部引用 this 将导致 ParseError。对于作用域定义内的类似行为,只需按名称引用别名:

const  = ({
	DisappointingGift: {
		label: "string",
		// 正确解析为当前类型的根部
		"box?": "DisappointingGift"
	}
}).export()

On this page