import type { ServerLifecycleStreamEvent } from "@t3tools/contracts"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as PubSub from "effect/PubSub"; import * as Ref from "effect/Ref"; import * as Stream from "effect/Stream"; type LifecycleEventInput = | Omit, "sequence"> | Omit, "sequence"> | Omit, "sequence">; interface SnapshotState { readonly sequence: number; readonly events: ReadonlyArray; } export class ServerLifecycleEvents extends Context.Service< ServerLifecycleEvents, { readonly publish: (event: LifecycleEventInput) => Effect.Effect; readonly snapshot: Effect.Effect; readonly stream: Stream.Stream; } >()("t3/serverLifecycleEvents") {} const make = Effect.gen(function* () { const pubsub = yield* PubSub.unbounded(); const state = yield* Ref.make({ sequence: 0, events: [], }); return { publish: (event) => Ref.modify(state, (current) => { const nextSequence = current.sequence + 1; const nextEvent = { ...event, sequence: nextSequence, } satisfies ServerLifecycleStreamEvent; const nextEvents = [ nextEvent, ...current.events.filter((entry) => entry.type !== nextEvent.type), ]; return [nextEvent, { sequence: nextSequence, events: nextEvents }] as const; }).pipe(Effect.tap((event) => PubSub.publish(pubsub, event))), snapshot: Ref.get(state), get stream() { return Stream.fromPubSub(pubsub); }, } satisfies ServerLifecycleEvents["Service"]; }); export const layer = Layer.effect(ServerLifecycleEvents, make);