From d04eb854c6ed80ef68385409cddde9ae2a2d8c7f Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 10 May 2026 00:48:26 -0400 Subject: [PATCH] Add background code migration service --- packages/opencode/src/code-migration.ts | 77 +++++++++++++++++++++++++ packages/opencode/src/index.ts | 2 + 2 files changed, 79 insertions(+) create mode 100644 packages/opencode/src/code-migration.ts diff --git a/packages/opencode/src/code-migration.ts b/packages/opencode/src/code-migration.ts new file mode 100644 index 0000000000..c03bd47605 --- /dev/null +++ b/packages/opencode/src/code-migration.ts @@ -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 +} + +export interface Interface { + readonly start: () => Effect.Effect +} + +export class Service extends Context.Service()("@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() + return new Set((await file.json()) as string[]) +} + +async function writeCompleted(completed: Set) { + 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" diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 4c8e447041..40be1a7679 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -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")