refactor(core): replace sync event cast with Schema guard

Use Schema.is to narrow converted sync events instead of asserting the payload type manually. Move the Zod-to-Effect bridge into effect-zod so remaining Zod-backed sync payloads have one explicit interop helper.
This commit is contained in:
Kit Langton 2026-04-23 10:30:54 -04:00
commit 53e1d7b8bc
3 changed files with 14 additions and 10 deletions

View file

@ -5,12 +5,14 @@ import { Session } from "@/session"
import { SessionTable } from "@/session/session.sql"
import { Database, eq } from "@/storage"
const isSessionUpdated = Schema.is(Session.Event.Updated.schema)
export function initProjectors() {
SyncEvent.init({
projectors: sessionProjectors,
convertEvent: (type, data) => {
if (type === "session.updated") {
const id = (data as Schema.Schema.Type<typeof Session.Event.Updated.schema>).sessionID
if (type === Session.Event.Updated.type && isSessionUpdated(data)) {
const id = data.sessionID
const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get())
if (!row) return data

View file

@ -28,7 +28,7 @@ import type { Provider } from "@/provider"
import { Permission } from "@/permission"
import { Global } from "@/global"
import { Effect, Layer, Option, Context, Schema, Types } from "effect"
import { ZodOverride, zod, zodObject } from "@/util/effect-zod"
import { fromZod, zod, zodObject } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
const log = Log.create({ service: "session" })
@ -215,13 +215,7 @@ export const MessagesInput = Schema.Struct({
limit: Schema.optional(Schema.Number),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
function schemaFromZod<T extends z.ZodTypeAny>(value: T) {
return Schema.declare((input): input is z.output<T> => value.safeParse(input).success).annotate({
[ZodOverride]: value,
})
}
const SessionUpdateInfoSchema = schemaFromZod(
const SessionUpdateInfoSchema = fromZod(
updateSchema(zodObject(Info)).extend({
share: updateSchema(zodObject(Share)).optional(),
time: updateSchema(zodObject(Time)).optional(),

View file

@ -49,6 +49,14 @@ function isZodType(value: unknown): value is z.ZodTypeAny {
return typeof value === "object" && value !== null && "_zod" in value
}
// Bridge a Zod-first schema into Effect Schema while preserving the original
// Zod for downstream JSON Schema/OpenAPI generation.
export function fromZod<T extends z.ZodTypeAny>(value: T) {
return Schema.declare((input): input is z.output<T> => value.safeParse(input).success).annotate({
[ZodOverride]: value,
})
}
function walk(ast: SchemaAST.AST): z.ZodTypeAny {
const cached = walkCache.get(ast)
if (cached) return cached