Token导航 LogoToken导航TokenDH.com
开发规范敏感数据github未标认证来源可访问许可证需确认审计通过

effect-best-practices影响最佳实践

Agent Skill

effect-best-practices 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

881

周安装

36

GitHub Stars

989

下载量

285
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:effect-best-practices(影响最佳实践)
来源仓库:https://github.com/forcedotcom/salesforcedx-vscode
仓库路径:skills/effect-best-practices
安装命令:
npx skills add https://github.com/forcedotcom/salesforcedx-vscode --skill effect-best-practices
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/forcedotcom/salesforcedx-vscode --skill effect-best-practices

简介

effect-best-practices 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态进行整理时使用。

  • 适用于监控代码变更、跟踪 Issue 进展或分析协作流程,提升开发效率。
  • 通过 GitHub API 交互,Agent 可自动提取关键信息并生成结构化报告。
  • 安装前需确认权限范围和维护状态,注意是否涉及联网、命令执行或文件读写操作。
  • 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

SKILL.md

Effect-TS Best Practices

This skill enforces opinionated, consistent patterns for Effect-TS codebases.

Effect LS diagnostics (agent usage)

Cursor's read_lints does not surface Effect Language Server diagnostics. Use the CLI:

npx effect-language-service diagnostics --file <path>
# or whole project:
npx effect-language-service diagnostics --project tsconfig.json
  • Run when editing Effect code; fix reported issues (e.g. unnecessaryFailYieldableError → yield error directly)
  • effect-language-service quickfixes shows proposed code changes

Quick Reference: Critical Rules

CategoryDODON'T
ServicesEffect.Service with accessors: trueContext.Tag for business logic
Dependenciesdependencies: [Dep.Default] in serviceManual Layer.provide at usage sites
ErrorsSchema.TaggedError with message fieldPlain classes or generic Error
Error SpecificityUserNotFoundError, SessionExpiredErrorGeneric NotFoundError, BadRequestError
Error HandlingcatchTag/catchTags; catch only when neededcatchAll; swallowing; catching "just in case"
IDsSchema.UUID.pipe(Schema.brand("@App/EntityId"))Plain string for entity IDs
FunctionsEffect.fn over Effect.gen; .gen only for shared pipesAnonymous generators; .gen for business logic
Params vs depsParams = runtime data; dependencies = yield from contextPassing Ref/PubSub/service as params
NamingFooCommand for commands, domain names for helpersFooEffect suffix (redundant; TS/Effect.fn already convey type)
LoggingEffect.log with structured dataconsole.log
ConfigConfig.* with validationprocess.env directly (except build-time vars like ESBUILD_*)
OptionsOption.match with both casesOption.getOrThrow
NullabilityOption<T> in domain typesnull/undefined
AtomsAtom.make outside componentsCreating atoms inside render
Atom StateAtom.keepAlive for global stateForgetting keepAlive for persistent state
Atom UpdatesuseAtomSet in React componentsAtom.update imperatively from React
Atom Cleanupget.addFinalizer() for side effectsMissing cleanup for event listeners
Atom ResultsResult.builder with onErrorTagIgnoring loading/error states

Service Definition Pattern

Always use Effect.Service for business logic services. This provides automatic accessors, built-in Default layer, and proper dependency declaration.

import { Effect } from 'effect';

export class UserService extends Effect.Service<UserService>()('UserService', {
  accessors: true,
  dependencies: [UserRepo.Default, CacheService.Default],
  effect: Effect.gen(function* () {
    const repo = yield* UserRepo;
    const cache = yield* CacheService;

    const findById = Effect.fn('UserService.findById')(function* (id: UserId) {
      const cached = yield* cache.get(id);
      if (Option.isSome(cached)) return cached.value;

      const user = yield* repo.findById(id);
      yield* cache.set(id, user);
      return user;
    });

    const create = Effect.fn('UserService.create')(function* (data: CreateUserInput) {
      const user = yield* repo.create(data);
      yield* Effect.log('User created', { userId: user.id });
      return user;
    });

    return { findById, create };
  })
}) {}

// Usage - dependencies are already wired
const program = Effect.gen(function* () {
  const user = yield* UserService.findById(userId);
  return user;
});

// At app root
const MainLive = Layer.mergeAll(UserService.Default, OtherService.Default);

When Context.Tag is acceptable:

  • Infrastructure with runtime injection (Cloudflare KV, worker bindings)
  • Factory patterns where resources are provided externally

Params vs Dependencies

  • Params = runtime data per call (IDs, user input, per-invocation config)
  • Dependencies = shared infrastructure (Ref, PubSub, SubscriptionRef, services) — provide via layer, yield inside the effect
  • Build Ref/PubSub/etc in the layer (e.g. buildAllServicesLayer); consumers yield them, don't receive as params
// WRONG - passing shared infra as params
const createStatusBar = (pubsub: PubSub.PubSub<void>, stateRef: SubscriptionRef.SubscriptionRef<State>) =>
  Effect.gen(...)
// Caller must create and pass; wiring scattered at call sites

// CORRECT - yield inside, build in layer
const PubSubTag = Context.GenericTag<PubSub.PubSub<void>>("PubSub")
const createStatusBar = Effect.gen(function* () {
  const pubsub = yield* PubSubTag
  const stateRef = yield* StateRefTag
  // ...
})
// Layer: Layer.effect(PubSubTag, PubSub.sliding<void>(1))

See references/service-patterns.md for detailed patterns.

Error Definition Pattern

Always use Schema.TaggedError for errors. This makes them serializable (required for RPC) and provides consistent structure.

import { Schema } from 'effect';
import { HttpApiSchema } from '@effect/platform';

export class UserNotFoundError extends Schema.TaggedError<UserNotFoundError>()(
  'UserNotFoundError',
  {
    userId: UserId,
    message: Schema.String
  },
  HttpApiSchema.annotations({ status: 404 })
) {}

export class UserCreateError extends Schema.TaggedError<UserCreateError>()(
  'UserCreateError',
  {
    message: Schema.String,
    cause: Schema.optional(Schema.String)
  },
  HttpApiSchema.annotations({ status: 400 })
) {}

Error handling - use catchTag/catchTags:

// CORRECT - preserves type information
yield *
  repo.findById(id).pipe(
    Effect.catchTag('DatabaseError', err =>
      Effect.fail(new UserNotFoundError({ userId: id, message: 'Lookup failed' }))
    ),
    Effect.catchTag('ConnectionError', err =>
      Effect.fail(new ServiceUnavailableError({ message: 'Database unreachable' }))
    )
  );

// CORRECT - multiple tags at once
yield *
  effect.pipe(
    Effect.catchTags({
      DatabaseError: err => Effect.fail(new UserNotFoundError({ userId: id, message: err.message })),
      ValidationError: err => Effect.fail(new InvalidEmailError({ email: input.email, message: err.message }))
    })
  );

When to Catch (and When Not To)

Most errors surface to the user (message/toast at runtime). Only catch when:

  • Genuinely ignore – accept failure and continue (e.g. optional pre-create)
  • Better message – default vague; map to clearer domain error

Catch sparingly. No catchAll or "swallow to be safe." Use catchTag/catchTags; log or fail with improved error.

Prefer Explicit Over Generic Errors

Every distinct failure reason deserves its own error type. Don't collapse multiple failure modes into generic HTTP errors.

// WRONG - Generic errors lose information
export class NotFoundError extends Schema.TaggedError<NotFoundError>()(
  'NotFoundError',
  { message: Schema.String },
  HttpApiSchema.annotations({ status: 404 })
) {}

// Then mapping everything to it:
Effect.catchTags({
  UserNotFoundError: err => Effect.fail(new NotFoundError({ message: 'Not found' })),
  ChannelNotFoundError: err => Effect.fail(new NotFoundError({ message: 'Not found' })),
  MessageNotFoundError: err => Effect.fail(new NotFoundError({ message: 'Not found' }))
});
// Frontend gets useless: { _tag: "NotFoundError", message: "Not found" }
// Which resource? User? Channel? Message? Can't tell!
// CORRECT - Explicit domain errors with rich context
export class UserNotFoundError extends Schema.TaggedError<UserNotFoundError>()(
  'UserNotFoundError',
  { userId: UserId, message: Schema.String },
  HttpApiSchema.annotations({ status: 404 })
) {}

export class ChannelNotFoundError extends Schema.TaggedError<ChannelNotFoundError>()(
  'ChannelNotFoundError',
  { channelId: ChannelId, message: Schema.String },
  HttpApiSchema.annotations({ status: 404 })
) {}

export class SessionExpiredError extends Schema.TaggedError<SessionExpiredError>()(
  'SessionExpiredError',
  { sessionId: SessionId, expiredAt: Schema.DateTimeUtc, message: Schema.String },
  HttpApiSchema.annotations({ status: 401 })
) {}

// Frontend can now show specific UI:
// - UserNotFoundError → "User doesn't exist"
// - ChannelNotFoundError → "Channel was deleted"
// - SessionExpiredError → "Your session expired. Please log in again."

See references/error-patterns.md for error remapping and retry patterns.

Schema & Branded Types Pattern

Brand all entity IDs for type safety across service boundaries:

import { Schema } from 'effect';

// Entity IDs - always branded
export const UserId = Schema.UUID.pipe(Schema.brand('@App/UserId'));
export type UserId = Schema.Schema.Type<typeof UserId>;

export const OrganizationId = Schema.UUID.pipe(Schema.brand('@App/OrganizationId'));
export type OrganizationId = Schema.Schema.Type<typeof OrganizationId>;

// Domain types - use Schema.Struct
export const User = Schema.Struct({
  id: UserId,
  email: Schema.String,
  name: Schema.String,
  organizationId: OrganizationId,
  createdAt: Schema.DateTimeUtc
});
export type User = Schema.Schema.Type<typeof User>;

// Input types for mutations
export const CreateUserInput = Schema.Struct({
  email: Schema.String.pipe(Schema.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)),
  name: Schema.String.pipe(Schema.minLength(1)),
  organizationId: OrganizationId
});
export type CreateUserInput = Schema.Schema.Type<typeof CreateUserInput>;

When NOT to brand:

  • Simple strings that don't cross service boundaries (URLs, file paths)
  • Primitive config values

See references/schema-patterns.md for transforms and advanced patterns.

Function Pattern: Prefer Effect.fn over Effect.gen

Prefer Effect.fn for effectful code. Provides automatic tracing with proper span names. Span name required; enforced by local/require-effect-fn-span-name.

Use Effect.gen only when you need a shared effect with common .pipe attached so multiple consumers don't each pipe the same things — e.g. provided dependencies, common error handlers, retries. (Less common with Runtimes.) Service definition bodies are a valid use (shared wiring).

// CORRECT - Effect.fn with descriptive name
const findById = Effect.fn('UserService.findById')(function* (id: UserId) {
  yield* Effect.annotateCurrentSpan('userId', id);
  const user = yield* repo.findById(id);
  return user;
});

// CORRECT - Effect.fn with multiple parameters
const transfer = Effect.fn('AccountService.transfer')(function* (fromId: AccountId, toId: AccountId, amount: number) {
  yield* Effect.annotateCurrentSpan('fromId', fromId);
  yield* Effect.annotateCurrentSpan('toId', toId);
  yield* Effect.annotateCurrentSpan('amount', amount);
  // ...
});

// WRONG - params on wrapper arrow, generator has none (closure capture)
// Enforced by local/no-effect-fn-wrapper
const findByIdBad = (id: UserId) =>
  Effect.fn('UserService.findById')(function* () {
    yield* repo.findById(id); // id from closure
  });

// Naming: Don't append Effect. For commands use FooCommand; for helpers/lifecycle use domain names.
// WRONG: logGetEffect, executeAnonymousDocumentEffect, activateEffect
// CORRECT: logGetCommand, executeAnonymousCommand, executeAnonymous (helper), activation (lifecycle)

Layer Composition

Declare dependencies in the service, not at usage sites:

// CORRECT - dependencies in service definition
export class OrderService extends Effect.Service<OrderService>()('OrderService', {
  accessors: true,
  dependencies: [UserService.Default, ProductService.Default, PaymentService.Default],
  effect: Effect.gen(function* () {
    const users = yield* UserService;
    const products = yield* ProductService;
    const payments = yield* PaymentService;
    // ...
  })
}) {}

// At app root - simple merge
const AppLive = Layer.mergeAll(
  OrderService.Default,
  // Infrastructure layers (intentionally not in dependencies)
  DatabaseLive,
  RedisLive
);

See references/layer-patterns.md for testing layers and config-dependent layers.

Option Handling

Never use Option.getOrThrow. Always handle both cases explicitly:

// CORRECT - explicit handling
yield *
  Option.match(maybeUser, {
    onNone: () => Effect.fail(new UserNotFoundError({ userId, message: 'Not found' })),
    onSome: user => Effect.succeed(user)
  });

// CORRECT - with getOrElse for defaults
const name = Option.getOrElse(maybeName, () => 'Anonymous');

// CORRECT - Option.map for transformations
const upperName = Option.map(maybeName, n => n.toUpperCase());

Effect Atom (Frontend State)

Effect Atom provides reactive state management for React with Effect integration.

Basic Atoms

import { Atom } from '@effect-atom/atom-react';

// Define atoms OUTSIDE components
const countAtom = Atom.make(0);

// Use keepAlive for global state that should persist
const userPrefsAtom = Atom.make({ theme: 'dark' }).pipe(Atom.keepAlive);

// Atom families for per-entity state
const modalAtomFamily = Atom.family((type: string) => Atom.make({ isOpen: false }).pipe(Atom.keepAlive));

React Integration

import { useAtomValue, useAtomSet, useAtom, useAtomMount } from "@effect-atom/atom-react"

function Counter() {
    const count = useAtomValue(countAtom)           // Read only
    const setCount = useAtomSet(countAtom)          // Write only
    const [value, setValue] = useAtom(countAtom)    // Read + write

    return <button onClick={() => setCount((c) => c + 1)}>{count}</button>
}

// Mount side-effect atoms without reading value
function App() {
    useAtomMount(keyboardShortcutsAtom)
    return <>{children}</>
}

Handling Results with Result.builder

Use Result.builder for rendering effectful atom results. It provides chainable error handling with onErrorTag:

import { Result } from "@effect-atom/atom-react"

function UserProfile() {
    const userResult = useAtomValue(userAtom) // Result<User, Error>

    return Result.builder(userResult)
        .onInitial(() => <div>Loading...</div>)
        .onErrorTag("NotFoundError", () => <div>User not found</div>)
        .onError((error) => <div>Error: {error.message}</div>)
        .onSuccess((user) => <div>Hello, {user.name}</div>)
        .render()
}

Atoms with Side Effects

const scrollYAtom = Atom.make(get => {
  const onScroll = () => get.setSelf(window.scrollY);

  window.addEventListener('scroll', onScroll);
  get.addFinalizer(() => window.removeEventListener('scroll', onScroll)); // REQUIRED

  return window.scrollY;
}).pipe(Atom.keepAlive);

See references/effect-atom-patterns.md for complete patterns including families, localStorage, and anti-patterns.

RPC & Cluster Patterns

For RPC contracts and cluster workflows, see:

  • references/rpc-cluster-patterns.md - RpcGroup, Workflow.make, Activity patterns

SubscriptionRef

SubscriptionRef<A> is a mutable ref whose .changes stream always emits the current value as element 0, then all future mutations.

Implemented as (from effect/src/internal/subscriptionRef.ts):

stream.concat(stream.make(currentValue), stream.fromPubSub(pubsub))

The Ref.get + pubsub subscription happen atomically under a semaphore — no events are missed.

// WRONG — prepended get is always redundant
Stream.concat(Stream.fromEffect(SubscriptionRef.get(ref)), ref.changes)
Stream.concat(Stream.make(yield* SubscriptionRef.get(ref)), ref.changes)
Stream.merge(Stream.fromEffect(SubscriptionRef.get(ref)), ref.changes)

// CORRECT — .changes already provides the snapshot
ref.changes.pipe(...)

To skip the initial snapshot (e.g. avoid a spurious refresh on activation), use Stream.drop(1).

Anti-Patterns (Forbidden)

These patterns are never acceptable:

// FORBIDDEN - runSync/runPromise inside services
const result = Effect.runSync(someEffect); // Never do this

// FORBIDDEN - throw inside Effect.gen
yield *
  Effect.gen(function* () {
    if (bad) throw new Error('No!'); // Use Effect.fail instead
  });

// FORBIDDEN - catchAll losing type info
yield * effect.pipe(Effect.catchAll(() => Effect.fail(new GenericError())));

// FORBIDDEN - swallowing errors (most errors surface to user; only catch when ignoring intentionally or providing better message)
yield * effect.pipe(Effect.catchAll(() => Effect.void));

// FORBIDDEN - console.log
console.log('debug'); // Use Effect.log

// FORBIDDEN - process.env directly (runtime config)
const key = process.env.API_KEY; // Use Config.string("API_KEY")

// EXCEPTION - build-time/bundle-time variables (e.g., ESBUILD_*)
const platform = process.env.ESBUILD_PLATFORM === 'web' ? webImpl : desktopImpl; // OK - build-time conditional

// FORBIDDEN - null/undefined in domain types
type User = { name: string | null }; // Use Option<string>

See references/anti-patterns.md for the complete list with rationale.

Observability

// Structured logging
yield * Effect.log('Processing order', { orderId, userId, amount });

// Metrics
const orderCounter = Metric.counter('orders_processed');
yield * Metric.increment(orderCounter);

// Config with validation
const config = Config.all({
  port: Config.integer('PORT').pipe(Config.withDefault(3000)),
  apiKey: Config.secret('API_KEY'),
  maxRetries: Config.integer('MAX_RETRIES').pipe(
    Config.validate({ message: 'Must be positive', validation: n => n > 0 })
  )
});

See references/observability-patterns.md for metrics and tracing patterns.

Reference Files

For detailed patterns, consult these reference files in the references/ directory:

  • service-patterns.md - Service definition, Effect.fn, Context.Tag exceptions
  • error-patterns.md - Schema.TaggedError, error remapping, retry patterns
  • schema-patterns.md - Branded types, transforms, Schema.Class
  • layer-patterns.md - Dependency composition, testing layers
  • rpc-cluster-patterns.md - RpcGroup, Workflow, Activity patterns
  • effect-atom-patterns.md - Atom, families, React hooks, Result handling
  • anti-patterns.md - Complete list of forbidden patterns
  • observability-patterns.md - Logging, metrics, config patterns

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

37.52%
按下载量换算107

Claude

27.12%
按下载量换算77

Cursor

18.29%
按下载量换算52

Gemini CLI

9.59%
按下载量换算27

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills