refactor(core): move database schema ownership (#29068)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Dax
2026-05-30 21:08:38 -04:00
committed by GitHub
co-authored by copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
parent 6bcb9cb9bb
commit 7f571d36ea
390 changed files with 11127 additions and 9164 deletions
+2 -3
View File
@@ -1,11 +1,10 @@
import { rm } from "fs/promises"
import { Database } from "@/storage/db"
import { Database } from "@opencode-ai/core/database/database"
import { disposeAllInstances } from "./fixture"
export async function resetDatabase() {
await disposeAllInstances().catch(() => undefined)
Database.close()
const dbPath = Database.getPath()
const dbPath = Database.path()
await rm(dbPath, { force: true }).catch(() => undefined)
await rm(`${dbPath}-wal`, { force: true }).catch(() => undefined)
await rm(`${dbPath}-shm`, { force: true }).catch(() => undefined)
+19 -35
View File
@@ -1,9 +1,8 @@
import { $ } from "bun"
import * as Observability from "@opencode-ai/core/effect/observability"
import * as fs from "fs/promises"
import os from "os"
import path from "path"
import { Effect, Context, Layer, ManagedRuntime } from "effect"
import { Effect, Context, Layer } from "effect"
import type * as PlatformError from "effect/PlatformError"
import type * as Scope from "effect/Scope"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
@@ -18,35 +17,31 @@ import { TestLLMServer } from "../lib/llm-server"
const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void }))
export const testInstanceStoreLayer = InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap))
const testInstanceRuntime = ManagedRuntime.make(testInstanceStoreLayer.pipe(Layer.provideMerge(Observability.layer)))
const runTestInstanceStore = <A>(fn: (store: InstanceStore.Interface) => Effect.Effect<A>) =>
testInstanceRuntime.runPromise(InstanceStore.Service.use(fn))
export async function provideTestInstance<R>(input: {
directory: string
init?: Effect.Effect<void>
fn: (ctx: InstanceContext) => R
}) {
const ctx = await runTestInstanceStore((store) => store.load({ directory: input.directory }))
const ctx = await InstanceRuntime.load({ directory: input.directory })
try {
if (input.init) await testInstanceRuntime.runPromise(input.init.pipe(Effect.provideService(InstanceRef, ctx)))
if (input.init) await Effect.runPromise(input.init.pipe(Effect.provideService(InstanceRef, ctx)))
return await input.fn(ctx)
} finally {
await runTestInstanceStore((store) => store.dispose(ctx))
await InstanceRuntime.disposeInstance(ctx)
}
}
export async function withTestInstance<R>(input: { directory: string; fn: (ctx: InstanceContext) => R }) {
return input.fn(await runTestInstanceStore((store) => store.load({ directory: input.directory })))
return input.fn(await InstanceRuntime.load({ directory: input.directory }))
}
export async function reloadTestInstance(input: { directory: string }) {
return runTestInstanceStore((store) => store.reload(input))
return InstanceRuntime.reloadInstance(input)
}
export async function disposeAllInstances() {
await Promise.all([InstanceRuntime.disposeAllInstances(), runTestInstanceStore((store) => store.disposeAll())])
await InstanceRuntime.disposeAllInstances()
}
// Strip null bytes from paths (defensive fix for CI environment issues)
@@ -119,9 +114,10 @@ export async function tmpdir<T>(options?: TmpDirOptions<T>) {
}
/** Effectful scoped tmpdir. Cleaned up when the scope closes. Make sure these stay in sync */
export function tmpdirScoped(options?: {
export function tmpdirScoped<E = never, R = never>(options?: {
git?: boolean
config?: Partial<Config.Info> | (() => Partial<Config.Info>)
init?: (directory: string) => Effect.Effect<void, E, R>
}) {
return Effect.gen(function* () {
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
@@ -158,19 +154,16 @@ export function tmpdirScoped(options?: {
)
}
if (options?.init) yield* options.init(dir)
return dir
})
}
export const provideInstance =
(directory: string) =>
<A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
Effect.contextWith((services: Context.Context<R>) =>
Effect.promise<A>(async () => {
const ctx = await runTestInstanceStore((store) => store.load({ directory }))
return Effect.runPromiseWith(services)(self.pipe(Effect.provideService(InstanceRef, ctx)))
}),
)
<A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R | InstanceStore.Service> =>
InstanceStore.Service.use((store) => store.provide({ directory }, self))
export const provideInstanceEffect =
(directory: string) =>
@@ -188,21 +181,8 @@ export function provideTmpdirInstance<A, E, R>(
) {
return Effect.gen(function* () {
const path = yield* tmpdirScoped(options)
let provided = false
yield* Effect.addFinalizer(() =>
provided
? Effect.promise(() =>
runTestInstanceStore((store) =>
store.load({ directory: path }).pipe(Effect.flatMap((ctx) => store.dispose(ctx))),
),
).pipe(Effect.ignore)
: Effect.void,
)
provided = true
return yield* self(path).pipe(provideInstance(path))
})
}).pipe(Effect.provide(testInstanceStoreLayer))
}
export class TestInstance extends Context.Service<TestInstance, { readonly directory: string }>()("@test/Instance") {}
@@ -214,7 +194,11 @@ export const requireInstance = Effect.gen(function* () {
})
export const withTmpdirInstance =
(options?: { git?: boolean; config?: Partial<Config.Info> | (() => Partial<Config.Info>) }) =>
<E2 = never, R2 = never>(options?: {
git?: boolean
config?: Partial<Config.Info> | (() => Partial<Config.Info>)
init?: (directory: string) => Effect.Effect<void, E2, R2>
}) =>
<A, E, R>(self: Effect.Effect<A, E, R>) =>
Effect.gen(function* () {
const directory = yield* tmpdirScoped(options)
+2 -2
View File
@@ -1,4 +1,4 @@
import type { WorkspaceID } from "@/control-plane/schema"
import type { WorkspaceV2 } from "@opencode-ai/core/workspace"
import { Flag } from "@opencode-ai/core/flag/flag"
import { Effect, Scope } from "effect"
@@ -7,7 +7,7 @@ import { Effect, Scope } from "effect"
* on entry and restores it via finalizer when the surrounding scope closes —
* preserves the original try/finally semantics regardless of test outcome.
*/
export function withFixedWorkspaceID(id: WorkspaceID): Effect.Effect<void, never, Scope.Scope> {
export function withFixedWorkspaceID(id: WorkspaceV2.ID): Effect.Effect<void, never, Scope.Scope> {
return Effect.gen(function* () {
const previous = Flag.OPENCODE_WORKSPACE_ID
Flag.OPENCODE_WORKSPACE_ID = id
+4 -2
View File
@@ -1,5 +1,6 @@
import { FetchHttpClient } from "effect/unstable/http"
import { Layer } from "effect"
import { Database } from "@opencode-ai/core/database/database"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Auth } from "../../src/auth"
import { Workspace } from "../../src/control-plane/workspace"
@@ -10,16 +11,17 @@ import { Project } from "../../src/project/project"
import { Vcs } from "../../src/project/vcs"
import { Session } from "../../src/session/session"
import { SessionPrompt } from "../../src/session/prompt"
import { SyncEvent } from "../../src/sync"
import { EventV2Bridge } from "../../src/event-v2-bridge"
export const workspaceLayerWithRuntimeFlags = (overrides: Partial<RuntimeFlags.Info>) =>
Workspace.layer.pipe(
Layer.provide(Auth.defaultLayer),
Layer.provide(Session.defaultLayer),
Layer.provide(SyncEvent.defaultLayer),
Layer.provide(SessionPrompt.defaultLayer),
Layer.provide(Project.defaultLayer),
Layer.provide(Vcs.defaultLayer),
Layer.provide(Database.defaultLayer),
Layer.provide(EventV2Bridge.defaultLayer),
Layer.provide(FetchHttpClient.layer),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(RuntimeFlags.layer(overrides)),