Applies Effect coding best practices for env access, typed errors, Effect composition, and error boundaries.
Use this skill when implementing or reviewing Effect-based code.
effect.AppEnv.Do not use process.env directly in Effect business code.
Read environment values through AppEnv from src/env.ts.
import { Effect } from "effect";
import { AppEnv } from "./env";
const program = Effect.gen(function* () {
const env = yield* AppEnv;
return env.appName;
});
const runnable = program.pipe(Effect.provide(AppEnv.Default));
Model domain failures as typed errors with .
Schema.TaggedErrorimport { Effect, Schema } from "effect";
class NotionFsError extends Schema.TaggedError<NotionFsError>("NotionFsError")({
message: Schema.String,
cause: Schema.optional(Schema.Defect),
}) {}
const program = Effect.fail(
new NotionFsError({
message: "Page not found",
cause: new Error("Notion API returned 404"),
}),
);
Store typed domain errors in src/errors.ts and export public ones from src/index.ts.
Effect.gen for multi-step composition.yield* to execute Effects.return yield* Effect.fail(...) for narrowing.Effect.fn("Service.method") for reusable Effect functions.Effect.annotateLogs and Effect.annotateCurrentSpan.Example:
import { Effect } from "effect";
export const parseUser = Effect.fn("UserService.parseUser")((input: string) =>
Effect.sync(() => JSON.parse(input)).pipe(
Effect.annotateLogs("inputLength", input.length),
),
);
try/catch Inside EffectsEffect.try for sync code that may throw.Effect.tryPromise for async code that may reject.import { Effect } from "effect";
const fromDisk = Effect.try({
try: () => Bun.file("data.json").text(),
catch: (error: unknown) =>
new Error(`Failed to read file: ${String(error)}`),
});
const fromApi = Effect.tryPromise({
try: () => fetch("https://example.com/data"),
catch: (error: unknown) =>
new Error(`Request failed: ${String(error)}`),
});
Effect.provide(...) through domain modules.bun run typecheck
bun run lint
If test harness is configured:
bun run test
bun run test:coverage