From effect-ts
This skill should be used when the user asks about "Effect Option", "Effect Either", "Option.some", "Option.none", "Either.left", "Either.right", "Cause", "Exit", "Chunk", "Data", "Data.TaggedEnum", "Data.Class", "Duration", "DateTime", "HashMap", "HashSet", "Redacted", or needs to understand Effect's built-in data types and functional data structures.
npx claudepluginhub andrueandersoncs/claude-skill-effect-ts --plugin effect-tsThis skill uses the workspace's default tool permissions.
Effect provides immutable, type-safe data structures:
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Effect provides immutable, type-safe data structures:
Represents a value that may or may not exist:
import { Option } from "effect";
const some = Option.some(42);
const none = Option.none();
const fromNull = Option.fromNullable(maybeNull);
const result = Option.match(option, {
onNone: () => "No value",
onSome: (value) => `Got: ${value}`,
});
const value = Option.getOrElse(option, () => defaultValue);
const doubled = Option.map(option, (n) => n * 2);
const chained = Option.flatMap(option, (n) => (n > 0 ? Option.some(n) : Option.none()));
const positive = Option.filter(option, (n) => n > 0);
const program = Effect.gen(function* () {
const maybeUser = yield* findUser(id);
// Convert Option to Effect
const user = yield* Option.match(maybeUser, {
onNone: () => Effect.fail(new UserNotFound()),
onSome: Effect.succeed,
});
// Or use Effect.fromOption
const user = yield* maybeUser.pipe(
Effect.fromOption,
Effect.mapError(() => new UserNotFound()),
);
});
NEVER nest Option.match calls. When chaining multiple optional operations that share the same fallback, use Option.flatMap with a single Option.getOrElse:
// ❌ FORBIDDEN: Nested Option.match (pyramid of doom)
// Every onNone returns the same default — this is the signal to use flatMap
const result = pipe(
users,
Array.findFirst((u) => u.role === "admin"),
Option.match({
onNone: () => defaultName,
onSome: (admin) =>
Option.match(admin.department, {
onNone: () => defaultName,
onSome: (dept) =>
pipe(
departments,
Array.findFirst((d) => d.id === dept),
Option.match({
onNone: () => defaultName,
onSome: (d) => d.name,
}),
),
}),
}),
);
// ✅ REQUIRED: Option.flatMap chain with single getOrElse
const result = pipe(
users,
Array.findFirst((u) => u.role === "admin"),
Option.flatMap((admin) => admin.department),
Option.flatMap((dept) =>
pipe(
departments,
Array.findFirst((d) => d.id === dept),
),
),
Option.map((d) => d.name),
Option.getOrElse(() => defaultName),
);
When to use which:
| Pattern | Use When |
|---|---|
Option.match | Converting Option to a different type (single use) |
Option.flatMap chain | Chaining multiple optional operations with same fallback |
Option.map | Transforming the inner value without changing Option wrapper |
Option.getOrElse | Extracting the value with a default at the end of a chain |
Option.filter | Adding a condition that may turn Some into None |
Represents a value that is either Left (failure) or Right (success):
import { Either } from "effect";
const right = Either.right(42);
const left = Either.left("error");
const result = Either.match(either, {
onLeft: (error) => `Error: ${error}`,
onRight: (value) => `Success: ${value}`,
});
const doubled = Either.map(either, (n) => n * 2);
const mapped = Either.mapLeft(either, (e) => new Error(e));
const both = Either.mapBoth(either, {
onLeft: (e) => new Error(e),
onRight: (n) => n * 2,
});
const chained = Either.flatMap(either, (n) => (n > 0 ? Either.right(n) : Either.left("negative")));
const value = Either.getOrThrow(either);
Complete failure information for an Effect:
import { Cause } from "effect";
Cause.fail(error);
Cause.die(defect);
Cause.interrupt(id);
Cause.empty;
Cause.sequential(c1, c2);
Cause.parallel(c1, c2);
Cause.isFailure(cause);
Cause.isDie(cause);
Cause.isInterrupt(cause);
const failures = Cause.failures(cause);
const defects = Cause.defects(cause);
const message = Cause.pretty(cause);
The result of running an Effect:
import { Exit } from "effect";
Exit.succeed(value);
Exit.fail(cause);
const result = Exit.match(exit, {
onFailure: (cause) => `Failed: ${Cause.pretty(cause)}`,
onSuccess: (value) => `Succeeded: ${value}`,
});
Exit.isSuccess(exit);
Exit.isFailure(exit);
const value = Exit.getOrElse(exit, () => defaultValue);
const mapped = Exit.map(exit, (a) => a * 2);
Create classes with structural equality:
import { Data, Schema } from "effect";
// Tagged class
class Person extends Data.Class<{
readonly name: string;
readonly age: number;
}> {}
const alice1 = new Person({ name: "Alice", age: 30 });
const alice2 = new Person({ name: "Alice", age: 30 });
alice1 === alice2; // false (reference)
Equal.equals(alice1, alice2); // true (structural)
// Tagged errors (used with Effect.fail)
// Use Schema.TaggedError for domain errors - works with Schema.is(), catchTag, and Match.tag
class UserNotFound extends Schema.TaggedError<UserNotFound>()("UserNotFound", { userId: Schema.String }) {}
// Tagged enum
type Shape = Data.TaggedEnum<{
Circle: { radius: number };
Rectangle: { width: number; height: number };
}>;
const { Circle, Rectangle } = Data.taggedEnum<Shape>();
const circle = Circle({ radius: 10 });
const rect = Rectangle({ width: 5, height: 3 });
Immutable indexed sequence optimized for Effect:
import { Chunk } from "effect";
const chunk = Chunk.make(1, 2, 3, 4, 5);
const fromArray = Chunk.fromIterable([1, 2, 3]);
const empty = Chunk.empty<number>();
const head = Chunk.head(chunk);
const tail = Chunk.tail(chunk);
const take = Chunk.take(chunk, 2);
const drop = Chunk.drop(chunk, 2);
const doubled = Chunk.map(chunk, (n) => n * 2);
const filtered = Chunk.filter(chunk, (n) => n > 2);
const sum = Chunk.reduce(chunk, 0, (acc, n) => acc + n);
const array = Chunk.toArray(chunk);
const readonlyArray = Chunk.toReadonlyArray(chunk);
Represent time spans:
import { Duration } from "effect";
const ms = Duration.millis(100);
const secs = Duration.seconds(5);
const mins = Duration.minutes(10);
const hours = Duration.hours(2);
const days = Duration.days(1);
const fromString = Duration.decode("5 seconds");
const total = Duration.sum(duration1, duration2);
const remaining = Duration.subtract(total, elapsed);
Duration.greaterThan(a, b);
Duration.lessThanOrEqualTo(a, b);
const milliseconds = Duration.toMillis(duration);
const seconds = Duration.toSeconds(duration);
Date and time handling:
import { DateTime } from "effect";
const now = DateTime.now;
const fromDate = DateTime.fromDate(new Date());
const specific = DateTime.make({
year: 2024,
month: 1,
day: 15,
hours: 10,
minutes: 30,
});
const tomorrow = DateTime.add(now, { days: 1 });
const lastWeek = DateTime.subtract(now, { weeks: 1 });
const formatted = DateTime.format(now, "yyyy-MM-dd");
const utc = DateTime.setZone(now, "UTC");
const local = DateTime.setZone(now, DateTime.zoneLocal);
Immutable hash-based collections:
import { HashMap, HashSet } from "effect";
const map = HashMap.make(["a", 1], ["b", 2], ["c", 3]);
const value = HashMap.get(map, "a");
const updated = HashMap.set(map, "d", 4);
const removed = HashMap.remove(map, "a");
const set = HashSet.make(1, 2, 3, 4, 5);
const has = HashSet.has(set, 3);
const added = HashSet.add(set, 6);
const removed = HashSet.remove(set, 1);
const union = HashSet.union(set1, set2);
const intersection = HashSet.intersection(set1, set2);
Protect sensitive values from logging:
import { Redacted } from "effect";
const apiKey = Redacted.make("sk-secret-key-123");
console.log(apiKey);
console.log(`Key: ${apiKey}`);
const actual = Redacted.value(apiKey);
For comprehensive data type documentation, consult ${CLAUDE_PLUGIN_ROOT}/references/llms-full.txt.
Search for these sections: