一、Effect 平台介绍
@effect/platform 是一个用于在 Node.js、Deno、Bun 和浏览器等环境中构建平台无关抽象的库。
通过 @effect/platform,你可以将文件系统(FileSystem)或终端(Terminal)等抽象服务集成到程序中。在组装最终应用时,你可以使用对应的包为目标平台提供特定层:
- 适用于 Node.js 或 Deno 的
@effect/platform-node - 适用于 Bun 的
@effect/platform-bun - 适用于浏览器的
@effect/platform-browser
稳定模块
以下模块已稳定,其文档可在本网站查阅:
| 模块 | 描述 | 状态 |
|---|---|---|
| Command(命令) | 提供与命令行交互的方式 | 稳定 |
| FileSystem(文件系统) | 用于文件系统操作的模块 | 稳定 |
| KeyValueStore(键值存储) | 管理用于数据存储的键值对 | 稳定 |
| Path(路径) | 用于处理文件路径的工具类 | 稳定 |
| PlatformLogger(平台日志器) | 使用文件系统 API 将日志消息记录到文件 | 稳定 |
| Runtime(运行时) | 运行程序,内置错误处理和日志记录功能 | 稳定 |
| Terminal(终端) | 用于终端交互的工具 | 稳定 |
不稳定模块
@effect/platform 中的部分模块仍处于开发阶段或标记为实验性模块,这些功能可能会发生变化。
| 模块 | 描述 | 状态 |
|---|---|---|
| Http API(HTTP 接口) | 提供声明式的 HTTP 接口定义方式 | 不稳定 |
| Http Client(HTTP 客户端) | 用于发送 HTTP 请求的客户端 | 不稳定 |
| Http Server(HTTP 服务器) | 用于处理 HTTP 请求的服务器 | 不稳定 |
| Socket(套接字) | 用于基于套接字通信的模块 | 不稳定 |
| Worker(工作线程) | 用于在独立工作线程中运行任务的模块 | 不稳定 |
安装
如需安装测试版,请执行以下命令:
| 包管理器 | 安装命令 |
|---|---|
| npm | npm install @effect/platform |
| pnpm | pnpm add @effect/platform |
| Yarn | yarn add @effect/platform |
| Bun | bun add @effect/platform |
| Deno | deno add npm:@effect/platform |
跨平台编程入门
以下是使用 Path 模块创建文件路径的基础示例,该示例可在不同环境中运行:
示例(跨平台路径处理)
index.ts
import { Path } from "@effect/platform"
import { Effect } from "effect"
const program = Effect.gen(function* () {
// 访问 Path 服务
const path = yield* Path.Path
// 拼接路径片段以创建完整的文件路径
const mypath = path.join("tmp", "file.txt")
console.log(mypath)
})
在 Node.js 或 Deno 中运行程序
首先,安装适用于 Node.js 的特定包:
| 包管理器 | 安装命令 |
|---|---|
| npm | npm install @effect/platform-node |
| pnpm | pnpm add @effect/platform-node |
| Yarn | yarn add @effect/platform-node |
| Deno | deno add npm:@effect/platform-node |
更新程序以加载 Node.js 特定上下文:
示例(提供 Node.js 上下文)
index.ts
import { Path } from "@effect/platform"
import { Effect } from "effect"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
const program = Effect.gen(function* () {
// 访问 Path 服务
const path = yield* Path.Path
// 拼接路径片段以创建完整的文件路径
const mypath = path.join("tmp", "file.txt")
console.log(mypath)
})
NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))
最后,使用 tsx 在 Node.js 中运行程序,或直接在 Deno 中运行:
| 运行环境 | 运行命令 | 输出结果 |
|---|---|---|
| npm | npx tsx index.ts | tmp/file.txt |
| pnpm | pnpm dlx tsx index.ts | tmp/file.txt |
| Yarn | yarn dlx tsx index.ts | tmp/file.txt |
| Deno | deno run index.ts 或 deno run -RE index.ts(无需提示即可授予所需的读取和环境权限) | tmp/file.txt |
在 Bun 中运行程序
要在 Bun 中运行同一个程序,首先安装适用于 Bun 的特定包:
bun add @effect/platform-bun
更新程序以使用 Bun 特定上下文:
示例(提供 Bun 上下文)
index.ts
import { Path } from "@effect/platform"
import { Effect } from "effect"
import { BunContext, BunRuntime } from "@effect/platform-bun"
const program = Effect.gen(function* () {
// 访问 Path 服务
const path = yield* Path.Path
// 拼接路径片段以创建完整的文件路径
const mypath = path.join("tmp", "file.txt")
console.log(mypath)
})
BunRuntime.runMain(program.pipe(Effect.provide(BunContext.layer)))
在 Bun 中运行程序:
bun index.ts
输出结果:tmp/file.txt
二、命令(Command)
@effect/platform/Command 模块提供了一种方式,可创建并运行带有指定进程名称和可选参数列表的命令。
创建命令
Command.make 函数会生成一个命令对象,包含进程名称、参数、环境变量等详情。
示例(定义目录列表命令)
import { Command } from "@effect/platform"
const command = Command.make("ls", "-al")
console.log(command)
/*
{
_id: '@effect/platform/Command',
_tag: 'StandardCommand',
command: 'ls',
args: [ '-al' ],
env: {},
cwd: { _id: 'Option', _tag: 'None' },
shell: false,
gid: { _id: 'Option', _tag: 'None' },
uid: { _id: 'Option', _tag: 'None' }
}
*/
该命令对象需通过执行器(executor)触发后才会执行。
运行命令
需通过 CommandExecutor 运行命令,它支持以字符串、行、流等多种格式捕获输出。
示例(运行命令并打印输出)
import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"
const command = Command.make("ls", "-al")
// 程序依赖 CommandExecutor
const program = Effect.gen(function* () {
// 运行命令并以字符串形式返回输出
const output = yield* Command.string(command)
console.log(output)
})
// 提供所需的 CommandExecutor
NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))
输出格式
可通过不同方法处理命令输出,具体如下:
| 方法 | 描述 |
|---|---|
string | 运行命令,以字符串形式返回输出(支持指定编码) |
lines | 运行命令,以行数组形式返回输出(支持指定编码) |
stream | 运行命令,以 Uint8Array 块流的形式返回输出 |
streamLines | 运行命令,以行流的形式返回输出(支持指定编码) |
退出码(exitCode)
若仅需获取命令的退出码,可使用 Command.exitCode。
示例(获取退出码)
import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"
const command = Command.make("ls", "-al")
const program = Effect.gen(function* () {
const exitCode = yield* Command.exitCode(command)
console.log(exitCode)
})
NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))
// 输出:0
自定义环境变量
可通过 Command.env 为命令自定义环境变量,适用于命令执行需特定变量的场景。
示例(设置环境变量)
本示例中,命令在 shell 中运行,以确保环境变量能正确解析。
import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"
const command = Command.make("echo", "-n", "$MY_CUSTOM_VAR").pipe(
Command.env({
MY_CUSTOM_VAR: "Hello, this is a custom environment variable!"
}),
// 在 Windows 和类 Unix 系统中,通过 shell 确保变量正确解析
Command.runInShell(true)
)
const program = Effect.gen(function* () {
const output = yield* Command.string(command)
console.log(output)
})
NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))
// 输出:Hello, this is a custom environment variable!
向命令输入数据
通过 Command.feed 函数,可直接向命令的标准输入(stdin)发送数据。
示例(向命令的标准输入发送数据)
import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"
const command = Command.make("cat").pipe(Command.feed("Hello"))
const program = Effect.gen(function* () {
console.log(yield* Command.string(command))
})
NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))
// 输出:Hello
获取进程详情
可获取运行中进程的详情,如 exitCode(退出码)、stdout(标准输出)、stderr(标准错误输出)等。
示例(获取运行中进程的退出码和流)
import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect, Stream, String, pipe } from "effect"
// 辅助函数:将流输出收集为字符串
const runString = <E, R>(
stream: Stream.Stream<Uint8Array, E, R>
): Effect.Effect<string, E, R> =>
stream.pipe(
Stream.decodeText(),
Stream.runFold(String.empty, String.concat)
)
const program = Effect.gen(function* () {
const command = Command.make("ls")
const [exitCode, stdout, stderr] = yield* pipe(
// 启动命令并返回运行中进程的句柄
Command.start(command),
Effect.flatMap((process) =>
Effect.all(
[
// 等待进程退出并返回命令的退出码
process.exitCode,
// 进程的标准输出流
runString(process.stdout),
// 进程的标准错误流
runString(process.stderr)
],
{ concurrency: 3 }
)
)
)
console.log({ exitCode, stdout, stderr })
})
NodeRuntime.runMain(
Effect.scoped(program).pipe(Effect.provide(NodeContext.layer))
)
将 stdout 流式传输到 process.stdout
可通过以下方式,将命令的 stdout(标准输出)直接流式传输到 process.stdout:
示例(将命令输出直接流式传输到标准输出)
import { Command } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"
// 创建命令:对文件执行 `cat` 命令并继承 stdout
const program = Command.make("cat", "./some-file.txt").pipe(
Command.stdout("inherit"), // 将 stdout 流式传输到 process.stdout
Command.exitCode // 获取退出码
)
NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))
三、文件系统(FileSystem)
@effect/platform/FileSystem 模块提供了一套用于读取和写入文件系统的操作方法。
基础用法
该模块提供唯一的 FileSystem 标签,作为与文件系统交互的入口。
示例(访问文件系统操作)
import { FileSystem } from "@effect/platform"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem
// 使用 `fs` 执行文件系统操作
})
文件系统操作列表
FileSystem 接口包含以下操作方法,详细说明如下:
| 操作方法 | 描述 |
|---|---|
| access | 检查文件是否可访问,可选择指定访问权限级别 |
| copy | 将文件或目录从 fromPath 复制到 toPath,等效于 cp -r 命令 |
| copyFile | 将文件从 fromPath 复制到 toPath |
| chmod | 修改文件权限 |
| chown | 修改文件的所有者和所属组 |
| exists | 检查路径是否存在 |
| link | 创建从 fromPath 到 toPath 的硬链接 |
| makeDirectory | 在指定 path 处创建目录,可选择指定权限模式(mode)和是否递归创建嵌套目录 |
| makeTempDirectory | 创建临时目录,默认在系统默认临时目录内创建 |
| makeTempDirectoryScoped | 在作用域(scope)内创建临时目录,功能与 makeTempDirectory 一致,但作用域关闭时会自动删除该目录 |
| makeTempFile | 创建临时文件,目录创建逻辑与 makeTempDirectory 一致,文件名随机生成 |
| makeTempFileScoped | 在作用域内创建临时文件,功能与 makeTempFile 一致,但作用域关闭时会自动删除该文件 |
| open | 以指定 options 打开 path 处的文件,作用域关闭时文件句柄会自动关闭 |
| readDirectory | 列出目录内容,设置 recursive 选项可递归列出嵌套目录内容 |
| readFile | 读取文件内容 |
| readFileString | 以字符串形式读取文件内容 |
| readLink | 读取符号链接的目标路径 |
| realPath | 将路径解析为规范化的绝对路径 |
| remove | 删除文件或目录,设置 recursive: true 可递归删除嵌套目录 |
| rename | 重命名文件或目录 |
| sink | 为指定 path 创建可写入的 Sink(接收器) |
| stat | 获取 path 处文件的相关信息 |
| stream | 为指定 path 创建可读的 Stream(流) |
| symlink | 创建从 fromPath 到 toPath 的符号链接 |
| truncate | 将文件截断至指定长度,未指定 length 时默认截断为 0 长度 |
| utimes | 修改 path 处文件的文件系统时间戳 |
| watch | 监听目录或文件的变化 |
| writeFile | 向 path 处的文件写入数据 |
| writeFileString | 向 path 处的文件写入字符串 |
示例(以字符串形式读取文件)
import { FileSystem } from "@effect/platform"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Effect } from "effect"
// ┌─── Effect<void, PlatformError, FileSystem>
// ▼
const program = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem
// 读取当前代码所在文件的内容
const content = yield* fs.readFileString("./index.ts", "utf8")
console.log(content)
})
// 提供必要的上下文并运行程序
NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))
模拟文件系统
在测试环境中,你可能需要模拟文件系统以避免执行实际的磁盘操作。FileSystem.layerNoop 提供了 FileSystem 服务的空操作(无实际效果)实现。
FileSystem.layerNoop 中的大多数操作会返回失败结果(例如,文件不存在时返回 Effect.fail)或缺陷(例如,未实现的功能返回 Effect.die)。不过,你可以向 FileSystem.layerNoop 传入一个对象,为选定的方法定义自定义返回值,从而覆盖特定行为。
示例(自定义行为的文件系统模拟)
import { FileSystem } from "@effect/platform"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem
const exists = yield* fs.exists("/some/path")
console.log(exists)
const content = yield* fs.readFileString("/some/path")
console.log(content)
})
// ┌─── Layer<FileSystem.FileSystem, never, never>
// ▼
const customMock = FileSystem.layerNoop({
readFileString: () => Effect.succeed("mocked content"),
exists: (path) => Effect.succeed(path === "/some/path")
})
// 提供自定义的 FileSystem 模拟实现
Effect.runPromise(program.pipe(Effect.provide(customMock)))
/*
输出:
true
mocked content
*/
四、键值存储(KeyValueStore)
@effect/platform/KeyValueStore 模块提供了一个健壮且支持 Effect 特性的键值对管理接口。它支持异步操作,确保数据完整性和一致性,并包含内存存储、文件系统存储和模式验证存储等内置实现。
基础用法
该模块暴露唯一的服务 KeyValueStore,作为与存储系统交互的入口。
示例(访问 KeyValueStore 服务)
import { KeyValueStore } from "@effect/platform"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const kv = yield* KeyValueStore.KeyValueStore
// 使用 `kv` 执行存储相关操作
})
键值存储操作列表
KeyValueStore 接口包含以下操作方法,详细说明如下:
| 操作方法 | 描述 |
|---|---|
| get | 若指定键存在,返回其字符串格式的值 |
| getUint8Array | 若指定键存在,返回其 Uint8Array 格式的值 |
| set | 设置指定键的值 |
| remove | 删除指定键 |
| clear | 删除所有条目 |
| size | 返回条目数量 |
| modify | 若指定键存在,更新其值 |
| modifyUint8Array | 若指定键存在,更新其 Uint8Array 格式的值 |
| has | 检查键是否存在 |
| isEmpty | 检查存储是否为空 |
| forSchema | 为指定模式(schema)创建 SchemaStore(模式化存储) |
示例(键值存储的基础操作)
import {
KeyValueStore,
layerMemory
} from "@effect/platform/KeyValueStore"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const kv = yield* KeyValueStore
// 存储初始为空
console.log(yield* kv.size)
// 设置键值对
yield* kv.set("key", "value")
console.log(yield* kv.size)
// 获取值
const value = yield* kv.get("key")
console.log(value)
// 删除键
yield* kv.remove("key")
console.log(yield* kv.size)
})
// 使用内存存储实现运行程序
Effect.runPromise(program.pipe(Effect.provide(layerMemory)))
/*
输出:
0
1
{ _id: 'Option', _tag: 'Some', value: 'value' }
0
*/
内置实现
该模块包含两种 KeyValueStore 接口的内置实现,均以 “层(layer)” 的形式提供,可注入到支持 Effect 的程序中。
| 实现类型 | 描述 |
|---|---|
| 内存存储(In-Memory Store) | layerMemory 提供简单的内存键值存储,适用于轻量级场景或测试环境 |
| 文件系统存储(File System Store) | layerFileSystem 提供基于文件的存储,满足持久化存储需求 |
处理非字符串值
默认情况下,KeyValueStore 支持 string 和 Uint8Array 类型的值。若需存储对象、数字、布尔值等其他类型,可使用 forSchema 方法创建 SchemaStore(模式化存储)。
SchemaStore 利用模式(schema)验证和转换值,内部通过 JSON.stringify 序列化数据,通过 JSON.parse 反序列化数据。
示例(使用模式存储类型化对象)
import {
KeyValueStore,
layerMemory
} from "@effect/platform/KeyValueStore"
import { Effect, Schema } from "effect"
// 定义 JSON 兼容的模式
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number
})
const program = Effect.gen(function* () {
// 基于模式创建类型化存储
const kv = (yield* KeyValueStore).forSchema(Person)
// 存储类型化值
const value = { name: "Alice", age: 30 }
yield* kv.set("user1", value)
console.log(yield* kv.size)
// 获取值
console.log(yield* kv.get("user1"))
})
// 本示例使用内存存储
Effect.runPromise(program.pipe(Effect.provide(layerMemory)))
/*
输出:
1
{ _id: 'Option', _tag: 'Some', value: { name: 'Alice', age: 30 } }
*/
585

被折叠的 条评论
为什么被折叠?



