refactor(core): replace Database.path with config-backed configuredPath

This commit is contained in:
Kit Langton 2026-07-02 09:36:46 -04:00
commit 651c23ba1d
4 changed files with 26 additions and 33 deletions

View file

@ -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: [] })

View file

@ -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<string, string>) =>
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 () => {

View file

@ -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)
}),
})

View file

@ -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)