Add background code migration service (#26652)
This commit is contained in:
@@ -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
@@ -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(),
|
||||||
|
})
|
||||||
@@ -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"
|
||||||
@@ -54,6 +54,7 @@ import { SessionShare } from "@/share/session"
|
|||||||
import { SyncEvent } from "@/sync"
|
import { SyncEvent } from "@/sync"
|
||||||
import { Npm } from "@opencode-ai/core/npm"
|
import { Npm } from "@opencode-ai/core/npm"
|
||||||
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
||||||
|
import { DataMigration } from "@/data-migration"
|
||||||
|
|
||||||
export const AppLayer = Layer.mergeAll(
|
export const AppLayer = Layer.mergeAll(
|
||||||
Npm.defaultLayer,
|
Npm.defaultLayer,
|
||||||
@@ -106,6 +107,7 @@ export const AppLayer = Layer.mergeAll(
|
|||||||
ShareNext.defaultLayer,
|
ShareNext.defaultLayer,
|
||||||
SessionShare.defaultLayer,
|
SessionShare.defaultLayer,
|
||||||
SyncEvent.defaultLayer,
|
SyncEvent.defaultLayer,
|
||||||
|
DataMigration.defaultLayer,
|
||||||
).pipe(Layer.provideMerge(InstanceLayer.layer), Layer.provideMerge(Observability.layer))
|
).pipe(Layer.provideMerge(InstanceLayer.layer), Layer.provideMerge(Observability.layer))
|
||||||
|
|
||||||
const rt = ManagedRuntime.make(AppLayer, { memoMap })
|
const rt = ManagedRuntime.make(AppLayer, { memoMap })
|
||||||
|
|||||||
Reference in New Issue
Block a user