From 651c23ba1dab9a3732a38e721e931de95523e17e Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 2 Jul 2026 09:36:46 -0400 Subject: [PATCH] refactor(core): replace Database.path with config-backed configuredPath --- packages/core/src/database/database.ts | 35 ++++++++++-------------- packages/core/test/database-path.test.ts | 17 ++++++------ packages/opencode/src/cli/cmd/db.ts | 4 +-- packages/opencode/test/fixture/db.ts | 3 +- 4 files changed, 26 insertions(+), 33 deletions(-) diff --git a/packages/core/src/database/database.ts b/packages/core/src/database/database.ts index 78585ce8a8..b9abce70f8 100644 --- a/packages/core/src/database/database.ts +++ b/packages/core/src/database/database.ts @@ -4,7 +4,7 @@ import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite" import { layer as sqliteLayer } from "#sqlite" import { Config, Context, Effect, Layer } from "effect" import { Global } from "../global" -import { truthy, truthyConfig } from "../flag/flag" +import { truthyConfig } from "../flag/flag" import { isAbsolute, join } from "path" import { DatabaseMigration } from "./migration" import { InstallationChannel } from "../installation/version" @@ -40,8 +40,7 @@ export function layerFromPath(filename: string) { return layer.pipe(Layer.provide(sqliteLayer({ filename }))) } -/** One placement rule shared by the config-backed layer and the V1 `path()` helper. */ -export function resolvePath(input: { readonly file: string | undefined; readonly disableChannelDb: boolean }) { +function resolvePath(input: { readonly file: string | undefined; readonly disableChannelDb: boolean }) { if (input.file) { if (input.file === ":memory:" || isAbsolute(input.file)) return input.file return join(Global.Path.data, input.file) @@ -51,24 +50,18 @@ export function resolvePath(input: { readonly file: string | undefined; readonly return join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`) } -/** V1 compatibility helper; reads the process environment at call time. */ -export function path() { - return resolvePath({ - file: process.env.OPENCODE_DB, - disableChannelDb: truthy("OPENCODE_DISABLE_CHANNEL_DB"), - }) -} +/** + * The database placement every consumer shares, resolved through Effect + * Config so tests and tooling can override it with a ConfigProvider. Used by + * the layer below and by external tooling such as `opencode db`. + */ +export const configuredPath = Effect.gen(function* () { + const file = yield* Config.string("OPENCODE_DB").pipe(Config.withDefault(undefined)) + const disableChannelDb = yield* truthyConfig("OPENCODE_DISABLE_CHANNEL_DB") + return resolvePath({ file, disableChannelDb }) +}).pipe(Effect.orDie) -// Placement is resolved through Effect Config when the layer is built, not at -// module import, so tests and tooling can override it with a ConfigProvider. -// truthyConfig shares the `truthy` grammar so this layer and the V1 `path()` -// helper always agree on the same environment. -const configuredLayer = Layer.unwrap( - Effect.gen(function* () { - const file = yield* Config.string("OPENCODE_DB").pipe(Config.withDefault(undefined)) - const disableChannelDb = yield* truthyConfig("OPENCODE_DISABLE_CHANNEL_DB") - return layerFromPath(resolvePath({ file, disableChannelDb })) - }), -).pipe(Layer.orDie) +// Placement is resolved when the layer is built, not at module import. +const configuredLayer = Layer.unwrap(Effect.map(configuredPath, layerFromPath)) export const node = makeGlobalNode({ service: Service, layer: configuredLayer, deps: [] }) diff --git a/packages/core/test/database-path.test.ts b/packages/core/test/database-path.test.ts index 20ab38957c..0524c4ef42 100644 --- a/packages/core/test/database-path.test.ts +++ b/packages/core/test/database-path.test.ts @@ -6,16 +6,15 @@ import { Global } from "@opencode-ai/core/global" import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { tmpdir } from "./fixture/tmpdir" +const resolve = (env: Record) => + Effect.runPromise(Effect.provide(Database.configuredPath, ConfigProvider.layer(ConfigProvider.fromUnknown(env)))) + describe("Database placement", () => { - test("resolves explicit files, relative names, and the channel default", () => { - expect(Database.resolvePath({ file: ":memory:", disableChannelDb: false })).toBe(":memory:") - expect(Database.resolvePath({ file: "/tmp/explicit.db", disableChannelDb: false })).toBe("/tmp/explicit.db") - expect(Database.resolvePath({ file: "relative.db", disableChannelDb: false })).toBe( - path.join(Global.Path.data, "relative.db"), - ) - expect(Database.resolvePath({ file: undefined, disableChannelDb: true })).toBe( - path.join(Global.Path.data, "opencode.db"), - ) + test("resolves explicit files, relative names, and the channel default", async () => { + expect(await resolve({ OPENCODE_DB: ":memory:" })).toBe(":memory:") + expect(await resolve({ OPENCODE_DB: "/tmp/explicit.db" })).toBe("/tmp/explicit.db") + expect(await resolve({ OPENCODE_DB: "relative.db" })).toBe(path.join(Global.Path.data, "relative.db")) + expect(await resolve({ OPENCODE_DISABLE_CHANNEL_DB: "true" })).toBe(path.join(Global.Path.data, "opencode.db")) }) test("reads placement from the active ConfigProvider when the layer is built", async () => { diff --git a/packages/opencode/src/cli/cmd/db.ts b/packages/opencode/src/cli/cmd/db.ts index 9e7e37e18e..b2721974cc 100644 --- a/packages/opencode/src/cli/cmd/db.ts +++ b/packages/opencode/src/cli/cmd/db.ts @@ -35,7 +35,7 @@ const QueryCommand = effectCmd({ } return } - const child = spawn("sqlite3", [Database.path()], { + const child = spawn("sqlite3", [yield* Database.configuredPath], { stdio: "inherit", }) yield* Effect.promise(() => new Promise((resolve) => child.on("close", resolve))) @@ -47,7 +47,7 @@ const PathCommand = effectCmd({ describe: "print the database path", instance: false, handler: Effect.fn("Cli.db.path")(function* () { - console.log(Database.path()) + console.log(yield* Database.configuredPath) }), }) diff --git a/packages/opencode/test/fixture/db.ts b/packages/opencode/test/fixture/db.ts index 88f1097f2d..6c7c41be7d 100644 --- a/packages/opencode/test/fixture/db.ts +++ b/packages/opencode/test/fixture/db.ts @@ -1,10 +1,11 @@ import { rm } from "fs/promises" import { Database } from "@opencode-ai/core/database/database" +import { Effect } from "effect" import { disposeAllInstances } from "./fixture" export async function resetDatabase() { await disposeAllInstances().catch(() => undefined) - const dbPath = Database.path() + const dbPath = await Effect.runPromise(Database.configuredPath) await rm(dbPath, { force: true }).catch(() => undefined) await rm(`${dbPath}-wal`, { force: true }).catch(() => undefined) await rm(`${dbPath}-shm`, { force: true }).catch(() => undefined)