skills/typescript-effect-ts/SKILL.md
Type-safe functional effects with Effect-TS. Use when building applications with Effect, using Effect.gen generators, handling typed errors, managing services with Layer and Context.Tag, validating data with Schema, or managing resources with acquireRelease.
npx skillsauth add martinffx/claude-code-atelier typescript-effect-tsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Effect is a powerful TypeScript library for building robust, type-safe applications. It provides a functional effect system with typed errors, dependency injection, resource management, and concurrency primitives.
Type-safe errors: Unlike Promise which loses error type information, Effect tracks errors in the type system with Effect<Success, Error, Requirements>.
Dependency injection: Services are declared explicitly using Context.Tag and provided via Layer, making dependencies visible in types.
Resource safety: Effect.acquireRelease ensures resources are always cleaned up, even on failure or interruption.
Composability: Effects compose naturally with pipe, Effect.gen generators, and combinators like Effect.all, Effect.flatMap.
For detailed patterns and examples, see:
The core type Effect<Success, Error, Requirements> represents a computation that:
Success on successErrorRequirements to runimport { Effect } from "effect"
// Effect<number, never, never> - succeeds with number, cannot fail, no requirements
const succeed = Effect.succeed(42)
// Effect<never, string, never> - always fails with string error
const fail = Effect.fail("Something went wrong")
// Effect<string, Error, never> - may succeed with string or fail with Error
const parse = (input: string): Effect.Effect<number, Error> =>
Effect.try({
try: () => JSON.parse(input),
catch: (e) => new Error(`Parse failed: ${e}`)
})
import { Effect } from "effect"
// From synchronous values
const fromValue = Effect.succeed(42)
const fromThunk = Effect.sync(() => Date.now())
// From failures
const fromError = Effect.fail(new Error("Failed"))
const fromDie = Effect.die("Unexpected error") // Defect, not typed
// From async operations
const fromPromise = Effect.tryPromise({
try: () => fetch("/api/data").then(r => r.json()),
catch: (e) => new Error(`Fetch failed: ${e}`)
})
// From nullable values
const fromNullable = Effect.fromNullable(maybeValue)
import { Effect } from "effect"
const program = Effect.succeed(42)
// Synchronous (throws if effect is async or fails)
const result = Effect.runSync(program) // 42
// Promise-based
const promise = Effect.runPromise(program) // Promise<42>
// With exit status
const exit = Effect.runSyncExit(program)
const exitPromise = Effect.runPromiseExit(program)
Write async-looking code with full type safety using generators:
import { Effect } from "effect"
const program = Effect.gen(function* () {
const user = yield* fetchUser(userId)
const posts = yield* fetchPosts(user.id)
const enriched = yield* enrichPosts(posts)
return { user, posts: enriched }
})
// Equivalent to:
const programPipe = fetchUser(userId).pipe(
Effect.flatMap(user =>
fetchPosts(user.id).pipe(
Effect.flatMap(posts =>
enrichPosts(posts).pipe(
Effect.map(enriched => ({ user, posts: enriched }))
)
)
)
)
)
Effect tracks errors in the type system, making error handling explicit:
import { Effect } from "effect"
class UserNotFound extends Error {
readonly _tag = "UserNotFound"
constructor(readonly userId: string) {
super(`User not found: ${userId}`)
}
}
class DatabaseError extends Error {
readonly _tag = "DatabaseError"
constructor(readonly cause: unknown) {
super("Database error")
}
}
// Effect<User, UserNotFound | DatabaseError, never>
const getUser = (id: string) => Effect.gen(function* () {
const record = yield* queryDatabase(id)
if (!record) {
return yield* Effect.fail(new UserNotFound(id))
}
return record
})
// Handle specific errors
const handled = getUser("123").pipe(
Effect.catchTag("UserNotFound", (e) =>
Effect.succeed({ id: e.userId, name: "Anonymous" })
)
)
// Handle all errors
const recovered = getUser("123").pipe(
Effect.catchAll((error) =>
Effect.succeed({ id: "unknown", name: "Fallback" })
)
)
Define services with Context.Tag and provide them with Layer:
import { Context, Effect, Layer } from "effect"
// Define a service interface and tag
class Database extends Context.Tag("Database")<
Database,
{
readonly query: (sql: string) => Effect.Effect<unknown[]>
readonly execute: (sql: string) => Effect.Effect<void>
}
>() {}
// Use the service in effects
const getUsers = Effect.gen(function* () {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
// Create a layer that provides the service
const DatabaseLive = Layer.succeed(Database, {
query: (sql) => Effect.sync(() => {
console.log(`Executing: ${sql}`)
return []
}),
execute: (sql) => Effect.sync(() => {
console.log(`Executing: ${sql}`)
})
})
// Provide the layer to run the effect
const runnable = Effect.provide(getUsers, DatabaseLive)
Effect.runPromise(runnable)
Use Effect Schema for type-safe data validation:
import { Schema } from "effect"
// Define a schema
const User = Schema.Struct({
id: Schema.String,
name: Schema.String,
email: Schema.String,
age: Schema.Number
})
// Infer types from schema
type User = Schema.Schema.Type<typeof User>
// Decode unknown data
const decoded = Schema.decodeUnknownSync(User)({
id: "123",
name: "Alice",
email: "[email protected]",
age: 30
})
// Decode with Effect (for better error handling)
const decodeEffect = Schema.decodeUnknown(User)
const result = decodeEffect({ id: "123", name: "Alice", email: "[email protected]", age: 30 })
// Effect<User, ParseError, never>
Safely manage resources that need cleanup:
import { Effect, Scope } from "effect"
// Define a resource with acquisition and release
const withConnection = Effect.acquireRelease(
// Acquire
Effect.sync(() => {
console.log("Opening connection")
return { query: (sql: string) => sql }
}),
// Release
(conn) => Effect.sync(() => {
console.log("Closing connection")
})
)
// Use the resource in a scoped effect
const program = Effect.scoped(
Effect.gen(function* () {
const conn = yield* withConnection
return conn.query("SELECT * FROM users")
})
)
// Connection is automatically closed after use
Effect.runPromise(program)
import { Effect } from "effect"
const tasks = [task1, task2, task3]
// Sequential execution
const sequential = Effect.all(tasks, { concurrency: 1 })
// Parallel execution (all at once)
const parallel = Effect.all(tasks, { concurrency: "unbounded" })
// Parallel with limit
const limited = Effect.all(tasks, { concurrency: 5 })
import { Effect, Schedule } from "effect"
const retried = fetchData.pipe(
Effect.retry(
Schedule.exponential("100 millis").pipe(
Schedule.jittered,
Schedule.compose(Schedule.recurs(5))
)
)
)
import { Effect, Duration } from "effect"
const withTimeout = longRunningTask.pipe(
Effect.timeout(Duration.seconds(30))
)
Effect.gen for complex flows - Generators make sequential operations readable_tag - Enables catchTag for precise error handlingContext.Tag - Makes dependencies explicit and testableLayer for service composition - Layers compose and manage lifecycleEffect.try over manual try/catch - Keeps errors in the Effect channelacquireRelease - Guarantees cleanup on success or failurerunPromise/runSync at application boundariesEffect-TS integrates well with:
This skill automatically loads when discussing:
development
Security architecture and threat modeling knowledge. Auto-invokes when designing features that handle untrusted data, authentication, authorization, external integrations, file uploads, or sensitive data. Provides risk assessment frameworks, trust boundary analysis, and security design principles — not implementation code.
testing
Adversarial review of non-trivial decisions using fresh-context scrutiny. Use when correctness matters more than speed, when stakes are high (production, security-sensitive logic, irreversible operations), or before committing significant architectural or implementation choices.
development
Compact the current conversation into a handoff document for another agent to pick up.
testing
Socratic interrogation of plans against the project's domain model and documented decisions. Use when the user wants to stress-test a plan, clarify terminology, or validate assumptions against existing domain language. Updates CONTEXT.md and ADRs inline as decisions crystallise.