effect
LibraryEffect-TS/effect
A type-safe library for building production-grade TypeScript applications.
Overview
Effect is a TypeScript library for building robust, maintainable, and type-safe production applications. It provides typed error handling, dependency injection, structured concurrency, scheduling, tracing, and unified schema validation. The monorepo also ships platform packages for Node.js, Bun, Deno and browsers, plus SQL and AI integrations.
README Preview
\n\n[](https://pkg.pr.new/~/Effect-TS/effect)\n\n# Effect\n\nEffect is a library for building robust, maintainable, type-safe, and production grade applications in TypeScript. It helps you handle the hard problems at scale: typed errors, dependency injection, structured concurrency, scheduling, tracing, and unified schema validation.\n\n> **Effect 4.x is a long-term support (LTS) release.** If you are upgrading from Effect 3.x, follow the [migration guide](MIGRATION.md).\n\n## Installation\n\n```sh\nnpm install effect\n```\n\n## Requirements\n\n- **TypeScript 5.9 or newer.** TypeScript 7 is recommended for the best performance and compatibility with [Effect's TypeScript tooling](https://github.com/Effect-TS/tsgo#installation).\n- **Node.js 18 or newer** is the general minimum for running Effect on Node.js. Some integration packages require newer runtimes; for example, `@effect/sql-sqlite-node` requires Node.js 22.16 or newer.\n- **Strict type-checking:** the `strict` flag must be enabled in your `tsconfig.json`.\n\n## Links\n\n- [Website](https://effect.website): documentation, guides, and news\n- [Discord](https://discord.gg/effect-ts): ask questions, share what you're building, and talk to the core team\n- [Community](https://effect.website/community-hub): meetups and events, or bring Effect to your own\n- [Issues](https://github.com/Effect-TS/effect/issues): bug reports and feature requests\n- [Jobs](https://effect.website/effect-jobs): companies hiring Effect developers\n- Follow us on [X](https://x.com/EffectTS_), [Bluesky](https://bsky.app/profile/effect-ts.bsky.social), and [LinkedIn](https://www.linkedin.com/company/effect-ts)\n\n## Let's talk\n\nWhether your team is considering Effect, rolling it out, or already running it in production, we'd love to hear from you: what you're building, what works, and what you need from Effect next.\n\n- **Talk to the maintainers.** Introduce your team on [Discord](https://discord.gg/effect-ts) or email [contact@effectful.
FAQ (2)
TroubleshootingWhy does Effect's RequestResolver.persisted keep serving stale data with a renewed TTL after a failed staleWhileRevalidate refresh?
This is a bug in Effect 4.0.5 (still present on main). With staleWhileRevalidate, when the background refresh's runAll fails as a whole batch (or succeeds without completing the entry), persisted writes the old stored value back with a fresh TTL instead of persisting the failure — so the stale entry gets resurrected and extended indefinitely. Per-entry failures are stored correctly. Workaround: make your resolver complete each request individually instead of failing the whole effect. Catch the error inside the resolver and fail every entry yourself:
const resolver = RequestResolver.make<GetValue>((entries) =>
Effect.catchAll(backendCall(entries), (err) =>
Effect.sync(() => {
for (const entry of entries) entry.completeUnsafe(Exit.fail(err))
})
)
)
With per-entry completion, the failure is persisted via PersistenceStore.setMany and the stale value is not rewritten. Track the upstream fix in effect-ts issue #9583.
TroubleshootingWhy does FiberMap.run / FiberHandle.run in Effect not wait for the replaced fiber's cleanup when the scope closes?
In effect@4.0.5, calling FiberMap.run (or FiberHandle.run, set, runtime) on an occupied key/handle interrupts the previous fiber without awaiting it and detaches it from the container. The scope's finalizer only awaits fibers still in the container, so the scope closes — releasing resources — while the replaced fiber's interruption cleanup (e.g. Effect.onInterrupt) is still running. Under NodeRuntime.runMain this can even make the process exit before that cleanup finishes after SIGTERM.
Workaround: explicitly remove and await the old fiber before replacing it, since remove/clear do wait for interruption to complete:
// FiberMap: remove first, then run
yield* FiberMap.remove(map, "k")
yield* FiberMap.run(map, "k", newEffect)
// FiberHandle: clear first, then run
yield* FiberHandle.clear(handle)
yield* FiberHandle.run(handle, newEffect)
This guarantees the old fiber's cleanup finishes before the replacement starts and before the scope closes, restoring the expected release order (cleanup runs before outer resources are released).