database.ts 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. export * as Database from "./database"
  2. import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
  3. import { sqliteLayer } from "#sqlite"
  4. import { Context, Effect, Layer, Schema } from "effect"
  5. import { Global } from "../global"
  6. import { isAbsolute, join } from "path"
  7. import { DatabaseMigration } from "./migration"
  8. import { InstallationChannel } from "../installation/version"
  9. import { makeGlobalNode } from "../effect/app-node"
  10. const makeDatabase = EffectDrizzleSqlite.makeWithDefaults()
  11. type DatabaseShape = Effect.Success<typeof makeDatabase>
  12. export interface Interface {
  13. db: DatabaseShape
  14. }
  15. export const Options = Schema.Struct({
  16. path: Schema.optional(Schema.String),
  17. })
  18. export type Options = typeof Options.Type
  19. export class Service extends Context.Service<Service, Interface>()("@opencode/v2/storage/Database") {}
  20. const databaseLayer = Layer.effect(
  21. Service,
  22. Effect.gen(function* () {
  23. const db = yield* makeDatabase
  24. yield* db.run("PRAGMA journal_mode = WAL")
  25. yield* db.run("PRAGMA synchronous = NORMAL")
  26. yield* db.run("PRAGMA busy_timeout = 5000")
  27. yield* db.run("PRAGMA cache_size = -64000")
  28. yield* db.run("PRAGMA foreign_keys = ON")
  29. yield* db.run("PRAGMA wal_checkpoint(PASSIVE)")
  30. yield* DatabaseMigration.apply(db)
  31. return { db }
  32. }).pipe(Effect.orDie),
  33. )
  34. export function layer(options?: Options) {
  35. return Layer.suspend(() => {
  36. const provide = (filename: string) => databaseLayer.pipe(Layer.provide(sqliteLayer({ filename })))
  37. if (options?.path === ":memory:" || (options?.path && isAbsolute(options.path))) return provide(options.path)
  38. if (options?.path) return provide(join(Global.Path.data, options.path))
  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 provide(join(Global.Path.data, "opencode.db"))
  45. return provide(
  46. join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`),
  47. )
  48. })
  49. }
  50. export const node = makeGlobalNode({
  51. service: Service,
  52. layer: layer({ path: ":memory:" }),
  53. deps: [],
  54. })