feat(id): brand WorkspaceID through Drizzle and Zod schemas (#16964)

This commit is contained in:
Kit Langton
2026-03-11 19:30:17 -04:00
committed by GitHub
parent f1c3a44190
commit 16a6d6feba
49 changed files with 205 additions and 157 deletions

View File

@@ -0,0 +1,17 @@
import { Schema } from "effect"
import z from "zod"
import { withStatics } from "@/util/schema"
import { Identifier } from "@/id/id"
const workspaceIdSchema = Schema.String.pipe(Schema.brand("WorkspaceId"))
export type WorkspaceID = typeof workspaceIdSchema.Type
export const WorkspaceID = workspaceIdSchema.pipe(
withStatics((schema: typeof workspaceIdSchema) => ({
make: (id: string) => schema.makeUnsafe(id),
ascending: (id?: string) => schema.makeUnsafe(Identifier.ascending("workspace", id)),
zod: z.string().startsWith("wrk").pipe(z.custom<WorkspaceID>()),
})),
)

View File

@@ -1,9 +1,9 @@
import z from "zod"
import { Identifier } from "@/id/id"
import { ProjectID } from "@/project/schema"
import { WorkspaceID } from "./schema"
export const WorkspaceInfo = z.object({
id: Identifier.schema("workspace"),
id: WorkspaceID.zod,
type: z.string(),
branch: z.string().nullable(),
name: z.string().nullable(),

View File

@@ -1,13 +1,14 @@
import { Context } from "../util/context"
import type { WorkspaceID } from "./schema"
interface Context {
workspaceID?: string
workspaceID?: WorkspaceID
}
const context = Context.create<Context>("workspace")
export const WorkspaceContext = {
async provide<R>(input: { workspaceID?: string; fn: () => R }): Promise<R> {
async provide<R>(input: { workspaceID?: WorkspaceID; fn: () => R }): Promise<R> {
return context.provide({ workspaceID: input.workspaceID }, async () => {
return input.fn()
})

View File

@@ -4,6 +4,7 @@ import { InstanceBootstrap } from "../../project/bootstrap"
import { SessionRoutes } from "../../server/routes/session"
import { WorkspaceServerRoutes } from "./routes"
import { WorkspaceContext } from "../workspace-context"
import { WorkspaceID } from "../schema"
export namespace WorkspaceServer {
export function App() {
@@ -20,9 +21,9 @@ export namespace WorkspaceServer {
return new Hono()
.use(async (c, next) => {
const workspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace")
const rawWorkspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace")
const raw = c.req.query("directory") || c.req.header("x-opencode-directory")
if (workspaceID == null) {
if (rawWorkspaceID == null) {
throw new Error("workspaceID parameter is required")
}
if (raw == null) {
@@ -38,7 +39,7 @@ export namespace WorkspaceServer {
})()
return WorkspaceContext.provide({
workspaceID,
workspaceID: WorkspaceID.make(rawWorkspaceID),
async fn() {
return Instance.provide({
directory,

View File

@@ -1,9 +1,10 @@
import { sqliteTable, text } from "drizzle-orm/sqlite-core"
import { ProjectTable } from "../project/project.sql"
import type { ProjectID } from "../project/schema"
import type { WorkspaceID } from "./schema"
export const WorkspaceTable = sqliteTable("workspace", {
id: text().primaryKey(),
id: text().$type<WorkspaceID>().primaryKey(),
type: text().notNull(),
branch: text(),
name: text(),

View File

@@ -1,5 +1,4 @@
import z from "zod"
import { Identifier } from "@/id/id"
import { fn } from "@/util/fn"
import { Database, eq } from "@/storage/db"
import { Project } from "@/project/project"
@@ -10,6 +9,7 @@ import { ProjectID } from "@/project/schema"
import { WorkspaceTable } from "./workspace.sql"
import { getAdaptor } from "./adaptors"
import { WorkspaceInfo } from "./types"
import { WorkspaceID } from "./schema"
import { parseSSE } from "./sse"
export namespace Workspace {
@@ -46,7 +46,7 @@ export namespace Workspace {
}
const CreateInput = z.object({
id: Identifier.schema("workspace").optional(),
id: WorkspaceID.zod.optional(),
type: Info.shape.type,
branch: Info.shape.branch,
projectID: ProjectID.zod,
@@ -54,7 +54,7 @@ export namespace Workspace {
})
export const create = fn(CreateInput, async (input) => {
const id = Identifier.ascending("workspace", input.id)
const id = WorkspaceID.ascending(input.id)
const adaptor = await getAdaptor(input.type)
const config = await adaptor.configure({ ...input, id, name: null, directory: null })
@@ -94,13 +94,13 @@ export namespace Workspace {
return rows.map(fromRow).sort((a, b) => a.id.localeCompare(b.id))
}
export const get = fn(Identifier.schema("workspace"), async (id) => {
export const get = fn(WorkspaceID.zod, async (id) => {
const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get())
if (!row) return
return fromRow(row)
})
export const remove = fn(Identifier.schema("workspace"), async (id) => {
export const remove = fn(WorkspaceID.zod, async (id) => {
const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get())
if (row) {
const info = fromRow(row)