From c45fbb3d4536d2fa9fb699c499060a32b4cbd186 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 12 Mar 2026 20:31:15 -0400 Subject: [PATCH] refactor(state): namespace InstanceState, use Map for pending - Convert InstanceState to namespace export pattern - Change pending OAuth state from Record to Map for type-safe lookups --- packages/opencode/src/project/instance.ts | 2 +- .../opencode/src/provider/auth-service.ts | 8 +-- packages/opencode/src/util/instance-state.ts | 66 ++++++++++--------- .../opencode/test/util/instance-state.test.ts | 4 +- 4 files changed, 41 insertions(+), 39 deletions(-) diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts index 425dc6611b..dac5e71ba1 100644 --- a/packages/opencode/src/project/instance.ts +++ b/packages/opencode/src/project/instance.ts @@ -6,7 +6,7 @@ import { State } from "./state" import { iife } from "@/util/iife" import { GlobalBus } from "@/bus/global" import { Filesystem } from "@/util/filesystem" -import * as InstanceState from "@/util/instance-state" +import { InstanceState } from "@/util/instance-state" interface Context { directory: string diff --git a/packages/opencode/src/provider/auth-service.ts b/packages/opencode/src/provider/auth-service.ts index 951a1dba48..f6af2d0b7e 100644 --- a/packages/opencode/src/provider/auth-service.ts +++ b/packages/opencode/src/provider/auth-service.ts @@ -5,7 +5,7 @@ import { filter, fromEntries, map, pipe } from "remeda" import type { AuthOuathResult } from "@opencode-ai/plugin" import { NamedError } from "@opencode-ai/util/error" import * as Auth from "@/auth/service" -import * as InstanceState from "@/util/instance-state" +import { InstanceState } from "@/util/instance-state" import { ProviderID } from "./schema" import z from "zod" @@ -78,7 +78,7 @@ export class ProviderAuthService extends ServiceMap.Service [x.auth!.provider, x.auth!] as const), fromEntries(), ) - return { methods, pending: {} as Record } + return { methods, pending: new Map() } }), }) @@ -97,7 +97,7 @@ export class ProviderAuthService extends ServiceMap.Service method.authorize()) const s = yield* InstanceState.get(state) - s.pending[input.providerID] = result + s.pending.set(input.providerID, result) return { url: result.url, method: result.method, @@ -110,7 +110,7 @@ export class ProviderAuthService extends ServiceMap.Service Effect.Effect const tasks = new Set() -export interface InstanceState { - readonly [TypeId]: typeof TypeId - readonly cache: ScopedCache.ScopedCache -} +export namespace InstanceState { + export interface State { + readonly [TypeId]: typeof TypeId + readonly cache: ScopedCache.ScopedCache + } -export const make = (input: { - lookup: (key: string) => Effect.Effect - release?: (value: A, key: string) => Effect.Effect -}): Effect.Effect, never, R | Scope.Scope> => - Effect.gen(function* () { - const cache = yield* ScopedCache.make({ - capacity: Number.POSITIVE_INFINITY, - lookup: (key) => - Effect.acquireRelease(input.lookup(key), (value) => (input.release ? input.release(value, key) : Effect.void)), + export const make = (input: { + lookup: (key: string) => Effect.Effect + release?: (value: A, key: string) => Effect.Effect + }): Effect.Effect, never, R | Scope.Scope> => + Effect.gen(function* () { + const cache = yield* ScopedCache.make({ + capacity: Number.POSITIVE_INFINITY, + lookup: (key) => + Effect.acquireRelease(input.lookup(key), (value) => (input.release ? input.release(value, key) : Effect.void)), + }) + + const task: Task = (key) => ScopedCache.invalidate(cache, key) + tasks.add(task) + yield* Effect.addFinalizer(() => Effect.sync(() => void tasks.delete(task))) + + return { + [TypeId]: TypeId, + cache, + } }) - const task: Task = (key) => ScopedCache.invalidate(cache, key) - tasks.add(task) - yield* Effect.addFinalizer(() => Effect.sync(() => void tasks.delete(task))) + export const get = (self: State) => ScopedCache.get(self.cache, Instance.directory) - return { - [TypeId]: TypeId, - cache, - } - }) + export const has = (self: State) => ScopedCache.has(self.cache, Instance.directory) -export const get = (self: InstanceState) => ScopedCache.get(self.cache, Instance.directory) + export const invalidate = (self: State) => + ScopedCache.invalidate(self.cache, Instance.directory) -export const has = (self: InstanceState) => ScopedCache.has(self.cache, Instance.directory) - -export const invalidate = (self: InstanceState) => - ScopedCache.invalidate(self.cache, Instance.directory) - -export const dispose = (key: string) => - Effect.all( - [...tasks].map((task) => task(key)), - { concurrency: "unbounded" }, - ) + export const dispose = (key: string) => + Effect.all( + [...tasks].map((task) => task(key)), + { concurrency: "unbounded" }, + ) +} diff --git a/packages/opencode/test/util/instance-state.test.ts b/packages/opencode/test/util/instance-state.test.ts index bd2df4bbf6..e5d2129fb0 100644 --- a/packages/opencode/test/util/instance-state.test.ts +++ b/packages/opencode/test/util/instance-state.test.ts @@ -2,10 +2,10 @@ import { afterEach, expect, test } from "bun:test" import { Effect } from "effect" import { Instance } from "../../src/project/instance" -import * as InstanceState from "../../src/util/instance-state" +import { InstanceState } from "../../src/util/instance-state" import { tmpdir } from "../fixture/fixture" -async function access(state: InstanceState.InstanceState, dir: string) { +async function access(state: InstanceState.State, dir: string) { return Instance.provide({ directory: dir, fn: () => Effect.runPromise(InstanceState.get(state)),