refactor(opencode): unify drizzle client through effect adapter

The Effect adapter is now the only Drizzle wrapper over the bun:sqlite
handle. Database.Client owns the lifecycle, DatabaseEffect.Service just
exposes that handle. Removes acquire/release ref counting that was
ceremony around the same module-level singleton.
This commit is contained in:
Kit Langton
2026-04-28 14:42:59 -04:00
parent aaa42cca07
commit 322bb01257
4 changed files with 16 additions and 58 deletions

View File

@@ -1,21 +1,12 @@
import { Database } from "@/storage/db"
import * as StorageSchema from "@/storage/schema"
import { Context, Effect, Layer } from "effect"
import { drizzle, type EffectSQLiteDatabase } from "@opencode-ai/effect-drizzle-sqlite"
import type { EffectSQLiteDatabase } from "@opencode-ai/effect-drizzle-sqlite"
import * as StorageSchema from "@/storage/schema"
const schema = { ...StorageSchema }
export class Service extends Context.Service<Service, EffectSQLiteDatabase<typeof StorageSchema>>()(
"@opencode/DatabaseEffect",
) {}
export class Service extends Context.Service<Service, EffectSQLiteDatabase<typeof schema>>()("@opencode/DatabaseEffect") {}
export const layer = Layer.effect(
Service,
Effect.acquireRelease(
Effect.sync(() => {
const lease = Database.acquire()
return { lease, db: drizzle({ client: lease.client.$client, schema }) }
}),
(value) => Effect.sync(() => value.lease.release()),
).pipe(Effect.map((value) => value.db)),
)
export const layer = Layer.effect(Service, Effect.sync(Database.Client))
export * as DatabaseEffect from "./db-effect"

View File

@@ -1,8 +1,6 @@
import { Database } from "bun:sqlite"
import { drizzle } from "drizzle-orm/bun-sqlite"
import { drizzle } from "@opencode-ai/effect-drizzle-sqlite"
export function init(path: string) {
const sqlite = new Database(path, { create: true })
const db = drizzle({ client: sqlite })
return db
export function init<TSchema extends Record<string, unknown>>(path: string, schema: TSchema) {
return drizzle({ client: new Database(path, { create: true }), schema })
}

View File

@@ -1,8 +1,6 @@
import { DatabaseSync } from "node:sqlite"
import { drizzle } from "drizzle-orm/node-sqlite"
export function init(path: string) {
const sqlite = new DatabaseSync(path)
const db = drizzle({ client: sqlite })
return db
export function init<TSchema extends Record<string, unknown>>(path: string, schema: TSchema) {
return drizzle({ client: new DatabaseSync(path), schema })
}

View File

@@ -1,6 +1,4 @@
import { type SQLiteBunDatabase } from "drizzle-orm/bun-sqlite"
import { migrate } from "drizzle-orm/bun-sqlite/migrator"
import { type SQLiteTransaction } from "drizzle-orm/sqlite-core"
export * from "drizzle-orm"
import { LocalContext } from "@/util/local-context"
import { lazy } from "../util/lazy"
@@ -14,6 +12,7 @@ import { Flag } from "@opencode-ai/core/flag/flag"
import { InstallationChannel } from "@opencode-ai/core/installation/version"
import { InstanceState } from "@/effect/instance-state"
import { iife } from "@/util/iife"
import * as StorageSchema from "@/storage/schema"
import { init } from "#db"
declare const OPENCODE_MIGRATIONS: { sql: string; timestamp: number; name: string }[] | undefined
@@ -42,9 +41,9 @@ export const Path = iife(() => {
return getChannelPath()
})
export type Transaction = SQLiteTransaction<"sync", void>
export type Client = ReturnType<typeof open>
export type Client = SQLiteBunDatabase
export type Transaction = Parameters<Parameters<Client["transaction"]>[0]>[0]
type Journal = { sql: string; timestamp: number; name: string }[]
@@ -91,7 +90,7 @@ function migrations(dir: string): Journal {
export function open() {
log.info("opening database", { path: Path })
const db = init(Path)
const db = init(Path, StorageSchema)
db.run("PRAGMA journal_mode = WAL")
db.run("PRAGMA synchronous = NORMAL")
@@ -123,38 +122,10 @@ export function open() {
export const Client = lazy(open)
let layerRefs = 0
let layerOwner: Client | undefined
export function acquire() {
const owner = Client.peek() === undefined
const client = Client()
if (owner) layerOwner = client
layerRefs++
let released = false
return {
client,
release() {
if (released) return
released = true
layerRefs--
if (layerRefs === 0 && layerOwner === client) {
layerOwner = undefined
close(client)
}
},
}
}
export function close(client = Client.peek()) {
if (!client) return
client.$client.close()
if (Client.peek() === client) {
layerRefs = 0
layerOwner = undefined
Client.reset()
}
if (Client.peek() === client) Client.reset()
}
export type TxOrDb = Transaction | Client