2026-05-31 01:08:38 +00:00
|
|
|
export * as Database from "./database"
|
|
|
|
|
|
|
|
|
|
import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
|
2026-07-20 17:01:01 +00:00
|
|
|
import { sqliteLayer } from "#sqlite"
|
2026-05-31 01:08:38 +00:00
|
|
|
import { Context, Effect, Layer } from "effect"
|
|
|
|
|
import { Global } from "../global"
|
|
|
|
|
import { isAbsolute, join } from "path"
|
|
|
|
|
import { DatabaseMigration } from "./migration"
|
|
|
|
|
import { InstallationChannel } from "../installation/version"
|
2026-06-28 15:30:38 +00:00
|
|
|
import { makeGlobalNode } from "../effect/app-node"
|
2026-05-31 01:08:38 +00:00
|
|
|
|
|
|
|
|
const makeDatabase = EffectDrizzleSqlite.makeWithDefaults()
|
|
|
|
|
type DatabaseShape = Effect.Success<typeof makeDatabase>
|
|
|
|
|
|
|
|
|
|
export interface Interface {
|
|
|
|
|
db: DatabaseShape
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-20 17:01:01 +00:00
|
|
|
export interface Options {
|
|
|
|
|
readonly path?: string
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-31 01:08:38 +00:00
|
|
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/storage/Database") {}
|
|
|
|
|
|
2026-07-03 17:58:26 +00:00
|
|
|
const databaseLayer = Layer.effect(
|
2026-05-31 01:08:38 +00:00
|
|
|
Service,
|
|
|
|
|
Effect.gen(function* () {
|
|
|
|
|
const db = yield* makeDatabase
|
|
|
|
|
|
|
|
|
|
yield* db.run("PRAGMA journal_mode = WAL")
|
|
|
|
|
yield* db.run("PRAGMA synchronous = NORMAL")
|
|
|
|
|
yield* db.run("PRAGMA busy_timeout = 5000")
|
|
|
|
|
yield* db.run("PRAGMA cache_size = -64000")
|
|
|
|
|
yield* db.run("PRAGMA foreign_keys = ON")
|
|
|
|
|
yield* db.run("PRAGMA wal_checkpoint(PASSIVE)")
|
|
|
|
|
yield* DatabaseMigration.apply(db)
|
|
|
|
|
|
|
|
|
|
return { db }
|
|
|
|
|
}).pipe(Effect.orDie),
|
|
|
|
|
)
|
|
|
|
|
|
2026-07-20 17:01:01 +00:00
|
|
|
export function layer(options?: Options) {
|
|
|
|
|
return Layer.suspend(() => {
|
|
|
|
|
const provide = (filename: string) => databaseLayer.pipe(Layer.provide(sqliteLayer({ filename })))
|
|
|
|
|
if (options?.path === ":memory:" || (options?.path && isAbsolute(options.path))) return provide(options.path)
|
|
|
|
|
if (options?.path) return provide(join(Global.Path.data, options.path))
|
|
|
|
|
if (
|
|
|
|
|
["latest", "beta", "prod"].includes(InstallationChannel) ||
|
|
|
|
|
process.env.OPENCODE_DISABLE_CHANNEL_DB === "1" ||
|
|
|
|
|
process.env.OPENCODE_DISABLE_CHANNEL_DB === "true"
|
|
|
|
|
)
|
|
|
|
|
return provide(join(Global.Path.data, "opencode.db"))
|
|
|
|
|
return provide(
|
|
|
|
|
join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`),
|
|
|
|
|
)
|
|
|
|
|
})
|
2026-05-31 01:08:38 +00:00
|
|
|
}
|
|
|
|
|
|
2026-07-09 21:25:40 +00:00
|
|
|
export const node = makeGlobalNode({
|
|
|
|
|
service: Service,
|
2026-07-20 17:01:01 +00:00
|
|
|
layer: layer(),
|
2026-07-09 21:25:40 +00:00
|
|
|
deps: [],
|
|
|
|
|
})
|