refactor(sync): make session events schema-first (#24019)

This commit is contained in:
Kit Langton
2026-04-23 12:43:08 -04:00
committed by GitHub
parent 353532b1c1
commit c50d65b4d6
9 changed files with 141 additions and 81 deletions

View File

@@ -1,5 +1,5 @@
import z from "zod"
import { Effect, Exit, Layer, PubSub, Scope, Context, Stream } from "effect"
import { Effect, Exit, Layer, PubSub, Scope, Context, Stream, Schema as EffectSchema, Types } from "effect"
import { EffectBridge } from "@/effect"
import { Log } from "../util"
import { BusEvent } from "./bus-event"
@@ -9,6 +9,12 @@ import { makeRuntime } from "@/effect/run-service"
const log = Log.create({ service: "bus" })
type BusProperties<D extends BusEvent.Definition = BusEvent.Definition> = D extends {
effectProperties: infer Properties extends EffectSchema.Top
}
? Types.DeepMutable<EffectSchema.Schema.Type<Properties>>
: z.infer<D["properties"]>
export const InstanceDisposed = BusEvent.define(
"server.instance.disposed",
z.object({
@@ -18,7 +24,7 @@ export const InstanceDisposed = BusEvent.define(
type Payload<D extends BusEvent.Definition = BusEvent.Definition> = {
type: D["type"]
properties: z.infer<D["properties"]>
properties: BusProperties<D>
}
type State = {
@@ -29,7 +35,7 @@ type State = {
export interface Interface {
readonly publish: <D extends BusEvent.Definition>(
def: D,
properties: z.output<D["properties"]>,
properties: BusProperties<D>,
) => Effect.Effect<void>
readonly subscribe: <D extends BusEvent.Definition>(def: D) => Stream.Stream<Payload<D>>
readonly subscribeAll: () => Stream.Stream<Payload>
@@ -79,7 +85,10 @@ export const layer = Layer.effect(
})
}
function publish<D extends BusEvent.Definition>(def: D, properties: z.output<D["properties"]>) {
function publish<D extends BusEvent.Definition>(
def: D,
properties: BusProperties<D>,
) {
return Effect.gen(function* () {
const s = yield* InstanceState.get(state)
const payload: Payload = { type: def.type, properties }
@@ -175,13 +184,16 @@ const { runPromise, runSync } = makeRuntime(Service, layer)
// runSync is safe here because the subscribe chain (InstanceState.get, PubSub.subscribe,
// Scope.make, Effect.forkScoped) is entirely synchronous. If any step becomes async, this will throw.
export async function publish<D extends BusEvent.Definition>(def: D, properties: z.output<D["properties"]>) {
export async function publish<D extends BusEvent.Definition>(
def: D,
properties: BusProperties<D>,
) {
return runPromise((svc) => svc.publish(def, properties))
}
export function subscribe<D extends BusEvent.Definition>(
def: D,
callback: (event: { type: D["type"]; properties: z.infer<D["properties"]> }) => unknown,
callback: (event: Payload<D>) => unknown,
) {
return runSync((svc) => svc.subscribeCallback(def, callback))
}