diff --git a/apps/web/src/content/blog/releases/effect/4.1.mdx b/apps/web/src/content/blog/releases/effect/4.1.mdx new file mode 100644 index 000000000..cd02eb054 --- /dev/null +++ b/apps/web/src/content/blog/releases/effect/4.1.mdx @@ -0,0 +1,264 @@ +--- +pagefind: false +title: Effect 4.1 (Release) +excerpt: New Effect release featuring the effect/crypto modules, Glob module, streaming CSV, HttpApi Problem Details, Decision.locate, and more! +date: 2026-10-20 +authors: + - tim_smart +tags: + - releases + - effect +--- + +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`: + +```ts +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. + +## Glob matching and file search + +The experimental `Glob` module matches paths, and `FileSystem.glob` searches +for files with the same pattern syntax on Node, Bun and Deno. + +```ts +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`: + +```ts +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](https://www.rfc-editor.org/rfc/rfc9457) Problem Details, using +`application/problem+json`. Declare a typed problem with +`HttpApiError.Problem` and use it as an endpoint's error schema: + +```ts +import { Schema } from "effect" +import { HttpApiError } from "effect/http-api" + +class OutOfCredit extends HttpApiError.Problem()( + "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](https://github.com/Effect-TS/effect/blob/main/packages/effect/CHANGELOG.md) +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 `:` 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>`, 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](https://github.com/Effect-TS/effect/blob/main/packages/effect/CHANGELOG.md). + +Join our [Discord Community](https://discord.gg/effect-ts) to follow the latest updates and discuss the release!