refactor(flags): move channel db flag to runtime flags (#27615)
This commit is contained in:
@@ -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"),
|
||||||
|
|
||||||
|
|||||||
2
packages/desktop/src/main/env.d.ts
vendored
2
packages/desktop/src/main/env.d.ts
vendored
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }))),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user