kastalien-research/thoughtbox-dot-claude

effect-ts

Comprehensive guide for Effect-TS, the functional TypeScript library. Use when building Effect applications, especially MCP servers. Covers correct APIs, common misconceptions, and MCP-specific patterns.

First seen Jan 20, 2026

Installation

$ npx skills add kastalien-research/thoughtbox-dot-claude --skill effect-ts

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from kastalien-research/thoughtbox-dot-claude · top by installs.

npx skills add kastalien-research/thoughtbox-dot-claude

Browse all from kastalien-research/thoughtbox-dot-claude

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 9
License MIT
Default branch main
Open issues 0
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 8,967 B
  • docs SUMMARY.md 220 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 297 installs

SKILL.md

Effect-TS Expert Guide

Effect-TS is a functional TypeScript library providing typed effects, structured concurrency, and a robust runtime. This skill covers correct usage patterns and addresses common misconceptions from LLM-generated content.

Quick Reference

import { Effect, Layer, Context, Fiber, Schedule, Cache, Scope } from "effect";
import { Schema, JSONSchema } from "@effect/schema";

Core Type Signature:

Effect<Success, Error, Requirements>
//      ↑        ↑       ↑
//      |        |       └── Dependencies (provided via Layers)
//      |        └── Expected errors (typed, must be handled)
//      └── Success value

Common Misconceptions

LLM outputs often contain incorrect APIs. Use this table to correct them:

Wrong (common in AI outputs) Correct
Effect.cachedWithTTL(...) Cache.make({ timeToLive: Duration })
Effect.cachedInvalidateWithTTL(...) cache.invalidate(key) / cache.invalidateAll()
Effect.match(...) Effect.either + Either.match, or Effect.catchTag
"thread-local storage" "fiber-local storage" via FiberRef
JSON Schema Draft 2020-12 @effect/schema generates Draft-07
fibers are "cancelled" fibers are "terminated" or "interrupted"
all queues have back-pressure only bounded queues; sliding/dropping do not
--only=production --omit=dev (npm 7+)

Error Handling: Two Error Types

Effect distinguishes between:

  1. Expected Errors (Failures) - Typed in E channel, must be handled
  2. Unexpected Errors (Defects) - Runtime bugs, captured but not typed
// Expected error - typed
const fetchUser = (id: string): Effect.Effect<User, UserNotFoundError | NetworkError> => ...

// Handle expected errors
const handled = pipe(
  fetchUser("123"),
  Effect.catchTag("UserNotFoundError", (e) => Effect.succeed(defaultUser)),
  Effect.catchTag("NetworkError", (e) => Effect.retry(schedule))
);

// Unexpected errors (defects) - captured by runtime
Effect.catchAllDefect(program, (defect) =>
  Console.error("Unexpected error", defect)
);

Fibers & Cancellation (Critical for MCP)

Fibers are lightweight virtual threads with native interruption:

// Fork a fiber
const fiber = yield* Effect.fork(longRunningTask);

// Interrupt it (e.g., when MCP client disconnects)
yield* Fiber.interrupt(fiber);

// Structured concurrency: child fibers auto-terminate with parent
const parent = Effect.gen(function* () {
  yield* Effect.fork(backgroundTask);  // Auto-interrupted when parent ends
  yield* mainTask;
});

// Daemon fibers outlive their parent
yield* Effect.forkDaemon(longLivedBackgroundTask);

Concurrency Primitives

Effect.race - First Wins, Losers Interrupted

// First to succeed wins; other is automatically interrupted
const result = yield* Effect.race(
  fetchFromCache,
  fetchFromDatabase
);

Effect.all with Concurrency Control

// Process 50 documents with max 5 concurrent
const results = yield* Effect.all(documents.map(processDoc), {
  concurrency: 5  // NOT a "worker pool" - limits concurrent tasks
});

Queue Types

// Bounded - applies back-pressure (offer suspends when full)
const bounded = yield* Queue.bounded<string>(100);

// Dropping - discards new items when full (no back-pressure)
const dropping = yield* Queue.dropping<string>(100);

// Sliding - discards oldest items when full (no back-pressure)
const sliding = yield* Queue.sliding<string>(100);

Layers for Dependency Injection

Layers construct services without leaking dependencies:

// Define a service
class Database extends Context.Tag("Database")<
  Database,
  { query: (sql: string) => Effect.Effect<Result> }
>() {}

// Create layer (dependencies handled at construction)
const DatabaseLive = Layer.effect(
  Database,
  Effect.gen(function* () {
    const config = yield* Config;  // Dependency injected here
    return {
      query: (sql) => Effect.tryPromise(() => runQuery(sql, config))
    };
  })
);

// Provide to program
const runnable = program.pipe(Effect.provide(DatabaseLive));

// For testing - swap implementation
const DatabaseTest = Layer.succeed(Database, {
  query: () => Effect.succeed(mockResult)
});

Resource Management

Effect.ensuring - Always Runs Finalizer

const program = pipe(
  Effect.tryPromise(() => openConnection()),
  Effect.ensuring(Console.log("Cleanup"))  // Runs on success, failure, OR interrupt
);

acquireUseRelease Pattern

const withConnection = Effect.acquireUseRelease(
  Effect.tryPromise(() => db.connect()),     // Acquire
  (conn) => Effect.tryPromise(() => conn.query("SELECT *")),  // Use
  (conn) => Effect.promise(() => conn.close())  // Release (always runs)
);

Scope for Resource Lifecycle

Effect.scoped(
  Effect.gen(function* () {
    const file = yield* openFile("data.txt");  // Acquired
    const data = yield* file.read();
    return data;
  })  // File automatically released when scope closes
);

Caching

There is no Effect.cachedWithTTL. Use the Cache module:

import { Cache } from "effect";

const cache = yield* Cache.make({
  capacity: 100,
  timeToLive: Duration.minutes(5),
  lookup: (key: string) => fetchExpensiveData(key)
});

// Use the cache
const value = yield* cache.get("my-key");

// Invalidate
yield* cache.invalidate("my-key");
yield* cache.invalidateAll();

Retry with Schedule

import { Schedule } from "effect";

// Retry 3 times with exponential backoff
const policy = Schedule.exponential("100 millis").pipe(
  Schedule.intersect(Schedule.recurs(3))
);

const robust = Effect.retry(unstableOperation, policy);

// Retry until condition
const untilSuccess = Effect.retry(operation, {
  until: (err) => err.code === "RATE_LIMITED"
});

Schema & JSON Schema

@effect/schema generates JSON Schema Draft-07 (not 2020-12):

import { Schema, JSONSchema } from "@effect/schema";

const User = Schema.Struct({
  id: Schema.String,
  age: Schema.Number.pipe(Schema.positive())
});

// Generate JSON Schema (Draft-07)
const jsonSchema = JSONSchema.make(User);
// { "$schema": "http://json-schema.org/draft-07/schema#", ... }

// Decode (parse)
const user = Schema.decodeUnknownSync(User)(rawData);

// Encode
const json = Schema.encodeSync(User)(user);

Observability & OpenTelemetry

Effect has native OpenTelemetry integration:

import { NodeSdk } from "@effect/opentelemetry";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";

// Add tracing to any effect
const traced = Effect.withSpan("processRequest")(myEffect);

// Logging with context
yield* Effect.log("Processing request");
yield* Effect.annotateLogs("requestId", "abc-123");

// FiberRef for fiber-local context propagation
const RequestId = FiberRef.unsafeMake<string>("");
yield* FiberRef.set(RequestId, "req-456");

When NOT to Use Effect

Scenario Recommendation
Simple MCP tool (< 100 LOC) Use FastMCP or vanilla SDK
Team unfamiliar with FP Steep learning curve; consider NestJS
Bundle size critical Effect adds 15-25kb gzipped minimum
Existing NestJS/TypeORM codebase Impedance mismatch with class-based DI

MCP Server Patterns

Tool Handler with Typed Errors

const searchTool = Effect.gen(function* () {
  const args = yield* parseArgs(input);
  const db = yield* Database;
  const results = yield* db.query(args.query);
  return formatResults(results);
}).pipe(
  Effect.catchTag("ParseError", () =>
    Effect.fail({ code: -32602, message: "Invalid params" })
  ),
  Effect.catchTag("DatabaseError", () =>
    Effect.fail({ code: -32603, message: "Internal error" })
  )
);

Request Scoping

// Each MCP request gets its own scope
const handleRequest = (request: MCPRequest) =>
  Effect.scoped(
    Effect.gen(function* () {
      // Resources acquired here auto-release when request completes
      const tempFile = yield* createTempFile();
      const result = yield* processRequest(request, tempFile);
      return result;
    })
  );

Resources