mcpField
E
effect-typescript

effect-typescript

github.com/darkmatter开发0295 次安装1 天前更新
代码仓库

关于 effect-typescript

这是一项指导 TypeScript 开发者使用 Effect 处理 I/O 和资源管理的编码技能,并针对 darkmatter 项目提供约定,明确错误、依赖、清理、测试及运行时边界。

主要功能

  1. 01Effect I/O 使用指导
  2. 02类型化错误与服务层
  3. 03资源生命周期和清理模式
  4. 04darkmatter 的 Bun 与 Alchemy 约定
  5. 05effect-orpc 使用指导

适用场景

  1. 01为 TypeScript I/O 代码引入 Effect
  2. 02审查现有 Effect 实现
  3. 03使用 effect-orpc 设计类型化 RPC 处理器
  4. 04为 darkmatter 构建 TypeScript 基础设施

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/darkmatter/skills --skill effect-typescript

安装后在 Claude Code 中输入/effect-typescript确认已加载。

附带文件effect-package-map.md

详细介绍

Effect TypeScript

使用 Effect 处理应用 I/O,让失败、依赖项和资源生命周期都明确表达。只在边界处适配一次基于 Promise 的驱动程序或 SDK;内部操作保持使用 Effect。在需要 Promise 或启动程序的调用方或运行时边界运行 Effect。纯转换仍使用普通 TypeScript。

遵循 codebase-design 中的共享可读性规则和示例。将相关查询、绑定、解码和私有辅助函数保留在其适配器中;仅在能隐藏复杂性或实现有用复用时才提取。操作应自行负责完成、错误和清理,或者明确将这些责任交给调用方。

此技能将 Effect 指南适配到 darkmatter 约定:darkmatter 项目使用 Bun 命令而非 pnpm,并优先使用 Alchemy 部署基础设施。

Bootstrap

如果 $PATH 中没有 effect-solutions,请安装:bun add -g effect-solutions@latest。API 详情优先参考仓库中固定版本的 Effect 源码或已安装的包;两者都不可用时,使用与版本匹配的检出版本。

对于新的 Darkmatter TypeScript 仓库,请使依赖项与 darkmatter/template 根目录的目录保持一致。使用 Effect 4、@effect/tsgo、 @effect/vitest、@effect/platform-bun 和 effect-solutions;在自行设计 Agent 循环契约之前,优先使用 Effect Agent API(effect/unstable/ai)。

Guidelines

  • 编写有副作用的代码时,参考 effect-solutions CLI 推荐的最佳实践。
  • 如果需要更具体的信息,参考目录中有一份本地检出的 Effect 源码。

When to use

  • 涉及有意义的 I/O,且工作适合通过显式的失败、依赖项、资源、重试、验证或测试边界来处理。
  • 代码已经使用 Effect,而你正在添加或审查 Effect 代码。
  • 你正在决定 TypeScript/Bun 功能是否应使用 Effect。
  • 任务涉及外部 API、数据库、队列、工作进程、CLI、计划任务、重试、配置、机密、结构化日志、运行时验证、资源清理或并发工作流。
  • 你需要类型化的领域错误,而不是缺乏结构的抛出异常。
  • 你需要通过服务和 Layers 切换线上/测试实现。
  • 你正在部署 TypeScript 基础设施或工作进程,并需要遵循适配 Alchemy 的约定。

When NOT to use

  • 小型一次性脚本用普通 TypeScript 就能清晰实现:读取一个文件、转换纯数据、写入一个文件,没有重试、注入依赖项或长期存在的资源。随着需求增长,在需要 I/O 的地方采用 Effect,并提取职责清晰的部分。仅凭代码行数并不足以成为拆散实现的理由;如有需要,应保留已配置的限制,并针对例外情况提供说明。
  • 纯函数、简单的数据映射器、UI 本地状态或少量胶水代码不需要 Effect 包装。
  • 项目没有 Effect 依赖,且该功能并不受益于类型化错误、Layers、资源安全、重试或可观测性。
  • 团队只需要用普通异步代码快速修复问题。不要为了顺手重构而引入 Effect。
  • 你无法说明服务、层、错误和测试的设计。请先停下来设计,而不是到处添加 Effect.runPromise 调用。

Darkmatter 约定

  • 使用 Bun 命令:bun install、bun test、bun run <script>、bunx <tool>。
  • 将上游的 pnpm 示例机械地转换为 Bun 命令。例如:如果项目使用 Bun test,将 pnpm test file.test.ts 转换为 bun test file.test.ts;如果 test 是 package 脚本,则转换为 bun run test file.test.ts。
  • 在相关场景中优先使用 Bun 运行时包,例如 Bun 入口点使用 @effect/platform-bun 和 BunRuntime.runMain。
  • 对可部署的基础设施优先使用 Alchemy。将基础设施代码放在 alchemy.run.ts 中,使用 Alchemy 创建资源,将资源绑定到 workers/services,并构建 Alchemy.Stack。如果要编写 Alchemy 代码或添加新的基础设施代码,请参考 alchemy 技能。
  • 应用数据优先使用 Postgres。对于查询较多的 TypeScript 适配器,使用 Kysely + pg;如果通过 Effect 服务和 Layers 暴露持久化能力,则使用 Effect SQL Postgres。D1/sqlite 仅用于范围有限的边缘本地状态、演示,或项目明确决定采用的场景。
  • 配置来自命名配置文件(config/<name>.json),通过 Config 读取。没有选择器标志:交互式运行会从列表中选择(Prompt.select),非交互式运行会读取 APP_CONFIG,或在列出配置名称后失败。环境变量和标志按键覆盖,并通过 ConfigProvider.orElse 链接;密钥来自同级的 .sops.json 文件,并由 effect-sops 读取。不要将程序的配置设计成标志或环境变量列表。参见 ADR-0014。

使用 effect-orpc 的类型化 RPC

当服务需要类型化 RPC 过程、生成或共享的 oRPC 客户端契约、Effect 服务注入以及类型化的 Effect 错误时,使用 effect-orpc。当路由是简单的 HTTP、webhook、静态内容、流式传输/SSE,或添加 RPC 契约只会增加繁琐流程且没有面向客户端的类型契约时,使用原生 Effect HttpRouter 或 Hono。

添加 effect-orpc 时,在 catalog 中固定 Effect 4 构建版本:

{
  "workspaces": {
    "catalog": {
      "effect-orpc": "1.0.0-effect-v4.8",
      "@orpc/server": "^1.13.0",
      "@orpc/client": "^1.13.0",
      "@orpc/contract": "^1.13.0",
      "@orpc/shared": "^1.13.0"
    }
  }
}

不要安装 effect-orpc@latest;npm 的 latest 指向 Effect 3 系列(0.5.0)。目前,effect-v4 dist-tag 指向 1.0.0-effect-v4.8,其 peer 依赖要求使用 Effect 4(effect >=4.0.0-beta.98)以及上文所列的 oRPC 包。

优先采用以下上游模式:

  • 从 eos.provide(AppLayer) 或 makeEffectORPC(runtime) 构建过程。当一个作用域 Layer 应在应用关闭时获取一次并释放时(例如 Postgres 连接池、遥测 SDK 或共享缓存),使用由调用方负责管理的 ManagedRuntime。
  • 使用 .effect(function* (...) { ... }) 编写处理程序和 Effect 中间件,并使用生成器 .use(function* ({ next }) { return yield* next(...) }),以确保服务需求、spans、取消操作和错误都留在 Effect 中。
  • 使用 ORPCTaggedError 建模 HTTP 错误,并将这些类传给 .errors(...) / eoc.errors(...);不要将相同的错误契约重复定义为无关的抛出异常和响应 schemas。
  • 如果仓库采用契约优先模式且已有 oRPC 契约,使用 implementEffect(contract, layerOrRuntime)。契约叶节点保留已声明的输入、输出和错误接口,同时添加 .effect(...) 处理程序。
  • 仅当需要 Node AsyncLocalStorage 来跨原生 oRPC 中间件或框架边界保留 FiberRefs 时,才导入 effect-orpc/node。如果请求本地的 FiberRef 状态是在 oRPC 流水线外创建、且必须能在处理程序中访问,则使用 withFiberContext(() => next())。

包布局参考

请参阅 effect-package-map.md,了解领域归属、公共包入口、适配器、工作流、测试和运行时边界。该文档描述的是职责,而不是为每个函数或 Effect 导出指定文件。

上游参考

需要当前 Effect 源码或示例时,请使用 reference/effect 或 effect-solutions CLI,而不要依赖记忆:

  • reference/effect/AGENTS.md — 上游仓库规则,包括 pnpm 验证命令、生成的 barrel、changeset 和 it.effect 约定。
  • reference/effect/packages/effect/ — 核心库源码和测试。
  • reference/effect/packages/platform-bun/ 和 reference/effect/packages/platform-node/ — 运行时/平台示例。
  • reference/effect/packages/vitest/ — 支持 Effect 的 Vitest 辅助工具。

如果全新检出时缺少子模块,请先初始化它,再使用本地参考资料:

git submodule update --init skills/effect-typescript/reference/effect

对于 darkmatter 应用开发,请将子模块视为只读,并将上游 pnpm 命令转换为仓库使用的包管理器。对于直接向上游 Effect 贡献代码,请严格遵循 reference/effect/AGENTS.md;不要在上游仓库中使用 darkmatter 的 Bun 默认设置。

JSON 编码与解码

使用 Schema.fromJsonString 解析 JSON 字符串,并通过你的 schema 一步完成验证。这样可以将 JSON.parse 与 schema 解码合并为一步,并在反向操作中将 JSON.stringify 与 schema 编码合并为一步:

import { Effect, Schema } from "effect"

const Row = Schema.Literals(["A", "B", "C", "D", "E", "F", "G", "H"])
const Column = Schema.Literals(["1", "2", "3", "4", "5", "6", "7", "8"])

class Position extends Schema.Class<Position>("Position")({
  row: Row,
  column: Column,
}) {}

class Move extends Schema.Class<Move>("Move")({
  from: Position,
  to: Position,
}) {}

// fromJsonString combines JSON.parse + schema decoding
// MoveFromJson is a schema that takes a JSON string and returns a Move
const MoveFromJson = Schema.fromJsonString(Move)

const program = Effect.gen(function* () {
  // Parse and validate JSON string in one step
  // Use MoveFromJson (not Move) to decode from JSON string
  const jsonString = '{"from":{"row":"A","column":"1"},"to":{"row":"B","column":"2"}}'
  const move = yield* Schema.decodeUnknownEffect(MoveFromJson)(jsonString)

  yield* Effect.log("Decoded move", move)

  // Encode to JSON string in one step (typed as string)
  // Use MoveFromJson (not Move) to encode to JSON string
  const json = yield* Schema.encodeEffect(MoveFromJson)(move)
  return json
})

评论

支持 Markdown