From 53e1d7b8bce1e3a9a661ef6b45f557d22d315ed9 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 23 Apr 2026 10:30:54 -0400 Subject: [PATCH] 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. --- packages/opencode/src/server/projectors.ts | 6 ++++-- packages/opencode/src/session/session.ts | 10 ++-------- packages/opencode/src/util/effect-zod.ts | 8 ++++++++ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/server/projectors.ts b/packages/opencode/src/server/projectors.ts index c32e08693e..b5cc446b87 100644 --- a/packages/opencode/src/server/projectors.ts +++ b/packages/opencode/src/server/projectors.ts @@ -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).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 diff --git a/packages/opencode/src/session/session.ts b/packages/opencode/src/session/session.ts index 46513801be..3b9e2ebb4b 100644 --- a/packages/opencode/src/session/session.ts +++ b/packages/opencode/src/session/session.ts @@ -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(value: T) { - return Schema.declare((input): input is z.output => 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(), diff --git a/packages/opencode/src/util/effect-zod.ts b/packages/opencode/src/util/effect-zod.ts index edbbf4d542..e949b255f5 100644 --- a/packages/opencode/src/util/effect-zod.ts +++ b/packages/opencode/src/util/effect-zod.ts @@ -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(value: T) { + return Schema.declare((input): input is z.output => value.safeParse(input).success).annotate({ + [ZodOverride]: value, + }) +} + function walk(ast: SchemaAST.AST): z.ZodTypeAny { const cached = walkCache.get(ast) if (cached) return cached