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 -5
View File
@@ -1,6 +1,5 @@
import { Session } from "@/session/session"
import { SessionID } from "@/session/schema"
import { SyncEvent } from "@/sync"
import { Effect, Layer, Scope, Context } from "effect"
import { Config } from "@/config/config"
import { RuntimeFlags } from "@/effect/runtime-flags"
@@ -21,20 +20,19 @@ export const layer = Layer.effect(
const session = yield* Session.Service
const shareNext = yield* ShareNext.Service
const scope = yield* Scope.Scope
const sync = yield* SyncEvent.Service
const flags = yield* RuntimeFlags.Service
const share = Effect.fn("SessionShare.share")(function* (sessionID: SessionID) {
const conf = yield* cfg.get()
if (conf.share === "disabled") throw new Error("Sharing is disabled in configuration")
const result = yield* shareNext.create(sessionID)
yield* sync.run(Session.Event.Updated, { sessionID, info: { share: { url: result.url } } })
yield* session.setShare({ sessionID, share: { url: result.url } })
return result
})
const unshare = Effect.fn("SessionShare.unshare")(function* (sessionID: SessionID) {
yield* shareNext.remove(sessionID)
yield* sync.run(Session.Event.Updated, { sessionID, info: { share: { url: null } } })
yield* session.setShare({ sessionID, share: undefined })
})
const create = Effect.fn("SessionShare.create")(function* (input?: Session.CreateInput) {
@@ -54,7 +52,6 @@ export const defaultLayer = layer.pipe(
Layer.provide(ShareNext.defaultLayer),
Layer.provide(Session.defaultLayer),
Layer.provide(Config.defaultLayer),
Layer.provide(SyncEvent.defaultLayer),
Layer.provide(RuntimeFlags.defaultLayer),
)
+47 -54
View File
@@ -3,18 +3,20 @@ import { serviceUse } from "@opencode-ai/core/effect/service-use"
import { Effect, Exit, Layer, Option, Schema, Scope, Context, Stream } from "effect"
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import { Account } from "@/account/account"
import { Bus } from "@/bus"
import { EventV2Bridge } from "@/event-v2-bridge"
import { InstanceState } from "@/effect/instance-state"
import { Provider } from "@/provider/provider"
import { ModelID, ProviderID } from "@/provider/schema"
import { Session } from "@/session/session"
import { MessageV2 } from "@/session/message-v2"
import type { SessionID } from "@/session/schema"
import { Database } from "@/storage/db"
import { Database } from "@opencode-ai/core/database/database"
import { eq } from "drizzle-orm"
import { Config } from "@/config/config"
import * as Log from "@opencode-ai/core/util/log"
import { SessionShareTable } from "./share.sql"
import { SessionShareTable } from "@opencode-ai/core/share/sql"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { EventV2 } from "@opencode-ai/core/event"
const log = Log.create({ service: "share-next" })
const disabled = process.env["OPENCODE_DISABLE_SHARE"] === "true" || process.env["OPENCODE_DISABLE_SHARE"] === "1"
@@ -79,9 +81,6 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Sh
export const use = serviceUse(Service)
const db = <T>(fn: (d: Parameters<typeof Database.use>[0] extends (trx: infer D) => any ? D : never) => T) =>
Effect.sync(() => Database.use(fn))
function api(resource: string): Api {
return {
create: `/api/${resource}`,
@@ -113,14 +112,15 @@ export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const account = yield* Account.Service
const bus = yield* Bus.Service
const events = yield* EventV2Bridge.Service
const cfg = yield* Config.Service
const { db } = yield* Database.Service
const http = yield* HttpClient.HttpClient
const httpOk = HttpClient.filterStatusOk(http)
const provider = yield* Provider.Service
const session = yield* Session.Service
function sync(sessionID: SessionID, data: Data[]): Effect.Effect<void> {
function sync(sessionID: SessionID, data: Data[]) {
return Effect.gen(function* () {
if (disabled) return
const share = yield* getCached(sessionID)
@@ -166,49 +166,39 @@ export const layer = Layer.effect(
if (disabled) return cache
const watch = <D extends { type: string }>(
const watch = <D extends EventV2.Definition>(
def: D,
fn: (evt: { properties: any }) => Effect.Effect<void, unknown>,
fn: (data: EventV2.Data<D>) => Effect.Effect<void, unknown>,
) =>
bus.subscribe(def as never).pipe(
Effect.flatMap((stream) =>
stream.pipe(
Stream.runForEach((evt) =>
fn(evt).pipe(
Effect.catchCause((cause) =>
Effect.sync(() => {
log.error("share subscriber failed", { type: def.type, cause })
}),
),
),
),
Effect.forkScoped,
),
),
)
events.listen((event) => {
if (event.type !== def.type || event.location?.directory !== _ctx.directory) return Effect.void
return fn(event.data as EventV2.Data<D>).pipe(
Effect.catchCause((cause) => Effect.sync(() => log.error("share subscriber failed", { type: def.type, cause }))),
)
})
yield* watch(Session.Event.Updated, (evt) =>
yield* watch(Session.Event.Updated, (data) =>
Effect.gen(function* () {
const info = evt.properties.info
yield* sync(info.id, [{ type: "session", data: info }])
const info = data.info
yield* sync(info.id, [{ type: "session", data: structuredClone(info) as SDK.Session }])
}),
)
yield* watch(MessageV2.Event.Updated, (evt) =>
yield* watch(MessageV2.Event.Updated, (data) =>
Effect.gen(function* () {
const info = evt.properties.info
yield* sync(info.sessionID, [{ type: "message", data: info }])
const info = data.info
yield* sync(info.sessionID, [{ type: "message", data: structuredClone(info) as SDK.Message }])
if (info.role !== "user") return
const model = yield* provider.getModel(info.model.providerID, info.model.modelID)
yield* sync(info.sessionID, [{ type: "model", data: [model] }])
}),
)
yield* watch(MessageV2.Event.PartUpdated, (evt) =>
sync(evt.properties.part.sessionID, [{ type: "part", data: evt.properties.part }]),
yield* watch(MessageV2.Event.PartUpdated, (data) =>
sync(data.part.sessionID, [{ type: "part", data: structuredClone(data.part) as SDK.Part }]),
)
yield* watch(Session.Event.Diff, (evt) =>
sync(evt.properties.sessionID, [{ type: "session_diff", data: evt.properties.diff }]),
yield* watch(Session.Event.Diff, (data) =>
sync(data.sessionID, [{ type: "session_diff", data: structuredClone(data.diff) as SDK.SnapshotFileDiff[] }]),
)
yield* watch(Session.Event.Deleted, (evt) => remove(evt.properties.sessionID))
yield* watch(Session.Event.Deleted, (data) => remove(data.sessionID))
return cache
}),
@@ -233,9 +223,12 @@ export const layer = Layer.effect(
})
const get = Effect.fnUntraced(function* (sessionID: SessionID) {
const row = yield* db((db) =>
db.select().from(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).get(),
)
const row = yield* db
.select()
.from(SessionShareTable)
.where(eq(SessionShareTable.session_id, sessionID))
.get()
.pipe(Effect.orDie)
if (!row) return
return { id: row.id, secret: row.secret, url: row.url } satisfies Share
})
@@ -289,7 +282,7 @@ export const layer = Layer.effect(
.map((item) => [`${item.providerID}/${item.modelID}`, item] as const),
).values(),
),
(item) => provider.getModel(ProviderID.make(item.providerID), ModelID.make(item.modelID)),
(item) => provider.getModel(ProviderV2.ID.make(item.providerID), ProviderV2.ModelID.make(item.modelID)),
{ concurrency: 8 },
)
@@ -321,16 +314,15 @@ export const layer = Layer.effect(
Effect.flatMap((r) => httpOk.execute(r)),
Effect.flatMap(HttpClientResponse.schemaBodyJson(ShareSchema)),
)
yield* db((db) =>
db
.insert(SessionShareTable)
.values({ session_id: sessionID, id: result.id, secret: result.secret, url: result.url })
.onConflictDoUpdate({
target: SessionShareTable.session_id,
set: { id: result.id, secret: result.secret, url: result.url },
})
.run(),
)
yield* db
.insert(SessionShareTable)
.values({ session_id: sessionID, id: result.id, secret: result.secret, url: result.url })
.onConflictDoUpdate({
target: SessionShareTable.session_id,
set: { id: result.id, secret: result.secret, url: result.url },
})
.run()
.pipe(Effect.orDie)
const s = yield* InstanceState.get(state)
s.shared.set(sessionID, result)
yield* full(sessionID).pipe(
@@ -362,7 +354,7 @@ export const layer = Layer.effect(
Effect.flatMap((r) => httpOk.execute(r)),
)
yield* db((db) => db.delete(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).run())
yield* db.delete(SessionShareTable).where(eq(SessionShareTable.session_id, sessionID)).run().pipe(Effect.orDie)
s.shared.delete(sessionID)
s.queue.delete(sessionID)
})
@@ -372,9 +364,10 @@ export const layer = Layer.effect(
)
export const defaultLayer = layer.pipe(
Layer.provide(Bus.layer),
Layer.provide(EventV2Bridge.defaultLayer),
Layer.provide(Account.defaultLayer),
Layer.provide(Config.defaultLayer),
Layer.provide(Database.defaultLayer),
Layer.provide(FetchHttpClient.layer),
Layer.provide(Provider.defaultLayer),
Layer.provide(Session.defaultLayer),
-13
View File
@@ -1,13 +0,0 @@
import { sqliteTable, text } from "drizzle-orm/sqlite-core"
import { SessionTable } from "../session/session.sql"
import { Timestamps } from "../storage/schema.sql"
export const SessionShareTable = sqliteTable("session_share", {
session_id: text()
.primaryKey()
.references(() => SessionTable.id, { onDelete: "cascade" }),
id: text().notNull(),
secret: text().notNull(),
url: text().notNull(),
...Timestamps,
})