diff --git a/packages/core/src/database/database.ts b/packages/core/src/database/database.ts index eac05ff98b..bb1c089f45 100644 --- a/packages/core/src/database/database.ts +++ b/packages/core/src/database/database.ts @@ -3,6 +3,7 @@ export * as Database from "./database" import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite" import { sqliteLayer, supportsForeignKeyToggle, supportsTuningPragmas } from "#sqlite" import { Context, Effect, Layer, Schema } from "effect" +import type { SqlClient } from "effect/unstable/sql" import { Global } from "@opencode-ai/util/global" import { isAbsolute, join } from "path" import { DatabaseMigration } from "./migration" @@ -46,14 +47,19 @@ export function layer(options: Options = { path: ":memory:" }) { return Layer.unwrap( Effect.gen(function* () { const global = yield* Global.Service - const provide = (filename: string) => databaseLayer.pipe(Layer.provide(sqliteLayer({ filename }))) const filename = options.path ?? ":memory:" - if (filename === ":memory:" || isAbsolute(filename)) return provide(filename) - return provide(join(global.data, filename)) + if (filename === ":memory:" || isAbsolute(filename)) return layerWith(sqliteLayer({ filename })) + return layerWith(sqliteLayer({ filename: join(global.data, filename) })) }), ) } +// Builds the database service over an already-configured SqlClient layer for +// runtimes that receive database storage instead of opening a filesystem path. +export function layerWith(sqlite: Layer.Layer) { + return databaseLayer.pipe(Layer.provide(sqlite)) +} + export function configured(options?: Options) { return makeGlobalNode({ service: Service, layer: layer(options), deps: [Global.node] }) } diff --git a/packages/core/test/sqlite-workerd.test.ts b/packages/core/test/sqlite-workerd.test.ts index 12b7ff6faa..74aa9df6c6 100644 --- a/packages/core/test/sqlite-workerd.test.ts +++ b/packages/core/test/sqlite-workerd.test.ts @@ -1,10 +1,11 @@ import { describe, expect, test } from "bun:test" import { Database } from "bun:sqlite" -import { Effect } from "effect" +import { Effect, Layer } from "effect" import { SqlClient } from "effect/unstable/sql" import { SqlError } from "effect/unstable/sql/SqlError" import { sqliteLayer } from "@opencode-ai/core/database/sqlite.workerd" import type { DurableObjectStorage } from "@opencode-ai/core/database/sqlite.workerd" +import { tempGlobalLayer } from "./fixture/global" // Emulates the Durable Object storage API over bun:sqlite so the adapter can // be verified without workerd or Cloudflare runtime dependencies. @@ -119,4 +120,18 @@ describe("sqlite.workerd", () => { ) expect(error).toBeInstanceOf(SqlError) }) + + test("boots the full database layer with migrations over injected storage", async () => { + const storage = makeFakeStorage() + const core = await import("@opencode-ai/core/database/database") + await Effect.runPromise( + Effect.scoped(Layer.build(core.Database.layerWith(sqliteLayer({ storage })).pipe(Layer.provide(tempGlobalLayer)))), + ) + const names = storage.sql + .exec("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name") + .toArray() + .map((row) => row.name) + expect(names).toContain("migration") + expect(names).toContain("session_v2") + }) })