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:
ts
import { Effect } from "effect"interface Logger {print: (message: string) => Effect.Effect<void>}
ts
import { Effect } from "effect"interface Logger {print: (message: string) => Effect.Effect<void>}
instead of:
ts
import { Effect } from "effect"interface Logger {print: (message: string) => Effect.Effect<never, never, void>}
ts
import { Effect } from "effect"interface Logger {print: (message: string) => Effect.Effect<never, never, void>}
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:
ts
import { Context } from "effect"interface ServiceId {readonly _: unique symbol}interface Service {// ...}const Service = Context.GenericTag<ServiceId, Service>("@services/Service")
ts
import { Context } from "effect"interface ServiceId {readonly _: unique symbol}interface Service {// ...}const Service = Context.GenericTag<ServiceId, Service>("@services/Service")
instead of:
ts
import { Context } from "effect"interface ServiceId {readonly _: unique symbol}interface Service {// ...}const Service = Context.Tag<ServiceId, Service>()
ts
import { Context } from "effect"interface ServiceId {readonly _: unique symbol}interface Service {// ...}const 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:
ts
import { Context, Effect } from "effect"class MyService extends Context.Tag("MyService")<MyService,{ methodA: Effect.Effect<void> }>() {}const effect: Effect.Effect<void, never, MyService> =Effect.flatMap(MyService, _ => _.methodA)
ts
import { Context, Effect } from "effect"class MyService extends Context.Tag("MyService")<MyService,{ methodA: Effect.Effect<void> }>() {}const effect: Effect.Effect<void, never, MyService> =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:
bash
npx @effect/codemod minor-2.3 src/**/*
bash
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:
ts
import { runMain } from "@effect/platform-node/NodeRuntime"import * as Http from "@effect/platform/HttpClient"
ts
import { runMain } from "@effect/platform-node/NodeRuntime"import * 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
:
ts
import * as Rpc from "@effect/rpc/Rpc"import * as S from "@effect/schema/Schema"import { pipe } from "effect/Function"export const UserId = pipe(S.number, S.int(), S.brand("UserId"))export type UserId = S.Schema.To<typeof UserId>export class User extends S.Class<User>()({id: UserId,name: S.string}) {}export class GetUserIds extends Rpc.StreamRequest<GetUserIds>()("GetUserIds", S.never, UserId, {}) {}export class GetUser extends S.TaggedRequest<GetUser>()("GetUser", S.never, User, {id: UserId}) {}
ts
import * as Rpc from "@effect/rpc/Rpc"import * as S from "@effect/schema/Schema"import { pipe } from "effect/Function"export const UserId = pipe(S.number, S.int(), S.brand("UserId"))export type UserId = S.Schema.To<typeof UserId>export class User extends S.Class<User>()({id: UserId,name: S.string}) {}export class GetUserIds extends Rpc.StreamRequest<GetUserIds>()("GetUserIds", S.never, UserId, {}) {}export class GetUser extends S.TaggedRequest<GetUser>()("GetUser", S.never, User, {id: UserId}) {}
We then define our router:
ts
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"import * as Http from "@effect/platform/HttpServer"import { Router, Rpc } from "@effect/rpc"import { HttpRouter } from "@effect/rpc-http"import { Effect, Layer, ReadonlyArray, Stream } from "effect"import { createServer } from "http"import { GetUser, GetUserIds, User, UserId } from "./schema.js"// Implement the RPC server routerconst router = Router.make(Rpc.stream(GetUserIds, () => Stream.fromIterable(ReadonlyArray.makeBy(1000, UserId))),Rpc.effect(GetUser, ({ id }) => Effect.succeed(new User({ id, name: "John Doe" }))))export type UserRouter = typeof router// Create the http serverconst HttpLive = Http.router.empty.pipe(Http.router.post("/rpc", HttpRouter.toHttpApp(router)),Http.server.serve(Http.middleware.logger),Http.server.withLogAddress,Layer.provide(NodeHttpServer.server.layer(createServer, { port: 3000 })))Layer.launch(HttpLive).pipe(NodeRuntime.runMain)
ts
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"import * as Http from "@effect/platform/HttpServer"import { Router, Rpc } from "@effect/rpc"import { HttpRouter } from "@effect/rpc-http"import { Effect, Layer, ReadonlyArray, Stream } from "effect"import { createServer } from "http"import { GetUser, GetUserIds, User, UserId } from "./schema.js"// Implement the RPC server routerconst router = Router.make(Rpc.stream(GetUserIds, () => Stream.fromIterable(ReadonlyArray.makeBy(1000, UserId))),Rpc.effect(GetUser, ({ id }) => Effect.succeed(new User({ id, name: "John Doe" }))))export type UserRouter = typeof router// Create the http serverconst HttpLive = Http.router.empty.pipe(Http.router.post("/rpc", HttpRouter.toHttpApp(router)),Http.server.serve(Http.middleware.logger),Http.server.withLogAddress,Layer.provide(NodeHttpServer.server.layer(createServer, { port: 3000 })))Layer.launch(HttpLive).pipe(NodeRuntime.runMain)
And finally the client:
ts
import * as Http from "@effect/platform/HttpClient"import { Resolver } from "@effect/rpc"import { HttpResolver } from "@effect/rpc-http"import { Console, Effect, Stream } from "effect"import type { UserRouter } from "./router.js"import { GetUser, GetUserIds } from "./schema.js"// Create the clientconst client = HttpResolver.make<UserRouter>(Http.client.fetchOk().pipe(Http.client.mapRequest(Http.request.prependUrl("http://localhost:3000/rpc")))).pipe(Resolver.toClient)// Use the clientclient(new GetUserIds()).pipe(Stream.runCollect,Effect.flatMap(Effect.forEach((id) => client(new GetUser({ id })), { batching: true })),Effect.tap(Console.log),Effect.runFork)
ts
import * as Http from "@effect/platform/HttpClient"import { Resolver } from "@effect/rpc"import { HttpResolver } from "@effect/rpc-http"import { Console, Effect, Stream } from "effect"import type { UserRouter } from "./router.js"import { GetUser, GetUserIds } from "./schema.js"// Create the clientconst client = HttpResolver.make<UserRouter>(Http.client.fetchOk().pipe(Http.client.mapRequest(Http.request.prependUrl("http://localhost:3000/rpc")))).pipe(Resolver.toClient)// Use the clientclient(new GetUserIds()).pipe(Stream.runCollect,Effect.flatMap(Effect.forEach((id) => client(new GetUser({ id })), { batching: true })),Effect.tap(Console.log),Effect.runFork)
- A new module called
RateLimiter
has been released to help you with rate limiting effects, an example of its usage:
ts
import { Context, Effect, Layer, RateLimiter } from "effect"class ApiLimiter extends Context.Tag("@services/ApiLimiter")<ApiLimiter,RateLimiter.RateLimiter>() {static Live = RateLimiter.make(10, "2 seconds").pipe(Layer.scoped(ApiLimiter))}const program = Effect.gen(function*($) {const rateLimit = yield* $(ApiLimiter)for (let n = 0; n < 100; n++) {yield* $(rateLimit(Effect.log("Calling RateLimited Effect")))}})program.pipe(Effect.provide(ApiLimiter.Live),Effect.runFork)
ts
import { Context, Effect, Layer, RateLimiter } from "effect"class ApiLimiter extends Context.Tag("@services/ApiLimiter")<ApiLimiter,RateLimiter.RateLimiter>() {static Live = RateLimiter.make(10, "2 seconds").pipe(Layer.scoped(ApiLimiter))}const program = Effect.gen(function*($) {const rateLimit = yield* $(ApiLimiter)for (let n = 0; n < 100; n++) {yield* $(rateLimit(Effect.log("Calling RateLimited Effect")))}})program.pipe(Effect.provide(ApiLimiter.Live),Effect.runFork)
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!