Compare commits

...

1 Commits

Author SHA1 Message Date
Dax Raad d04eb854c6 Add background code migration service 2026-05-10 00:48:26 -04:00
2 changed files with 79 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
import { Context, Effect, Layer } from "effect"
import { makeRuntime } from "./effect/run-service"
import { Database, type TxOrDb } from "./storage/db"
import { Global } from "@opencode-ai/core/global"
import * as Log from "@opencode-ai/core/util/log"
import path from "path"
import { mkdir } from "fs/promises"
export type Migration = {
name: string
run: (db: TxOrDb) => void | Promise<void>
}
export interface Interface {
readonly start: () => Effect.Effect<void>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/CodeMigration") {}
const log = Log.create({ service: "code-migration" })
const marker = path.join(Global.Path.data, "code-migration.json")
export const All: Migration[] = []
async function readCompleted() {
const file = Bun.file(marker)
if (!(await file.exists())) return new Set<string>()
return new Set((await file.json()) as string[])
}
async function writeCompleted(completed: Set<string>) {
await mkdir(path.dirname(marker), { recursive: true })
await Bun.write(marker, JSON.stringify([...completed].sort(), null, 2))
}
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
let started = false
const start = Effect.fn("CodeMigration.start")(function* () {
if (started) return
started = true
yield* Effect.sync(() => {
void runPending().catch((error) => {
log.error("failed to run code migrations", { error })
})
})
})
return Service.of({ start })
}),
)
export const defaultLayer = layer
async function runPending() {
if (All.length === 0) return
const db = Database.Client()
const completed = await readCompleted()
for (const migration of All.filter((item) => !completed.has(item.name))) {
log.info("running code migration", { name: migration.name })
await migration.run(db)
completed.add(migration.name)
await writeCompleted(completed)
}
}
const { runFork } = makeRuntime(Service, defaultLayer)
export function start() {
runFork((svc) => svc.start())
}
export * as CodeMigration from "./code-migration"
+2
View File
@@ -39,6 +39,7 @@ import { PluginCommand } from "./cli/cmd/plug"
import { Heap } from "./cli/heap"
import { drizzle } from "drizzle-orm/bun-sqlite"
import { ensureProcessMetadata } from "@opencode-ai/core/util/opencode-process"
import { CodeMigration } from "./code-migration"
const processMetadata = ensureProcessMetadata("main")
@@ -151,6 +152,7 @@ const cli = yargs(args)
}
process.stderr.write("Database migration complete." + EOL)
}
CodeMigration.start()
})
.usage("")
.completion("completion", "generate shell completion script")