database.ts 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. export * as Database from "./database"
  2. import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
  3. import { layer } from "#sqlite"
  4. import { Context, Effect, Layer } from "effect"
  5. import { Global } from "../global"
  6. import { Flag } from "../flag/flag"
  7. import { isAbsolute, join } from "path"
  8. import { DatabaseMigration } from "./migration"
  9. import { InstallationChannel } from "../installation/version"
  10. import { makeGlobalNode } from "../effect/app-node"
  11. const makeDatabase = EffectDrizzleSqlite.makeWithDefaults()
  12. type DatabaseShape = Effect.Success<typeof makeDatabase>
  13. export interface Interface {
  14. db: DatabaseShape
  15. }
  16. export class Service extends Context.Service<Service, Interface>()("@opencode/v2/storage/Database") {}
  17. const databaseLayer = Layer.effect(
  18. Service,
  19. Effect.gen(function* () {
  20. const db = yield* makeDatabase
  21. yield* db.run("PRAGMA journal_mode = WAL")
  22. yield* db.run("PRAGMA synchronous = NORMAL")
  23. yield* db.run("PRAGMA busy_timeout = 5000")
  24. yield* db.run("PRAGMA cache_size = -64000")
  25. yield* db.run("PRAGMA foreign_keys = ON")
  26. yield* db.run("PRAGMA wal_checkpoint(PASSIVE)")
  27. yield* DatabaseMigration.apply(db)
  28. return { db }
  29. }).pipe(Effect.orDie),
  30. )
  31. export function layerFromPath(filename: string) {
  32. return databaseLayer.pipe(Layer.provide(layer({ filename })))
  33. }
  34. export function path() {
  35. if (Flag.OPENCODE_DB) {
  36. if (Flag.OPENCODE_DB === ":memory:" || isAbsolute(Flag.OPENCODE_DB)) return Flag.OPENCODE_DB
  37. return join(Global.Path.data, Flag.OPENCODE_DB)
  38. }
  39. if (
  40. ["latest", "beta", "prod"].includes(InstallationChannel) ||
  41. process.env.OPENCODE_DISABLE_CHANNEL_DB === "1" ||
  42. process.env.OPENCODE_DISABLE_CHANNEL_DB === "true"
  43. )
  44. return join(Global.Path.data, "opencode.db")
  45. return join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`)
  46. }
  47. // Resolve the database path lazily so tests and embedders that set
  48. // Flag.OPENCODE_DB after module evaluation still control the storage target.
  49. export const node = makeGlobalNode({
  50. service: Service,
  51. layer: Layer.suspend(() => layerFromPath(path())),
  52. deps: [],
  53. })