refactor(flags): move channel db flag to runtime flags (#27615)

This commit is contained in:
Shoubhit Dash
2026-05-15 04:09:10 +05:30
committed by GitHub
parent cb4f5cdea9
commit fc34c74567
8 changed files with 93 additions and 52 deletions

View File

@@ -62,7 +62,6 @@ export const Flag = {
OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"], OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"],
OPENCODE_MODELS_PATH: process.env["OPENCODE_MODELS_PATH"], OPENCODE_MODELS_PATH: process.env["OPENCODE_MODELS_PATH"],
OPENCODE_DB: process.env["OPENCODE_DB"], OPENCODE_DB: process.env["OPENCODE_DB"],
OPENCODE_DISABLE_CHANNEL_DB: truthy("OPENCODE_DISABLE_CHANNEL_DB"),
OPENCODE_SKIP_MIGRATIONS: truthy("OPENCODE_SKIP_MIGRATIONS"), OPENCODE_SKIP_MIGRATIONS: truthy("OPENCODE_SKIP_MIGRATIONS"),
OPENCODE_STRICT_CONFIG_DEPS: truthy("OPENCODE_STRICT_CONFIG_DEPS"), OPENCODE_STRICT_CONFIG_DEPS: truthy("OPENCODE_STRICT_CONFIG_DEPS"),

View File

@@ -19,7 +19,7 @@ declare module "virtual:opencode-server" {
export const init: typeof import("../../../opencode/dist/types/src/node").Log.init export const init: typeof import("../../../opencode/dist/types/src/node").Log.init
} }
export namespace Database { export namespace Database {
export const Path: typeof import("../../../opencode/dist/types/src/node").Database.Path export const getPath: typeof import("../../../opencode/dist/types/src/node").Database.getPath
export const Client: typeof import("../../../opencode/dist/types/src/node").Database.Client export const Client: typeof import("../../../opencode/dist/types/src/node").Database.Client
} }
export namespace JsonMigration { export namespace JsonMigration {

View File

@@ -28,7 +28,7 @@ const QueryCommand = cmd({
handler: async (args: { query?: string; format: string }) => { handler: async (args: { query?: string; format: string }) => {
const query = args.query as string | undefined const query = args.query as string | undefined
if (query) { if (query) {
const db = new BunDatabase(Database.Path, { readonly: true }) const db = new BunDatabase(Database.getPath(), { readonly: true })
try { try {
const result = db.query(query).all() as Record<string, unknown>[] const result = db.query(query).all() as Record<string, unknown>[]
if (args.format === "json") { if (args.format === "json") {
@@ -47,7 +47,7 @@ const QueryCommand = cmd({
db.close() db.close()
return return
} }
const child = spawn("sqlite3", [Database.Path], { const child = spawn("sqlite3", [Database.getPath()], {
stdio: "inherit", stdio: "inherit",
}) })
await new Promise((resolve) => child.on("close", resolve)) await new Promise((resolve) => child.on("close", resolve))
@@ -58,7 +58,7 @@ const PathCommand = cmd({
command: "path", command: "path",
describe: "print the database path", describe: "print the database path",
handler: () => { handler: () => {
console.log(Database.Path) console.log(Database.getPath())
}, },
}) })
@@ -66,7 +66,7 @@ const MigrateCommand = cmd({
command: "migrate", command: "migrate",
describe: "migrate JSON data to SQLite (merges with existing data)", describe: "migrate JSON data to SQLite (merges with existing data)",
handler: async () => { handler: async () => {
const sqlite = new BunDatabase(Database.Path) const sqlite = new BunDatabase(Database.getPath())
const tty = process.stderr.isTTY const tty = process.stderr.isTTY
const width = 36 const width = 36
const orange = "\x1b[38;5;214m" const orange = "\x1b[38;5;214m"

View File

@@ -15,6 +15,7 @@ export class Service extends ConfigService.Service<Service>()("@opencode/Runtime
autoShare: bool("OPENCODE_AUTO_SHARE"), autoShare: bool("OPENCODE_AUTO_SHARE"),
pure: bool("OPENCODE_PURE"), pure: bool("OPENCODE_PURE"),
disableDefaultPlugins: bool("OPENCODE_DISABLE_DEFAULT_PLUGINS"), disableDefaultPlugins: bool("OPENCODE_DISABLE_DEFAULT_PLUGINS"),
disableChannelDb: bool("OPENCODE_DISABLE_CHANNEL_DB"),
disableEmbeddedWebUi: bool("OPENCODE_DISABLE_EMBEDDED_WEB_UI"), disableEmbeddedWebUi: bool("OPENCODE_DISABLE_EMBEDDED_WEB_UI"),
disableClaudeCodeSkills: Config.all({ disableClaudeCodeSkills: Config.all({
broad: bool("OPENCODE_DISABLE_CLAUDE_CODE"), broad: bool("OPENCODE_DISABLE_CLAUDE_CODE"),

View File

@@ -2,8 +2,8 @@ import { type SQLiteBunDatabase } from "drizzle-orm/bun-sqlite"
import { migrate } from "drizzle-orm/bun-sqlite/migrator" import { migrate } from "drizzle-orm/bun-sqlite/migrator"
import { type SQLiteTransaction } from "drizzle-orm/sqlite-core" import { type SQLiteTransaction } from "drizzle-orm/sqlite-core"
export * from "drizzle-orm" export * from "drizzle-orm"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { LocalContext } from "@/util/local-context" import { LocalContext } from "@/util/local-context"
import { lazy } from "../util/lazy"
import { Global } from "@opencode-ai/core/global" import { Global } from "@opencode-ai/core/global"
import * as Log from "@opencode-ai/core/util/log" import * as Log from "@opencode-ai/core/util/log"
import { NamedError } from "@opencode-ai/core/util/error" import { NamedError } from "@opencode-ai/core/util/error"
@@ -12,9 +12,8 @@ import { readFileSync, readdirSync, existsSync } from "fs"
import { Flag } from "@opencode-ai/core/flag/flag" import { Flag } from "@opencode-ai/core/flag/flag"
import { InstallationChannel } from "@opencode-ai/core/installation/version" import { InstallationChannel } from "@opencode-ai/core/installation/version"
import { InstanceState } from "@/effect/instance-state" import { InstanceState } from "@/effect/instance-state"
import { iife } from "@/util/iife"
import { init } from "#db" import { init } from "#db"
import { Schema } from "effect" import { Effect, Schema } from "effect"
declare const OPENCODE_MIGRATIONS: { sql: string; timestamp: number; name: string }[] | undefined declare const OPENCODE_MIGRATIONS: { sql: string; timestamp: number; name: string }[] | undefined
@@ -24,24 +23,29 @@ export const NotFoundError = NamedError.create("NotFoundError", {
const log = Log.create({ service: "db" }) const log = Log.create({ service: "db" })
export function getChannelPath() { type ChannelDbFlags = Pick<RuntimeFlags.Info, "disableChannelDb">
if (["latest", "beta", "prod"].includes(InstallationChannel) || Flag.OPENCODE_DISABLE_CHANNEL_DB)
const readRuntimeFlags = () =>
Effect.runSync(RuntimeFlags.Service.useSync((flags) => flags).pipe(Effect.provide(RuntimeFlags.defaultLayer)))
export function getChannelPath(flags: ChannelDbFlags = readRuntimeFlags()) {
if (["latest", "beta", "prod"].includes(InstallationChannel) || flags.disableChannelDb)
return path.join(Global.Path.data, "opencode.db") return path.join(Global.Path.data, "opencode.db")
const safe = InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-") const safe = InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")
return path.join(Global.Path.data, `opencode-${safe}.db`) return path.join(Global.Path.data, `opencode-${safe}.db`)
} }
export const Path = iife(() => { export const getPath = (flags?: ChannelDbFlags) => {
if (Flag.OPENCODE_DB) { if (Flag.OPENCODE_DB) {
if (Flag.OPENCODE_DB === ":memory:" || path.isAbsolute(Flag.OPENCODE_DB)) return Flag.OPENCODE_DB if (Flag.OPENCODE_DB === ":memory:" || path.isAbsolute(Flag.OPENCODE_DB)) return Flag.OPENCODE_DB
return path.join(Global.Path.data, Flag.OPENCODE_DB) return path.join(Global.Path.data, Flag.OPENCODE_DB)
} }
return getChannelPath() return getChannelPath(flags)
}) }
export type Transaction = SQLiteTransaction<"sync", void> export type Transaction = SQLiteTransaction<"sync", void>
type Client = SQLiteBunDatabase type Client = ReturnType<typeof init>
type Journal = { sql: string; timestamp: number; name: string }[] type Journal = { sql: string; timestamp: number; name: string }[]
@@ -85,38 +89,55 @@ function migrations(dir: string): Journal {
return sql.sort((a, b) => a.timestamp - b.timestamp) return sql.sort((a, b) => a.timestamp - b.timestamp)
} }
export const Client = lazy(() => { let client: Client | undefined
log.info("opening database", { path: Path }) let loaded = false
const db = init(Path) export const Client = Object.assign(
(flags?: ChannelDbFlags): Client => {
if (loaded) return client as Client
db.run("PRAGMA journal_mode = WAL") const dbPath = getPath(flags)
db.run("PRAGMA synchronous = NORMAL") log.info("opening database", { path: dbPath })
db.run("PRAGMA busy_timeout = 5000")
db.run("PRAGMA cache_size = -64000")
db.run("PRAGMA foreign_keys = ON")
db.run("PRAGMA wal_checkpoint(PASSIVE)")
// Apply schema migrations const db = init(dbPath)
const entries =
typeof OPENCODE_MIGRATIONS !== "undefined" db.run("PRAGMA journal_mode = WAL")
? OPENCODE_MIGRATIONS db.run("PRAGMA synchronous = NORMAL")
: migrations(path.join(import.meta.dirname, "../../migration")) db.run("PRAGMA busy_timeout = 5000")
if (entries.length > 0) { db.run("PRAGMA cache_size = -64000")
log.info("applying migrations", { db.run("PRAGMA foreign_keys = ON")
count: entries.length, db.run("PRAGMA wal_checkpoint(PASSIVE)")
mode: typeof OPENCODE_MIGRATIONS !== "undefined" ? "bundled" : "dev",
}) // Apply schema migrations
if (Flag.OPENCODE_SKIP_MIGRATIONS) { const entries =
for (const item of entries) { typeof OPENCODE_MIGRATIONS !== "undefined"
item.sql = "select 1;" ? OPENCODE_MIGRATIONS
: migrations(path.join(import.meta.dirname, "../../migration"))
if (entries.length > 0) {
log.info("applying migrations", {
count: entries.length,
mode: typeof OPENCODE_MIGRATIONS !== "undefined" ? "bundled" : "dev",
})
if (Flag.OPENCODE_SKIP_MIGRATIONS) {
for (const item of entries) {
item.sql = "select 1;"
}
} }
applyMigrations(db, entries)
} }
applyMigrations(db, entries)
}
return db client = db
}) loaded = true
return db
},
{
reset: () => {
loaded = false
client = undefined
},
loaded: () => loaded,
},
)
export function close() { export function close() {
if (!Client.loaded()) return if (!Client.loaded()) return

View File

@@ -24,6 +24,7 @@ describe("RuntimeFlags", () => {
fromConfig({ fromConfig({
OPENCODE_PURE: "true", OPENCODE_PURE: "true",
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true", OPENCODE_DISABLE_DEFAULT_PLUGINS: "true",
OPENCODE_DISABLE_CHANNEL_DB: "true",
OPENCODE_AUTO_SHARE: "true", OPENCODE_AUTO_SHARE: "true",
OPENCODE_DISABLE_EMBEDDED_WEB_UI: "true", OPENCODE_DISABLE_EMBEDDED_WEB_UI: "true",
OPENCODE_EXPERIMENTAL: "true", OPENCODE_EXPERIMENTAL: "true",
@@ -39,6 +40,7 @@ describe("RuntimeFlags", () => {
expect(flags.pure).toBe(true) expect(flags.pure).toBe(true)
expect(flags.autoShare).toBe(true) expect(flags.autoShare).toBe(true)
expect(flags.disableDefaultPlugins).toBe(true) expect(flags.disableDefaultPlugins).toBe(true)
expect(flags.disableChannelDb).toBe(true)
expect(flags.disableEmbeddedWebUi).toBe(true) expect(flags.disableEmbeddedWebUi).toBe(true)
expect(flags.enableExa).toBe(true) expect(flags.enableExa).toBe(true)
expect(flags.enableParallel).toBe(true) expect(flags.enableParallel).toBe(true)
@@ -80,6 +82,7 @@ describe("RuntimeFlags", () => {
expect(flags.pure).toBe(false) expect(flags.pure).toBe(false)
expect(flags.autoShare).toBe(false) expect(flags.autoShare).toBe(false)
expect(flags.disableDefaultPlugins).toBe(true) expect(flags.disableDefaultPlugins).toBe(true)
expect(flags.disableChannelDb).toBe(false)
expect(flags.disableEmbeddedWebUi).toBe(false) expect(flags.disableEmbeddedWebUi).toBe(false)
expect(flags.disableClaudeCodeSkills).toBe(false) expect(flags.disableClaudeCodeSkills).toBe(false)
expect(flags.enableExa).toBe(false) expect(flags.enableExa).toBe(false)
@@ -200,6 +203,7 @@ describe("RuntimeFlags", () => {
expect(flags.pure).toBe(false) expect(flags.pure).toBe(false)
expect(flags.disableDefaultPlugins).toBe(false) expect(flags.disableDefaultPlugins).toBe(false)
expect(flags.disableChannelDb).toBe(false)
expect(flags.disableEmbeddedWebUi).toBe(false) expect(flags.disableEmbeddedWebUi).toBe(false)
expect(flags.disableClaudeCodeSkills).toBe(false) expect(flags.disableClaudeCodeSkills).toBe(false)
expect(flags.enableExa).toBe(false) expect(flags.enableExa).toBe(false)

View File

@@ -5,7 +5,8 @@ import { disposeAllInstances } from "./fixture"
export async function resetDatabase() { export async function resetDatabase() {
await disposeAllInstances().catch(() => undefined) await disposeAllInstances().catch(() => undefined)
Database.close() Database.close()
await rm(Database.Path, { force: true }).catch(() => undefined) const dbPath = Database.getPath()
await rm(`${Database.Path}-wal`, { force: true }).catch(() => undefined) await rm(dbPath, { force: true }).catch(() => undefined)
await rm(`${Database.Path}-shm`, { force: true }).catch(() => undefined) await rm(`${dbPath}-wal`, { force: true }).catch(() => undefined)
await rm(`${dbPath}-shm`, { force: true }).catch(() => undefined)
} }

View File

@@ -1,14 +1,29 @@
import { describe, expect, test } from "bun:test" import { describe, expect } from "bun:test"
import path from "path" import path from "path"
import { Effect } from "effect"
import { Global } from "@opencode-ai/core/global" import { Global } from "@opencode-ai/core/global"
import { InstallationChannel } from "@opencode-ai/core/installation/version" import { InstallationChannel } from "@opencode-ai/core/installation/version"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { Database } from "@/storage/db" import { Database } from "@/storage/db"
import { it } from "../lib/effect"
describe("Database.Path", () => { describe("Database.getChannelPath", () => {
test("returns database path for the current channel", () => { it.effect("returns database path for the current channel", () =>
const expected = ["latest", "beta"].includes(InstallationChannel) Effect.gen(function* () {
? path.join(Global.Path.data, "opencode.db") const flags = yield* RuntimeFlags.Service
: path.join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`) const expected = ["latest", "beta", "prod"].includes(InstallationChannel)
expect(Database.getChannelPath()).toBe(expected) ? path.join(Global.Path.data, "opencode.db")
}) : path.join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`)
expect(Database.getChannelPath(flags)).toBe(expected)
}).pipe(Effect.provide(RuntimeFlags.layer())),
)
it.effect("uses the shared database path when channel databases are disabled", () =>
Effect.gen(function* () {
const flags = yield* RuntimeFlags.Service
expect(Database.getChannelPath(flags)).toBe(path.join(Global.Path.data, "opencode.db"))
}).pipe(Effect.provide(RuntimeFlags.layer({ disableChannelDb: true }))),
)
}) })