基本用法
学习定义和使用基本 schema,包括基本类型、字面量、联合与结构体,以进行高效的数据校验与转换。
基本类型
Schema 模块为常见的基本类型提供了内置 schema。
| Schema | 等价的 TypeScript 类型 |
|---|---|
Schema.String | string |
Schema.Number | number |
Schema.Boolean | boolean |
Schema.BigIntFromSelf | BigInt |
Schema.SymbolFromSelf | symbol |
Schema.Object | object |
Schema.Undefined | undefined |
Schema.Void | void |
Schema.Any | any |
Schema.Unknown | unknown |
Schema.Never | never |
示例(使用基本类型 schema)
import { Schema } from "effect"
const schema = Schema.String
// Infers the type as string
//
// ┌─── string
// ▼
type Type = typeof schema.Type
// Attempt to decode a null value, which will throw a parse error
Schema.decodeUnknownSync(schema)(null)
/*
throws:
ParseError: Expected string, actual null
*/
asSchema
为了方便使用 schema,内置 schema 在可能的情况下会以更简短的不透明类型(opaque type)暴露出来。
Schema.asSchema 函数让你可以把任意 schema 视为 Schema<Type, Encoded, Context>。
示例(用 asSchema 展开一个 schema)
例如,Schema.String 被定义为一个类型为 typeof Schema.String 的类,而使用 Schema.asSchema 则可以以扩展形式 Schema<string, string, never> 得到该 schema。
import { Schema } from "effect"
// ┌─── typeof Schema.String
// ▼
const schema = Schema.String
// ┌─── Schema<string, string, never>
// ▼
const nomalized = Schema.asSchema(schema)
唯一符号
你可以使用 Schema.UniqueSymbolFromSelf 为唯一符号创建 schema。
示例(为唯一符号创建 schema)
import { Schema } from "effect"
const mySymbol = Symbol.for("mySymbol")
const schema = Schema.UniqueSymbolFromSelf(mySymbol)
// ┌─── typeof mySymbol
// ▼
type Type = typeof schema.Type
Schema.decodeUnknownSync(schema)(null)
/*
throws:
ParseError: Expected Symbol(mySymbol), actual null
*/
字面量
字面量 schema 表示字面量类型。 你可以用它们来指定类型必须具有的精确值。
字面量可以是以下几种类型:
stringnumberbooleannullbigint
示例(定义字面量 schema)
import { Schema } from "effect"
// Define various literal schemas
Schema.Null // Same as S.Literal(null)
Schema.Literal("a") // string literal
Schema.Literal(1) // number literal
Schema.Literal(true) // boolean literal
Schema.Literal(2n) // BigInt literal
示例(为 "a" 定义字面量 schema)
import { Schema } from "effect"
// ┌─── Literal<["a"]>
// ▼
const schema = Schema.Literal("a")
// ┌─── "a"
// ▼
type Type = typeof schema.Type
console.log(Schema.decodeUnknownSync(schema)("a"))
// Output: "a"
console.log(Schema.decodeUnknownSync(schema)("b"))
/*
throws:
ParseError: Expected "a", actual "b"
*/
字面量的联合
你可以把多个字面量作为参数传给 Schema.Literal 构造函数,从而创建它们的联合:
示例(定义字面量的联合)
import { Schema } from "effect"
// ┌─── Literal<["a", "b", "c"]>
// ▼
const schema = Schema.Literal("a", "b", "c")
// ┌─── "a" | "b" | "c"
// ▼
type Type = typeof schema.Type
Schema.decodeUnknownSync(schema)(null)
/*
throws:
ParseError: "a" | "b" | "c"
├─ Expected "a", actual null
├─ Expected "b", actual null
└─ Expected "c", actual null
*/
如果你想为整个字面量联合设置自定义错误消息,可以使用 override: true 选项(更多细节见自定义错误消息)来指定一条统一的消息。
示例(为字面量的联合添加自定义消息)
import { Schema } from "effect"
// Schema with individual messages for each literal
const individualMessages = Schema.Literal("a", "b", "c")
console.log(Schema.decodeUnknownSync(individualMessages)(null))
/*
throws:
ParseError: "a" | "b" | "c"
├─ Expected "a", actual null
├─ Expected "b", actual null
└─ Expected "c", actual null
*/
// Schema with a unified custom message for all literals
const unifiedMessage = Schema.Literal("a", "b", "c").annotations({
message: () => ({ message: "Not a valid code", override: true }),
})
console.log(Schema.decodeUnknownSync(unifiedMessage)(null))
/*
throws:
ParseError: Not a valid code
*/
暴露的值
你可以通过 literals 属性访问字面量 schema 中定义的字面量:
import { Schema } from "effect"
const schema = Schema.Literal("a", "b", "c")
// ┌─── readonly ["a", "b", "c"]
// ▼
const literals = schema.literals
pickLiteral 工具
你可以把 Schema.pickLiteral 用于字面量 schema,以缩小其可能的取值范围。
示例(用 pickLiteral 收窄取值)
import { Schema } from "effect"
// Create a schema for a subset of literals ("a" and "b") from a larger set
//
// ┌─── Literal<["a", "b"]>
// ▼
const schema = Schema.Literal("a", "b", "c").pipe(Schema.pickLiteral("a", "b"))
有时你可能需要在代码的其他部分复用一个字面量 schema。下面的示例演示了如何做到这一点:
示例(从字面量 schema 创建子类型)
import { Schema } from "effect"
// Define the base set of fruit categories
const FruitCategory = Schema.Literal("sweet", "citrus", "tropical")
// Define a general Fruit schema with the base category set
const Fruit = Schema.Struct({
id: Schema.Number,
category: FruitCategory,
})
// Define a specific Fruit schema for only "sweet" and "citrus" categories
const SweetAndCitrusFruit = Schema.Struct({
id: Schema.Number,
category: FruitCategory.pipe(Schema.pickLiteral("sweet", "citrus")),
})
在这个示例中,FruitCategory 是各类水果分类的事实来源。
我们复用它创建了 Fruit 的一个子类型 SweetAndCitrusFruit,确保只允许指定的分类("sweet" 和 "citrus")。
这种做法有助于在整个代码中保持一致,并在分类定义发生变化时提供类型安全。
模板字面量
在 TypeScript 中,模板字面量类型允许你在字符串字面量中嵌入表达式。
Schema.TemplateLiteral 构造函数让你可以为这些模板字面量类型创建 schema。
示例(定义模板字面量)
import { Schema } from "effect"
// This creates a schema for: `a${string}`
//
// ┌─── TemplateLiteral<`a${string}`>
// ▼
const schema1 = Schema.TemplateLiteral("a", Schema.String)
// This creates a schema for:
// `https://${string}.com` | `https://${string}.net`
const schema2 = Schema.TemplateLiteral(
"https://",
Schema.String,
".",
Schema.Literal("com", "net"),
)
示例(来自模板字面量类型文档)
再来看一个更复杂的例子。假设你有两组用于邮件和页脚的 locale ID。
你可以使用 Schema.TemplateLiteral 构造函数创建一个合并这些 ID 的 schema:
import { Schema } from "effect"
const EmailLocaleIDs = Schema.Literal("welcome_email", "email_heading")
const FooterLocaleIDs = Schema.Literal("footer_title", "footer_sendoff")
// This creates a schema for:
// "welcome_email_id" | "email_heading_id" |
// "footer_title_id" | "footer_sendoff_id"
const schema = Schema.TemplateLiteral(
Schema.Union(EmailLocaleIDs, FooterLocaleIDs),
"_id",
)
支持的 span 类型
Schema.TemplateLiteral 构造函数支持以下 span 类型:
Schema.StringSchema.Number- 字面量:
string | number | boolean | null | bigint。它们既可以由Schema.Literal包装,也可以直接使用 - 上述类型的联合
- 上述类型的 Brand
示例(在模板字面量中使用带品牌的字符串)
import { Schema } from "effect"
// Create a branded string schema for an authorization token
const AuthorizationToken = Schema.String.pipe(
Schema.brand("AuthorizationToken"),
)
// This creates a schema for:
// `Bearer ${string & Brand<"AuthorizationToken">}`
const schema = Schema.TemplateLiteral("Bearer ", AuthorizationToken)
TemplateLiteralParser
Schema.TemplateLiteral 构造函数作为简单的校验器很有用,但它只是把模板字面量定义转换成正则表达式,从而验证输入是否符合特定的字符串模式。类似地,Schema.pattern直接使用正则表达式来达到同样的目的。在校验之后,这两种方法都需要额外的手工解析,才能把通过校验的字符串转换成可用的数据格式。
为了解决这些限制、省去校验后的手工解析,我们开发了 Schema.TemplateLiteralParser API。它不仅校验输入格式,还会自动把它解析成结构更清晰、类型更安全的输出,具体来说就是元组格式。
Schema.TemplateLiteralParser 构造函数支持与 Schema.TemplateLiteral 相同的 span 类型。
示例(使用 TemplateLiteralParser 进行解析与编码)
import { Schema } from "effect"
// ┌─── Schema<readonly [number, "a", string], `${string}a${string}`>
// ▼
const schema = Schema.TemplateLiteralParser(
Schema.NumberFromString,
"a",
Schema.NonEmptyString,
)
console.log(Schema.decodeSync(schema)("100afoo"))
// Output: [ 100, 'a', 'foo' ]
console.log(Schema.encodeSync(schema)([100, "a", "foo"]))
// Output: '100afoo'
原生枚举
Schema 模块支持 TypeScript 的原生枚举。你可以使用 Schema.Enums 为枚举定义 schema,从而校验属于该枚举的值。
示例(为枚举定义 schema)
import { Schema } from "effect"
enum Fruits {
Apple,
Banana,
}
// ┌─── Enums<typeof Fruits>
// ▼
const schema = Schema.Enums(Fruits)
//
// ┌─── Fruits
// ▼
type Type = typeof schema.Type
暴露的值
枚举可以通过 schema 的 enums 属性访问。你可以用这个属性获取单个成员或整个枚举值集合。
import { Schema } from "effect"
enum Fruits {
Apple,
Banana,
}
const schema = Schema.Enums(Fruits)
schema.enums // Returns all enum members
schema.enums.Apple // Access the Apple member
schema.enums.Banana // Access the Banana member
联合
Schema 模块内置了 Schema.Union 构造函数,用于创建“或”类型,让你可以定义能够表示多种类型的 schema。
示例(定义联合 schema)
import { Schema } from "effect"
// ┌─── Union<[typeof Schema.String, typeof Schema.Number]>
// ▼
const schema = Schema.Union(Schema.String, Schema.Number)
// ┌─── string | number
// ▼
type Type = typeof schema.Type
联合成员的求值顺序
解码时,联合成员按它们定义的顺序依次求值。如果某个值与第一个成员匹配,就会用那个 schema 解码。如果不匹配,解码过程会继续尝试下一个成员。
如果多个 schema 都能解码同一个值,顺序就很关键。把更通用的 schema 放在更具体的 schema 前面,可能会导致属性丢失,因为会使用第一个匹配的 schema。
示例(处理联合中相互重叠的 schema)
import { Schema } from "effect"
// Define two overlapping schemas
const Member1 = Schema.Struct({
a: Schema.String,
})
const Member2 = Schema.Struct({
a: Schema.String,
b: Schema.Number,
})
// ❌ Define a union where Member1 appears first
const Bad = Schema.Union(Member1, Member2)
console.log(Schema.decodeUnknownSync(Bad)({ a: "a", b: 12 }))
// Output: { a: 'a' } (Member1 matched first, so `b` was ignored)
// ✅ Define a union where Member2 appears first
const Good = Schema.Union(Member2, Member1)
console.log(Schema.decodeUnknownSync(Good)({ a: "a", b: 12 }))
// Output: { a: 'a', b: 12 } (Member2 matched first, so `b` was included)
字面量联合
你固然可以通过组合各个字面量 schema 来创建字面量联合:
示例(使用各个字面量 schema)
import { Schema } from "effect"
// ┌─── Union<[Schema.Literal<["a"]>, Schema.Literal<["b"]>, Schema.Literal<["c"]>]>
// ▼
const schema = Schema.Union(
Schema.Literal("a"),
Schema.Literal("b"),
Schema.Literal("c"),
)
你可以把多个字面量直接传给 Schema.Literal 构造器,从而简化这一过程:
示例(定义字面量联合)
import { Schema } from "effect"
// ┌─── Literal<["a", "b", "c"]>
// ▼
const schema = Schema.Literal("a", "b", "c")
// ┌─── "a" | "b" | "c"
// ▼
type Type = typeof schema.Type
如果你想为整个字面量联合设置自定义错误信息,可以使用 override: true 选项(更多细节见自定义错误信息)来指定一条统一的信息。
示例(为字面量联合添加自定义信息)
import { Schema } from "effect"
// Schema with individual messages for each literal
const individualMessages = Schema.Literal("a", "b", "c")
console.log(Schema.decodeUnknownSync(individualMessages)(null))
/*
throws:
ParseError: "a" | "b" | "c"
├─ Expected "a", actual null
├─ Expected "b", actual null
└─ Expected "c", actual null
*/
// Schema with a unified custom message for all literals
const unifiedMessage = Schema.Literal("a", "b", "c").annotations({
message: () => ({ message: "Not a valid code", override: true }),
})
console.log(Schema.decodeUnknownSync(unifiedMessage)(null))
/*
throws:
ParseError: Not a valid code
*/
可空类型
Schema 模块提供了一些工具函数,用于定义允许可空类型的 schema,帮助你处理可能是 null、undefined 或两者兼有的值。
示例(创建可空 Schema)
import { Schema } from "effect"
// Represents a schema for a string or null value
Schema.NullOr(Schema.String)
// Represents a schema for a string, null, or undefined value
Schema.NullishOr(Schema.String)
// Represents a schema for a string or undefined value
Schema.UndefinedOr(Schema.String)
可辨识联合
TypeScript 中的可辨识联合是一种对复杂数据结构建模的方式,这类结构可能根据一组特定的条件或属性呈现不同的形态。它允许你定义一个表示多个相关形状的类型,其中每个形状都由一个共享的判别属性唯一标识。
在可辨识联合中,联合的每个变体都有一个公共属性,称为判别属性(discriminant)。判别属性是字面量类型,这意味着它只能取有限的一组可能值。TypeScript 可以根据判别属性的值推断出当前使用的是联合中的哪个变体。
示例(在 TypeScript 中定义可辨识联合)
type Circle = {
readonly kind: "circle"
readonly radius: number
}
type Square = {
readonly kind: "square"
readonly sideLength: number
}
type Shape = Circle | Square
在 Schema 模块中,你可以为每个类型指定一个字面量字段作为判别属性,从而以类似的方式定义可辨识联合。
示例(使用 Schema 定义可辨识联合)
import { Schema } from "effect"
const Circle = Schema.Struct({
kind: Schema.Literal("circle"),
radius: Schema.Number,
})
const Square = Schema.Struct({
kind: Schema.Literal("square"),
sideLength: Schema.Number,
})
const Shape = Schema.Union(Circle, Square)
在这个例子中,Schema.Literal 构造器把 kind 属性设置为 Circle 和 Square 两个 schema 共同的判别属性。随后 Shape schema 表示这两个类型的联合,让 TypeScript 能够根据 kind 的值推断出具体的形状。
把简单联合转换为可辨识联合
如果你从一个简单联合开始,并想把它转换为可辨识联合,可以为每个成员添加一个特殊属性。这样 TypeScript 就能根据判别属性的值自动推断出正确的类型。
示例(最初的简单联合)
例如,假设你定义了一个由 Circle 和 Square 组合而成、不带任何特殊属性的 Shape 联合:
import { Schema } from "effect"
const Circle = Schema.Struct({
radius: Schema.Number,
})
const Square = Schema.Struct({
sideLength: Schema.Number,
})
const Shape = Schema.Union(Circle, Square)
为了让代码更易于管理,你可能想把简单联合转换为可辨识联合。这样,TypeScript 就能根据某个特定属性的值自动判断你正在处理联合中的哪个成员。
为此,你可以为联合的每个成员添加一个特殊属性,让 TypeScript 在运行时知道它面对的是哪个类型。
下面演示如何把 Shape schema 转换为另一个表示可辨识联合的 schema:
示例(添加判别属性)
import { Schema } from "effect"
const Circle = Schema.Struct({
radius: Schema.Number,
})
const Square = Schema.Struct({
sideLength: Schema.Number,
})
const DiscriminatedShape = Schema.Union(
Schema.transform(
Circle,
// Add a "kind" property with the literal value "circle" to Circle
Schema.Struct({ ...Circle.fields, kind: Schema.Literal("circle") }),
{
strict: true,
// Add the discriminant property to Circle
decode: (circle) => ({ ...circle, kind: "circle" as const }),
// Remove the discriminant property
encode: ({ kind: _kind, ...rest }) => rest,
},
),
Schema.transform(
Square,
// Add a "kind" property with the literal value "square" to Square
Schema.Struct({ ...Square.fields, kind: Schema.Literal("square") }),
{
strict: true,
// Add the discriminant property to Square
decode: (square) => ({ ...square, kind: "square" as const }),
// Remove the discriminant property
encode: ({ kind: _kind, ...rest }) => rest,
},
),
)
console.log(Schema.decodeUnknownSync(DiscriminatedShape)({ radius: 10 }))
// Output: { radius: 10, kind: 'circle' }
console.log(Schema.decodeUnknownSync(DiscriminatedShape)({ sideLength: 10 }))
// Output: { sideLength: 10, kind: 'square' }
前面这个方案完全可行,也展示了我们可以随意为 schema 添加属性,让结果更容易在领域模型中使用。
不过,它需要大量样板代码。所幸有一个专门为此场景设计的 API —— Schema.attachPropertySignature,它让我们用少得多的代码实现同样的效果:
示例(使用 Schema.attachPropertySignature 减少代码量)
import { Schema } from "effect"
const Circle = Schema.Struct({
radius: Schema.Number,
})
const Square = Schema.Struct({
sideLength: Schema.Number,
})
const DiscriminatedShape = Schema.Union(
Circle.pipe(Schema.attachPropertySignature("kind", "circle")),
Square.pipe(Schema.attachPropertySignature("kind", "square")),
)
// decoding
console.log(Schema.decodeUnknownSync(DiscriminatedShape)({ radius: 10 }))
// Output: { radius: 10, kind: 'circle' }
// encoding
console.log(
Schema.encodeSync(DiscriminatedShape)({
kind: "circle",
radius: 10,
}),
)
// Output: { radius: 10 }
请注意,使用 Schema.attachPropertySignature 时,你只能添加属性,
它无法替换或覆盖已有属性。
暴露的值
你可以访问以元组形式表示的联合 schema 中的各个成员:
import { Schema } from "effect"
const schema = Schema.Union(Schema.String, Schema.Number)
// Accesses the members of the union
const members = schema.members
// ┌─── typeof Schema.String
// ▼
const firstMember = members[0]
// ┌─── typeof Schema.Number
// ▼
const secondMember = members[1]
元组
Schema 模块允许你定义元组,即元素类型可以不同的有序集合。 你可以定义包含必需元素、可选元素或剩余元素的元组。
必需元素
要定义包含必需元素的元组,可以使用 Schema.Tuple 构造器,按顺序列出各个元素 schema 即可:
示例(定义包含必需元素的元组)
import { Schema } from "effect"
// Define a tuple with a string and a number as required elements
//
// ┌─── Tuple<[typeof Schema.String, typeof Schema.Number]>
// ▼
const schema = Schema.Tuple(Schema.String, Schema.Number)
// ┌─── readonly [string, number]
// ▼
type Type = typeof schema.Type
追加必需元素
你可以使用展开运算符,向已有元组追加额外的必需元素:
示例(向已有元组添加元素)
import { Schema } from "effect"
const tuple1 = Schema.Tuple(Schema.String, Schema.Number)
// Append a boolean to the existing tuple
const tuple2 = Schema.Tuple(...tuple1.elements, Schema.Boolean)
// ┌─── readonly [string, number, boolean]
// ▼
type Type = typeof tuple2.Type
可选元素
要定义可选元素,请使用 Schema.optionalElement 构造器。
示例(定义包含可选元素的元组)
import { Schema } from "effect"
// Define a tuple with a required string and an optional number
const schema = Schema.Tuple(
Schema.String, // required element
Schema.optionalElement(Schema.Number), // optional element
)
// ┌─── readonly [string, number?]
// ▼
type Type = typeof schema.Type
剩余元素
要定义剩余元素,请把它添加在必需元素或可选元素列表之后。 剩余元素让元组可以接受特定类型的额外元素。
示例(使用剩余元素)
import { Schema } from "effect"
// Define a tuple with required elements and a rest element of type boolean
const schema = Schema.Tuple(
[Schema.String, Schema.optionalElement(Schema.Number)], // elements
Schema.Boolean, // rest element
)
// ┌─── readonly [string, number?, ...boolean[]]
// ▼
type Type = typeof schema.Type
你还可以在剩余元素之后包含其他元素:
示例(在剩余元素之后包含额外元素)
import { Schema } from "effect"
// Define a tuple with required elements, a rest element,
// and an additional element
const schema = Schema.Tuple(
[Schema.String, Schema.optionalElement(Schema.Number)], // elements
Schema.Boolean, // rest element
Schema.String, // additional element
)
// ┌─── readonly [string, number | undefined, ...boolean[], string]
// ▼
type Type = typeof schema.Type
元素注解
注解(annotation)可用于为元组元素添加元数据,从而更容易描述它们的用途或要求。 这在生成文档或 JSON schema 时尤其有用。
示例(为元组元素添加注解)
import { JSONSchema, Schema } from "effect"
// Define a tuple representing a point with annotations for each coordinate
const Point = Schema.Tuple(
Schema.element(Schema.Number).annotations({
title: "X",
description: "X coordinate",
}),
Schema.optionalElement(Schema.Number).annotations({
title: "Y",
description: "optional Y coordinate",
}),
)
// Generate a JSON Schema from the tuple
console.log(JSONSchema.make(Point))
/*
Output:
{
'$schema': 'http://json-schema.org/draft-07/schema#',
type: 'array',
minItems: 1,
items: [
{ type: 'number', description: 'X coordinate', title: 'X' },
{
type: 'number',
description: 'optional Y coordinate',
title: 'Y'
}
],
additionalItems: false
}
*/
暴露的值
你可以使用 elements 和 rest 属性访问元组 schema 的元素与剩余元素:
示例(访问元组 schema 的元素与剩余元素)
import { Schema } from "effect"
// Define a tuple with required, optional, and rest elements
const schema = Schema.Tuple(
[Schema.String, Schema.optionalElement(Schema.Number)], // elements
Schema.Boolean, // rest element
Schema.String, // additional element
)
// Access the required and optional elements of the tuple
//
// ┌─── readonly [typeof Schema.String, Schema.Element<typeof Schema.Number, "?">]
// ▼
const tupleElements = schema.elements
// Access the rest element of the tuple
//
// ┌─── readonly [typeof Schema.Boolean, typeof Schema.String]
// ▼
const restElement = schema.rest
数组
Schema 模块允许你为数组定义 schema,从而轻松校验由特定类型的元素组成的集合。
示例(定义数组 Schema)
import { Schema } from "effect"
// Define a schema for an array of numbers
//
// ┌─── Array$<typeof Schema.Number>
// ▼
const schema = Schema.Array(Schema.Number)
// ┌─── readonly number[]
// ▼
type Type = typeof schema.Type
可变数组
默认情况下,Schema.Array 生成的类型被标记为 readonly。
要为可变数组创建 schema,可以使用 Schema.mutable 函数,它以浅层方式让数组类型变为可变。
示例(创建可变数组 Schema)
import { Schema } from "effect"
// Define a schema for a mutable array of numbers
//
// ┌─── mutable<Schema.Array$<typeof Schema.Number>>
// ▼
const schema = Schema.mutable(Schema.Array(Schema.Number))
// ┌─── number[]
// ▼
type Type = typeof schema.Type
暴露的值
你可以使用 value 属性访问数组 schema 的值类型:
示例(访问数组 Schema 的值类型)
import { Schema } from "effect"
const schema = Schema.Array(Schema.Number)
// Access the value type of the array schema
//
// ┌─── typeof Schema.Number
// ▼
const value = schema.value
非空数组
Schema 模块还提供了为非空数组定义 schema 的方式,确保数组始终至少包含一个元素。
示例(定义非空数组 Schema)
import { Schema } from "effect"
// Define a schema for a non-empty array of numbers
//
// ┌─── NonEmptyArray<typeof Schema.Number>
// ▼
const schema = Schema.NonEmptyArray(Schema.Number)
// ┌─── readonly [number, ...number[]]
// ▼
type Type = typeof schema.Type
暴露的值
你可以使用 value 属性访问非空数组 schema 的值类型:
示例(访问非空数组 Schema 的值类型)
import { Schema } from "effect"
// Define a schema for a non-empty array of numbers
const schema = Schema.NonEmptyArray(Schema.Number)
// Access the value type of the non-empty array schema
//
// ┌─── typeof Schema.Number
// ▼
const value = schema.value
Record
Schema 模块提供了定义 record 类型的支持:record 是键值对的集合,其中的键可以是字符串、symbol 或其他类型,而值则具有一个已定义的 schema。
字符串键
你可以定义键为字符串、并为其值指定类型的 record。
示例(字符串键与数字值)
import { Schema } from "effect"
// Define a record schema with string keys and number values
//
// ┌─── Record$<typeof Schema.String, typeof Schema.Number>
// ▼
const schema = Schema.Record({ key: Schema.String, value: Schema.Number })
// ┌─── { readonly [x: string]: number; }
// ▼
type Type = typeof schema.Type
Symbol 键
Record 也可以使用 symbol 作为键。
示例(Symbol 键与数字值)
import { Schema } from "effect"
// Define a record schema with symbol keys and number values
const schema = Schema.Record({
key: Schema.SymbolFromSelf,
value: Schema.Number,
})
// ┌─── { readonly [x: symbol]: number; }
// ▼
type Type = typeof schema.Type
字面量键的联合
使用字面量的联合可以把键限制在一组特定的值上。
示例(用字符串字面量作为键)
import { Schema } from "effect"
// Define a record schema where keys are limited
// to specific string literals ("a" or "b")
const schema = Schema.Record({
key: Schema.Union(Schema.Literal("a"), Schema.Literal("b")),
value: Schema.Number,
})
// ┌─── { readonly a: number; readonly b: number; }
// ▼
type Type = typeof schema.Type
模板字面量键
Record 可以使用模板字面量作为键,从而支持更复杂的键模式。
示例(模板字面量键与数字值)
import { Schema } from "effect"
// Define a record schema with keys that match
// the template literal pattern "a${string}"
const schema = Schema.Record({
key: Schema.TemplateLiteral(Schema.Literal("a"), Schema.String),
value: Schema.Number,
})
// ┌─── { readonly [x: `a${string}`]: number; }
// ▼
type Type = typeof schema.Type
细化后的键
你可以用额外的约束来细化键的类型。
示例(按最小长度过滤键)
import { Schema } from "effect"
// Define a record schema where keys are strings with a minimum length of 2
const schema = Schema.Record({
key: Schema.String.pipe(Schema.minLength(2)),
value: Schema.Number,
})
// ┌─── { readonly [x: string]: number; }
// ▼
type Type = typeof schema.Type
对键的细化起的是过滤作用,而不会导致解码失败。 如果某个键不满足约束(例如模式或最小长度检查),它会被从解码输出中移除,而不是触发错误。
示例(不满足约束的键会被移除)
import { Schema } from "effect"
const schema = Schema.Record({
key: Schema.String.pipe(Schema.minLength(2)),
value: Schema.Number,
})
console.log(Schema.decodeUnknownSync(schema)({ a: 1, bb: 2 }))
// Output: { bb: 2 } ("a" is removed because it is too short)
如果你希望在键不满足约束时让解码失败,可以把 onExcessProperty 设为 "error"。
示例(对无效键强制报错)
import { Schema } from "effect"
const schema = Schema.Record({
key: Schema.String.pipe(Schema.minLength(2)),
value: Schema.Number,
})
console.log(
Schema.decodeUnknownSync(schema, { onExcessProperty: "error" })({
a: 1,
bb: 2,
}),
)
/*
throws:
ParseError: { readonly [x: minLength(2)]: number }
└─ ["a"]
└─ is unexpected, expected: minLength(2)
*/
转换键
Schema.Record API 不支持对键 schema 做转换。
尝试对键应用转换会得到 Unsupported key schema 错误:
示例(尝试转换键)
import { Schema } from "effect"
const schema = Schema.Record({
key: Schema.Trim,
value: Schema.NumberFromString,
})
/*
throws:
Error: Unsupported key schema
schema (Transformation): Trim
*/
存在这一限制是因为:如果多个键在转换后映射到同一个值,转换就会产生冲突。为避免这些问题,键的转换必须由用户显式处理。
要修改 record 的键,你必须在 Schema.Record 之外应用转换。
一种常见做法是用 Schema.transform 在解码过程中调整键。
示例(解码时修剪键)
import { Schema, Record, identity } from "effect"
const schema = Schema.transform(
// Define the input schema with unprocessed keys
Schema.Record({
key: Schema.String,
value: Schema.NumberFromString,
}),
// Define the output schema with transformed keys
Schema.Record({
key: Schema.Trimmed,
value: Schema.Number,
}),
{
strict: true,
// Trim keys during decoding
decode: (record) => Record.mapKeys(record, (key) => key.trim()),
encode: identity,
},
)
console.log(Schema.decodeUnknownSync(schema)({ " key1 ": "1", key2: "2" }))
// Output: { key1: 1, key2: 2 }
可变 Record
默认情况下,Schema.Record 生成的类型被标记为 readonly。
要创建可变 record 的 schema,可以使用 Schema.mutable 函数,它以浅层(shallow)方式让 record 类型可变。
示例(创建可变 Record 的 Schema)
import { Schema } from "effect"
// Create a schema for a mutable record with string keys and number values
const schema = Schema.mutable(
Schema.Record({ key: Schema.String, value: Schema.Number }),
)
// ┌─── { [x: string]: number; }
// ▼
type Type = typeof schema.Type
暴露的值
你可以使用 key 和 value 属性访问 record schema 的 key 和 value 类型:
示例(访问键与值的类型)
import { Schema } from "effect"
const schema = Schema.Record({ key: Schema.String, value: Schema.Number })
// Accesses the key
//
// ┌─── typeof Schema.String
// ▼
const key = schema.key
// Accesses the value
//
// ┌─── typeof Schema.Number
// ▼
const value = schema.value
Struct
属性签名
Schema.Struct 构造器为具有特定属性的对象定义 schema。
示例(定义 Struct Schema)
这个示例为一个对象定义了 struct schema,该对象具有以下属性:
name:字符串age:数字
import { Schema } from "effect"
// ┌─── Schema.Struct<{
// │ name: typeof Schema.String;
// │ age: typeof Schema.Number;
// │ }>
// ▼
const schema = Schema.Struct({
name: Schema.String,
age: Schema.Number,
})
// The inferred TypeScript type from the schema
//
// ┌─── {
// │ readonly name: string;
// │ readonly age: number;
// │ }
// ▼
type Type = typeof schema.Type
使用 Schema.Struct({}) 会得到一个 TypeScript 类型 {},它的行为与 unknown 类似。这意味着任何数据都会被视为有效,因为没有定义任何约束。
索引签名
Schema.Struct 构造器还可以可选地接受一组表示索引签名的键/值对,允许你定义额外的动态属性。
declare const Struct: (props, ...indexSignatures) => Struct<...>
示例(添加索引签名)
import { Schema } from "effect"
// Define a struct with a specific property "a"
// and an index signature allowing additional properties
const schema = Schema.Struct(
// Defined properties
{ a: Schema.Number },
// Index signature: allows additional string keys with number values
{ key: Schema.String, value: Schema.Number },
)
// The inferred TypeScript type:
//
// ┌─── {
// │ readonly [x: string]: number;
// │ readonly a: number;
// │ }
// ▼
type Type = typeof schema.Type
示例(使用 Schema.Record)
你也可以用 Schema.Record 达到同样的效果:
import { Schema } from "effect"
// Define a struct with a fixed property "a"
// and a dynamic index signature using Schema.Record
const schema = Schema.Struct(
{ a: Schema.Number },
Schema.Record({ key: Schema.String, value: Schema.Number }),
)
// The inferred TypeScript type:
//
// ┌─── {
// │ readonly [x: string]: number;
// │ readonly a: number;
// │ }
// ▼
type Type = typeof schema.Type
多个索引签名
每种键类型(string 或 symbol)只能定义一个索引签名。不允许定义多个同类型的索引签名。
示例(合法的多个索引签名)
import { Schema } from "effect"
// Define a struct with a fixed property "a"
// and valid index signatures for both strings and symbols
const schema = Schema.Struct(
{ a: Schema.Number },
// String index signature
{ key: Schema.String, value: Schema.Number },
// Symbol index signature
{ key: Schema.SymbolFromSelf, value: Schema.Number },
)
// The inferred TypeScript type:
//
// ┌─── {
// │ readonly [x: string]: number;
// │ readonly [x: symbol]: number;
// │ readonly a: number;
// │ }
// ▼
type Type = typeof schema.Type
定义多个同一种键类型(string 或 symbol)的索引签名会导致错误。
示例(非法的多个索引签名)
import { Schema } from "effect"
Schema.Struct(
{ a: Schema.Number },
// Attempting to define multiple string index signatures
{ key: Schema.String, value: Schema.Number },
{ key: Schema.String, value: Schema.Boolean },
)
/*
throws:
Error: Duplicate index signature
details: string index signature
*/
冲突的索引签名
在使用索引签名定义 schema 时,如果某个固定属性的类型与索引签名所允许的值类型不同,就会产生冲突。 这可能导致 TypeScript 出现意料之外的行为。
示例(冲突的索引签名)
import { Schema } from "effect"
// Attempting to define a struct with a conflicting index signature
// - The fixed property "a" is a string
// - The index signature requires all values to be numbers
const schema = Schema.Struct(
{ a: Schema.String },
{ key: Schema.String, value: Schema.Number },
)
// ❌ Incorrect TypeScript type:
//
// ┌─── {
// │ readonly [x: string]: number;
// │ readonly a: string;
// │ }
// ▼
type Type = typeof schema.Type
当你手动定义该类型时,TypeScript 编译器会把它标记为一个错误:
// @errors: 2411
// This type is invalid because the index signature
// conflicts with the fixed property `a`
type Test = {
readonly a: string
readonly [x: string]: number
}
这是因为 TypeScript 不允许索引签名与固定属性相矛盾。
冲突索引签名的变通方案
在使用 schema 时,如果某个固定属性的类型与索引签名所允许的值类型不同,就可能发生冲突。这种情况常常出现在与不遵循严格 TypeScript 约定的外部 API 打交道时。
为避免冲突,你可以把固定属性与索引属性分开,把它们当作 schema 中两个独立的部分来处理。
示例(提取固定属性与索引属性)
考虑这样一个对象:
"a"是类型为string的固定属性。- 所有其他键都存储数字,这与
"a"冲突。
// @errors: 2411
// This type is invalid because the index signature
// conflicts with the fixed property `a`
type Test = {
a: string
[x: string]: number
}
为避免这个问题,我们可以把这些属性拆分成两个不同的类型:
// Fixed properties schema
type FixedProperties = {
readonly a: string
}
// Index signature properties schema
type IndexSignatureProperties = {
readonly [x: string]: number
}
// The final output groups both properties in a tuple
type OutputData = readonly [FixedProperties, IndexSignatureProperties]
通过使用 Schema.transform 和 Schema.compose,你可以在校验之前预处理输入数据。这种方式能确保固定属性与索引签名属性被独立处理。
import { Schema } from "effect"
// Define a schema for the fixed property "a"
const FixedProperties = Schema.Struct({
a: Schema.String,
})
// Define a schema for index signature properties
const IndexSignatureProperties = Schema.Record({
// Exclude keys that are already present in FixedProperties
key: Schema.String.pipe(
Schema.filter((key) => !Object.keys(FixedProperties.fields).includes(key)),
),
value: Schema.Number,
})
// Create a schema that duplicates an object into two parts
const Duplicate = Schema.transform(
Schema.Object,
Schema.Tuple(Schema.Object, Schema.Object),
{
strict: true,
// Create a tuple containing the input twice
decode: (a) => [a, a] as const,
// Merge both parts back when encoding
encode: ([a, b]) => ({ ...a, ...b }),
},
)
// ┌─── Schema<readonly [
// | { readonly a: string; },
// | { readonly [x: string]: number; }
// | ], object>
// ▼
const Result = Schema.compose(
Duplicate,
Schema.Tuple(FixedProperties, IndexSignatureProperties).annotations({
parseOptions: { onExcessProperty: "ignore" },
}),
)
// Decoding: Separates fixed and indexed properties
console.log(Schema.decodeUnknownSync(Result)({ a: "a", b: 1, c: 2 }))
// Output: [ { a: 'a' }, { b: 1, c: 2 } ]
// Encoding: Combines them back into an object
console.log(Schema.encodeSync(Result)([{ a: "a" }, { b: 1, c: 2 }]))
// Output: { a: 'a', b: 1, c: 2 }
暴露的值
你可以使用 fields 和 records 属性访问 struct schema 的字段与 record:
示例(访问字段与 record)
import { Schema } from "effect"
const schema = Schema.Struct(
{ a: Schema.Number },
Schema.Record({ key: Schema.String, value: Schema.Number }),
)
// Accesses the fields
//
// ┌─── { readonly a: typeof Schema.Number; }
// ▼
const fields = schema.fields
// Accesses the records
//
// ┌─── readonly [Schema.Record$<typeof Schema.String, typeof Schema.Number>]
// ▼
const records = schema.records
可变 Struct
默认情况下,Schema.Struct 生成的类型中,属性被标记为 readonly。
要为 struct 创建可变版本,可以使用 Schema.mutable 函数,它以浅层方式让属性变为可变。
示例(创建可变 Struct Schema)
import { Schema } from "effect"
const schema = Schema.mutable(
Schema.Struct({ a: Schema.String, b: Schema.Number }),
)
// ┌─── { a: string; b: number; }
// ▼
type Type = typeof schema.Type
带标签的结构体
在 TypeScript 中,标签有助于增强类型判别与模式匹配,它提供了一种简单而强大的方式来定义和识别不同的数据类型。
什么是标签?
标签是添加到数据结构上的一个字面量值,常用于 struct 中,用来区分带标签联合里的各种对象类型或变体。这个字面量充当判别属性,让人能更轻松、更高效地正确处理不同类型的数据。
使用 tag 构造器
Schema.tag 构造器专门用于创建一个持有特定字面量值的属性签名,作为对象类型的判别属性。
示例(定义带标签的结构体)
import { Schema } from "effect"
const User = Schema.Struct({
_tag: Schema.tag("User"),
name: Schema.String,
age: Schema.Number,
})
// ┌─── { readonly _tag: "User"; readonly name: string; readonly age: number; }
// ▼
type Type = typeof User.Type
console.log(User.make({ name: "John", age: 44 }))
/*
Output:
{ _tag: 'User', name: 'John', age: 44 }
*/
在上面的例子中,Schema.tag("User") 为 User struct schema 附加了一个 _tag 属性,从而把该 struct 类型的对象标记为 “User”。
当使用 make 方法创建新实例时,这个标签会被自动应用,从而简化对象创建并保证标签一致。
用 TaggedStruct 简化带标签的结构体
Schema.TaggedStruct 构造器把标签直接集成到 struct 定义中,从而简化了创建带标签 struct 的过程。这种方式为构建带内嵌判别属性的数据结构提供了更清晰、更具声明性的写法。
示例(使用 TaggedStruct 简化带标签的结构体)
import { Schema } from "effect"
const User = Schema.TaggedStruct("User", {
name: Schema.String,
age: Schema.Number,
})
// `_tag` is automatically applied when constructing an instance
console.log(User.make({ name: "John", age: 44 }))
// Output: { _tag: 'User', name: 'John', age: 44 }
// `_tag` is required when decoding from an unknown source
console.log(Schema.decodeUnknownSync(User)({ name: "John", age: 44 }))
/*
throws:
ParseError: { readonly _tag: "User"; readonly name: string; readonly age: number }
└─ ["_tag"]
└─ is missing
*/
在这个例子中:
- 使用
make构造实例时,_tag属性是可选的,因为 schema 会自动应用它。 - 在解码未知数据时,
_tag是必需的,以确保正确的类型识别。这种在实例构造与解码之间的区别很有用:它既保留了标签作为类型判别属性的作用,又简化了实例创建。
如果你希望 _tag 在解码期间也能自动应用,可以创建 Schema.TaggedStruct 的定制版本:
示例(定制 TaggedStruct,在解码时应用 _tag)
import type { SchemaAST } from "effect"
import { Schema } from "effect"
const TaggedStruct = <
Tag extends SchemaAST.LiteralValue,
Fields extends Schema.Struct.Fields,
>(
tag: Tag,
fields: Fields,
) =>
Schema.Struct({
_tag: Schema.Literal(tag).pipe(
Schema.optional,
Schema.withDefaults({
constructor: () => tag, // Apply _tag during instance construction
decoding: () => tag, // Apply _tag during decoding
}),
),
...fields,
})
const User = TaggedStruct("User", {
name: Schema.String,
age: Schema.Number,
})
console.log(User.make({ name: "John", age: 44 }))
// Output: { _tag: 'User', name: 'John', age: 44 }
console.log(Schema.decodeUnknownSync(User)({ name: "John", age: 44 }))
// Output: { _tag: 'User', name: 'John', age: 44 }
多个标签
虽然通常一个主标签就足够了,但 TypeScript 允许你定义多个标签,以满足更复杂的数据结构需求。下面是一个在单个 struct 中使用多个标签的示例:
示例(为一个 struct 添加多个标签)
这个示例定义了一个产品 schema,它带有一个主标签("Product")以及一个额外的分类标签("Electronics"),为数据结构增添了更多特异性。
import { Schema } from "effect"
const Product = Schema.TaggedStruct("Product", {
category: Schema.tag("Electronics"),
name: Schema.String,
price: Schema.Number,
})
// `_tag` and `category` are optional when creating an instance
console.log(Product.make({ name: "Smartphone", price: 999 }))
/*
Output:
{
_tag: 'Product',
category: 'Electronics',
name: 'Smartphone',
price: 999
}
*/
instanceOf
当你需要为通过 class 定义的自定义数据类型定义 schema 时,最方便快捷的方式就是使用 Schema.instanceOf 构造器。
示例(使用 instanceOf 定义 schema)
import { Schema } from "effect"
// Define a custom class
class MyData {
constructor(readonly name: string) {}
}
// Create a schema for the class
const MyDataSchema = Schema.instanceOf(MyData)
// ┌─── MyData
// ▼
type Type = typeof MyDataSchema.Type
console.log(Schema.decodeUnknownSync(MyDataSchema)(new MyData("name")))
// Output: MyData { name: 'name' }
console.log(Schema.decodeUnknownSync(MyDataSchema)({ name: "name" }))
/*
throws:
ParseError: Expected MyData, actual {"name":"name"}
*/
Schema.instanceOf 构造器只是 Schema.declare API 的一个轻量封装,而后者是 effect/Schema 中用于声明新自定义数据类型的原语。
私有构造器
注意,Schema.instanceOf 只能用于暴露了公开构造器的类。
如果你尝试把它用于出于某种原因把构造器标记为 private 的类,就会收到一个 TypeScript 错误:
示例(私有构造器导致的错误)
import { Schema } from "effect"
class MyData {
static make = (name: string) => new MyData(name)
private constructor(readonly name: string) {}
}
// @errors: 2345
const MyDataSchema = Schema.instanceOf(MyData)
在这种情况下,你不能使用 Schema.instanceOf,而必须像这样依赖 Schema.declare:
示例(对私有构造器使用 Schema.declare)
import { Schema } from "effect"
class MyData {
static make = (name: string) => new MyData(name)
private constructor(readonly name: string) {}
}
const MyDataSchema = Schema.declare(
(input: unknown): input is MyData => input instanceof MyData,
).annotations({ identifier: "MyData" })
console.log(Schema.decodeUnknownSync(MyDataSchema)(MyData.make("name")))
// Output: MyData { name: 'name' }
console.log(Schema.decodeUnknownSync(MyDataSchema)({ name: "name" }))
/*
throws:
ParseError: Expected MyData, actual {"name":"name"}
*/
校验实例的字段
要校验类实例的字段,你可以使用过滤器(filter)。这种方式把实例校验与对实例字段的额外检查结合起来。
示例(为实例 schema 添加字段校验)
import { Either, ParseResult, Schema } from "effect"
class MyData {
constructor(readonly name: string) {}
}
const MyDataFields = Schema.Struct({
name: Schema.NonEmptyString,
})
// Define a schema for the class instance with additional field validation
const MyDataSchema = Schema.instanceOf(MyData).pipe(
Schema.filter((a, options) =>
// Validate the fields of the instance
ParseResult.validateEither(MyDataFields)(a, options).pipe(
// Invert success and failure for filtering
Either.flip,
// Return undefined if validation succeeds, or an error if it fails
Either.getOrUndefined,
),
),
)
// Example: Valid instance
console.log(Schema.validateSync(MyDataSchema)(new MyData("John")))
// Output: MyData { name: 'John' }
// Example: Invalid instance (empty name)
console.log(Schema.validateSync(MyDataSchema)(new MyData("")))
/*
throws:
ParseError: { MyData | filter }
└─ Predicate refinement failure
└─ { readonly name: NonEmptyString }
└─ ["name"]
└─ NonEmptyString
└─ Predicate refinement failure
└─ Expected a non empty string, actual ""
*/
挑选
每个 struct schema 上都可用的 pick 静态函数,可以通过从已有 Struct 中选取特定属性来创建一个新的 Struct。
示例(从 struct 中挑选属性)
import { Schema } from "effect"
// Define a struct schema with properties "a", "b", and "c"
const MyStruct = Schema.Struct({
a: Schema.String,
b: Schema.Number,
c: Schema.Boolean,
})
// Create a new schema that picks properties "a" and "c"
//
// ┌─── Struct<{
// | a: typeof Schema.String;
// | c: typeof Schema.Boolean;
// | }>
// ▼
const PickedSchema = MyStruct.pick("a", "c")
Schema.pick 函数的适用范围不只局限于 Struct 类型,例如也可以用于 schema 的联合。
不过它返回的是一个通用的 SchemaClass。
示例(从联合中挑选属性)
import { Schema } from "effect"
// Define a union of two struct schemas
const MyUnion = Schema.Union(
Schema.Struct({ a: Schema.String, b: Schema.String, c: Schema.String }),
Schema.Struct({ a: Schema.Number, b: Schema.Number, d: Schema.Number }),
)
// Create a new schema that picks properties "a" and "b"
//
// ┌─── SchemaClass<{
// | readonly a: string | number;
// | readonly b: string | number;
// | }>
// ▼
const PickedSchema = MyUnion.pipe(Schema.pick("a", "b"))
省略
每个 struct schema 上都提供了 omit 静态函数,可用于从已有 Struct 中排除特定属性,从而创建新的 Struct。
示例(从 struct 中省略属性)
import { Schema } from "effect"
// Define a struct schema with properties "a", "b", and "c"
const MyStruct = Schema.Struct({
a: Schema.String,
b: Schema.Number,
c: Schema.Boolean,
})
// Create a new schema that omits property "b"
//
// ┌─── Schema.Struct<{
// | a: typeof Schema.String;
// | c: typeof Schema.Boolean;
// | }>
// ▼
const PickedSchema = MyStruct.omit("b")
Schema.omit 函数的适用范围并不局限于 Struct 类型,还可以用于 schema 的 union 等场景。
不过它返回的是泛化的 Schema。
示例(从 union 中省略属性)
import { Schema } from "effect"
// Define a union of two struct schemas
const MyUnion = Schema.Union(
Schema.Struct({ a: Schema.String, b: Schema.String, c: Schema.String }),
Schema.Struct({ a: Schema.Number, b: Schema.Number, d: Schema.Number }),
)
// Create a new schema that omits property "b"
//
// ┌─── SchemaClass<{
// | readonly a: string | number;
// | }>
// ▼
const PickedSchema = MyUnion.pipe(Schema.omit("b"))
partial
Schema.partial 函数会让 schema 中的所有属性都变为可选。
示例(让所有属性都可选)
import { Schema } from "effect"
// Create a schema with an optional property "a"
const schema = Schema.partial(Schema.Struct({ a: Schema.String }))
// ┌─── { readonly a?: string | undefined; }
// ▼
type Type = typeof schema.Type
默认情况下,Schema.partial 操作会为每个属性的类型加上 undefined。如果不想如此,可以使用 Schema.partialWith,并把 { exact: true } 作为参数传入。
示例(定义精确的 partial schema)
import { Schema } from "effect"
// Create a schema with an optional property "a" without allowing undefined
const schema = Schema.partialWith(
Schema.Struct({
a: Schema.String,
}),
{ exact: true },
)
// ┌─── { readonly a?: string; }
// ▼
type Type = typeof schema.Type
required
Schema.required 函数会确保 schema 中的所有属性都是必需的。
示例(让所有属性都必需)
import { Schema } from "effect"
// Create a schema and make all properties required
const schema = Schema.required(
Schema.Struct({
a: Schema.optionalWith(Schema.String, { exact: true }),
b: Schema.optionalWith(Schema.Number, { exact: true }),
}),
)
// ┌─── { readonly a: string; readonly b: number; }
// ▼
type Type = typeof schema.Type
在这个示例中,尽管 a 和 b 最初都被定义为可选,但它们最终都被设为必需。
keyof
Schema.keyof 操作会创建一个 schema,用来表示给定对象 schema 的键。
示例(从对象 schema 中提取键)
import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.Number,
})
const keys = Schema.keyof(schema)
// ┌─── "a" | "b"
// ▼
type Type = typeof keys.Type