Skip to content

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 to Effect<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:

1
import { Effect } from "effect"
2
3
interface Logger {
4
print: (message: string) => Effect.Effect<void>
5
}

instead of:

1
import { Effect } from "effect"
2
3
interface 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 to Context.GenericTag, string identifiers are now mandatory.

The following should be used:

1
import { Context } from "effect"
2
3
interface ServiceId {
4
readonly _: unique symbol
5
}
6
7
interface Service {
8
// ...
9
}
10
11
const Service = Context.GenericTag<ServiceId, Service>("@services/Service")

instead of:

1
import { Context } from "effect"
2
3
interface ServiceId {
4
readonly _: unique symbol
5
}
6
7
interface Service {
8
// ...
9
}
10
11
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:

1
import { Context, Effect } from "effect"
2
3
class MyService extends Context.Tag("MyService")<
4
MyService,
5
{ methodA: Effect.Effect<void> }
6
>() {}
7
8
const 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:

Terminal window
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:

1
import { runMain } from "@effect/platform-node/NodeRuntime"
2
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:

1
import * as Rpc from "@effect/rpc/Rpc"
2
import * as S from "@effect/schema/Schema"
3
import { pipe } from "effect/Function"
4
5
export const UserId = pipe(S.number, S.int(), S.brand("UserId"))
6
export type UserId = S.Schema.To<typeof UserId>
7
8
export class User extends S.Class<User>()({
9
id: UserId,
10
name: S.string
11
}) {}
12
13
export class GetUserIds extends Rpc.StreamRequest<GetUserIds>()("GetUserIds", S.never, UserId, {}) {}
14
export class GetUser extends S.TaggedRequest<GetUser>()("GetUser", S.never, User, {
15
id: UserId
16
}) {}

We then define our router:

1
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
2
import * as Http from "@effect/platform/HttpServer"
3
import { Router, Rpc } from "@effect/rpc"
4
import { HttpRouter } from "@effect/rpc-http"
5
import { Effect, Layer, Array, Stream } from "effect"
6
import { createServer } from "http"
7
import { GetUser, GetUserIds, User, UserId } from "./schema.js"
8
9
// Implement the RPC server router
10
const 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
15
export type UserRouter = typeof router
16
17
// Create the http server
18
const 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
25
Layer.launch(HttpLive).pipe(
26
NodeRuntime.runMain
27
)

And finally the client:

1
import * as Http from "@effect/platform/HttpClient"
2
import { Resolver } from "@effect/rpc"
3
import { HttpResolver } from "@effect/rpc-http"
4
import { Console, Effect, Stream } from "effect"
5
import type { UserRouter } from "./router.js"
6
import { GetUser, GetUserIds } from "./schema.js"
7
8
// Create the client
9
const 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 client
16
client(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.runFork
21
)
  • A new module called RateLimiter has been released to help you with rate limiting effects, an example of its usage:
1
import { Context, Effect, Layer, RateLimiter } from "effect"
2
3
class ApiLimiter extends Context.Tag("@services/ApiLimiter")<
4
ApiLimiter,
5
RateLimiter.RateLimiter
6
>() {
7
static Live = RateLimiter.make(10, "2 seconds").pipe(
8
Layer.scoped(ApiLimiter)
9
)
10
}
11
12
const program = Effect.gen(function* () {
13
const rateLimit = yield* ApiLimiter
14
for (let n = 0; n < 100; n++) {
15
yield* rateLimit(Effect.log("Calling RateLimited Effect"))
16
}
17
})
18
19
program.pipe(
20
Effect.provide(ApiLimiter.Live),
21
Effect.runFork
22
)

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!