Effect 2.3 (Release)
A new minor release of Effect
has gone out, with some big changes we wanted to let you know about:
Effect<R, E, A>
has been changed toEffect<A, E = never, R = never>
. This change makes for cleaner type signatures (Effect<void>
) and ensures types are ordered by importance. The full list of types that got updated with the same change:Effect
,Stream
,STM
,STMGen
,Layer
,Exit
,Take
,Fiber
,FiberRuntime
,Request
,Resource
,TExit
,Deferred
,TDeferred
,Pool
.
The following should be used:
1import { Effect } from "effect"2
3interface Logger {4 print: (message: string) => Effect.Effect<void>5}
instead of:
1import { Effect } from "effect"2
3interface Logger {4 print: (message: string) => Effect.Effect<never, never, void>5}
For this inhuman work we have to thank deeply Giulio Canti, who’s now officially known as <Canti, Giulio = never>
!
Context.Tag
has been renamed toContext.GenericTag
, string identifiers are now mandatory.
The following should be used:
1import { Context } from "effect"2
3interface ServiceId {4 readonly _: unique symbol5}6
7interface Service {8 // ...9}10
11const Service = Context.GenericTag<ServiceId, Service>("@services/Service")
instead of:
1import { Context } from "effect"2
3interface ServiceId {4 readonly _: unique symbol5}6
7interface Service {8 // ...9}10
11const Service = Context.Tag<ServiceId, Service>()
- A new
Context.Tag
base class has been added. The added class approach assists with creating unique tag identifiers, making use of the opaque type that you get for free using a class.
For example:
1import { Context, Effect } from "effect"2
3class MyService extends Context.Tag("MyService")<4 MyService,5 { methodA: Effect.Effect<void> }6>() {}7
8const effect: Effect.Effect<void, never, MyService> =9 Effect.flatMap(MyService, _ => _.methodA)
For the changes above, a code-mod has been released to make migration as easy as possible.
You can run it by executing:
npx @effect/codemod minor-2.3 src/**/*
It might not be perfect - if you encounter issues, let us know! Also make sure you commit any changes before running it, in case you need to revert anything.
@effect/platform
has been refactored to remove re-exports from the base package.
You will now need to install both @effect/platform
& the corresponding @effect/platform-*
package to make use of platform specific implementations.
You can see an example at platform-node/examples/http-client.ts
Notice the imports:
1import { runMain } from "@effect/platform-node/NodeRuntime"2import * as Http from "@effect/platform/HttpClient"
- A rewrite of the
@effect/rpc
package has been released, which simplifies the design and adds support for streaming responses.
You can see a small example of usage here: rpc-http/examples.
We start by defining our requests using S.TaggedRequest
or Rpc.StreamingRequest
:
1import * as Rpc from "@effect/rpc/Rpc"2import * as S from "@effect/schema/Schema"3import { pipe } from "effect/Function"4
5export const UserId = pipe(S.number, S.int(), S.brand("UserId"))6export type UserId = S.Schema.To<typeof UserId>7
8export class User extends S.Class<User>()({9 id: UserId,10 name: S.string11}) {}12
13export class GetUserIds extends Rpc.StreamRequest<GetUserIds>()("GetUserIds", S.never, UserId, {}) {}14export class GetUser extends S.TaggedRequest<GetUser>()("GetUser", S.never, User, {15 id: UserId16}) {}
We then define our router:
1import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"2import * as Http from "@effect/platform/HttpServer"3import { Router, Rpc } from "@effect/rpc"4import { HttpRouter } from "@effect/rpc-http"5import { Effect, Layer, Array, Stream } from "effect"6import { createServer } from "http"7import { GetUser, GetUserIds, User, UserId } from "./schema.js"8
9// Implement the RPC server router10const router = Router.make(11 Rpc.stream(GetUserIds, () => Stream.fromIterable(Array.makeBy(1000, UserId))),12 Rpc.effect(GetUser, ({ id }) => Effect.succeed(new User({ id, name: "John Doe" })))13)14
15export type UserRouter = typeof router16
17// Create the http server18const HttpLive = Http.router.empty.pipe(19 Http.router.post("/rpc", HttpRouter.toHttpApp(router)),20 Http.server.serve(Http.middleware.logger),21 Http.server.withLogAddress,22 Layer.provide(NodeHttpServer.server.layer(createServer, { port: 3000 }))23)24
25Layer.launch(HttpLive).pipe(26 NodeRuntime.runMain27)
And finally the client:
1import * as Http from "@effect/platform/HttpClient"2import { Resolver } from "@effect/rpc"3import { HttpResolver } from "@effect/rpc-http"4import { Console, Effect, Stream } from "effect"5import type { UserRouter } from "./router.js"6import { GetUser, GetUserIds } from "./schema.js"7
8// Create the client9const client = HttpResolver.make<UserRouter>(10 Http.client.fetchOk().pipe(11 Http.client.mapRequest(Http.request.prependUrl("http://localhost:3000/rpc"))12 )13).pipe(Resolver.toClient)14
15// Use the client16client(new GetUserIds()).pipe(17 Stream.runCollect,18 Effect.flatMap(Effect.forEach((id) => client(new GetUser({ id })), { batching: true })),19 Effect.tap(Console.log),20 Effect.runFork21)
- A new module called
RateLimiter
has been released to help you with rate limiting effects, an example of its usage:
1import { Context, Effect, Layer, RateLimiter } from "effect"2
3class ApiLimiter extends Context.Tag("@services/ApiLimiter")<4 ApiLimiter,5 RateLimiter.RateLimiter6>() {7 static Live = RateLimiter.make(10, "2 seconds").pipe(8 Layer.scoped(ApiLimiter)9 )10}11
12const program = Effect.gen(function* () {13 const rateLimit = yield* ApiLimiter14 for (let n = 0; n < 100; n++) {15 yield* rateLimit(Effect.log("Calling RateLimited Effect"))16 }17})18
19program.pipe(20 Effect.provide(ApiLimiter.Live),21 Effect.runFork22)
There were several other smaller changes made, feel free to read through the changelog to see them all: Changelog.
Don’t forget to join our Discord Community to follow the last updates and discuss every tiny detail!