mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-15 17:08:21 -04:00
78 lines
2.8 KiB
TypeScript
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:" })
|