已发布 上游基线 bf46254 原文 ↗ 在 GitHub 编辑

基本用法

学习定义和使用基本 schema,包括基本类型、字面量、联合与结构体,以进行高效的数据校验与转换。

基本类型

Schema 模块为常见的基本类型提供了内置 schema。

Schema等价的 TypeScript 类型
Schema.Stringstring
Schema.Numbernumber
Schema.Booleanboolean
Schema.BigIntFromSelfBigInt
Schema.SymbolFromSelfsymbol
Schema.Objectobject
Schema.Undefinedundefined
Schema.Voidvoid
Schema.Anyany
Schema.Unknownunknown
Schema.Nevernever

示例(使用基本类型 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 表示字面量类型。 你可以用它们来指定类型必须具有的精确值。

字面量可以是以下几种类型:

  • string
  • number
  • boolean
  • null
  • bigint

示例(定义字面量 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.String
  • Schema.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,帮助你处理可能是 nullundefined 或两者兼有的值。

示例(创建可空 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 属性设置为 CircleSquare 两个 schema 共同的判别属性。随后 Shape schema 表示这两个类型的联合,让 TypeScript 能够根据 kind 的值推断出具体的形状。

把简单联合转换为可辨识联合

如果你从一个简单联合开始,并想把它转换为可辨识联合,可以为每个成员添加一个特殊属性。这样 TypeScript 就能根据判别属性的值自动推断出正确的类型。

示例(最初的简单联合)

例如,假设你定义了一个由 CircleSquare 组合而成、不带任何特殊属性的 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 }
Property Addition Only

请注意,使用 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
}
*/

暴露的值

你可以使用 elementsrest 属性访问元组 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
*/
Why Key Transformations Are Not Allowed

存在这一限制是因为:如果多个键在转换后映射到同一个值,转换就会产生冲突。为避免这些问题,键的转换必须由用户显式处理。

要修改 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

暴露的值

你可以使用 keyvalue 属性访问 record schema 的 keyvalue 类型:

示例(访问键与值的类型)

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
Empty Structs Allow Any Data

使用 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

多个索引签名

每种键类型(stringsymbol)只能定义一个索引签名。不允许定义多个同类型的索引签名。

示例(合法的多个索引签名)

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

定义多个同一种键类型(stringsymbol)的索引签名会导致错误。

示例(非法的多个索引签名)

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.transformSchema.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 }

暴露的值

你可以使用 fieldsrecords 属性访问 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

在这个示例中,尽管 ab 最初都被定义为可选,但它们最终都被设为必需。

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