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

Effect 数据类型

使用 schema 转换并管理各类数据类型,以获得更好的 JSON 序列化支持,涵盖 Option、Either、Set、Map、Duration 以及敏感的 Redacted 数据。

与 Data 互操作

Effect 生态中的 Data 模块会自动实现 EqualHash 两个 trait,从而简化值比较。这样一来就无需手动实现,相等性检查也变得直接明了。

示例(用 Data 比较 Struct)

import { Data, Equal } from "effect"

const person1 = Data.struct({ name: "Alice", age: 30 })
const person2 = Data.struct({ name: "Alice", age: 30 })

console.log(Equal.equals(person1, person2))
// Output: true

默认情况下,Schema.Struct 这类 schema 并不会实现 EqualHash 这两个 trait。这意味着两个值完全相同的已解码对象不会被视为相等。

示例(不使用 EqualHash 时的默认行为)

import { Schema } from "effect"
import { Equal } from "effect"

const schema = Schema.Struct({
  name: Schema.String,
  age: Schema.Number,
})

const decode = Schema.decode(schema)

const person1 = decode({ name: "Alice", age: 30 })
const person2 = decode({ name: "Alice", age: 30 })

console.log(Equal.equals(person1, person2))
// Output: false

Schema.Data 函数可以用来增强一个 schema,让它带上 EqualHash 这两个 trait。这样得到的对象就支持基于值的相等性。

示例(使用 Schema.Data 添加相等性)

import { Schema } from "effect"
import { Equal } from "effect"

const schema = Schema.Data(
  Schema.Struct({
    name: Schema.String,
    age: Schema.Number,
  }),
)

const decode = Schema.decode(schema)

const person1 = decode({ name: "Alice", age: 30 })
const person2 = decode({ name: "Alice", age: 30 })

console.log(Equal.equals(person1, person2))
// Output: true

Config

Schema.Config 函数允许你使用结构化的 schema 来解码并管理应用的配置项。 它保证配置数据的一致性,并为解码错误提供详细的反馈。

语法

Config: <A, I extends string>(name: string, schema: Schema<A, I>) => Config<A>

该函数接收两个参数:

  • name:配置项的标识符。
  • schema:描述期望的数据类型与结构的 schema。

它返回一个 Config 对象,可与你的应用配置系统集成。

Encoded 类型 I 必须扩展自 string,因此该 schema 必须能够从字符串解码,这包括 Schema.StringSchema.Literal("...")Schema.NumberFromString 这类 schema,并且它们还可以附加 refinement。

在幕后,Schema.Config 会执行以下步骤:

  1. 获取值:使用提供的名称(例如从环境变量中获取)。
  2. 解码值:使用给定的 schema。如果值无效,解码就会失败。
  3. 格式化错误:使用 TreeFormatter.formatErrorSync,它有助于产出可读且详细的错误消息。

示例(解码一个配置值)

import { Effect, Schema } from "effect"

// Define a config that expects a string with at least 4 characters
const myConfig = Schema.Config("Foo", Schema.String.pipe(Schema.minLength(4)))

const program = Effect.gen(function* () {
  const foo = yield* myConfig
  console.log(`ok: ${foo}`)
})

Effect.runSync(program)

要测试这份配置,请执行以下命令:

测试(配置数据缺失)

npx tsx config.ts
# Output:
# [(Missing data at Foo: "Expected Foo to exist in the process context")]

测试(数据无效)

Foo=bar npx tsx config.ts
# Output:
# [(Invalid data at Foo: "a string at least 4 character(s) long
# └─ Predicate refinement failure
#    └─ Expected a string at least 4 character(s) long, actual "bar"")]

测试(数据有效)

Foo=foobar npx tsx config.ts
# Output:
# ok: foobar

Option

Option

Schema.Option 函数可用于把 Option 转换成可 JSON 序列化的格式。

语法

Schema.Option(schema: Schema<A, I, R>)
Decoding
InputOutput
{ _tag: "None" }转换为 Option.none()
{ _tag: "Some", value: I }转换为 Option.some(a):其中 I 用内部 schema 解码为 A
Encoding
InputOutput
Option.none()转换为 { _tag: "None" }
Option.some(A)转换为 { _tag: "Some", value: I }:其中 A 用内部 schema 编码为 I

示例

import { Schema } from "effect"
import { Option } from "effect"

const schema = Schema.Option(Schema.NumberFromString)

//     ┌─── OptionEncoded<string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Option<number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode({ _tag: "None" }))
// Output: { _id: 'Option', _tag: 'None' }

console.log(decode({ _tag: "Some", value: "1" }))
// Output: { _id: 'Option', _tag: 'Some', value: 1 }

// Encoding examples

console.log(encode(Option.none()))
// Output: { _tag: 'None' }

console.log(encode(Option.some(1)))
// Output: { _tag: 'Some', value: '1' }

OptionFromSelf

Schema.OptionFromSelf 函数面向这样的场景:Option 值已经处于 Option 格式,需要在其内部值按照所提供的 schema 进行转换的同时完成解码或编码。

语法

Schema.OptionFromSelf(schema: Schema<A, I, R>)

Decoding

InputOutput
Option.none()保持为 Option.none()
Option.some(I)转换为 Option.some(A):其中 I 用内部 schema 解码为 A

Encoding

InputOutput
Option.none()保持为 Option.none()
Option.some(A)转换为 Option.some(I):其中 A 用内部 schema 编码为 I

示例

import { Schema } from "effect"
import { Option } from "effect"

const schema = Schema.OptionFromSelf(Schema.NumberFromString)

//     ┌─── Option<string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Option<number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(Option.none()))
// Output: { _id: 'Option', _tag: 'None' }

console.log(decode(Option.some("1")))
// Output: { _id: 'Option', _tag: 'Some', value: 1 }

// Encoding examples

console.log(encode(Option.none()))
// Output: { _id: 'Option', _tag: 'None' }

console.log(encode(Option.some(1)))
// Output: { _id: 'Option', _tag: 'Some', value: '1' }

OptionFromUndefinedOr

Schema.OptionFromUndefinedOr 函数处理这样的场景:undefined 被视为 Option.none(),而根据所提供的 schema,其他所有值都被解释为 Option.some()

语法

Schema.OptionFromUndefinedOr(schema: Schema<A, I, R>)

Decoding

InputOutput
undefined转换为 Option.none()
I转换为 Option.some(A):其中 I 用内部 schema 解码为 A

Encoding

InputOutput
Option.none()转换为 undefined
Option.some(A)转换为 I:其中 A 用内部 schema 编码为 I

示例

import { Schema } from "effect"
import { Option } from "effect"

const schema = Schema.OptionFromUndefinedOr(Schema.NumberFromString)

//     ┌─── string | undefined
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Option<number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(undefined))
// Output: { _id: 'Option', _tag: 'None' }

console.log(decode("1"))
// Output: { _id: 'Option', _tag: 'Some', value: 1 }

// Encoding examples

console.log(encode(Option.none()))
// Output: undefined

console.log(encode(Option.some(1)))
// Output: "1"

OptionFromNullOr

Schema.OptionFromUndefinedOr 函数处理这样的场景:null 被视为 Option.none(),而根据所提供的 schema,其他所有值都被解释为 Option.some()

语法

Schema.OptionFromNullOr(schema: Schema<A, I, R>)

Decoding

InputOutput
null转换为 Option.none()
I转换为 Option.some(A):其中 I 用内部 schema 解码为 A

Encoding

InputOutput
Option.none()转换为 null
Option.some(A)转换为 I:其中 A 用内部 schema 编码为 I

示例

import { Schema } from "effect"
import { Option } from "effect"

const schema = Schema.OptionFromNullOr(Schema.NumberFromString)

//     ┌─── string | null
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Option<number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(null))
// Output: { _id: 'Option', _tag: 'None' }

console.log(decode("1"))
// Output: { _id: 'Option', _tag: 'Some', value: 1 }

// Encoding examples

console.log(encode(Option.none()))
// Output: null
console.log(encode(Option.some(1)))
// Output: "1"

OptionFromNullishOr

Schema.OptionFromNullishOr 函数处理这样的场景:nullundefined 被视为 Option.none(),而根据所提供的 schema,其他所有值都被解释为 Option.some()。此外,它还允许自定义 Option.none() 的编码方式(nullundefined)。

语法

Schema.OptionFromNullishOr(
  schema: Schema<A, I, R>,
  onNoneEncoding: null | undefined
)

Decoding

InputOutput
undefined转换为 Option.none()
null转换为 Option.none()
I转换为 Option.some(A):其中 I 用内部 schema 解码为 A

Encoding

InputOutput
Option.none()根据用户的选择(onNoneEncoding)转换为 undefinednull
Option.some(A)转换为 I:其中 A 用内部 schema 编码为 I

示例

import { Schema } from "effect"
import { Option } from "effect"

const schema = Schema.OptionFromNullishOr(
  Schema.NumberFromString,
  undefined, // Encode Option.none() as undefined
)

//     ┌─── string | null | undefined
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Option<number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(null))
// Output: { _id: 'Option', _tag: 'None' }

console.log(decode(undefined))
// Output: { _id: 'Option', _tag: 'None' }

console.log(decode("1"))
// Output: { _id: 'Option', _tag: 'Some', value: 1 }

// Encoding examples

console.log(encode(Option.none()))
// Output: undefined

console.log(encode(Option.some(1)))
// Output: "1"

OptionFromNonEmptyTrimmedString

Schema.OptionFromNonEmptyTrimmedString schema 用于处理这样的字符串:去掉首尾空白后为空字符串的,会被当作 Option.none();所有其他字符串则会被转换成 Option.some()

解码

InputOutput
s: string如果 s.trim().length > 0,则转换为 Option.some(s)
否则转换为 Option.none()

编码

InputOutput
Option.none()转换为 ""
Option.some(s: string)转换为 s

示例

import { Schema, Option } from "effect"

//     ┌─── string
//     ▼
type Encoded = typeof Schema.OptionFromNonEmptyTrimmedString

//     ┌─── Option<string>
//     ▼
type Type = typeof Schema.OptionFromNonEmptyTrimmedString

const decode = Schema.decodeUnknownSync(Schema.OptionFromNonEmptyTrimmedString)
const encode = Schema.encodeSync(Schema.OptionFromNonEmptyTrimmedString)

// Decoding examples

console.log(decode(""))
// Output: { _id: 'Option', _tag: 'None' }

console.log(decode(" a "))
// Output: { _id: 'Option', _tag: 'Some', value: 'a' }

console.log(decode("a"))
// Output: { _id: 'Option', _tag: 'Some', value: 'a' }

// Encoding examples

console.log(encode(Option.none()))
// Output: ""

console.log(encode(Option.some("example")))
// Output: "example"

Either

Either

Schema.Either 函数可用于把 Either 转换成可 JSON 序列化的格式。

语法

Schema.Either(options: {
  left: Schema<LA, LI, LR>,
  right: Schema<RA, RI, RR>
})
解码
InputOutput
{ _tag: "Left", left: LI }转换为 Either.left(LA):其中 LI 用内部 left schema 解码为 LA
{ _tag: "Right", right: RI }转换为 Either.right(RA):其中 RI 用内部 right schema 解码为 RA
编码
InputOutput
Either.left(LA)转换为 { _tag: "Left", left: LI }:其中 LA 用内部 left schema 编码为 LI
Either.right(RA)转换为 { _tag: "Right", right: RI }:其中 RA 用内部 right schema 编码为 RI

示例

import { Schema, Either } from "effect"

const schema = Schema.Either({
  left: Schema.Trim,
  right: Schema.NumberFromString,
})

//     ┌─── EitherEncoded<string, string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Either<number, string>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode({ _tag: "Left", left: " a " }))
// Output: { _id: 'Either', _tag: 'Left', left: 'a' }

console.log(decode({ _tag: "Right", right: "1" }))
// Output: { _id: 'Either', _tag: 'Right', right: 1 }

// Encoding examples

console.log(encode(Either.left("a")))
// Output: { _tag: 'Left', left: 'a' }

console.log(encode(Either.right(1)))
// Output: { _tag: 'Right', right: '1' }

EitherFromSelf

Schema.EitherFromSelf 函数面向这样的场景:Either 值已经处于 Either 格式,需要在内部值按照所提供的 schema 进行转换的同时完成解码或编码。

语法

Schema.EitherFromSelf(options: {
  left: Schema<LA, LI, LR>,
  right: Schema<RA, RI, RR>
})
解码
InputOutput
Either.left(LI)转换为 Either.left(LA):其中 LI 用内部 left schema 解码为 LA
Either.right(RI)转换为 Either.right(RA):其中 RI 用内部 right schema 解码为 RA
编码
InputOutput
Either.left(LA)转换为 Either.left(LI):其中 LA 用内部 left schema 编码为 LI
Either.right(RA)转换为 Either.right(RI):其中 RA 用内部 right schema 编码为 RI

示例

import { Schema, Either } from "effect"

const schema = Schema.EitherFromSelf({
  left: Schema.Trim,
  right: Schema.NumberFromString,
})

//     ┌─── Either<string, string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Either<number, string>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(Either.left(" a ")))
// Output: { _id: 'Either', _tag: 'Left', left: 'a' }

console.log(decode(Either.right("1")))
// Output: { _id: 'Either', _tag: 'Right', right: 1 }

// Encoding examples

console.log(encode(Either.left("a")))
// Output: { _id: 'Either', _tag: 'Left', left: 'a' }

console.log(encode(Either.right(1)))
// Output: { _id: 'Either', _tag: 'Right', right: '1' }

EitherFromUnion

Schema.EitherFromUnion 函数用于解码和编码这样的 Either 值:其 leftright 两侧被表示为彼此不同的类型。这个 schema 支持在原始联合类型与结构化的 Either 类型之间进行转换。

语法

Schema.EitherFromUnion(options: {
  left: Schema<LA, LI, LR>,
  right: Schema<RA, RI, RR>
})
解码
InputOutput
LI转换为 Either.left(LA):其中 LI 用内部 left schema 解码为 LA
RI转换为 Either.right(RA):其中 RI 用内部 right schema 解码为 RA
编码
InputOutput
Either.left(LA)转换为 LI:其中 LA 用内部 left schema 编码为 LI
Either.right(RA)转换为 RI:其中 RA 用内部 right schema 编码为 RI

示例

import { Schema, Either } from "effect"

const schema = Schema.EitherFromUnion({
  left: Schema.Boolean,
  right: Schema.NumberFromString,
})

//     ┌─── string | boolean
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Either<number, boolean>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(true))
// Output: { _id: 'Either', _tag: 'Left', left: true }

console.log(decode("1"))
// Output: { _id: 'Either', _tag: 'Right', right: 1 }

// Encoding examples

console.log(encode(Either.left(true)))
// Output: true

console.log(encode(Either.right(1)))
// Output: "1"

Exit

Exit

Schema.Exit 函数可用于把 Exit 转换成可 JSON 序列化的格式。

语法

Schema.Exit(options: {
  failure: Schema<FA, FI, FR>,
  success: Schema<SA, SI, SR>,
  defect: Schema<DA, DI, DR>
})
解码
InputOutput
{ _tag: "Failure", cause: CauseEncoded<FI, DI> }转换为 Exit.failCause(Cause<FA>):其中 CauseEncoded<FI, DI> 用内部 failuredefect schema 解码为 Cause<FA>
{ _tag: "Success", value: SI }转换为 Exit.succeed(SA):其中 SI 用内部 success schema 解码为 SA
编码
InputOutput
Exit.failCause(Cause<FA>)转换为 { _tag: "Failure", cause: CauseEncoded<FI, DI> }:其中 Cause<FA> 用内部 failuredefect schema 编码为 CauseEncoded<FI, DI>
Exit.succeed(SA)转换为 { _tag: "Success", value: SI }:其中 SA 用内部 success schema 编码为 SI

示例

import { Schema, Exit } from "effect"

const schema = Schema.Exit({
  failure: Schema.String,
  success: Schema.NumberFromString,
  defect: Schema.String,
})

//     ┌─── ExitEncoded<string, string, string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Exit<number, string>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode({ _tag: "Failure", cause: { _tag: "Fail", error: "a" } }))
/*
Output:
{
  _id: 'Exit',
  _tag: 'Failure',
  cause: { _id: 'Cause', _tag: 'Fail', failure: 'a' }
}
*/

console.log(decode({ _tag: "Success", value: "1" }))
/*
Output:
{ _id: 'Exit', _tag: 'Success', value: 1 }
*/

// Encoding examples

console.log(encode(Exit.fail("a")))
/*
Output:
{ _tag: 'Failure', cause: { _tag: 'Fail', error: 'a' } }
 */

console.log(encode(Exit.succeed(1)))
/*
Output:
{ _tag: 'Success', value: '1' }
*/

处理序列化中的 defect

Effect 内置了 Defect schema,用于处理 JavaScript 错误(Error 实例)以及其他类型的不可恢复的 defect。

  • 解码时,如果输入包含 message,并且可选地包含 namestack,它就会重建出 Error 实例。
  • 编码时,它会把 Error 实例转换成仅保留必要属性的普通对象。

当需要在网络请求或日志系统中传递错误,而 Error 对象默认不会被序列化时,这一点非常有用。

示例(编码与解码 defect)

import { Schema, Exit } from "effect"

const schema = Schema.Exit({
  failure: Schema.String,
  success: Schema.NumberFromString,
  defect: Schema.Defect,
})

const decode = Schema.decodeSync(schema)
const encode = Schema.encodeSync(schema)

console.log(encode(Exit.die(new Error("Message"))))
/*
Output:
{
  _tag: 'Failure',
  cause: { _tag: 'Die', defect: { name: 'Error', message: 'Message' } }
}
*/

console.log(encode(Exit.fail("a")))

console.log(
  decode({
    _tag: "Failure",
    cause: { _tag: "Die", defect: { name: "Error", message: "Message" } },
  }),
)
/*
Output:
{
  _id: 'Exit',
  _tag: 'Failure',
  cause: {
    _id: 'Cause',
    _tag: 'Die',
    defect: [Error: Message] { [cause]: [Object] }
  }
}
*/

ExitFromSelf

Schema.ExitFromSelf 函数面向这样的场景:Exit 值已经处于 Exit 格式,需要在内部值按照所提供的 schema 进行转换的同时完成解码或编码。

语法

Schema.ExitFromSelf(options: {
  failure: Schema<FA, FI, FR>,
  success: Schema<SA, SI, SR>,
  defect: Schema<DA, DI, DR>
})
解码
InputOutput
Exit.failCause(Cause<FI>)转换为 Exit.failCause(Cause<FA>):其中 Cause<FI> 用内部 failuredefect schema 解码为 Cause<FA>
Exit.succeed(SI)转换为 Exit.succeed(SA):其中 SI 用内部 success schema 解码为 SA
编码
InputOutput
Exit.failCause(Cause<FA>)转换为 Exit.failCause(Cause<FI>):其中 Cause<FA> 用内部 failuredefect schema 解码为 Cause<FI>
Exit.succeed(SA)转换为 Exit.succeed(SI):其中 SA 用内部 success schema 编码为 SI

示例

import { Schema, Exit } from "effect"

const schema = Schema.ExitFromSelf({
  failure: Schema.String,
  success: Schema.NumberFromString,
  defect: Schema.String,
})

//     ┌─── Exit<string, string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Exit<number, string>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(Exit.fail("a")))
/*
Output:
{
  _id: 'Exit',
  _tag: 'Failure',
  cause: { _id: 'Cause', _tag: 'Fail', failure: 'a' }
}
*/

console.log(decode(Exit.succeed("1")))
/*
Output:
{ _id: 'Exit', _tag: 'Success', value: 1 }
*/

// Encoding examples

console.log(encode(Exit.fail("a")))
/*
Output:
{
  _id: 'Exit',
  _tag: 'Failure',
  cause: { _id: 'Cause', _tag: 'Fail', failure: 'a' }
}
*/

console.log(encode(Exit.succeed(1)))
/*
Output:
{ _id: 'Exit', _tag: 'Success', value: '1' }
*/

ReadonlySet

ReadonlySet

Schema.ReadonlySet 函数可用于把 ReadonlySet 转换成可 JSON 序列化的格式。

语法

Schema.ReadonlySet(schema: Schema<A, I, R>)
解码
InputOutput
ReadonlyArray<I>转换为 ReadonlySet<A>:其中 I 用内部 schema 解码为 A
编码
InputOutput
ReadonlySet<A>ReadonlyArray<I>,其中 A 用内部 schema 编码为 I

示例

import { Schema } from "effect"

const schema = Schema.ReadonlySet(Schema.NumberFromString)

//     ┌─── readonly string[]
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── ReadonlySet<number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(["1", "2", "3"]))
// Output: Set(3) { 1, 2, 3 }

// Encoding examples

console.log(encode(new Set([1, 2, 3])))
// Output: [ '1', '2', '3' ]

ReadonlySetFromSelf

Schema.ReadonlySetFromSelf 函数面向这样的场景:ReadonlySet 值已经处于 ReadonlySet 格式,需要在内部值按照所提供的 schema 进行转换的同时完成解码或编码。

语法

Schema.ReadonlySetFromSelf(schema: Schema<A, I, R>)
解码
InputOutput
ReadonlySet<I>转换为 ReadonlySet<A>:其中 I 用内部 schema 解码为 A
编码
InputOutput
ReadonlySet<A>ReadonlySet<I>,其中 A 用内部 schema 编码为 I

示例

import { Schema } from "effect"

const schema = Schema.ReadonlySetFromSelf(Schema.NumberFromString)

//     ┌─── ReadonlySet<string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── ReadonlySet<number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(new Set(["1", "2", "3"])))
// Output: Set(3) { 1, 2, 3 }

// Encoding examples

console.log(encode(new Set([1, 2, 3])))
// Output: Set(3) { '1', '2', '3' }

ReadonlyMap

Schema.ReadonlyMap 函数可用于把 ReadonlyMap 转换成可 JSON 序列化的格式。

ReadonlyMap

语法

Schema.ReadonlyMap(options: {
  key: Schema<KA, KI, KR>,
  value: Schema<VA, VI, VR>
})
解码
InputOutput
ReadonlyArray<readonly [KI, VI]>转换为 ReadonlyMap<KA, VA>:其中 KI 用内部 key schema 解码为 KAVI 用内部 value schema 解码为 VA
编码
InputOutput
ReadonlyMap<KA, VA>转换为 ReadonlyArray<readonly [KI, VI]>:其中 KA 用内部 key schema 解码为 KIVA 用内部 value schema 解码为 VI

示例

import { Schema } from "effect"

const schema = Schema.ReadonlyMap({
  key: Schema.String,
  value: Schema.NumberFromString,
})

//     ┌─── readonly (readonly [string, string])[]
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── ReadonlyMap<string, number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(
  decode([
    ["a", "2"],
    ["b", "2"],
    ["c", "3"],
  ]),
)
// Output: Map(3) { 'a' => 2, 'b' => 2, 'c' => 3 }

// Encoding examples

console.log(
  encode(
    new Map([
      ["a", 1],
      ["b", 2],
      ["c", 3],
    ]),
  ),
)
// Output: [ [ 'a', '1' ], [ 'b', '2' ], [ 'c', '3' ] ]

ReadonlyMapFromSelf

Schema.ReadonlyMapFromSelf 函数面向这样的场景:ReadonlyMap 值已经处于 ReadonlyMap 格式,需要在内部值按照所提供的 schema 进行转换的同时完成解码或编码。

语法

Schema.ReadonlyMapFromSelf(options: {
  key: Schema<KA, KI, KR>,
  value: Schema<VA, VI, VR>
})
解码
InputOutput
ReadonlyMap<KI, VI>转换为 ReadonlyMap<KA, VA>:其中 KI 用内部 key schema 解码为 KAVI 用内部 value schema 解码为 VA
编码
InputOutput
ReadonlyMap<KA, VA>转换为 ReadonlyMap<KI, VI>:其中 KA 用内部 key schema 解码为 KIVA 用内部 value schema 解码为 VI

示例

import { Schema } from "effect"

const schema = Schema.ReadonlyMapFromSelf({
  key: Schema.String,
  value: Schema.NumberFromString,
})

//     ┌─── ReadonlyMap<string, string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── ReadonlyMap<string, number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(
  decode(
    new Map([
      ["a", "2"],
      ["b", "2"],
      ["c", "3"],
    ]),
  ),
)
// Output: Map(3) { 'a' => 2, 'b' => 2, 'c' => 3 }

// Encoding examples

console.log(
  encode(
    new Map([
      ["a", 1],
      ["b", 2],
      ["c", 3],
    ]),
  ),
)
// Output: Map(3) { 'a' => '1', 'b' => '2', 'c' => '3' }

ReadonlyMapFromRecord

Schema.ReadonlyMapFromRecord 函数是一个工具,用于把 ReadonlyMap 转换成对象格式(键为字符串、值为可序列化),反之亦然。

语法

Schema.ReadonlyMapFromRecord({
  key: Schema<KA, KI, KR>,
  value: Schema<VA, VI, VR>,
})

解码

InputOutput
{ readonly [x: string]: VI }转换为 ReadonlyMap<KA, VA>:其中 xkey schema 解码为 KAVIvalue schema 解码为 VA

编码

InputOutput
ReadonlyMap<KA, VA>转换为 { readonly [x: string]: VI }:其中 KAkey schema 编码为 xVAvalue schema 编码为 VI

示例

import { Schema } from "effect"

const schema = Schema.ReadonlyMapFromRecord({
  key: Schema.NumberFromString,
  value: Schema.NumberFromString,
})

//     ┌─── { readonly [x: string]: string; }
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── ReadonlyMap<number, number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(
  decode({
    "1": "4",
    "2": "5",
    "3": "6",
  }),
)
// Output: Map(3) { 1 => 4, 2 => 5, 3 => 6 }

// Encoding examples

console.log(
  encode(
    new Map([
      [1, 4],
      [2, 5],
      [3, 6],
    ]),
  ),
)
// Output: { '1': '4', '2': '5', '3': '6' }

HashSet

HashSet

Schema.HashSet 函数提供了一种在 HashSet 与数组表示之间互相映射的方式,从而支持 JSON 序列化与反序列化。

语法

Schema.HashSet(schema: Schema<A, I, R>)

解码

InputOutput
ReadonlyArray<I>转换为 HashSet<A>:使用该 schema 把数组中的每个元素解码为类型 A

编码

InputOutput
HashSet<A>转换为 ReadonlyArray<I>:使用该 schema 把 HashSet 中的每个元素编码为类型 I

示例

import { Schema } from "effect"
import { HashSet } from "effect"

const schema = Schema.HashSet(Schema.NumberFromString)

//     ┌─── readonly string[]
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── HashSet<number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(["1", "2", "3"]))
// Output: { _id: 'HashSet', values: [ 1, 2, 3 ] }

// Encoding examples

console.log(encode(HashSet.fromIterable([1, 2, 3])))
// Output: [ '1', '2', '3' ]

HashSetFromSelf

Schema.HashSetFromSelf 函数面向这样的场景:HashSet 值已经处于 HashSet 格式,需要在内部值按照所提供的 schema 进行转换的同时完成解码或编码。

语法

Schema.HashSetFromSelf(schema: Schema<A, I, R>)

解码

InputOutput
HashSet<I>转换为 HashSet<A>:使用该 schema 把每个元素从类型 I 解码为类型 A

编码

InputOutput
HashSet<A>转换为 HashSet<I>:使用该 schema 把每个元素从类型 A 编码为类型 I

示例

import { Schema } from "effect"
import { HashSet } from "effect"

const schema = Schema.HashSetFromSelf(Schema.NumberFromString)

//     ┌─── HashSet<string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── HashSet<number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(HashSet.fromIterable(["1", "2", "3"])))
// Output: { _id: 'HashSet', values: [ 1, 2, 3 ] }

// Encoding examples

console.log(encode(HashSet.fromIterable([1, 2, 3])))
// Output: { _id: 'HashSet', values: [ '1', '3', '2' ] }

HashMap

HashMap

Schema.HashMap 函数可用于把 HashMap 转换成可 JSON 序列化的格式。

语法

Schema.HashMap(options: {
  key: Schema<KA, KI, KR>,
  value: Schema<VA, VI, VR>
})
InputOutput
ReadonlyArray<readonly [KI, VI]>转换为 HashMap<KA, VA>:其中 KI 用指定的 schema 解码为 KAVI 用指定的 schema 解码为 VA

编码

InputOutput
HashMap<KA, VA>转换为 ReadonlyArray<readonly [KI, VI]>:其中 KA 用指定的 schema 编码为 KIVA 用指定的 schema 编码为 VI

示例

import { Schema } from "effect"
import { HashMap } from "effect"

const schema = Schema.HashMap({
  key: Schema.String,
  value: Schema.NumberFromString,
})

//     ┌─── readonly (readonly [string, string])[]
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── HashMap<string, number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(
  decode([
    ["a", "2"],
    ["b", "2"],
    ["c", "3"],
  ]),
)
// Output: { _id: 'HashMap', values: [ [ 'a', 2 ], [ 'c', 3 ], [ 'b', 2 ] ] }

// Encoding examples

console.log(
  encode(
    HashMap.fromIterable([
      ["a", 1],
      ["b", 2],
      ["c", 3],
    ]),
  ),
)
// Output: [ [ 'a', '1' ], [ 'c', '3' ], [ 'b', '2' ] ]

HashMapFromSelf

Schema.HashMapFromSelf 函数面向这样的场景:HashMap 值已经处于 HashMap 格式,需要在其内部值按照所提供的 schema 进行转换的同时完成解码或编码。

语法

Schema.HashMapFromSelf(options: {
  key: Schema<KA, KI, KR>,
  value: Schema<VA, VI, VR>
})

解码

InputOutput
HashMap<KI, VI>转换为 HashMap<KA, VA>:其中 KI 用指定的 schema 解码为 KAVI 用指定的 schema 解码为 VA

编码

InputOutput
HashMap<KA, VA>转换为 HashMap<KI, VI>:其中 KA 用指定的 schema 编码为 KIVA 用指定的 schema 编码为 VI

示例

import { Schema } from "effect"
import { HashMap } from "effect"

const schema = Schema.HashMapFromSelf({
  key: Schema.String,
  value: Schema.NumberFromString,
})

//     ┌─── HashMap<string, string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── HashMap<string, number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(
  decode(
    HashMap.fromIterable([
      ["a", "2"],
      ["b", "2"],
      ["c", "3"],
    ]),
  ),
)
// Output: { _id: 'HashMap', values: [ [ 'a', 2 ], [ 'c', 3 ], [ 'b', 2 ] ] }

// Encoding examples

console.log(
  encode(
    HashMap.fromIterable([
      ["a", 1],
      ["b", 2],
      ["c", 3],
    ]),
  ),
)
// Output: { _id: 'HashMap', values: [ [ 'a', '1' ], [ 'c', '3' ], [ 'b', '2' ] ] }

SortedSet

SortedSet

Schema.SortedSet 函数提供了在 SortedSet 与数组表示之间相互映射的方式,从而支持 JSON 序列化与反序列化。

语法

Schema.SortedSet(schema: Schema<A, I, R>, order: Order<A>)

解码

InputOutput
ReadonlyArray<I>转换为 SortedSet<A>:使用该 schema 把数组中的每个元素解码为类型 A

编码

InputOutput
SortedSet<A>转换为 ReadonlyArray<I>:使用该 schema 把 SortedSet 中的每个元素编码为类型 I

示例

import { Schema } from "effect"
import { Number, SortedSet } from "effect"

const schema = Schema.SortedSet(Schema.NumberFromString, Number.Order)

//     ┌─── readonly string[]
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── SortedSet<number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(["1", "2", "3"]))
// Output: { _id: 'SortedSet', values: [ 1, 2, 3 ] }

// Encoding examples

console.log(encode(SortedSet.fromIterable(Number.Order)([1, 2, 3])))
// Output: [ '1', '2', '3' ]

SortedSetFromSelf

Schema.SortedSetFromSelf 函数面向这样的场景:SortedSet 值已经处于 SortedSet 格式,需要在其内部值按照所提供的 schema 进行转换的同时完成解码或编码。

语法

Schema.SortedSetFromSelf(
  schema: Schema<A, I, R>,
  decodeOrder: Order<A>,
  encodeOrder: Order<I>
)

解码

InputOutput
SortedSet<I>转换为 SortedSet<A>:使用该 schema 把每个元素从类型 I 解码为类型 A

编码

InputOutput
SortedSet<A>转换为 SortedSet<I>:使用该 schema 把每个元素从类型 A 编码为类型 I

示例

import { Schema } from "effect"
import { Number, SortedSet, String } from "effect"

const schema = Schema.SortedSetFromSelf(
  Schema.NumberFromString,
  Number.Order,
  String.Order,
)

//     ┌─── SortedSet<string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── SortedSet<number>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)
const encode = Schema.encodeSync(schema)

// Decoding examples

console.log(decode(SortedSet.fromIterable(String.Order)(["1", "2", "3"])))
// Output: { _id: 'SortedSet', values: [ 1, 2, 3 ] }

// Encoding examples

console.log(encode(SortedSet.fromIterable(Number.Order)([1, 2, 3])))
// Output: { _id: 'SortedSet', values: [ '1', '2', '3' ] }

Duration

Duration schema 家族支持对各种格式的时长值进行转换与校验,包括 hrtime、毫秒与纳秒。

Duration

把 hrtime(即 [seconds: number, nanos: number])转换为 Duration

示例

import { Schema } from "effect"

const schema = Schema.Duration

//     ┌─── readonly [seconds: number, nanos: number]
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Duration
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)

// Decoding examples

console.log(decode([0, 0]))
// Output: { _id: 'Duration', _tag: 'Millis', millis: 0 }

console.log(decode([5000, 0]))
// Output: { _id: 'Duration', _tag: 'Nanos', hrtime: [ 5000, 0 ] }

DurationFromSelf

DurationFromSelf schema 用于校验给定值是否符合 Duration 类型。

示例

import { Schema, Duration } from "effect"

const schema = Schema.DurationFromSelf

//     ┌─── Duration
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Duration
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)

// Decoding examples

console.log(decode(Duration.seconds(2)))
// Output: { _id: 'Duration', _tag: 'Millis', millis: 2000 }

console.log(decode(null))
/*
throws:
ParseError: Expected DurationFromSelf, actual null
*/

DurationFromMillis

number 转换为 Duration,其中的数字表示毫秒数。

示例

import { Schema } from "effect"

const schema = Schema.DurationFromMillis

//     ┌─── number
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Duration
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)

// Decoding examples

console.log(decode(0))
// Output: { _id: 'Duration', _tag: 'Millis', millis: 0 }

console.log(decode(5000))
// Output: { _id: 'Duration', _tag: 'Millis', millis: 5000 }

DurationFromNanos

BigInt 转换为 Duration,其中的数字表示纳秒数。

示例

import { Schema } from "effect"

const schema = Schema.DurationFromNanos

//     ┌─── bigint
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Duration
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)

// Decoding examples

console.log(decode(0n))
// Output: { _id: 'Duration', _tag: 'Millis', millis: 0 }

console.log(decode(5000000000n))
// Output: { _id: 'Duration', _tag: 'Nanos', hrtime: [ 5, 0 ] }

clampDuration

Duration 限制在最小值与最大值之间。

示例

import { Schema, Duration } from "effect"

const schema = Schema.DurationFromSelf.pipe(
  Schema.clampDuration("5 seconds", "10 seconds"),
)

//     ┌─── Duration
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Duration
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)

// Decoding examples

console.log(decode(Duration.decode("2 seconds")))
// Output: { _id: 'Duration', _tag: 'Millis', millis: 5000 }

console.log(decode(Duration.decode("6 seconds")))
// Output: { _id: 'Duration', _tag: 'Millis', millis: 6000 }

console.log(decode(Duration.decode("11 seconds")))
// Output: { _id: 'Duration', _tag: 'Millis', millis: 10000 }

Redacted

Redacted

Schema.Redacted 函数专门用于处理敏感信息,它把 string 转换为 Redacted 对象。 这种转换能确保敏感数据不会暴露在应用的输出中。

示例(基础的 Redacted schema)

import { Schema } from "effect"

const schema = Schema.Redacted(Schema.String)

//     ┌─── string
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Redacted<string>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)

// Decoding examples

console.log(decode("keep it secret, keep it safe"))
// Output: <redacted>

需要注意的是,成功解码 Redacted 时,输出会被有意遮蔽为 <redacted>,以免真正的机密内容泄露到日志或控制台输出中。

Potential Risks

Redacted schema 与其他 schema 组合使用时必须格外小心,因为解码或编码错误 有可能暴露敏感信息。

示例(错误期间的暴露风险)

在下面的示例中,如果输入字符串不符合条件(例如包含空格),生成的错误消息可能会无意中暴露输入中包含的敏感信息。

import { Schema } from "effect"
import { Redacted } from "effect"

const schema = Schema.Trimmed.pipe(
  Schema.compose(Schema.Redacted(Schema.String)),
)

console.log(Schema.decodeUnknownEither(schema)(" SECRET"))
/*
{
  _id: 'Either',
  _tag: 'Left',
  left: {
    _id: 'ParseError',
    message: '(Trimmed <-> (string <-> Redacted(<redacted>)))\n' +
      '└─ Encoded side transformation failure\n' +
      '   └─ Trimmed\n' +
      '      └─ Predicate refinement failure\n' +
      '         └─ Expected Trimmed (a string with no leading or trailing whitespace), actual " SECRET"'
  }
}
*/

console.log(Schema.encodeEither(schema)(Redacted.make(" SECRET")))
/*
{
  _id: 'Either',
  _tag: 'Left',
  left: {
    _id: 'ParseError',
    message: '(Trimmed <-> (string <-> Redacted(<redacted>)))\n' +
      '└─ Encoded side transformation failure\n' +
      '   └─ Trimmed\n' +
      '      └─ Predicate refinement failure\n' +
      '         └─ Expected Trimmed (a string with no leading or trailing whitespace), actual " SECRET"'
  }
}
*/

缓解暴露风险

为了降低错误消息中敏感信息泄露的风险,你可以自定义错误消息,以遮蔽敏感细节:

示例(自定义错误消息)

import { Schema } from "effect"
import { Redacted } from "effect"

const schema = Schema.Trimmed.annotations({
  message: () => "Expected Trimmed, actual <redacted>",
}).pipe(Schema.compose(Schema.Redacted(Schema.String)))

console.log(Schema.decodeUnknownEither(schema)(" SECRET"))
/*
{
  _id: 'Either',
  _tag: 'Left',
  left: {
    _id: 'ParseError',
    message: '(Trimmed <-> (string <-> Redacted(<redacted>)))\n' +
      '└─ Encoded side transformation failure\n' +
      '   └─ Expected Trimmed, actual <redacted>'
  }
}
*/

console.log(Schema.encodeEither(schema)(Redacted.make(" SECRET")))
/*
{
  _id: 'Either',
  _tag: 'Left',
  left: {
    _id: 'ParseError',
    message: '(Trimmed <-> (string <-> Redacted(<redacted>)))\n' +
      '└─ Encoded side transformation failure\n' +
      '   └─ Expected Trimmed, actual <redacted>'
  }
}
*/

RedactedFromSelf

Schema.RedactedFromSelf schema 用于校验给定值是否符合 effect 库中的 Redacted 类型。

示例

import { Schema } from "effect"
import { Redacted } from "effect"

const schema = Schema.RedactedFromSelf(Schema.String)

//     ┌─── Redacted<string>
//     ▼
type Encoded = typeof schema.Encoded

//     ┌─── Redacted<string>
//     ▼
type Type = typeof schema.Type

const decode = Schema.decodeUnknownSync(schema)

// Decoding examples

console.log(decode(Redacted.make("mysecret")))
// Output: <redacted>

console.log(decode(null))
/*
throws:
ParseError: Expected Redacted(<redacted>), actual null
*/

需要注意的是,成功解码一个 Redacted 时,输出会被有意遮蔽为(<redacted>),以防止真正的机密内容在日志或控制台输出中暴露出来。