Effect essencial: o dialeto reduzido
Objetivo: cobrir a maior parte do dia a dia com o menor conjunto possível de APIs. Tudo que não estiver aqui entra na categoria “aprendo quando precisar”.
O Effect acumulou várias formas de fazer a mesma coisa ao longo das versões 1.x → 2.x → 3.x. Este dialeto reduzido escolhe uma forma idiomática por problema e ignora o resto até precisar. Esta página é essa escolha, o complemento prático das páginas conceituais da seção.
O modelo mental em uma frase
Section titled “O modelo mental em uma frase”Effect<A, E, R> é uma descrição (não uma execução) de um programa que:
- produz um valor de sucesso do tipo
A, - pode falhar com um erro tipado
E, - precisa de dependências (requirements)
Rpara rodar.
Nada acontece até você rodar o Effect (runPromise/runFork, ou o
BunRuntime.runMain no entrypoint). Enquanto não roda, é só um valor imutável que
você compõe. Pense nele como uma “receita”. O porquê disso está em
a descrição e a execução.
Os três canais (A sucesso, E erro, R dependências) são o que diferencia
Effect de uma Promise, que só tem “deu certo” e “deu ruim (any)”.
Escreva lógica com Effect.gen
Section titled “Escreva lógica com Effect.gen”Para qualquer código sequencial de negócio, use Effect.gen. Cada yield*
“desembrulha” um Effect (equivalente a um await), mas mantendo erros e
dependências tipados.
import { Effect } from "effect"
const program = Effect.gen(function* () { const user = yield* getUser(id) // yield* = "await" tipado const posts = yield* getPosts(user.id) return { user, posts }})Regra prática: gen para orquestração; pipe só para transformações curtas.
import { pipe, Effect } from "effect"
const doubled = pipe( Effect.succeed(21), Effect.map((n) => n * 2))Esqueça flatMap/zip/Do/andThen como estilo padrão de negócio. Eles
funcionam, não estão errados, mas produzem código mais difícil de ler e revisar.
gen é o consenso da comunidade para lógica sequencial (ver
generators e pipe).
Nuance honesta: a doc oficial chama generators de “opcional”. Não é proibido usar
pipe/flatMap, só é menos ergonômico. A heurística “gen para negócio, pipe para transformação” é a que menos gera discussão em code review.
Criar Effects a partir de valores e de código existente
Section titled “Criar Effects a partir de valores e de código existente”import { Effect } from "effect"
Effect.succeed(42) // Effect<number> valor prontoEffect.fail(new MyError()) // Effect<never, MyError> falha tipadaEffect.sync(() => Date.now()) // computação síncrona que NÃO lançaEffect.suspend(() => expensive()) // adia a construção do EffectIntegrando Promises (o ponto que mais confunde)
Section titled “Integrando Promises (o ponto que mais confunde)”// ✅ tryPromise: a Promise PODE rejeitar → você tipa o erroconst res = Effect.tryPromise({ try: () => fetch(url), catch: (cause) => new NetworkError({ cause }),})// tipo: Effect<Response, NetworkError>
// ⚠️ promise: você GARANTE que nunca rejeita. Se rejeitar, vira "defect" (crash)const now = Effect.promise(() => Promise.resolve(Date.now()))// tipo: Effect<number, never>Use tryPromise por padrão. Só use promise quando a rejeição for logicamente
impossível. O mesmo par existe para código síncrono: Effect.try({ try, catch })
vs Effect.sync. É assim que se embrulha qualquer client externo que devolve
Promise (um driver de banco, um SDK de terceiros). A diferença entre sync e try,
e por que ela decide se o erro nasce tipado, está em
a borda e os erros.
Erros tipados (nunca throw)
Section titled “Erros tipados (nunca throw)”Não use throw new Error(). Modele erros como dados, com uma tag discriminante.
import { Data } from "effect"
class UserNotFound extends Data.TaggedError("UserNotFound")<{ readonly id: string}> {}
return yield* Effect.fail(new UserNotFound({ id }))A tag permite tratar o erro por nome, com exaustividade verificada pelo compilador:
program.pipe( Effect.catchTag("UserNotFound", (e) => Effect.succeed(`usuário ${e.id} não existe`) ))catchTag/catchTags: trata erros específicos por tag (o idiomático).catchAll: trata qualquer erro do canalE.mapError: transforma o erro sem tratá-lo.orElse/Effect.either: degrada para um valor ou converteEemEither.
Distinção que importa na camada HTTP: use
Data.TaggedErrorpara erros internos (não serializados) eSchema.TaggedErrorpara erros que cruzam a rede (precisam ser encodados/decodados). Mesmo padrão, um leva Schema junto.
Dependências: Context.Tag + Layer (visão de 30 s)
Section titled “Dependências: Context.Tag + Layer (visão de 30 s)”A injeção de dependências tem uma página dedicada. O resumo:
import { Effect, Context } from "effect"
// 1. Declarar o serviço (a "interface" + a "chave" de DI) via CLASSEclass Clock extends Context.Tag("Clock")<Clock, { readonly now: Effect.Effect<number>}>() {}
// 2. Usar no negócio: o requirement R aparece sozinho no tipoconst program = Effect.gen(function* () { const clock = yield* Clock return yield* clock.now})❌ Não use
Context.GenericTag<T>("Nome"). É a forma genérica de baixo nível. A forma idiomática é a classe acima (ouEffect.Service, ver Layers).
Configuração: Config
Section titled “Configuração: Config”Leia env vars / config de forma tipada e validada. Um Config<A> já é um Effect,
então basta yield*.
import { Effect, Config } from "effect"
const load = Effect.gen(function* () { const port = yield* Config.integer("PORT") // inteiro const host = yield* Config.string("HOST") const debug = yield* Config.boolean("DEBUG").pipe( // com default Config.withDefault(false) ) return { port, host, debug }})APIs úteis: Config.string, Config.integer, Config.number (float),
Config.boolean, Config.withDefault, Config.all, Config.nested,
Config.redacted (segredos, ex.: DATABASE_URL).
❌
Config.unwrapNÃO lê valor. Ela constrói umConfiga partir de um objeto envolto emWrap<A>, o oposto do que parece. Para ler, useyield* Config.integer(...). ❌ Não useprocess.envdireto.
Recursos com ciclo de vida: acquireRelease + scoped
Section titled “Recursos com ciclo de vida: acquireRelease + scoped”Para qualquer coisa que abre e precisa fechar (conexão, arquivo, socket), pareie
aquisição e liberação. O release roda sempre, mesmo em erro ou interrupção.
import { Effect } from "effect"
const dbConnection = Effect.acquireRelease( Effect.sync(() => openConnection()), // acquire (conn) => Effect.sync(() => conn.close()) // release (sempre roda))
const program = Effect.gen(function* () { const conn = yield* dbConnection return yield* conn.query("SELECT 1")}).pipe(Effect.scoped)Na prática, recursos de infra (o pool do banco) são adquiridos dentro de um
Layer.scoped (ver Layers), e o
BunRuntime.runMain fecha o escopo no shutdown.
Concorrência
Section titled “Concorrência”import { Effect } from "effect"
Effect.all([taskA, taskB, taskC], { concurrency: "unbounded" }) // coletar todosEffect.forEach(ids, (id) => getUser(id), { concurrency: 10 }) // map efetivoconst fiber = yield* Effect.fork(longTask) // backgroundPor padrão tudo é sequencial. Você opta pela concorrência com { concurrency }
(número | "unbounded" | "inherit"). Se um falha, os outros são
interrompidos automaticamente. É concorrência estruturada de graça.
Cache / memoização
Section titled “Cache / memoização”import { Effect } from "effect"
const cached = yield* Effect.cached(expensiveEffect) // indefinidoconst withTtl = yield* Effect.cachedWithTTL(expensiveEffect, "5 minutes")const getUserCached = yield* Effect.cachedFunction((id: string) => getUser(id))As três existem, são atuais e idiomáticas. Substituem memoização manual com Map.
Rodando o programa
Section titled “Rodando o programa”No backend você precisa de pouquíssimas variantes:
import { Effect } from "effect"
Effect.runPromise(program) // Effect<A> → Promise<A> (rejeita em erro)Effect.runFork(program) // roda em um fiber, retorna o handleNo entrypoint do servidor, o ponto de entrada é BunRuntime.runMain(...), que
cuida do ciclo de vida, dos sinais de shutdown e da finalização dos recursos scoped.
runPromiseExit é útil em testes, porque retorna o Exit completo sem lançar.
O subconjunto de módulos que você realmente usa
Section titled “O subconjunto de módulos que você realmente usa”| Módulo | Para quê |
|---|---|
Effect | o core: criar, compor, rodar |
Layer | montar o grafo de dependências |
Context | declarar serviços (tags) |
Schema | validação, parse/encode, contratos HTTP |
Config | configuração tipada |
Data | erros e estruturas com igualdade |
Duration | tempos ("5 minutes", Duration.seconds(30)) |
Option | valor opcional sem null |
Either | sucesso/erro síncrono |
Array | helpers imutáveis |
Console | logging simples |
”Aprenda quando precisar” (evite no começo)
Section titled “”Aprenda quando precisar” (evite no começo)”Stream, Channel, Sink · STM · Mailbox, Queue, Hub/PubSub ·
Deferred · FiberRef · Schedule avançado · Runtime customizado. Só puxe um
destes quando tiver um problema concreto que o exige.
Nota de versão
Section titled “Nota de versão”- Produção → use Effect v3.x. É a versão oficialmente recomendada.
- Effect v4 existe (beta desde 18/02/2026, ainda em beta) e consolida APIs
duplicadas: por exemplo, os quatro jeitos de declarar serviço (
Context.Tag,Context.GenericTag,Effect.Tag,Effect.Service) colapsam num únicoContext.Service. Escrever no dialeto reduzido hoje é o caminho de menor atrito para essa migração futura. - Os módulos HTTP (
HttpApi,HttpClient) do@effect/platformsão oficialmente “Unstable”: excelentes e recomendados, mas a superfície pode mudar entre releases minor. Vale assumir isso ao construir sobre eles.
Palavras-chave
Section titled “Palavras-chave”Conceitos: dialeto reduzido, descrição, três canais, erro tipado, concorrência estruturada, memoização, ciclo de vida, defect
Core: Effect.gen, yield*, pipe, Effect.succeed, Effect.fail, Effect.sync, Effect.suspend
Promises: Effect.tryPromise, Effect.promise, Effect.try
Erros: Data.TaggedError, Schema.TaggedError, catchTag, catchAll, mapError, orElse, Effect.either
Configuração: Config.string, Config.integer, Config.redacted, Config.withDefault
Recursos e concorrência: acquireRelease, Effect.scoped, Effect.all, Effect.forEach, Effect.fork
Rodar: runPromise, runFork, runPromiseExit, BunRuntime.runMain
Módulos: Effect, Layer, Context, Schema, Config, Data, Duration, Option, Either
Versões: v3.x, v4 beta, @effect/platform