From 7f12976ea0f5b9b5c0ca471cd98851f5882a4b32 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 12 Mar 2026 13:25:52 -0400 Subject: [PATCH] refactor(auth): use Effect Schema internally Model auth entries with Effect Schema inside AuthService and use Schema decoding when reading persisted auth data. Keep the Auth facade on Zod at the boundary so existing validators and callers stay stable during the migration. --- packages/opencode/src/auth/index.ts | 48 +++++++++++++++-------- packages/opencode/src/auth/service.ts | 55 ++++++++++++--------------- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index d362069f20..79e9e615d2 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -1,27 +1,43 @@ import { Effect } from "effect" +import z from "zod" import { runtime } from "@/effect/runtime" -import { - Api as ApiSchema, - AuthService, - type AuthServiceError, - Info as InfoSchema, - Oauth as OauthSchema, - WellKnown as WellKnownSchema, - type Info as AuthInfo, -} from "./service" +import * as S from "./service" export { OAUTH_DUMMY_KEY } from "./service" -function runPromise(f: (service: AuthService.Service) => Effect.Effect) { - return runtime.runPromise(AuthService.use(f)) +function runPromise(f: (service: S.AuthService.Service) => Effect.Effect) { + return runtime.runPromise(S.AuthService.use(f)) } export namespace Auth { - export const Oauth = OauthSchema - export const Api = ApiSchema - export const WellKnown = WellKnownSchema - export const Info = InfoSchema - export type Info = AuthInfo + export const Oauth = z + .object({ + type: z.literal("oauth"), + refresh: z.string(), + access: z.string(), + expires: z.number(), + accountId: z.string().optional(), + enterpriseUrl: z.string().optional(), + }) + .meta({ ref: "OAuth" }) + + export const Api = z + .object({ + type: z.literal("api"), + key: z.string(), + }) + .meta({ ref: "ApiAuth" }) + + export const WellKnown = z + .object({ + type: z.literal("wellknown"), + key: z.string(), + token: z.string(), + }) + .meta({ ref: "WellKnownAuth" }) + + export const Info = z.discriminatedUnion("type", [Oauth, Api, WellKnown]).meta({ ref: "Auth" }) + export type Info = z.infer export async function get(providerID: string) { return runPromise((service) => service.get(providerID)) diff --git a/packages/opencode/src/auth/service.ts b/packages/opencode/src/auth/service.ts index c98087934c..0898b39df7 100644 --- a/packages/opencode/src/auth/service.ts +++ b/packages/opencode/src/auth/service.ts @@ -1,39 +1,32 @@ import path from "path" -import { Effect, Layer, Schema, ServiceMap } from "effect" -import z from "zod" +import { Effect, Layer, Option, Schema, ServiceMap } from "effect" import { Global } from "../global" import { Filesystem } from "../util/filesystem" export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key" -export const Oauth = z - .object({ - type: z.literal("oauth"), - refresh: z.string(), - access: z.string(), - expires: z.number(), - accountId: z.string().optional(), - enterpriseUrl: z.string().optional(), - }) - .meta({ ref: "OAuth" }) +export class Oauth extends Schema.Class("OAuth")({ + type: Schema.Literal("oauth"), + refresh: Schema.String, + access: Schema.String, + expires: Schema.Number, + accountId: Schema.optional(Schema.String), + enterpriseUrl: Schema.optional(Schema.String), +}) {} -export const Api = z - .object({ - type: z.literal("api"), - key: z.string(), - }) - .meta({ ref: "ApiAuth" }) +export class Api extends Schema.Class("ApiAuth")({ + type: Schema.Literal("api"), + key: Schema.String, +}) {} -export const WellKnown = z - .object({ - type: z.literal("wellknown"), - key: z.string(), - token: z.string(), - }) - .meta({ ref: "WellKnownAuth" }) +export class WellKnown extends Schema.Class("WellKnownAuth")({ + type: Schema.Literal("wellknown"), + key: Schema.String, + token: Schema.String, +}) {} -export const Info = z.discriminatedUnion("type", [Oauth, Api, WellKnown]).meta({ ref: "Auth" }) -export type Info = z.infer +export const Info = Schema.Union([Oauth, Api, WellKnown]) +export type Info = Schema.Schema.Type export class AuthServiceError extends Schema.TaggedErrorClass()("AuthServiceError", { message: Schema.String, @@ -57,15 +50,17 @@ export class AuthService extends ServiceMap.Service Effect.tryPromise({ try: async () => { const data = await Filesystem.readJson>(file).catch(() => ({})) return Object.entries(data).reduce( (acc, [key, value]) => { - const parsed = Info.safeParse(value) - if (!parsed.success) return acc - acc[key] = parsed.data + const parsed = decode(value) + if (Option.isNone(parsed)) return acc + acc[key] = parsed.value return acc }, {} as Record,