t3code 的 Effect 库开发指南:从 Effect.gen、Schema 到服务与可观测性的完整实践
2026/9/15 12:36:27 网站建设 项目流程

t3code 的 Effect 库开发指南:从 Effect.gen、Schema 到服务与可观测性的完整实践

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

导读

本文以本仓库.repos/effect-smol/LLMS.md中收录的 Effect 官方开发文档为骨架,系统讲解在 t3code(一个以 TypeScript 为主、横跨桌面端、服务端与移动端的 monorepo)中如何规范地编写 Effect 代码:包括Effect.gen/Effect.fn的写法、Schema领域建模、Context.Service服务与Layer组合、错误处理、资源管理、流处理、可观测性与测试等全链路实践。读完本文,你将掌握 Effect 推荐的核心编码范式,并能在 t3code 的packages/sharedapps/desktop等目录中看到这些范式在生产代码中的真实落地。

说明:.repos/effect-smol是 t3code 仓库内置的一份 Effect 源码与文档快照。官方建议在开发中直接使用这份文档与该快照中的 Effect 源码,避免引用环境中其他可能过时或错误的 Effect 资料副本(见 LLMS.md)。文档中的示例均含教学注释,实际编码时无需保留这些注释。


一、文档背景:LLMS.md 从何而来

.repos/effect-smol/LLMS.md并非手写文档,而是由.repos/effect-smol/ai-docs/src目录下的示例源码与各小节index.md自动生成的一份"面向 LLM/Agent 的库文档"(详见 ai-docs/README.md):

  • 每个主题一节,正文来自各目录下的index.md
  • 完整示例以.ts文件放在同目录,文件名以数字前缀控制顺序(如10_20_);
  • 通过pnpm ai-docgen一次性重新生成,pnpm ai-docgen:watch进入监听模式;
  • 示例要求"必须代表真实世界用法与最佳实践",优先采用 service 风格的代码组织。

因此在阅读本指南时,你可以随时打开.repos/effect-smol/ai-docs/src/下对应的.ts示例文件对照学习。


二、编写 Effect 代码:Effect.gen 与 Effect.fn 双范式

官方文档给出的核心写作原则是:

优先使用Effect.genEffect.fn("name")编写 Effect 代码,再用组合子(combinator)附加额外行为。这种风格比单纯堆叠组合子更易读、更易维护。

2.1 使用 Effect.gen 编写命令式代码

Effect.gen让你以类似async/await的命令式风格编写 Effect 代码,用yield*取出一个 effect 的结果:

import { Effect, Schema } from "effect" Effect.gen(function*() { yield* Effect.log("Starting the file processing...") yield* Effect.log("Reading file...") // 抛出错误时务必显式 return,以确保 TypeScript 理解函数不会继续执行。 return yield* new FileProcessingError({ message: "Failed to read the file" }) }).pipe( // 用 .pipe 附加额外功能 Effect.catch((error) => Effect.logError(`An error occurred: ${error}`)), Effect.withSpan("fileProcessing", { attributes: { method: "Effect.gen" } }) ) // 使用 Schema.TaggedError 定义自定义错误 export class FileProcessingError extends Schema.TaggedError<FileProcessingError>()("FileProcessingError", { message: Schema.String }) {}

关键点:

  • yield*一个 effect 即可"解包"其结果,失败会沿错误通道传播;
  • 抛错分支必须return yield* new ...Error(...),这既构造了错误,又让 TypeScript 的收窄逻辑生效(后续代码不可达);
  • 额外行为(错误捕获、追踪 span、日志标注)统一通过.pipe(...)挂接。

对应源码示例见 01_effect-gen.ts。

2.2 使用 Effect.fn 定义返回 Effect 的函数

凡是返回 Effect 的函数,都用Effect.fn而不是"返回一个 Effect.gen 的函数",这是文档明确强调的纪律:

import { Effect, Schema } from "effect" // 传给 Effect.fn 的字符串会改善堆栈信息,并自动附加一个追踪 span // (内部使用 Effect.withSpan)。 // 该名字应与函数名保持一致。 export const effectFunction = Effect.fn("effectFunction")( // 用 Effect.fn.Return 指定返回类型,它接受与 Effect.Effect 相同的类型参数。 function*(n: number): Effect.fn.Return<string, SomeError> { yield* Effect.logInfo("Received number:", n) // 抛出错误时务必显式 return,以确保 TypeScript 理解函数不会继续执行。 return yield* new SomeError({ message: "Failed to read the file" }) }, // 通过额外参数附加功能;**不要**对 Effect.fn 使用 .pipe。 Effect.catch((error) => Effect.logError(`An error occurred: ${error}`)), Effect.annotateLogs({ method: "effectFunction" }) ) // 使用 Schema.TaggedError 定义自定义错误 export class SomeError extends Schema.TaggedError<SomeError>()("SomeError", { message: Schema.String }) {}

要点:

  • 字符串名称同时改善堆栈可读性并自动挂载追踪 span;
  • 附加行为作为额外参数传入,而不是.pipe
  • Effect.fn.Return<成功值, 错误, 依赖>接受与Effect.Effect相同的类型参数(成功、错误、环境依赖三通道)。

对应源码示例见 02_effect-fn.ts。

2.3 从常见数据源创建 Effect

在实际代码里,Effect 往往不是凭空出现的,而是从各种外部来源"包装"而来。文档归纳了如下常见构造器(完整示例见 10_creating-effects.ts):

构造器适用场景示例
Effect.succeed包装内存中已有的值Effect.succeed({ env: "prod", retries: 3 })
Effect.sync包装不会抛出异常的同步副作用Effect.sync(() => Date.now())
Effect.try包装可能抛异常的同步代码Effect.try({ try: () => JSON.parse(input), catch: (cause) => new InvalidPayload({ input, cause }) })
Effect.tryPromise包装可能 reject/throw 的 Promise APIEffect.tryPromise({ async try() {...}, catch: (cause) => new UserLookupError({ userId, cause }) })
Effect.fromNullishOr把可空值转为类型化的 effectEffect.fromNullishOr(headers.get("x-workspace-id")).pipe(Effect.mapError(() => new MissingWorkspaceId()))
Effect.callback包装回调风格异步 APIEffect.callback<number>((resume) => { ... resume(Effect.succeed(200)) })

注意Effect.callback可以返回一个 finalizer,使中断(interruption)能取消底层的回调源(例如清除setTimeout)。

2.4 t3code 生产代码中的印证

这些范式不是纸面规范,而是 t3code 的真实编码风格:

  • shell.ts 中resolveCommandPathForPlatform直接使用Effect.fn("shell.resolveCommandPathForPlatform")(...),返回类型标注为Effect.fn.Return<string, CommandResolutionError, FileSystem.FileSystem | Path.Path>,第三个类型参数清晰声明了所需的环境依赖;
  • httpReadiness.ts 中waitForHttpReady同样以Effect.fn("shared.httpReadiness.waitForHttpReady")定义,内部通过HttpClient.HttpClientRefSchedule完成对 HTTP 服务就绪状态的轮询探测;
  • nodeSqliteClient.ts 中makeWithDatabase也遵循Effect.fn命名风格,并把依赖声明为Scope.Scope | Reactivity.Reactivity

三、用 Schema 定义领域模型与校验

文档规定:Effect 中所有校验与领域建模都使用Schema,避免使用谓词(predicate)或手工解析来处理不可信数据。详细指南可参阅快照内 SCHEMA.md(LLMS.md 中给出了官方链接并提醒"文档很大,请分段阅读")。

基础用法(示例见 10_schema-basics.ts):

import { Effect, Schema } from "effect" // Schema.Class 同时定义运行时校验器与 TypeScript 类, // 适合"只能从合法数据构造"的领域模型。 export class User extends Schema.Class<User>("path/to/module/User")({ id: Schema.Int, name: Schema.NonEmptyString, email: Schema.String, role: Schema.Literals(["admin", "member"]) }) {} // 校验后的类型就是 User 类本身 export type UserType = typeof User["Type"] // 外部表示(编码后)的类型 export type UserEncoded = typeof User["Encoded"] // 在应用边界复用解析器,而不是每次请求重建。 // 在 Effect 代码内部请使用返回 Effect 的 API,让校验错误保留在错误通道中。 export const decodeUser = Schema.decodeUnknownEffect(User) export const encodeUser = Schema.encodeEffect(User) export class InvalidUserPayload extends Schema.TaggedError<InvalidUserPayload>()("InvalidUserPayload", { message: Schema.String }) {} export const parseUserPayload = Effect.fn("parseUserPayload")((input: unknown) => decodeUser(input).pipe( Effect.mapError((error) => new InvalidUserPayload({ message: error.message })) ) )

要点:

  • Schema.Class<ClassName>(...)生成一个既是类型又是运行时守卫的类;
  • Schema.IntSchema.NonEmptyStringSchema.Literals([...])等内建 schema 直接表达约束;
  • decodeUnknownEffect/encodeEffect在应用边界(HTTP 请求、外部消息等)完成"未知输入 → 强类型值"和"强类型值 → 外部表示"的双向转换;
  • 自定义错误统一用Schema.TaggedError声明,天然携带结构化字段并可被catchTag按 tag 捕获。

在 t3code 中,这一模式被大量复用:例如 nodeSqliteClient.ts 用Schema.TaggedError<UnsupportedNodeSqliteOperationError>()("UnsupportedNodeSqliteOperationError", {})定义"Node SQLite 不支持 executeStream"的领域错误;Net.ts 则用Data.TaggedError("NetError")声明带message与可选cause的错误类型。


四、编写 Effect Services:模块化、可测试、可维护

文档指出:Service 是组织 Effect 代码最常见的方式,优先用服务封装行为,以保证代码模块化、可测试、可维护。

4.1 默认方式:Context.Service

// file: src/db/Database.ts import { Context, Effect, Layer, Schema } from "effect" // 第一个类型参数传服务类名,第二个传服务接口。 export class Database extends Context.Service<Database, { query(sql: string): Effect.Effect<Array<unknown>, DatabaseError> }>()( // 服务标识字符串,应包含包名与服务文件所在子目录路径。 "myapp/db/Database" ) { // 为服务挂一个静态 Layer,用于提供服务实现。 static readonly layer = Layer.effect( Database, Effect.gen(function*() { // 用 Effect.fn 定义服务方法 const query = Effect.fn("Database.query")(function*(sql: string) { yield* Effect.log("Executing SQL query:", sql) return [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }] }) // 用 Database.of 返回实现了服务接口的实例 return Database.of({ query }) }) ) } export class DatabaseError extends Schema.TaggedError<DatabaseError>()("DatabaseError", { cause: Schema.Defect() }) {} // 需要访问服务类型时,使用 Database["Service"] export type DatabaseService = Database["Service"]

要点:

  • Context.Service<ClassName, Interface>()("包名/子目录/服务名")是推荐的服务声明方式,字符串标识具备全局唯一性与可追踪性;
  • 静态layer字段让服务自带默认实现,消费者无需关心装配细节;
  • 方法用Effect.fn定义,具备良好堆栈与 span;
  • Database["Service"]可随时取出服务接口类型。

t3code 中的实例:Net.ts 声明class NetService extends Context.Service<NetService, NetServiceShape>()("@t3tools/shared/Net/NetService"),服务形状NetServiceShape定义了端口可用性探测、回环端口预留、可用端口查找等一组方法。

4.2 配置项与特性开关:Context.Reference

对于带默认值的配置值、特性开关等服务,使用Context.Reference

import { Context } from "effect" export const FeatureFlag = Context.Reference<boolean>("myapp/FeatureFlag", { defaultValue: () => false })

t3code 中 hostProcess.ts 连续定义了HostProcessPlatformHostProcessArchitectureHostProcessHostname三个Context.Reference,分别以process.platformprocess.archNodeOS.hostname()作为默认值——这正是"可测试的当前环境"这一设计目标(测试中可覆盖这些 Reference 以模拟任意平台)。

4.3 Layer 的组合与动态装配

  • 组合:先构建聚焦的 service layer,再用Layer.provide/Layer.provideMerge组合,按需决定对外暴露哪些服务(见 20_layer-composition.ts);
  • 动态装配:用Layer.unwrap从 Effect / Config 动态构建 layer(见 20_layer-unwrap.ts)。

t3code 的 nodeSqliteClient.ts 展示了Layer.provide的组合用法:Layer.effect(Client.SqlClient, make(config)).pipe(Layer.provide(Reactivity.layer)),把 SQLite 客户端层挂到 Reactivity 依赖之上。


五、错误处理:类型化错误 + 按 tag 捕获

文档给出了错误处理的完整范式(示例见 01_error-handling.ts):

import { Effect, Schema } from "effect" // 用 Schema.TaggedError 定义自定义错误 export class ParseError extends Schema.TaggedError<ParseError>()("ParseError", { input: Schema.String, message: Schema.String }) {} export class ReservedPortError extends Schema.TaggedError<ReservedPortError>()("ReservedPortError", { port: Schema.Int }) {} declare const loadPort: (input: string) => Effect.Effect<number, ParseError | ReservedPortError> export const recovered = loadPort("80").pipe( // 用 Effect.catchTag 一次捕获多个错误,返回默认端口号 Effect.catchTag(["ParseError", "ReservedPortError"], (_) => Effect.succeed(3000)) ) export const withFinalFallback = loadPort("invalid").pipe( // 先捕获特定错误 Effect.catchTag("ReservedPortError", (_) => Effect.succeed(3000)), // 再兜底捕获所有错误 Effect.catch((_) => Effect.succeed(3000)) )

文档还提供了两个进阶示例:

  • 10_catch-tags.ts:用Effect.catchTags在一个位置集中处理多个 tagged 错误;
  • 20_reason-errors.ts:为错误定义一个带 tag 的reason字段,然后用Effect.catchReasonEffect.catchReasons捕获,或用Effect.unwrapReason把 reason 解包进错误通道——适合需要"按失败原因分类处理"的业务。

六、资源管理与 Scope:acquireRelease 与后台任务

Effect 用Scope与 finalizer 安全管理资源生命周期:

  • 10_acquire-release.ts:用Effect.acquireRelease管理资源生命周期——Layer 构建时创建资源(如 SMTP transporter),Layer 拆除时自动释放(transporter.close()),保证资源始终被正确清理。示例还展示了Config.string/Config.redacted读取配置,以及Redacted.value安全取用密钥;
  • 20_layer-side-effects.ts:用Layer.effectDiscard封装没有服务接口的后台任务(如常驻 worker);
  • 30_layer-map.ts:用LayerMap.Service按标识(如租户 ID)动态构建与管理多个资源实例。

t3code 中 preview/Manager.ts 大量使用Effect.acquireRelease管理浏览器实例等长生命周期资源;nodeSqliteClient.ts 的依赖签名Scope.Scope | Reactivity.Reactivity也印证了"资源生命周期绑定 Scope"的实践。


七、运行 Effect 程序:runMain 与 Layer.launch

文档给出了两种进程入口模式:

  • 10_run-main.ts:NodeRuntime.runMain(program)作为进程入口,自动安装 SIGINT/SIGTERM 处理器并在退出时优雅中断 fiber;BunRuntime.runMain提供完全相同的 API 形态。可用disableErrorReporting: true关闭自动错误上报(若你的应用已自行集中处理错误);
  • 20_layer-launch.ts:当整个应用都由 layer 表示(HTTP 服务器 + 后台 worker)时,用Layer.launch把 layer 变成长期运行的Effect<never>,示例中配合HttpRouter.serveNodeHttpServer.layer起一个带/health/healthz健康检查的 HTTP 服务。
// 摘要示例:Layer.launch 作为入口 export const HttpServerLive = HttpRouter.serve(HealthRoutes).pipe( Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })) ) export const main = Layer.launch(HttpServerLive) NodeRuntime.runMain(main)

八、PubSub 事件广播:一个生产者,多个消费者

当需要一个生产者向多个消费者扇出消息时,使用PubSub(示例见 10_pubsub.ts)。文档示例构建了一个订单事件总线服务:

// 关键片段 const pubsub = yield* PubSub.bounded<OrderEvent>({ capacity: 256, // 有界 + 背压;不需要背压可用 PubSub.unbounded replay: 50 // 可选:重放缓冲区,让迟到订阅者补收最近的旧事件 }) yield* Effect.addFinalizer(() => PubSub.shutdown(pubsub)) // 服务拆除时关闭 const subscribe = Stream.fromPubSub(pubsub) // 把 PubSub 变成 Stream

服务接口暴露publishpublishAll与只读的subscribe: Stream.Stream<OrderEvent>Stream.fromPubSub让每个订阅者收到订阅后发布的所有事件(配置 replay 时还能收到订阅前的最近若干条)。t3code 的 updates/DesktopUpdates.ts 同样用到了PubSub相关能力。


九、Stream:拉取式、可组合的数据流

Effect Stream 表示基于 effect 的、拉取式的、随时间产生的值序列,可建模有限或无限数据源。文档归纳了从常见数据源创建流的 API(示例见 10_creating-streams.ts):

  • Stream.fromIterable:数组等可迭代对象;
  • Stream.fromEffectSchedule:轮询 effect(适用于指标、健康检查、缓存刷新),示例用Schedule.spaced("30 seconds")配合Stream.take(3)
  • Stream.paginate:分页 API,函数返回"当前页值 + 可选下一页游标";
  • Stream.fromAsyncIterable:异步可迭代对象;
  • Stream.fromEventListener:DOM 事件;
  • Stream.callback:任意回调风格 API;
  • NodeStream.fromReadable:Node.js readable 流。

其余示例:20_consuming-streams.ts 讲解mapflatMapfiltermapEffect及各类run*消费方法;30_encoding.ts 演示用Stream.pipeThroughChannel配合NdjsonMsgpack模块对流式结构化数据编解码。


十、集成既有应用:ManagedRuntime

ManagedRuntime是 Effect 程序与非 Effect 代码之间的桥梁:从应用 Layer 构建一个runtime,然后在任意需要命令式执行的地方使用——Web handler、框架钩子、worker 队列、遗留回调 API 均可(见 10_managed-runtime.ts 的 Hono 示例:TodoRepo服务 +TodoSchema 模型 +ManagedRuntime.make,路由 handler 中runtime.runPromise(...)调用领域逻辑)。这让你在既有框架中逐步引入 Effect,同时保持领域逻辑全部收敛在 service 与 layer 中。


十一、批量请求:RequestResolver

文档展示了如何把多个请求合并为更少的外部调用(见 10_request-resolver.ts):用Request.Class定义请求类型,再用RequestResolver成批解析。这在高频查询外部 API 时能显著减少往返次数。


十二、Schedule:重试、重复与轮询的模式

Schedule定义重试、重复与轮询的重复模式(见 10_schedules.ts),可组合后用于Effect.retryEffect.repeat

t3code 中的真实用例:httpReadiness.ts 用Schedule.spaced(Duration.millis(intervalMs)).pipe(Schedule.upTo({ times: Math.ceil(timeoutMs / intervalMs) }))构造"每 100ms 探测一次、至多若干次"的轮询策略——这正是Schedule模块在就绪探测场景的典型应用;preview/Manager.ts 中也有Effect.retry({ times: 2 })等重试调用。


十三、DateTime:测试友好、类型安全的时间处理

文档建议:处理日期时间时使用DateTime模块替代DateDate.now(),当程序需要可测试的当前时间、安全解析、稳定的 ISO 格式化、时区转换、日历运算时优先使用它。

  • 10_creating-and-formatting.ts:安全解析输入日期、使用 Clock 驱动的当前时间、为 API 载荷或用户界面格式化 instants;
  • 20_time-zones.ts:给 instants 附加 IANA 时区、渲染带时区的 ISO 字符串,并提供CurrentTimeZone服务供需要"工作区/用户时区"的代码使用。

十四、可观测性:结构化日志、分布式追踪与指标

Effect 内建对结构化日志、分布式追踪和指标的支持。导出遥测数据时,文档建议:

  • 新项目使用effect/unstable/observability下轻量的 Otlp 模块;
  • 已有 OpenTelemetry 集成时使用@effect/opentelemetryNodeSdk

相关示例:10_logging.ts 配置生产环境的 logger 与日志级别过滤;20_otlp-tracing.ts 用可复用的 observability layer 配置 OTLP 追踪与日志导出。

t3code 的落地情况与文档建议完全一致:

  • observability.ts 与 relayTracing.ts 均直接导入effect/unstable/observabilityOtlpResourceOtlpTracerOtlpSerialization);
  • preview/Manager.ts 与 BrowserSession.ts 使用Effect.withSpan("PreviewManager.make")自动创建追踪 span;
  • DesktopBackendConfiguration.ts 与 preview/Manager.ts 使用Effect.annotateLogs({...})为日志附加结构化字段(如tabIdwebContentsId)。

十五、测试:@effect/vitest 与共享 Layer

  • 10_effect-tests.ts:用it.effect编写基于 Effect 的测试;
  • 20_layer-tests.ts:测试依赖其他服务的 Effect 服务,通过共享 layer 装配被测系统。

t3code 中 Net.test.ts 直接从@effect/vitest导入assert, describe, it,并用Effect.callback包装 Node 网络 API 来构造测试服务器——与文档推荐的测试范式一致。


十六、Predicate:不要重复造轮子

文档强调:永远不要自己写isRecordisString之类的辅助函数,直接使用Predicate模块。谓词可通过Predicate.andPredicate.orPredicate.notPredicate.compose组合:

import { Predicate } from "effect" const thing: unknown = { a: 1 } if (Predicate.isObject(thing)) { if (Predicate.isNumber(thing.a)) { console.log("number", thing.a) } }

t3code 中 Net.ts 用Predicate.isObject(cause) && Predicate.hasProperty(cause, "code") && Predicate.isString(cause.code)收窄NodeJS.ErrnoExceptioncode字段,正是该模块的典型组合用法。


十七、扩展主题速览:SQL / HttpClient / HttpApi / 子进程 / CLI / AI / Cluster

LLMS.md后半部分还覆盖了 Effect 生态的完整拼图,每项都配有可运行的示例文件:

  • SQL(40_sql):用effect/unstable/sql配合驱动(如@effect/sql-sqlite-node),用Model.Class为数据库与 JSON 边界派生 schema、运行迁移、写类型安全查询。t3code 的 nodeSqliteClient.ts 是这一方向在仓库内的完整实现;
  • HttpClient(50_http-client):定义一个服务,用HttpClient模块请求外部 API;
  • HttpApi(51_http-server):schema-first 的类型安全 HTTP API——运行时校验、类型化客户端、OpenAPI 文档均从一份定义生成;20_testing.ts 演示用HttpApiTest通过内存类型化客户端测试 handler,无需起 HTTP 服务或连真实数据库;
  • 子进程(60_child-process):用effect/unstable/process定义子进程并用ChildProcessSpawner运行,收集输出、组合管道、流式读取长命令输出;
  • CLI(70_cli):用effect/unstable/cli解析命令行参数、处理用户输入、管理 CLI 流程,把子命令 handler 装配成单一可执行命令;
  • AI 模块(71_ai):provider 无关的语言模型接口——LanguageModel生成纯文本、用Schema解码结构化对象、流式输出(10_language-model.ts);用 schema 定义 AI 工具并分组为 toolkit(20_tools.ts);Chat模块自动维护会话历史,适合构建 agent 或聊天助手(30_chat.ts)。示例中还展示了ExecutionPlan定义"先试便宜模型、失败回退昂贵模型"的多 provider 策略;
  • Cluster(80_cluster):把有状态服务建模为实体并在多台机器间分布,实现分布式应用。

十八、结语:把文档变成可复用的编码规范

回顾.repos/effect-smol/LLMS.md的全文,可以提炼出 t3code 项目团队在 Effect 使用上的几条铁律,它们同样适用于任何采用 Effect 的新代码:

  1. 写法统一:所有返回 Effect 的函数用Effect.fn("name")(自动获得堆栈与 span),流程用Effect.gen+yield*,附加行为用组合子;
  2. 领域建模用 Schema:拒绝手工解析与自定义谓词,Schema.Class+Schema.TaggedError是唯一正道;
  3. 行为封装进 ServiceContext.Service+ 静态Layer,配置项用Context.Reference,用Layer.provide/provideMerge/unwrap组装;
  4. 错误类型化:所有失败都是类型化、带 tag 的错误,用catchTag/catchTags/catchReason恢复;
  5. 资源交给 ScopeacquireRelease管理生命周期,后台任务用Layer.effectDisc<|begin▁of▁sentence|>rd
  6. 可观测性与测试是标配Effect.withSpan/annotateLogs从开发第一天就挂上,测试用@effect/vitestit.effect

这些规则在 t3code 的packages/sharedapps/desktop等目录中均有大量真实代码可以对照学习,建议在阅读本文后结合 LLMS.md 原文与 ai-docs/src 下的示例文件逐一验证,从而把这套文档转化为团队内可落地的编码规范。

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询