Add background code migration service (#26652)

This commit is contained in:
Dax
2026-05-10 20:16:42 -04:00
committed by GitHub
parent 128d10d9e9
commit 7cea32ee08
5 changed files with 1635 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
CREATE TABLE `data_migration` (
`name` text PRIMARY KEY,
`time_completed` integer NOT NULL
);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"
export const DataMigrationTable = sqliteTable("data_migration", {
name: text().primaryKey(),
time_completed: integer().notNull(),
})

View File

@@ -0,0 +1,59 @@
import { Context, Effect, Layer } from "effect"
import { Database } from "./storage/db"
import { DataMigrationTable } from "./data-migration.sql"
import * as Log from "@opencode-ai/core/util/log"
import { eq } from "drizzle-orm"
export type Migration<R = never> = {
name: string
run: Effect.Effect<void, unknown, R>
}
const log = Log.create({ service: "data-migration" })
export interface Interface {}
export class Service extends Context.Service<Service, Interface>()("@opencode/DataMigration") {}
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const migrations: Migration[] = []
yield* Effect.gen(function* () {
if (migrations.length === 0) return
// Migrations run in a background fiber, so they must be resumable until
// their completion row is written.
for (const migration of migrations) {
const completed = Database.use((db) =>
db
.select({ name: DataMigrationTable.name })
.from(DataMigrationTable)
.where(eq(DataMigrationTable.name, migration.name))
.get(),
)
if (completed) continue
log.info("running data migration", { name: migration.name })
yield* migration.run
Database.use((db) =>
db
.insert(DataMigrationTable)
.values({ name: migration.name, time_completed: Date.now() })
.onConflictDoNothing()
.run(),
)
}
}).pipe(
Effect.tapCause((cause) => Effect.logError("failed to run data migrations", { cause })),
Effect.ignore,
Effect.forkScoped,
)
return Service.of({})
}),
)
export const defaultLayer = layer
export * as DataMigration from "./data-migration"

View File

@@ -54,6 +54,7 @@ import { SessionShare } from "@/share/session"
import { SyncEvent } from "@/sync"
import { Npm } from "@opencode-ai/core/npm"
import { memoMap } from "@opencode-ai/core/effect/memo-map"
import { DataMigration } from "@/data-migration"
export const AppLayer = Layer.mergeAll(
Npm.defaultLayer,
@@ -106,6 +107,7 @@ export const AppLayer = Layer.mergeAll(
ShareNext.defaultLayer,
SessionShare.defaultLayer,
SyncEvent.defaultLayer,
DataMigration.defaultLayer,
).pipe(Layer.provideMerge(InstanceLayer.layer), Layer.provideMerge(Observability.layer))
const rt = ManagedRuntime.make(AppLayer, { memoMap })