60 lines
1.8 KiB
TypeScript
60 lines
1.8 KiB
TypeScript
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.pipe(Effect.withSpan("DataMigration", { attributes: { name: migration.name } }))
|
|
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"
|