Files
opencode/packages/core/src/database/database.ts
T
Kit Langton 15fd792d92 refactor: simplify the workerd profile surface
Review pass over the profile. fff.workerd.ts becomes the standard bind()
shim over the shared fff module instead of a hand-copied 119-line type
surface that had already drifted. The dead Options.paths and its
redundant Global override are deleted. The copy-pasted MCP clientInfo
block becomes a ServerOptions mcp.stdio capability flag handled by the
standard routes replacement. Database.configuredClient joins configured()
so the profile stops hand-assembling the node's Global dependency.
ServerFetch.make folds overrides into BootOptions so the embed seam has
one shape, and sdk-next's EmbedOptions aliases it. The drizzle session's
duck-typed transactionStatements check becomes the named
NativeTransactionSqlClient contract that sqlite.workerd satisfies. The
bundled models.dev snapshot is decoded and normalized once per isolate
instead of per runtime, which matters when one isolate hosts many
Durable Objects. Spike-test interceptor and log-read boilerplate
collapse into the existing helpers, and the vitest 3.2.7 pins get their
rationale in the config.
2026-08-11 23:18:25 -04:00

78 lines
2.8 KiB
TypeScript

export * as Database from "./database.js"
import { EffectDrizzleSqlite } from "./drizzle.js"
import { sqliteLayer, supportsForeignKeyToggle, supportsTuningPragmas } from "#sqlite"
import { Context, Effect, Layer, Schema } from "effect"
import type { SqlClient } from "effect/unstable/sql"
import { Global } from "@opencode-ai/util/global"
import { isAbsolute, join } from "path"
import { DatabaseMigration } from "./migration.js"
import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
const makeDatabase = EffectDrizzleSqlite.makeWithDefaults()
type DatabaseShape = Effect.Success<typeof makeDatabase>
export interface Interface {
db: DatabaseShape
}
export const Options = Schema.Struct({
path: Schema.optional(Schema.String),
})
export type Options = typeof Options.Type
export class Service extends Context.Service<Service, Interface>()("@opencode/storage/Database") {}
const databaseLayer = Layer.effect(
Service,
Effect.gen(function* () {
const db = yield* makeDatabase
if (supportsTuningPragmas) {
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 wal_checkpoint(PASSIVE)")
}
// Durable Object SQLite always enforces foreign keys and rejects the pragma.
if (supportsForeignKeyToggle) yield* db.run("PRAGMA foreign_keys = ON")
yield* DatabaseMigration.apply(db)
return { db }
}).pipe(Effect.orDie),
)
export function layer(options: Options = { path: ":memory:" }) {
return Layer.unwrap(
Effect.gen(function* () {
const provide = (filename: string) => layerFromClient.pipe(Layer.provide(sqliteLayer({ filename })))
const filename = options.path ?? ":memory:"
if (filename === ":memory:" || isAbsolute(filename)) return provide(filename)
const global = yield* Global.Service
return provide(join(global.data, filename))
}),
)
}
// The database service over an injected SqlClient, for runtimes that receive
// database storage instead of opening a filesystem path. Any client provided
// here still goes through the pragma guards and migrations; Global is required
// because migrations may read it (the v1 import).
export const layerFromClient: Layer.Layer<Service, never, SqlClient.SqlClient | Global.Service> = databaseLayer
export function configured(options?: Options) {
return makeGlobalNode({ service: Service, layer: layer(options), deps: [Global.node] })
}
/** `configured`, but over an injected SqlClient layer instead of a filesystem path. */
export function configuredClient(client: Layer.Layer<SqlClient.SqlClient>) {
return makeGlobalNode({
service: Service,
layer: layerFromClient.pipe(Layer.provide(client)),
deps: [Global.node],
})
}
export const node = configured({ path: ":memory:" })