Cluster.Entity reference
Entity
Section titled “Entity”Source:
packages/effect-cluster/src/Entity.ts
An Entity in an Effect Cluster: a stateful, addressable instance per entity id, whose messages are typed by its RPCs. Runners place each instance on one runner and deliver its messages one at a time.
Declare it from an Effect Cluster entity with its implementation, and
the class is both what callers yield* and what deploys it. Or declare
it bare, like Effect Cluster’s Entity.make, from a type and its RPCs,
and implement it with .make(props, impl), as a Layer: callers import
the bare declaration without bundling the implementation or its pool.
The implementation has two phases. The outer Effect runs once per runner
and resolves bindings. The inner Effect runs once per instance, on its
first message, and returns its handlers, typed by the entity’s .of. Providing a
pool Layer to the implementation, such as
ClusterDocker.SwarmPool("Default"), is what deploys it.
yield* Counter from any runtime returns the entity’s namespace, like a
Durable Object’s: getByName(entityId) returns a client with the same
shape as Effect Cluster’s Entity.client(entityId). Inside the cluster,
calls go through Effect Cluster directly; elsewhere they go through the
pool’s gateway over HTTP, so callers need no database access and no
route to the pool.
Entity: Declaring an entity
Section titled “Entity: Declaring an entity”const Counters = Entity.make("Counter", [ Rpc.make("Increment", { payload: { amount: Schema.Int }, success: Schema.Int }),]);
export default class Counter extends Cluster.Entity<Counter>()( Counters, { main: import.meta.url }, Effect.gen(function* () { return Effect.gen(function* () { const count = yield* Ref.make(0); return Counters.of({ Increment: ({ payload }) => Ref.updateAndGet(count, (n) => n + payload.amount), }); }); }).pipe(Effect.provide(Default)),) {}Entity: Keeping its code out of its callers
Section titled “Entity: Keeping its code out of its callers”A bare declaration, which callers import
export const Increment = Rpc.make("Increment", { payload: { amount: Schema.Int }, success: Schema.Int,});
export class Counter extends Cluster.Entity<Counter>()("Counter", [Increment]) {}An existing Effect Cluster entity
import { Counter as CounterEntity } from "./entities/counter.ts";
export class Counter extends Cluster.Entity<Counter>()(CounterEntity) {}Its implementation, as a Layer, deployed to a pool
export default Counter.make( { main: import.meta.url }, Effect.gen(function* () { return Effect.gen(function* () { const count = yield* Ref.make(0); return Counter.of({ Increment: ({ payload }) => Ref.updateAndGet(count, (n) => n + payload.amount), }); }); }).pipe(Effect.provide(Default)),);Middleware, provided with the pool
export const Increment = Rpc.make("Increment", { payload: { amount: Schema.Int }, success: Schema.Int,}).middleware(Auth);
export default Counter.make( { main: import.meta.url }, Effect.gen(function* () { return Effect.gen(function* () { return Counter.of({ Increment: () => Effect.map(CurrentUser, (user) => user.id.length), }); }); }).pipe(Effect.provide(Default.pipe(Layer.provide(AuthLive)))),);An existing Effect Cluster program, run as it is
import { Tally as TallyEntity } from "./entities/tally.ts";
export const Tally = Cluster.Entity(TallyEntity, { main: "./entities/tally.ts", program: "./runner.ts",}).pipe(Effect.provide(Legacy));Entity: Calling an entity
Section titled “Entity: Calling an entity”From any runtime
const counters = yield* Counter;const counter = yield* counters.getByName("visits");const count = yield* counter.Increment({ amount: 1 });Without waiting for the reply
yield* counter.Increment({ amount: 1 }, { discard: true });Through client middleware
const counter = yield* counters.getByName("visits");yield* counter.Increment({ amount: 1 }).pipe(Effect.provide(AuthClient));