npx skills add https://github.com/paulrberg/agent-skills --skill effect-ts提供使用 Effect 库进行函数式编程的专家指导,涵盖错误处理、依赖注入、可组合性和测试模式。
在开始任何与 Effect 相关的工作之前,请验证 Effect-TS 源代码是否存在于 ~/.effect。
如果缺失,请立即停止并通知用户。 在继续之前克隆它:
git clone https://github.com/Effect-TS/effect.git ~/.effect
Effect-TS 有许多方法可以完成相同的任务。在处理 Effect 模式时,尤其是在处理中等至高度复杂任务时,应主动使用 Task 工具生成研究代理来研究最佳实践。
代码库模式优先 — 在实现之前,先检查当前项目中的类似模式。如果代码库中存在 Effect 模式,请遵循它们以确保一致性。如果不存在模式,则跳过此步骤。
Effect 源代码 — 对于复杂的类型错误、不明确的行为或实现细节,请检查位于 ~/.effect/packages/effect/src/ 的 Effect 源代码。这里包含核心的 Effect 逻辑和模块。
高优先级(始终研究):
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
中等优先级(如果复杂则研究):
在使用 Effect 的项目中工作时,在实现新代码之前检查现有模式:
'effect' 导入的文件以了解现有用法如果代码库中不存在 Effect 模式,则继续使用 Effect 源代码和示例中的规范模式。不要因为缺少代码库模式而受阻。
编写 Effect 代码时应用以下核心原则:
Effect.fail、Effect.catchTag、Effect.catchAll 进行错误控制流./references/critical-rules.mdContext.Tag 定义服务Layer.merge、Layer.provide 组合层Effect.provide 注入依赖项Effect.succeed、Effect.fail、Effect.tryPromise、Effect.tryEffect.flatMap、Effect.map、Effect.tap 链接操作Effect.gen 编写可读的顺序代码Effect.fn() 以获得自动遥测和更好的堆栈跟踪在编写任何 Effect 代码之前,请阅读并内化 ./references/critical-rules.md。关键指南:
return yield* 模式(使终止显式化)常见问题模式的快速链接:
unsafeMake is not a function → 快速参考提供解决方案时,请解释所使用的 Effect-TS 概念以及为什么它们适用于特定用例。如果遇到文档中未涵盖的模式,建议改进,同时保持与现有代码库模式的一致性(当它们存在时)。
Effect.succeed(value) // 包装成功值
Effect.fail(error) // 创建失败的 effect
Effect.tryPromise(fn) // 包装返回 promise 的函数
Effect.try(fn) // 包装同步抛出函数
Effect.sync(fn) // 包装同步非抛出函数
Effect.flatMap(effect, fn) // 链接 effect
Effect.map(effect, fn) // 转换成功值
Effect.tap(effect, fn) // 副作用,不改变值
Effect.all([...effects]) // 运行 effect(可配置并发性)
Effect.forEach(items, fn) // 对项目进行映射并应用 effect
// 收集所有错误(不仅仅是第一个)
Effect.all([e1, e2, e3], { mode: "validate" }) // 返回所有失败
// 部分成功处理
Effect.partition([e1, e2, e3]) // 返回 [失败, 成功]
// 使用 Data.TaggedError 定义类型化错误(推荐)
class UserNotFoundError extends Data.TaggedError("UserNotFoundError")<{
userId: string
}> {}
// 直接 yield 错误(不需要 Effect.fail 包装器)
Effect.gen(function* () {
if (!user) {
return yield* new UserNotFoundError({ userId })
}
})
Effect.catchTag(effect, tag, fn) // 处理特定错误标签
Effect.catchAll(effect, fn) // 处理所有错误
Effect.result(effect) // 转换为 Exit 值
Effect.orElse(effect, alt) // 备用 effect
对错误进行分类以便适当处理:
| 类别 | 示例 | 处理方式 |
|---|---|---|
| 预期拒绝 | 用户取消、拒绝 | 优雅退出,不重试 |
| 领域错误 | 验证、业务规则 | 显示给用户,不重试 |
| 缺陷 | 错误、断言 | 记录 + 告警,调查 |
| 中断 | Fiber 取消、超时 | 清理,可能重试 |
| 未知/外部 | 抛出的异常 | 在边界处规范化 |
// 模式:在边界处规范化未知错误
const safeBoundary = Effect.catchAllDefect(effect, (defect) =>
Effect.fail(new UnknownError({ cause: defect }))
)
// 模式:单独捕获用户发起的取消
Effect.catchTag(effect, "UserCancelledError", () => Effect.succeed(null))
// 模式:以不同于失败的方式处理中断
Effect.onInterrupt(effect, () => Effect.log("操作已取消"))
用于标记联合类型和复杂条件分支的默认工具。
import { Match } from "effect"
// 对标记错误进行类型安全的穷举匹配
const handleError = Match.type<AppError>().pipe(
Match.tag("UserCancelledError", () => null), // 预期拒绝
Match.tag("ValidationError", (e) => e.message), // 领域错误
Match.tag("NetworkError", () => "连接失败"), // 可重试
Match.exhaustive // 如果缺少情况,编译错误
)
// 替换嵌套的 catchTag 链
// 之前:effect.pipe(catchTag("A", ...), catchTag("B", ...), catchTag("C", ...))
// 之后:
Effect.catchAll(effect, (error) =>
Match.value(error).pipe(
Match.tag("A", handleA),
Match.tag("B", handleB),
Match.tag("C", handleC),
Match.exhaustive
)
)
// 对值进行匹配(比 if/else 更清晰)
const describe = Match.value(status).pipe(
Match.when("pending", () => "加载中..."),
Match.when("success", () => "完成!"),
Match.orElse(() => "未知")
)
// 模式 1:Context.Tag(实现通过 Layer 单独提供)
class MyService extends Context.Tag("MyService")<MyService, { ... }>() {}
const MyServiceLive = Layer.succeed(MyService, { ... })
Effect.provide(effect, MyServiceLive)
// 模式 2:Effect.Service(捆绑了默认实现)
class UserRepo extends Effect.Service<UserRepo>()("UserRepo", {
effect: Effect.gen(function* () {
const db = yield* Database
return { findAll: db.query("SELECT * FROM users") }
}),
dependencies: [Database.Default], // 可选的服务依赖项
accessors: true // 自动生成方法访问器
}) {}
Effect.provide(effect, UserRepo.Default) // .Default 层自动生成
// 当依赖项单独提供时,使用 UserRepo.DefaultWithoutDependencies
// 带参数的 Effect.Service(3.16.0+)
class ConfiguredApi extends Effect.Service<ConfiguredApi>()("ConfiguredApi", {
effect: (config: { baseUrl: string }) =>
Effect.succeed({ fetch: (path: string) => `${config.baseUrl}/${path}` })
}) {}
// 模式 3:Context.Reference(可默认化的标签 - 3.11.0+)
class SpecialNumber extends Context.Reference<SpecialNumber>()(
"SpecialNumber",
{ defaultValue: () => 2048 }
) {}
// 如果默认值足够,则不需要 Layer
// 模式 4:Context.ReadonlyTag(协变 - 3.18.0+)
// 用于消费服务但不修改类型的函数
function effectHandler<I, A, E, R>(service: Context.ReadonlyTag<I, Effect.Effect<A, E, R>>) {
// 处理程序可以在协变位置使用服务
}
Effect.gen(function* () {
const a = yield* effectA;
const b = yield* effectB;
if (error) {
return yield* Effect.fail(new MyError());
}
return result;
});
// Effect.fn - 自动跟踪和遥测(命名函数推荐使用)
const fetchUser = Effect.fn("fetchUser")(function* (id: string) {
const db = yield* Database
return yield* db.query(id)
})
// 创建跨度,捕获调用点,提供更好的堆栈跟踪
Effect.acquireUseRelease(acquire, use, release) // Bracket 模式
Effect.scoped(effect) // 将生命周期限定在 effect 内
Effect.addFinalizer(cleanup) // 注册清理操作
Effect 在任何需要 DurationInput 的地方都接受人类可读的持续时间字符串:
// 字符串语法(推荐)- 单数或复数形式均可
Duration.toMillis("5 minutes") // 300000
Duration.toMillis("1 minute") // 60000
Duration.toMillis("30 seconds") // 30000
Duration.toMillis("100 millis") // 100
// 详细语法(避免使用)
Duration.toMillis(Duration.minutes(5)) // 相同结果,更冗长
// 常见单位:millis, seconds, minutes, hours, days, weeks
// 还有:nanos, micros
Effect.retry(effect, Schedule.exponential("100 millis")) // 使用退避重试
Effect.repeat(effect, Schedule.fixed("1 second")) // 按计划重复
Schedule.compose(s1, s2) // 组合调度器
Ref.make(initialValue) // 可变引用
Ref.get(ref) // 读取值
Ref.set(ref, value) // 写入值
Deferred.make<E, A>() // 一次性异步值
// 警告:切勿使用 unsafeMake - 它可能不存在于你的 Effect 版本中。
// 如果看到 "unsafeMake is not a function",请使用下面的安全 API。
SubscriptionRef.make(initial) // 创建响应式引用(安全)
SubscriptionRef.get(ref) // 读取当前值
SubscriptionRef.set(ref, value) // 更新值(通知订阅者)
SubscriptionRef.changes(ref) // 值变化的流
// React 集成(effect-atom 模式)
const ref = yield* SubscriptionRef.make<User | null>(null)
// Hook 读取:useSubscriptionRef(ref) — 返回当前值或 null
// 在组件中显式处理 null
Effect.fork(effect) // 在后台 fiber 中运行
Fiber.join(fiber) // 等待 fiber 结果
Effect.race(effect1, effect2) // 先完成者胜出
Effect.all([...effects], { concurrency: "unbounded" })
import { Config, ConfigProvider, Effect, Layer, Redacted } from "effect"
// 基本配置值
const port = Config.number("PORT") // 必需的数字
const host = Config.string("HOST").pipe( // 带默认值的可选值
Config.withDefault("localhost")
)
// 敏感值(在日志中掩码)
const apiKey = Config.redacted("API_KEY") // 返回 Redacted<string>
const secret = Redacted.value(yield* apiKey) // 需要时解包
// 带前缀的嵌套配置
const dbConfig = Config.all({
host: Config.string("HOST"),
port: Config.number("PORT"),
name: Config.string("NAME"),
}).pipe(Config.nested("DATABASE")) // DATABASE_HOST, DATABASE_PORT 等
// 在 effect 中使用配置
const program = Effect.gen(function* () {
const p = yield* Config.number("PORT")
const key = yield* Config.redacted("API_KEY")
return { port: p, apiKey: Redacted.value(key) }
})
// 自定义配置提供程序(例如,从对象而不是环境变量)
const customProvider = ConfigProvider.fromMap(
new Map([["PORT", "3000"], ["API_KEY", "secret"]])
)
const withCustomConfig = Effect.provide(
program,
Layer.setConfigProvider(customProvider)
)
// 配置验证和转换
const validPort = Config.number("PORT").pipe(
Config.validate({
message: "端口必须在 1 到 65535 之间",
validation: (n) => n >= 1 && n <= 65535,
})
)
import { Array as Arr, Order } from "effect"
// 使用内置排序进行排序(接受任何 Iterable)
Arr.sort([3, 1, 2], Order.number) // [1, 2, 3]
Arr.sort(["b", "a", "c"], Order.string) // ["a", "b", "c"]
Arr.sort(new Set([3n, 1n, 2n]), Order.bigint) // [1n, 2n, 3n]
// 按派生值排序
Arr.sortWith(users, (u) => u.age, Order.number)
// 按多个条件排序
Arr.sortBy(
users,
Order.mapInput(Order.number, (u: User) => u.age),
Order.mapInput(Order.string, (u: User) => u.name)
)
// 内置排序:Order.string, Order.number, Order.bigint, Order.boolean, Order.Date
// 反向排序:Order.reverse(Order.number)
import { constVoid as noop } from "effect/Function"
// constVoid 返回 undefined,用作无操作回调
noop() // undefined
// 常见用例:
Effect.tap(effect, noop) // 忽略值,仅运行 effect
Promise.catch(noop) // 吞掉错误
eventEmitter.on("event", noop) // 注册空处理程序
BigDecimal.fromNumber — 请改用 BigDecimal.unsafeFromNumber(3.11.0+)Schema.annotations() — 现在会移除先前设置的标识符注解;标识符仅与模式的 ast 引用关联(3.17.10)~/.effect/packages/effect/src/ — 核心 Effect 模块和实现./references/critical-rules.md — 禁止的模式和强制约定./references/effect-atom.md — 用于 React 的 Effect-Atom 响应式状态管理./references/next-js.md — Effect + Next.js 15+ App Router 集成模式./references/option-null.md — Option 与 null 边界模式./references/streams.md — 流模式和反压陷阱./references/testing.md — Vitest 确定性测试模式每周安装数
83
仓库
GitHub 星标数
41
首次出现
2026年3月6日
安全审计
安装于
gemini-cli82
github-copilot82
amp82
codex82
kimi-cli82
opencode82
Expert guidance for functional programming with the Effect library, covering error handling, dependency injection, composability, and testing patterns.
Before starting any Effect-related work, verify the Effect-TS source code exists at ~/.effect.
If missing, stop immediately and inform the user. Clone it before proceeding:
git clone https://github.com/Effect-TS/effect.git ~/.effect
Effect-TS has many ways to accomplish the same task. Proactively research best practices using the Task tool to spawn research agents when working with Effect patterns, especially for moderate to high complexity tasks.
Codebase Patterns First — Examine similar patterns in the current project before implementing. If Effect patterns exist in the codebase, follow them for consistency. If no patterns exist, skip this step.
Effect Source Code — For complex type errors, unclear behavior, or implementation details, examine the Effect source at ~/.effect/packages/effect/src/. This contains the core Effect logic and modules.
HIGH Priority (Always Research):
MEDIUM Priority (Research if Complex):
When working in a project that uses Effect, check for existing patterns before implementing new code:
'effect' to understand existing usageIf no Effect patterns exist in the codebase , proceed using canonical patterns from the Effect source and examples. Do not block on missing codebase patterns.
Apply these core principles when writing Effect code:
Effect.fail, Effect.catchTag, Effect.catchAll for error control flow./references/critical-rules.md for forbidden patternsContext.TagLayer.merge, Layer.provideEffect.provide to inject dependenciesEffect.succeed, Effect.fail, Effect.tryPromise, Effect.tryEffect.flatMap, Effect.map, Effect.tapEffect.gen for readable sequential codeEffect.fn() for automatic telemetry and better stack tracesRead and internalize ./references/critical-rules.md before writing any Effect code. Key guidelines:
return yield* pattern for errors (makes termination explicit)Quick links to patterns that frequently cause issues:
unsafeMake is not a function → Quick ReferenceWhen providing solutions, explain the Effect-TS concepts being used and why they're appropriate for the specific use case. If encountering patterns not covered in the documentation, suggest improvements while maintaining consistency with existing codebase patterns (when they exist).
Effect.succeed(value) // Wrap success value
Effect.fail(error) // Create failed effect
Effect.tryPromise(fn) // Wrap promise-returning function
Effect.try(fn) // Wrap synchronous throwing function
Effect.sync(fn) // Wrap synchronous non-throwing function
Effect.flatMap(effect, fn) // Chain effects
Effect.map(effect, fn) // Transform success value
Effect.tap(effect, fn) // Side effect without changing value
Effect.all([...effects]) // Run effects (concurrency configurable)
Effect.forEach(items, fn) // Map over items with effects
// Collect ALL errors (not just first)
Effect.all([e1, e2, e3], { mode: "validate" }) // Returns all failures
// Partial success handling
Effect.partition([e1, e2, e3]) // Returns [failures, successes]
// Define typed errors with Data.TaggedError (preferred)
class UserNotFoundError extends Data.TaggedError("UserNotFoundError")<{
userId: string
}> {}
// Direct yield of errors (no Effect.fail wrapper needed)
Effect.gen(function* () {
if (!user) {
return yield* new UserNotFoundError({ userId })
}
})
Effect.catchTag(effect, tag, fn) // Handle specific error tag
Effect.catchAll(effect, fn) // Handle all errors
Effect.result(effect) // Convert to Exit value
Effect.orElse(effect, alt) // Fallback effect
Categorize errors for appropriate handling:
| Category | Examples | Handling |
|---|---|---|
| Expected Rejections | User cancel, deny | Graceful exit, no retry |
| Domain Errors | Validation, business rules | Show to user, don't retry |
| Defects | Bugs, assertions | Log + alert, investigate |
| Interruptions | Fiber cancel, timeout | Cleanup, may retry |
| Unknown/Foreign | Thrown exceptions | Normalize at boundary |
// Pattern: Normalize unknown errors at boundary
const safeBoundary = Effect.catchAllDefect(effect, (defect) =>
Effect.fail(new UnknownError({ cause: defect }))
)
// Pattern: Catch user-initiated cancellations separately
Effect.catchTag(effect, "UserCancelledError", () => Effect.succeed(null))
// Pattern: Handle interruptions differently from failures
Effect.onInterrupt(effect, () => Effect.log("Operation cancelled"))
Default branching tool for tagged unions and complex conditionals.
import { Match } from "effect"
// Type-safe exhaustive matching on tagged errors
const handleError = Match.type<AppError>().pipe(
Match.tag("UserCancelledError", () => null), // Expected rejection
Match.tag("ValidationError", (e) => e.message), // Domain error
Match.tag("NetworkError", () => "Connection failed"), // Retryable
Match.exhaustive // Compile error if case missing
)
// Replace nested catchTag chains
// BEFORE: effect.pipe(catchTag("A", ...), catchTag("B", ...), catchTag("C", ...))
// AFTER:
Effect.catchAll(effect, (error) =>
Match.value(error).pipe(
Match.tag("A", handleA),
Match.tag("B", handleB),
Match.tag("C", handleC),
Match.exhaustive
)
)
// Match on values (cleaner than if/else)
const describe = Match.value(status).pipe(
Match.when("pending", () => "Loading..."),
Match.when("success", () => "Done!"),
Match.orElse(() => "Unknown")
)
// Pattern 1: Context.Tag (implementation provided separately via Layer)
class MyService extends Context.Tag("MyService")<MyService, { ... }>() {}
const MyServiceLive = Layer.succeed(MyService, { ... })
Effect.provide(effect, MyServiceLive)
// Pattern 2: Effect.Service (default implementation bundled)
class UserRepo extends Effect.Service<UserRepo>()("UserRepo", {
effect: Effect.gen(function* () {
const db = yield* Database
return { findAll: db.query("SELECT * FROM users") }
}),
dependencies: [Database.Default], // Optional service dependencies
accessors: true // Auto-generate method accessors
}) {}
Effect.provide(effect, UserRepo.Default) // .Default layer auto-generated
// Use UserRepo.DefaultWithoutDependencies when deps provided separately
// Effect.Service with parameters (3.16.0+)
class ConfiguredApi extends Effect.Service<ConfiguredApi>()("ConfiguredApi", {
effect: (config: { baseUrl: string }) =>
Effect.succeed({ fetch: (path: string) => `${config.baseUrl}/${path}` })
}) {}
// Pattern 3: Context.Reference (defaultable tags - 3.11.0+)
class SpecialNumber extends Context.Reference<SpecialNumber>()(
"SpecialNumber",
{ defaultValue: () => 2048 }
) {}
// No Layer required if default value suffices
// Pattern 4: Context.ReadonlyTag (covariant - 3.18.0+)
// Use for functions that consume services without modifying the type
function effectHandler<I, A, E, R>(service: Context.ReadonlyTag<I, Effect.Effect<A, E, R>>) {
// Handler can use service in a covariant position
}
Effect.gen(function* () {
const a = yield* effectA;
const b = yield* effectB;
if (error) {
return yield* Effect.fail(new MyError());
}
return result;
});
// Effect.fn - automatic tracing and telemetry (preferred for named functions)
const fetchUser = Effect.fn("fetchUser")(function* (id: string) {
const db = yield* Database
return yield* db.query(id)
})
// Creates spans, captures call sites, provides better stack traces
Effect.acquireUseRelease(acquire, use, release) // Bracket pattern
Effect.scoped(effect) // Scope lifetime to effect
Effect.addFinalizer(cleanup) // Register cleanup action
Effect accepts human-readable duration strings anywhere a DurationInput is expected:
// String syntax (preferred) - singular or plural forms work
Duration.toMillis("5 minutes") // 300000
Duration.toMillis("1 minute") // 60000
Duration.toMillis("30 seconds") // 30000
Duration.toMillis("100 millis") // 100
// Verbose syntax (avoid)
Duration.toMillis(Duration.minutes(5)) // Same result, more verbose
// Common units: millis, seconds, minutes, hours, days, weeks
// Also: nanos, micros
Effect.retry(effect, Schedule.exponential("100 millis")) // Retry with backoff
Effect.repeat(effect, Schedule.fixed("1 second")) // Repeat on schedule
Schedule.compose(s1, s2) // Combine schedules
Ref.make(initialValue) // Mutable reference
Ref.get(ref) // Read value
Ref.set(ref, value) // Write value
Deferred.make<E, A>() // One-time async value
// WARNING: Never use unsafeMake - it may not exist in your Effect version.
// If you see "unsafeMake is not a function", use the safe API below.
SubscriptionRef.make(initial) // Create reactive reference (safe)
SubscriptionRef.get(ref) // Read current value
SubscriptionRef.set(ref, value) // Update value (notifies subscribers)
SubscriptionRef.changes(ref) // Stream of value changes
// React integration (effect-atom pattern)
const ref = yield* SubscriptionRef.make<User | null>(null)
// Hook reads: useSubscriptionRef(ref) — returns current value or null
// Handle null explicitly in components
Effect.fork(effect) // Run in background fiber
Fiber.join(fiber) // Wait for fiber result
Effect.race(effect1, effect2) // First to complete wins
Effect.all([...effects], { concurrency: "unbounded" })
import { Config, ConfigProvider, Effect, Layer, Redacted } from "effect"
// Basic config values
const port = Config.number("PORT") // Required number
const host = Config.string("HOST").pipe( // Optional with default
Config.withDefault("localhost")
)
// Sensitive values (masked in logs)
const apiKey = Config.redacted("API_KEY") // Returns Redacted<string>
const secret = Redacted.value(yield* apiKey) // Unwrap when needed
// Nested configuration with prefix
const dbConfig = Config.all({
host: Config.string("HOST"),
port: Config.number("PORT"),
name: Config.string("NAME"),
}).pipe(Config.nested("DATABASE")) // DATABASE_HOST, DATABASE_PORT, etc.
// Using config in effects
const program = Effect.gen(function* () {
const p = yield* Config.number("PORT")
const key = yield* Config.redacted("API_KEY")
return { port: p, apiKey: Redacted.value(key) }
})
// Custom config provider (e.g., from object instead of env)
const customProvider = ConfigProvider.fromMap(
new Map([["PORT", "3000"], ["API_KEY", "secret"]])
)
const withCustomConfig = Effect.provide(
program,
Layer.setConfigProvider(customProvider)
)
// Config validation and transformation
const validPort = Config.number("PORT").pipe(
Config.validate({
message: "Port must be between 1 and 65535",
validation: (n) => n >= 1 && n <= 65535,
})
)
import { Array as Arr, Order } from "effect"
// Sorting with built-in orderings (accepts any Iterable)
Arr.sort([3, 1, 2], Order.number) // [1, 2, 3]
Arr.sort(["b", "a", "c"], Order.string) // ["a", "b", "c"]
Arr.sort(new Set([3n, 1n, 2n]), Order.bigint) // [1n, 2n, 3n]
// Sort by derived value
Arr.sortWith(users, (u) => u.age, Order.number)
// Sort by multiple criteria
Arr.sortBy(
users,
Order.mapInput(Order.number, (u: User) => u.age),
Order.mapInput(Order.string, (u: User) => u.name)
)
// Built-in orderings: Order.string, Order.number, Order.bigint, Order.boolean, Order.Date
// Reverse ordering: Order.reverse(Order.number)
import { constVoid as noop } from "effect/Function"
// constVoid returns undefined, useful as a no-operation callback
noop() // undefined
// Common use cases:
Effect.tap(effect, noop) // Ignore value, just run effect
Promise.catch(noop) // Swallow errors
eventEmitter.on("event", noop) // Register empty handler
BigDecimal.fromNumber — Use BigDecimal.unsafeFromNumber instead (3.11.0+)Schema.annotations() — Now removes previously set identifier annotations; identifiers are tied to the schema's ast reference only (3.17.10)~/.effect/packages/effect/src/ — Core Effect modules and implementation./references/critical-rules.md — Forbidden patterns and mandatory conventions./references/effect-atom.md — Effect-Atom reactive state management for React./references/next-js.md — Effect + Next.js 15+ App Router integration patterns./references/option-null.md — Option vs null boundary patterns./references/streams.md — Stream patterns and backpressure gotchas./references/testing.md — Vitest deterministic testing patternsWeekly Installs
83
Repository
GitHub Stars
41
First Seen
Mar 6, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
gemini-cli82
github-copilot82
amp82
codex82
kimi-cli82
opencode82
Tailwind CSS v4 + shadcn/ui 生产级技术栈配置指南与最佳实践
2,600 周安装