refactor: unwrap Workspace namespace + self-reexport (#22934)

This commit is contained in:
Kit Langton
2026-04-17 00:00:15 +00:00
committed by GitHub
parent f9aa3d77cd
commit bae80af1b4
@@ -26,27 +26,26 @@ import { AppRuntime } from "@/effect/app-runtime"
import { EventSequenceTable } from "@/sync/event.sql"
import { waitEvent } from "./util"
export namespace Workspace {
export const Info = WorkspaceInfo.meta({
export const Info = WorkspaceInfo.meta({
ref: "Workspace",
})
export type Info = z.infer<typeof Info>
})
export type Info = z.infer<typeof Info>
export const ConnectionStatus = z.object({
export const ConnectionStatus = z.object({
workspaceID: WorkspaceID.zod,
status: z.enum(["connected", "connecting", "disconnected", "error"]),
error: z.string().optional(),
})
export type ConnectionStatus = z.infer<typeof ConnectionStatus>
})
export type ConnectionStatus = z.infer<typeof ConnectionStatus>
const Restore = z.object({
const Restore = z.object({
workspaceID: WorkspaceID.zod,
sessionID: SessionID.zod,
total: z.number().int().min(0),
step: z.number().int().min(0),
})
})
export const Event = {
export const Event = {
Ready: BusEvent.define(
"workspace.ready",
z.object({
@@ -61,9 +60,9 @@ export namespace Workspace {
),
Restore: BusEvent.define("workspace.restore", Restore),
Status: BusEvent.define("workspace.status", ConnectionStatus),
}
}
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
return {
id: row.id,
type: row.type,
@@ -73,17 +72,17 @@ export namespace Workspace {
extra: row.extra,
projectID: row.project_id,
}
}
}
const CreateInput = z.object({
const CreateInput = z.object({
id: WorkspaceID.zod.optional(),
type: Info.shape.type,
branch: Info.shape.branch,
projectID: ProjectID.zod,
extra: Info.shape.extra,
})
})
export const create = fn(CreateInput, async (input) => {
export const create = fn(CreateInput, async (input) => {
const id = WorkspaceID.ascending(input.id)
const adaptor = await getAdaptor(input.projectID, input.type)
@@ -134,14 +133,14 @@ export namespace Workspace {
})
return info
})
})
const SessionRestoreInput = z.object({
const SessionRestoreInput = z.object({
workspaceID: WorkspaceID.zod,
sessionID: SessionID.zod,
})
})
export const sessionRestore = fn(SessionRestoreInput, async (input) => {
export const sessionRestore = fn(SessionRestoreInput, async (input) => {
log.info("session restore requested", {
workspaceID: input.workspaceID,
sessionID: input.sessionID,
@@ -292,9 +291,9 @@ export namespace Workspace {
})
throw err
}
})
})
export function list(project: Project.Info) {
export function list(project: Project.Info) {
const rows = Database.use((db) =>
db.select().from(WorkspaceTable).where(eq(WorkspaceTable.project_id, project.id)).all(),
)
@@ -302,22 +301,22 @@ export namespace Workspace {
for (const space of spaces) startSync(space)
return spaces
}
}
function lookup(id: WorkspaceID) {
function lookup(id: WorkspaceID) {
const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get())
if (!row) return
return fromRow(row)
}
}
export const get = fn(WorkspaceID.zod, async (id) => {
export const get = fn(WorkspaceID.zod, async (id) => {
const space = lookup(id)
if (!space) return
startSync(space)
return space
})
})
export const remove = fn(WorkspaceID.zod, async (id) => {
export const remove = fn(WorkspaceID.zod, async (id) => {
const sessions = Database.use((db) =>
db.select({ id: SessionTable.id }).from(SessionTable).where(eq(SessionTable.workspace_id, id)).all(),
)
@@ -340,13 +339,13 @@ export namespace Workspace {
Database.use((db) => db.delete(WorkspaceTable).where(eq(WorkspaceTable.id, id)).run())
return info
}
})
})
const connections = new Map<WorkspaceID, ConnectionStatus>()
const aborts = new Map<WorkspaceID, AbortController>()
const TIMEOUT = 5000
const connections = new Map<WorkspaceID, ConnectionStatus>()
const aborts = new Map<WorkspaceID, AbortController>()
const TIMEOUT = 5000
function setStatus(id: WorkspaceID, status: ConnectionStatus["status"], error?: string) {
function setStatus(id: WorkspaceID, status: ConnectionStatus["status"], error?: string) {
const prev = connections.get(id)
if (prev?.status === status && prev?.error === error) return
const next = { workspaceID: id, status, error }
@@ -364,13 +363,13 @@ export namespace Workspace {
properties: next,
},
})
}
}
export function status(): ConnectionStatus[] {
export function status(): ConnectionStatus[] {
return [...connections.values()]
}
}
function synced(state: Record<string, number>) {
function synced(state: Record<string, number>) {
const ids = Object.keys(state)
if (ids.length === 0) return true
@@ -390,13 +389,13 @@ export namespace Workspace {
return ids.every((id) => {
return (done[id] ?? -1) >= state[id]
})
}
}
export async function isSyncing(workspaceID: WorkspaceID) {
export async function isSyncing(workspaceID: WorkspaceID) {
return aborts.has(workspaceID)
}
}
export async function waitForSync(workspaceID: WorkspaceID, state: Record<string, number>, signal?: AbortSignal) {
export async function waitForSync(workspaceID: WorkspaceID, state: Record<string, number>, signal?: AbortSignal) {
if (synced(state)) return
try {
@@ -414,19 +413,19 @@ export namespace Workspace {
if (signal?.aborted) throw signal.reason ?? new Error("Request aborted")
throw new Error(`Timed out waiting for sync fence: ${JSON.stringify(state)}`)
}
}
}
const log = Log.create({ service: "workspace-sync" })
const log = Log.create({ service: "workspace-sync" })
function route(url: string | URL, path: string) {
function route(url: string | URL, path: string) {
const next = new URL(url)
next.pathname = `${next.pathname.replace(/\/$/, "")}${path}`
next.search = ""
next.hash = ""
return next
}
}
async function syncWorkspace(space: Info, signal: AbortSignal) {
async function syncWorkspace(space: Info, signal: AbortSignal) {
while (!signal.aborted) {
log.info("connecting to global sync", { workspace: space.name })
setStatus(space.id, "connecting")
@@ -489,9 +488,9 @@ export namespace Workspace {
// TODO: Implement exponential backoff
await sleep(1000)
}
}
}
async function startSync(space: Info) {
async function startSync(space: Info) {
if (!Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) return
const adaptor = await getAdaptor(space.projectID, space.type)
@@ -520,11 +519,12 @@ export namespace Workspace {
error,
})
})
}
}
function stopSync(id: WorkspaceID) {
function stopSync(id: WorkspaceID) {
aborts.get(id)?.abort()
aborts.delete(id)
connections.delete(id)
}
}
export * as Workspace from "./workspace"