Work can start too early
A Promise executes before the caller can attach a timeout, retry schedule, or test double.
A Promise<A> tells you what succeeds, conceals how it fails, and hides what it needs to run. Learn how Effect<A, E, R> turns async TypeScript into pure, composable values with explicit outputs (A), typed failure modes (E), and declared dependencies (R).
A (success value) • E (typed errors) • R (required context)
Follow one real-world operation as each hidden assumption becomes an explicit policy.
A Promise executes before the caller can attach a timeout, retry schedule, or test double.
The next step must receive the value produced by the previous step without using hardcoded placeholders.
An empty result, an invalid query, and an external provider outage require fundamentally different responses.
Domain logic should not construct database clients, locks, or HTTP providers internally.
Retries, cleanup guarantees, and concurrency limits decide how the system behaves under load.
An Effect is an immutable value describing a computation: what it produces, how it can fail, and what capabilities it requires.
A is the type produced when the operation succeeds, such as parsed records or a validated entity.
E names expected domain failures, forcing callers to handle them without falling back to catch(e: unknown).
R defines environmental capabilities and services the operation needs. The execution boundary must provide them.
Step through each operational decision before adding the next abstraction.
type SyncResult = { readonly imported: number }
declare function fetchWorkspaceIssues(): Promise<readonly string[]>
declare function saveIssues(issues: readonly string[]): Promise<SyncResult>
async function syncWorkspace(): Promise<SyncResult> {
const issues = await fetchWorkspaceIssues()
return saveIssues(issues)
}
// Where do timeout, retry, cleanup, and a test provider live?
const result = await syncWorkspace()Build the same end-to-end operation from input validation to resource cleanup instead of jumping between disconnected toy examples.
Open the full syllabusEach lab starts with a broken policy or failing contract. Inspect the test assertion, repair the implementation, and verify it.
Keep program assembly side-effect free until Effect.runPromise executes it.
pnpm exec tsx src/exercises/exercise1/exercise.test.ts starterFeed the result of the first Effect directly into the next operation using Effect.gen.
pnpm exec tsx src/exercises/exercise2/exercise.test.ts starterPreserve provider outages and invalid queries in the E channel instead of masking them as empty arrays.
pnpm exec tsx src/exercises/exercise3/exercise.test.ts starterSupply the declared Context.Tag at the execution boundary using Effect.provideService.
pnpm exec tsx src/exercises/exercise4/exercise.test.ts starterRegister finalizers that guarantee releasing locks and flushing buffers on both success and failure.
pnpm exec tsx src/exercises/exercise5/exercise.test.ts starterRetry temporary 503s with exponential backoff while failing immediately on 401 Unauthorized.
pnpm exec tsx src/exercises/exercise6/exercise.test.ts starterProcess a full batch while bounding active concurrent fibers to prevent rate-limit exhaustion.
pnpm exec tsx src/exercises/exercise7/exercise.test.ts starterRun the examples, strict typechecks, and test labs locally. The browser explains the mental model. The terminal verifies it.