Skip to content
Effect 4.0 is here. One ecosystem. Zero dependencies.

Effect 4.1 (Release)

New Effect release featuring the effect/crypto modules, Glob module, streaming CSV, HttpApi Problem Details, Decision.locate, and more!

Effect 4.1 has been released! This release includes a number of new features and improvements. Here’s a summary of what’s new:

Crypto services

The new effect/crypto modules cover randomness, hashing, encryption, signing and key management. Provide the services explicitly with a platform layer such as NodeCrypto.layer:

import { NodeCrypto } from "@effect/platform-node"
import { Effect } from "effect"
import { Digest, SecureRandom } from "effect/crypto"
const program = Effect.gen(function* () {
const random = yield* SecureRandom.SecureRandom
const digest = yield* Digest.Digest
const id = yield* random.uuidV7()
const checksum = yield* digest.digestHex(
Digest.sha256,
new TextEncoder().encode(id),
)
yield* Effect.log(id, checksum)
})
program.pipe(Effect.provide(NodeCrypto.layer), Effect.runFork)

These replace effect/Crypto. See the upgrade notes below before switching.

The experimental Glob module matches paths, and FileSystem.glob searches for files with the same pattern syntax on Node, Bun and Deno.

import { NodeFileSystem } from "@effect/platform-node"
import { Effect, FileSystem, Glob } from "effect"
// Match paths without touching the file system
const sources = Glob.make("src/**/*.{ts,tsx}")
Glob.match(sources, "src/ui/Button.tsx") // => true
Glob.match(sources, "src/.cache/index.ts") // => false
// Or search the file system with the same pattern syntax
const program = Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem
const files = yield* fs.glob("src/**/*.ts", {
exclude: ["**/*.test.ts"],
})
yield* Effect.log(files)
})
program.pipe(Effect.provide(NodeFileSystem.layer), Effect.runFork)

Patterns use / as the separator. Results are relative to root unless the pattern is absolute.

Streaming CSV

An unstable Csv module has been added to effect/encoding, for parsing and writing CSV as a stream of text or UTF-8 bytes. It supports header records and can convert cells using a Schema:

import { Effect, Schema, Stream } from "effect"
import { Csv } from "effect/encoding"
const Person = Schema.Struct({
name: Schema.String,
age: Schema.Number,
}).pipe(Schema.encodeKeys({ name: "Full Name", age: "Age (years)" }))
const people = Stream.make("Full Name,Age (years)\nAlice,32\nBob,41\n").pipe(
Stream.pipeThroughChannel(Csv.decodeSchemaString(Person)({ headers: true })),
Stream.runCollect,
)
Effect.runPromise(people).then(console.log)
// [{ name: "Alice", age: 32 }, { name: "Bob", age: 41 }]

HttpApi Problem Details

HttpApi can now render, document and decode errors as RFC 9457 Problem Details, using application/problem+json. Declare a typed problem with HttpApiError.Problem and use it as an endpoint’s error schema:

import { Schema } from "effect"
import { HttpApiError } from "effect/http-api"
class OutOfCredit extends HttpApiError.Problem<OutOfCredit>()(
"OutOfCredit",
{
type: "https://example.com/probs/out-of-credit",
status: 403,
title: "You do not have enough credit.",
},
{ balance: Schema.Number },
) {}

Built-in errors can opt in with HttpApiSchema.asProblem. Add HttpApiMiddleware.ProblemDetails and its layer to render request decoding failures as 400 problems and response encoding failures as 500 problems. Nothing changes unless you opt in.

More highlights

  • Child processes: effect/process/CommandLine quotes and joins command lines. ChildProcess accepts Redacted arguments and environment values, hiding them in errors. Use CommandLine.display for logs; join and quote include secrets because their output is meant to run.
  • AI decisions: Decision.Image declares images in the input schema, and Decision.locate returns a bounding box around a target. Providers must opt in to locate support.
  • MCP servers: supply handler services and middleware per request, and use McpServer.visibility to hide tools, resources and prompts for that request. McpTest.makeClient tests registrations in memory; cacheHints controls discovery caching for clients on the 2026-07-28 protocol revision.
  • Pool metrics: Pool.stats, PgPool.stats and PgClient.poolStats report idle items, used items and waiting borrowers.
  • Durations: Duration.fromIso, Duration.formatIso and Schema.DurationFromIsoString support ISO 8601 durations with fixed-length units (weeks through seconds, not months or years).
  • Pattern matching: Match.literals and Match.literalsExhaustive match string or number literal unions with a record of handlers.
  • Failure logging: Effect.ignore({ log: "Unhandled" }) uses the configured UnhandledLogLevel. Unobserved release failures in RcMap, RcRef and ScopedCache are now reported at that level too.

Upgrade notes

This release includes breaking changes. Expand the notes for the APIs you use, and check the full CHANGELOG before upgrading.

Crypto
  • Use SecureRandom for randomness and UUIDs, and Digest for hashing. effect/testing/TestCrypto has been removed; use Layer.mock for the services you need instead.
  • Crypto is no longer included in the runtime *Services bundles, so it has to be provided explicitly.
  • SqlMessageStorage and SingleRunner now require Digest.Digest instead of Crypto.Crypto. SqlEventLogServerEncrypted.makeStorage and layerStorage require Digest.Digest instead of EventLogEncryption.
  • EventLogEncryption.sha256String has been removed. Use digest.digestHex(Digest.sha256, bytes).
  • Encoding secret or private keys through the byte / format schemas requires { allowEncode: true }.

Existing workflow IDs, activity idempotency keys, event log table names, identities and encrypted batches are unchanged.

Child processes
  • ChildProcess.make templates throw a CommandLineError for shell syntax such as |, $HOME or *. Quote those characters or pass them with ${}.
  • With the shell option, templates cannot contain ${} values, and extra arguments fail with BadArgument. Pass the whole script as the command, built with CommandLine.join, and name a POSIX shell: on Windows, shell: true runs cmd.exe, which no CommandLine format quotes safely.
  • ChildProcess.TemplateExpression and ChildProcess.TemplateExpressionItem have been removed. Use the CommandLine types of the same name.
File systems, AI and MCP
  • Custom FileSystem implementations must provide readDirectoryEntries. FileSystem.make derives glob for you.
  • DecideOptions.images has been removed. Add Decision.Image fields to your input schema and refer to each image by its path in your instructions.
  • Locate decisions on TypeSafeDecisionModel require Jev 1.14 (jev-preview or jev-1.14.0).
  • MCP registration layers now go through the transport’s registrations option. Use Layer.unwrap instead of Layer.provide for handler services captured at construction time.
  • @effect/ai-anthropic has breaking changes to Files and Skills client methods and file uploads; see its CHANGELOG for details.
Persistence, networking and other API changes
  • FetchHttpClient fails with EncodeError on runtimes without streaming request-body support. Set FetchHttpClient.StreamBodyFallback to "buffer" to buffer the body instead.
  • Custom CLI help formatters should read ancestor flags from HelpDoc.inheritedFlags.
  • Persistence stores on KeyValueStore and Redis are now isolated from each other, using <storeId>:<key> keys. With Persistence.layerKvs, entries written by earlier versions are no longer read, so cached results are recomputed and Chat.layerPersisted loses its stored histories. Redis keys only change for store ids containing % or :. KeyValueStore.clearPrefix has been added.
  • Redis PersistedQueue locks use a new format. Stop all old workers and let their locks expire before starting upgraded workers on the same prefix.
  • RPC calls with discard: true now complete when the server acknowledges them. SchemaBinary peers have to be upgraded together.
  • Toml.parse returns a Result instead of throwing.
  • sql.insert and sql.updateValues require a non-empty array of rows.
  • Migrator.fromFileSystem has moved to effect/sql/MigratorFileSystem, so effect/sql can be imported under Metro in React Native.
  • Received and forwarded HTTP request methods are typed as string. Use HttpMethod.isHttpMethod to narrow them.
  • Atom.Serializable encodes to and decodes from Schema.Json.
  • Node stream and socket pulls are bounded by chunkSize (default 4096). If you used chunkSize as a byte read size, use the new readSize option.
  • HttpClient.withCookiesRef takes a Ref<ReadonlyArray<Cookie>>, and only sends stored cookies to a matching host, path and scheme. Seed the ref with Cookies.mergeIntoArray, or with cookies that set a domain.
  • BunHttpServer disables Bun’s 10 second idleTimeout unless you set one, so quiet response streams and slow uploads are no longer cut off. Servers exposed directly to the internet should set idleTimeout.
  • @effect/opentelemetry no longer re-exports NodeSdk and WebSdk from the package root. Import them from @effect/opentelemetry/NodeSdk and @effect/opentelemetry/WebSdk.
  • Custom terminals passed to Terminal.make have to provide size.
  • McpServer middleware that wraps a visibility runs on list requests, so MiddlewareMethod includes the list methods. Middleware provided both locally and globally now runs once. Unmet per-request services are now McpRequest<"Requires", S> layer requirements, replacing MissingRequestDependencies.
  • Custom stores built with KeyValueStore.make or KeyValueStore.makeStringOnly have to provide keys. size and isEmpty on KeyValueStore.prefix(...) now only count keys under the prefix.
  • HttpApiClient.Upgraded values carry a [HttpApiClient.UpgradedTypeId] brand, so hand-built values such as client mocks have to add it.
  • AiError.HttpRequestDetails and Response.HttpRequestDetails have been removed, so AI errors and response metadata no longer carry request headers or query parameters. AiError.NetworkError has method and url fields instead of request.

For all additions and fixes, see the CHANGELOG.

Join our Discord Community to follow the latest updates and discuss the release!

Share

Last updated

// Effect Community

Join the conversation on Discord

Meet engineers running Effect in production.