Skip to content

PaulJPhilp/EffectPatterns

Repository files navigation

The Effect Patterns Hub

A community-driven knowledge base of practical, goal-oriented patterns for building robust applications with Effect-TS.

This repository is designed to be a living document that helps developers move from core concepts to advanced architectural strategies by focusing on the "why" behind the code.

Looking for machine-readable rules for AI IDEs and coding agents? See the AI Coding Rules section below.

Table of Contents


Error Management

Pattern Skill Level Summary
Handle Errors with catchTag, catchTags, and catchAll 🟑 Intermediate Use catchTag for type-safe recovery from specific tagged errors, and catchAll to recover from any possible failure.
Mapping Errors to Fit Your Domain 🟑 Intermediate Use Effect.mapError to transform specific, low-level errors into more general domain errors, creating clean architectural boundaries.
Control Repetition with Schedule 🟑 Intermediate Use Schedule to create composable, stateful policies that define precisely how an effect should be repeated or retried.
Model Optional Values Safely with Option 🟑 Intermediate Use Option to explicitly represent a value that may or may not exist, eliminating null and undefined errors.
Define Type-Safe Errors with Data.TaggedError 🟑 Intermediate Create custom, type-safe error classes by extending Data.TaggedError to make error handling robust, predictable, and self-documenting.
Leverage Effect's Built-in Structured Logging 🟑 Intermediate Use Effect's built-in logging functions (Effect.log, Effect.logInfo, etc.) for structured, configurable, and context-aware logging.
Conditionally Branching Workflows 🟑 Intermediate Use predicate-based operators like Effect.filter and Effect.if to make decisions and control the flow of your application based on runtime values.
Retry Operations Based on Specific Errors 🟑 Intermediate Use Effect.retry and predicate functions to selectively retry an operation only when specific, recoverable errors occur.
Handle Flaky Operations with Retries and Timeouts 🟑 Intermediate Use Effect.retry and Effect.timeout to build resilience against slow or intermittently failing operations, such as network requests.
Distinguish 'Not Found' from Errors 🟑 Intermediate Use Effect<Option> to clearly distinguish between a recoverable 'not found' case (None) and a true failure (Fail).
Accumulate Multiple Errors with Either 🟑 Intermediate Use Either<E, A> to represent computations that can fail, allowing you to accumulate multiple errors instead of short-circuiting on the first one.
Handle Unexpected Errors by Inspecting the Cause 🟠 Advanced Use Effect.catchAllCause or Effect.runFork to inspect the Cause of a failure, distinguishing between expected errors (Fail) and unexpected defects (Die).

Building APIs

Pattern Skill Level Summary
Handle a GET Request 🟒 Beginner Define a route that responds to a specific HTTP GET request path.
Send a JSON Response 🟒 Beginner Create and send a structured JSON response with the correct headers and status code.
Extract Path Parameters 🟒 Beginner Capture and use dynamic segments from a request URL, such as a resource ID.
Create a Basic HTTP Server 🟒 Beginner Launch a simple, effect-native HTTP server to respond to incoming requests.
Validate Request Body 🟑 Intermediate Safely parse and validate an incoming JSON request body against a predefined Schema.
Provide Dependencies to Routes 🟑 Intermediate Inject services like database connections into HTTP route handlers using Layer and Effect.Service.
Handle API Errors 🟑 Intermediate Translate application-specific errors from the Effect failure channel into meaningful HTTP error responses.
Make an Outgoing HTTP Client Request 🟑 Intermediate Use the built-in Effect HTTP client to make safe and composable requests to external services from within your API.

Core Concepts

Pattern Skill Level Summary
Understand that Effects are Lazy Blueprints 🟒 Beginner An Effect is a lazy, immutable blueprint describing a computation, which does nothing until it is explicitly executed by a runtime.
Wrap Asynchronous Computations with tryPromise 🟒 Beginner Use Effect.tryPromise to safely convert a function that returns a Promise into an Effect, capturing rejections in the error channel.
Write Sequential Code with Effect.gen 🟒 Beginner Use Effect.gen with yield* to write sequential, asynchronous code in a style that looks and feels like familiar async/await.
Transform Effect Values with map and flatMap 🟒 Beginner Use Effect.map for synchronous transformations and Effect.flatMap to chain operations that return another Effect.
Create Pre-resolved Effects with succeed and fail 🟒 Beginner Use Effect.succeed(value) to create an Effect that immediately succeeds with a value, and Effect.fail(error) for an Effect that immediately fails.
Solve Promise Problems with Effect 🟒 Beginner Understand how Effect solves the fundamental problems of native Promises, such as untyped errors, lack of dependency injection, and no built-in cancellation.
Wrap Synchronous Computations with sync and try 🟒 Beginner Use Effect.sync for non-throwing synchronous code and Effect.try for synchronous code that might throw an exception.
Use .pipe for Composition 🟒 Beginner Use the .pipe() method to chain multiple operations onto an Effect in a readable, top-to-bottom sequence.
Understand the Three Effect Channels (A, E, R) 🟒 Beginner Learn about the three generic parameters of an Effect: the success value (A), the failure error (E), and the context requirements (R).
Control Repetition with Schedule 🟑 Intermediate Use Schedule to create composable, stateful policies that define precisely how an effect should be repeated or retried.
Conditionally Branching Workflows 🟑 Intermediate Use predicate-based operators like Effect.filter and Effect.if to make decisions and control the flow of your application based on runtime values.
Control Flow with Conditional Combinators 🟑 Intermediate Use combinators like Effect.if, Effect.when, and Effect.cond to handle conditional logic in a declarative, composable way.
Process Streaming Data with Stream 🟑 Intermediate Use Stream<A, E, R> to represent and process data that arrives over time, such as file reads, WebSocket messages, or paginated API results.
Manage Shared State Safely with Ref 🟑 Intermediate Use Ref to model shared, mutable state in a concurrent environment, ensuring all updates are atomic and free of race conditions.
Understand Layers for Dependency Injection 🟑 Intermediate A Layer is a blueprint that describes how to build a service, detailing its own requirements and any potential errors during its construction.
Use Chunk for High-Performance Collections 🟑 Intermediate Use Chunk as a high-performance, immutable alternative to JavaScript's Array, especially for data processing pipelines.
Understand Fibers as Lightweight Threads 🟠 Advanced A Fiber is a lightweight, virtual thread managed by the Effect runtime, enabling massive concurrency on a single OS thread without the overhead of traditional threading.

Concurrency

Pattern Skill Level Summary
Control Repetition with Schedule 🟑 Intermediate Use Schedule to create composable, stateful policies that define precisely how an effect should be repeated or retried.
Race Concurrent Effects for the Fastest Result 🟑 Intermediate Use Effect.race to run multiple effects concurrently and proceed with the result of the one that succeeds first, automatically interrupting the others.
Manage Shared State Safely with Ref 🟑 Intermediate Use Ref to model shared, mutable state in a concurrent environment, ensuring all updates are atomic and free of race conditions.
Run Independent Effects in Parallel with Effect.all 🟑 Intermediate Use Effect.all to run multiple independent effects concurrently and collect all their results into a single tuple.
Process a Collection in Parallel with Effect.forEach 🟑 Intermediate Use Effect.forEach with the concurrency option to process a collection of items in parallel with a fixed limit, preventing resource exhaustion.
Add Caching by Wrapping a Layer 🟠 Advanced Implement caching by creating a new layer that wraps a live service, intercepting method calls to add caching logic without modifying the original service.
Manage Resource Lifecycles with Scope 🟠 Advanced Use Scope for fine-grained, manual control over resource lifecycles, ensuring cleanup logic (finalizers) is always executed.
Run Background Tasks with Effect.fork 🟠 Advanced Use Effect.fork to start a computation in a background fiber, allowing the parent fiber to continue its work without waiting.
Execute Long-Running Apps with Effect.runFork 🟠 Advanced Use Effect.runFork at the application's entry point to launch a long-running process as a detached fiber, allowing for graceful shutdown.
Implement Graceful Shutdown for Your Application 🟠 Advanced Use Effect.runFork and listen for OS signals (SIGINT, SIGTERM) to trigger a Fiber.interrupt, ensuring all resources are safely released.
Decouple Fibers with Queues and PubSub 🟠 Advanced Use Queue for point-to-point work distribution and PubSub for broadcast messaging to enable safe, decoupled communication between concurrent fibers.
Poll for Status Until a Task Completes 🟠 Advanced Use Effect.race to run a repeating polling effect alongside a main task, automatically stopping the polling when the main task finishes.
Understand Fibers as Lightweight Threads 🟠 Advanced A Fiber is a lightweight, virtual thread managed by the Effect runtime, enabling massive concurrency on a single OS thread without the overhead of traditional threading.

Testing

Pattern Skill Level Summary
Accessing the Current Time with Clock 🟑 Intermediate Use the Clock service to access the current time in a testable, deterministic way, avoiding direct calls to Date.now().
Write Tests That Adapt to Application Code 🟑 Intermediate A cardinal rule of testing: Tests must adapt to the application's interface, not the other way around. Never modify application code solely to make a test pass.
Use the Auto-Generated .Default Layer in Tests 🟑 Intermediate When testing, always use the MyService.Default layer that is automatically generated by the Effect.Service class for dependency injection.
Mocking Dependencies in Tests 🟑 Intermediate Use a test-specific Layer to provide mock implementations of services your code depends on, enabling isolated and deterministic unit tests.
Model Dependencies as Services 🟑 Intermediate Abstract external dependencies and capabilities into swappable, testable services using Effect's dependency injection system.
Create a Testable HTTP Client Service 🟑 Intermediate Define an HttpClient service with separate 'Live' and 'Test' layers to enable robust, testable interactions with external APIs.
Organize Layers into Composable Modules 🟠 Advanced Structure a large application by grouping related services into 'module' layers, which are then composed together with a shared base layer.

Tooling and Debugging

Pattern Skill Level Summary
Supercharge Your Editor with the Effect LSP 🟑 Intermediate Install the Effect Language Server (LSP) extension for your editor to get rich, inline type information and enhanced error checking for your Effect code.
Teach your AI Agents Effect with the MCP Server 🟠 Advanced Use the Effect MCP server to provide live, contextual information about your application's structure directly to AI coding agents.

Domain Modeling

Pattern Skill Level Summary
Model Optional Values Safely with Option 🟑 Intermediate Use Option to explicitly represent a value that may or may not exist, eliminating null and undefined errors.
Use Effect.gen for Business Logic 🟑 Intermediate Encapsulate sequential business logic, control flow, and dependency access within Effect.gen for improved readability and maintainability.
Transform Data During Validation with Schema 🟑 Intermediate Use Schema.transform to safely convert data from one type to another during the parsing phase, such as from a string to a Date.
Define Type-Safe Errors with Data.TaggedError 🟑 Intermediate Create custom, type-safe error classes by extending Data.TaggedError to make error handling robust, predictable, and self-documenting.
Define Contracts Upfront with Schema 🟑 Intermediate Use Schema to define the types for your data models and function signatures before writing the implementation, creating clear, type-safe contracts.
Parse and Validate Data with Schema.decode 🟑 Intermediate Use Schema.decode(schema) to create an Effect that parses and validates unknown data, which integrates seamlessly with Effect's error handling.
Avoid Long Chains of .andThen; Use Generators Instead 🟑 Intermediate Prefer Effect.gen over long chains of .andThen for sequential logic to improve readability and maintainability.
Distinguish 'Not Found' from Errors 🟑 Intermediate Use Effect<Option> to clearly distinguish between a recoverable 'not found' case (None) and a true failure (Fail).
Model Validated Domain Types with Brand 🟑 Intermediate Use Brand to turn primitive types like string or number into specific, validated domain types like Email or PositiveInt, making illegal states unrepresentable.
Accumulate Multiple Errors with Either 🟑 Intermediate Use Either<E, A> to represent computations that can fail, allowing you to accumulate multiple errors instead of short-circuiting on the first one.

Modeling Data

Pattern Skill Level Summary
Comparing Data by Value with Structural Equality 🟒 Beginner Use Data.struct and Equal.equals to safely compare objects by their value instead of their reference, avoiding common JavaScript pitfalls.

Making HTTP Requests

Pattern Skill Level Summary
Add Custom Metrics to Your Application 🟑 Intermediate Use Effect's Metric module to instrument your code with counters, gauges, and histograms to track key business and performance indicators.
Model Dependencies as Services 🟑 Intermediate Abstract external dependencies and capabilities into swappable, testable services using Effect's dependency injection system.
Create a Testable HTTP Client Service 🟑 Intermediate Define an HttpClient service with separate 'Live' and 'Test' layers to enable robust, testable interactions with external APIs.
Add Caching by Wrapping a Layer 🟠 Advanced Implement caching by creating a new layer that wraps a live service, intercepting method calls to add caching logic without modifying the original service.
Build a Basic HTTP Server 🟠 Advanced Combine Layer, Runtime, and Effect to create a simple, robust HTTP server using Node.js's built-in http module.
Create a Managed Runtime for Scoped Resources 🟠 Advanced Use Layer.launch to safely manage the lifecycle of layers containing scoped resources, ensuring finalizers are always run.

Observability

Pattern Skill Level Summary
Add Custom Metrics to Your Application 🟑 Intermediate Use Effect's Metric module to instrument your code with counters, gauges, and histograms to track key business and performance indicators.
Trace Operations Across Services with Spans 🟑 Intermediate Use Effect.withSpan to create custom tracing spans, providing detailed visibility into the performance and flow of your application's operations.

Resource Management

Pattern Skill Level Summary
Safely Bracket Resource Usage with acquireRelease 🟒 Beginner Use Effect.acquireRelease to guarantee a resource's cleanup logic runs, even if errors or interruptions occur.
Create a Service Layer from a Managed Resource 🟑 Intermediate Use Layer.scoped with Effect.Service to transform a managed resource into a shareable, application-wide service.
Compose Resource Lifecycles with Layer.merge 🟑 Intermediate Combine multiple resource-managing layers, letting Effect automatically handle the acquisition and release order.
Manage Resource Lifecycles with Scope 🟠 Advanced Use Scope for fine-grained, manual control over resource lifecycles, ensuring cleanup logic (finalizers) is always executed.
Manually Manage Lifecycles with Scope 🟠 Advanced Use Scope directly to manage complex resource lifecycles or when building custom layers.
Implement Graceful Shutdown for Your Application 🟠 Advanced Use Effect.runFork and listen for OS signals (SIGINT, SIGTERM) to trigger a Fiber.interrupt, ensuring all resources are safely released.
Create a Managed Runtime for Scoped Resources 🟠 Advanced Use Layer.launch to safely manage the lifecycle of layers containing scoped resources, ensuring finalizers are always run.

File Handling

Pattern Skill Level Summary
Safely Bracket Resource Usage with acquireRelease 🟒 Beginner Use Effect.acquireRelease to guarantee a resource's cleanup logic runs, even if errors or interruptions occur.

Database Connections

Pattern Skill Level Summary
Safely Bracket Resource Usage with acquireRelease 🟒 Beginner Use Effect.acquireRelease to guarantee a resource's cleanup logic runs, even if errors or interruptions occur.

Network Requests

Pattern Skill Level Summary
Safely Bracket Resource Usage with acquireRelease 🟒 Beginner Use Effect.acquireRelease to guarantee a resource's cleanup logic runs, even if errors or interruptions occur.

Building Data Pipelines

Pattern Skill Level Summary
Create a Stream from a List 🟒 Beginner Turn a simple in-memory array or list into a foundational data pipeline using Stream.
Run a Pipeline for its Side Effects 🟒 Beginner Execute a pipeline for its effects without collecting the results, saving memory.
Collect All Results into a List 🟒 Beginner Run a pipeline and gather all of its results into an in-memory array.
Turn a Paginated API into a Single Stream 🟑 Intermediate Convert a paginated API into a continuous, easy-to-use stream, abstracting away the complexity of fetching page by page.
Process Items Concurrently 🟑 Intermediate Perform an asynchronous action for each item in a stream with controlled parallelism to dramatically improve performance.
Process Items in Batches 🟑 Intermediate Group items into chunks for efficient bulk operations, like database inserts or batch API calls.
Process collections of data asynchronously 🟑 Intermediate Process collections of data asynchronously in a lazy, composable, and resource-safe manner using Effect's Stream.
Process a Large File with Constant Memory 🟑 Intermediate Create a data pipeline from a file on disk, processing it line-by-line without loading the entire file into memory.
Automatically Retry Failed Operations 🟑 Intermediate Build a self-healing pipeline that can automatically retry failed processing steps using a configurable backoff strategy.
Manage Resources Safely in a Pipeline 🟠 Advanced Ensure resources like file handles or connections are safely acquired at the start of a pipeline and always released at the end, even on failure.

Application Configuration

Pattern Skill Level Summary
Access Configuration from the Context 🟑 Intermediate Access your type-safe configuration within an Effect.gen block by yielding the Config object you defined.
Define a Type-Safe Configuration Schema 🟑 Intermediate Use Effect.Config primitives to define a schema for your application's configuration, ensuring type-safety and separation from code.
Provide Configuration to Your App via a Layer 🟑 Intermediate Use Config.layer(schema) to create a Layer that provides your configuration schema to the application's context.

Modeling Time

Pattern Skill Level Summary
Accessing the Current Time with Clock 🟑 Intermediate Use the Clock service to access the current time in a testable, deterministic way, avoiding direct calls to Date.now().
Representing Time Spans with Duration 🟑 Intermediate Use the Duration data type to represent time intervals in a type-safe, human-readable, and composable way.
Beyond the Date Type - Real World Dates, Times, and Timezones 🟑 Intermediate Use the Clock service for testable access to the current time and prefer immutable primitives for storing and passing timestamps.

Project Setup & Execution

Pattern Skill Level Summary
Execute Synchronous Effects with Effect.runSync 🟒 Beginner Use Effect.runSync at the 'end of the world' to execute a purely synchronous Effect and get its value directly.
Execute Asynchronous Effects with Effect.runPromise 🟒 Beginner Use Effect.runPromise at the 'end of the world' to execute an asynchronous Effect and get its result as a JavaScript Promise.
Set Up a New Effect Project 🟒 Beginner Initialize a new Node.js project with the necessary TypeScript configuration and Effect dependencies to start building.
Execute Long-Running Apps with Effect.runFork 🟠 Advanced Use Effect.runFork at the application's entry point to launch a long-running process as a detached fiber, allowing for graceful shutdown.
Create a Reusable Runtime from Layers 🟠 Advanced Compile your application's layers into a reusable Runtime object to efficiently execute multiple effects that share the same context.
Create a Managed Runtime for Scoped Resources 🟠 Advanced Use Layer.launch to safely manage the lifecycle of layers containing scoped resources, ensuring finalizers are always run.

Advanced Dependency Injection

Pattern Skill Level Summary
Manually Manage Lifecycles with Scope 🟠 Advanced Use Scope directly to manage complex resource lifecycles or when building custom layers.

Custom Layers

Pattern Skill Level Summary
Manually Manage Lifecycles with Scope 🟠 Advanced Use Scope directly to manage complex resource lifecycles or when building custom layers.

Dependency Injection

Pattern Skill Level Summary
Create a Service Layer from a Managed Resource 🟑 Intermediate Use Layer.scoped with Effect.Service to transform a managed resource into a shareable, application-wide service.
Compose Resource Lifecycles with Layer.merge 🟑 Intermediate Combine multiple resource-managing layers, letting Effect automatically handle the acquisition and release order.

Application Architecture

Pattern Skill Level Summary
Create a Service Layer from a Managed Resource 🟑 Intermediate Use Layer.scoped with Effect.Service to transform a managed resource into a shareable, application-wide service.
Compose Resource Lifecycles with Layer.merge 🟑 Intermediate Combine multiple resource-managing layers, letting Effect automatically handle the acquisition and release order.

About

A community-driven knowledge base of practical patterns for Effect-TS.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published