Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions packages/client/src/effect/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1223,13 +1223,23 @@ export type Endpoint13_0Output = ReadonlyArray<Project.Info>
export type ProjectListOperation<E = never> = () => Effect.Effect<Endpoint13_0Output, E>

export type Endpoint13_1Input = {
readonly projectID: Project.ID
readonly name?: string | undefined
readonly icon?: Project.Icon | undefined
readonly commands?: Project.Commands | undefined
}
export type Endpoint13_1Output = Project.Info
export type ProjectUpdateOperation<E = never> = (input: Endpoint13_1Input) => Effect.Effect<Endpoint13_1Output, E>

export type Endpoint13_2Input = {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
}
export type Endpoint13_1Output = Project.Current
export type ProjectCurrentOperation<E = never> = (input?: Endpoint13_1Input) => Effect.Effect<Endpoint13_1Output, E>
export type Endpoint13_2Output = Project.Current
export type ProjectCurrentOperation<E = never> = (input?: Endpoint13_2Input) => Effect.Effect<Endpoint13_2Output, E>

export interface ProjectApi<E = never> {
readonly list: ProjectListOperation<E>
readonly update: ProjectUpdateOperation<E>
readonly current: ProjectCurrentOperation<E>
}

Expand Down
18 changes: 16 additions & 2 deletions packages/client/src/effect/generated/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ import type {
Endpoint13_0Output,
Endpoint13_1Input,
Endpoint13_1Output,
Endpoint13_2Input,
Endpoint13_2Output,
Endpoint14_0Input,
Endpoint14_0Output,
Endpoint14_1Input,
Expand Down Expand Up @@ -863,12 +865,24 @@ const adaptGroup12 = (raw: RawClient["server.credential"]) => ({ update: Endpoin
const Endpoint13_0 = (raw: RawClient["server.project"]) => () =>
preserveEffect<Endpoint13_0Output>()(raw["project.list"]({}).pipe(Effect.mapError(mapClientError)))

const Endpoint13_1 = (raw: RawClient["server.project"]) => (input?: Endpoint13_1Input) =>
const Endpoint13_1 = (raw: RawClient["server.project"]) => (input: Endpoint13_1Input) =>
preserveEffect<Endpoint13_1Output>()(
raw["project.update"]({
params: { projectID: input["projectID"] },
payload: { name: input["name"], icon: input["icon"], commands: input["commands"] },
}).pipe(Effect.mapError(mapClientError)),
)

const Endpoint13_2 = (raw: RawClient["server.project"]) => (input?: Endpoint13_2Input) =>
preserveEffect<Endpoint13_2Output>()(
raw["project.current"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)),
)

const adaptGroup13 = (raw: RawClient["server.project"]) => ({ list: Endpoint13_0(raw), current: Endpoint13_1(raw) })
const adaptGroup13 = (raw: RawClient["server.project"]) => ({
list: Endpoint13_0(raw),
update: Endpoint13_1(raw),
current: Endpoint13_2(raw),
})

const Endpoint14_0 = (raw: RawClient["server.form"]) => (input?: Endpoint14_0Input) =>
preserveEffect<Endpoint14_0Output>()(
Expand Down
14 changes: 14 additions & 0 deletions packages/client/src/promise/generated/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ import type {
CredentialRemoveInput,
CredentialRemoveOutput,
ProjectListOutput,
ProjectUpdateInput,
ProjectUpdateOutput,
ProjectCurrentInput,
ProjectCurrentOutput,
FormRequestListInput,
Expand Down Expand Up @@ -1235,6 +1237,18 @@ export function make(options: ClientOptions) {
{ method: "GET", path: `/api/project`, successStatus: 200, declaredStatuses: [401, 400], empty: false },
requestOptions,
),
update: (input: ProjectUpdateInput, requestOptions?: RequestOptions) =>
request<ProjectUpdateOutput>(
{
method: "PATCH",
path: `/api/project/${encodeURIComponent(input.projectID)}`,
body: { name: input["name"], icon: input["icon"], commands: input["commands"] },
successStatus: 200,
declaredStatuses: [404, 401, 400],
empty: false,
},
requestOptions,
),
current: (input?: ProjectCurrentInput, requestOptions?: RequestOptions) =>
request<ProjectCurrentOutput>(
{
Expand Down
48 changes: 48 additions & 0 deletions packages/client/src/promise/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,24 @@ export type Project = {
sandboxes: Array<string>
}

export type ProjectUpdated = {
id: string
created: number
metadata?: { [x: string]: any }
type: "project.updated"
location?: LocationRef
data: {
id: string
canonical: string
vcs?: ProjectVcs
name?: string
icon?: ProjectIcon
commands?: ProjectCommands
time: ProjectTime
sandboxes: Array<string>
}
}

export type FormAnswer = { [x: string]: FormValue }

export type PermissionRequest = {
Expand Down Expand Up @@ -2059,6 +2077,7 @@ export type V2Event =
| PermissionReplied
| PluginAdded
| PluginUpdated
| ProjectUpdated
| WorktreeUpdated
| WorktreeResolved
| CommandUpdated
Expand Down Expand Up @@ -2214,6 +2233,14 @@ export type McpServerNotFoundError = {
export const isMcpServerNotFoundError = (value: unknown): value is McpServerNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "McpServerNotFoundError"

export type ProjectNotFoundError = {
readonly _tag: "ProjectNotFoundError"
readonly projectID: string
readonly message: string
}
export const isProjectNotFoundError = (value: unknown): value is ProjectNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ProjectNotFoundError"

export type FormNotFoundError = { readonly _tag: "FormNotFoundError"; readonly id: string; readonly message: string }
export const isFormNotFoundError = (value: unknown): value is FormNotFoundError =>
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "FormNotFoundError"
Expand Down Expand Up @@ -4286,6 +4313,27 @@ export type CredentialRemoveOutput = void

export type ProjectListOutput = Array<Project>

export type ProjectUpdateInput = {
readonly projectID: { readonly projectID: string }["projectID"]
readonly name?: {
readonly name?: string
readonly icon?: { readonly url?: string; readonly override?: string; readonly color?: string }
readonly commands?: { readonly start?: string }
}["name"]
readonly icon?: {
readonly name?: string
readonly icon?: { readonly url?: string; readonly override?: string; readonly color?: string }
readonly commands?: { readonly start?: string }
}["icon"]
readonly commands?: {
readonly name?: string
readonly icon?: { readonly url?: string; readonly override?: string; readonly color?: string }
readonly commands?: { readonly start?: string }
}["commands"]
}

export type ProjectUpdateOutput = Project

export type ProjectCurrentInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
Expand Down
39 changes: 38 additions & 1 deletion packages/core/src/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ export type Current = ProjectSchema.Current
export const Info = ProjectSchema.Info
export interface Info extends Schema.Schema.Type<typeof Info> {}

export const UpdateInput = ProjectSchema.UpdateInput
export type UpdateInput = ProjectSchema.UpdateInput

export const Event = ProjectSchema.Event

export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("Project.NotFoundError", {
projectID: ID,
}) {}

export interface Resolved {
readonly previous?: ID
readonly id: ID
Expand All @@ -47,6 +56,7 @@ export const root = Effect.fn("Project.root")(function* (fs: FSUtil.Interface, i

export interface Interface {
readonly list: () => Effect.Effect<ReadonlyArray<Info>>
readonly update: (projectID: ID, input: UpdateInput) => Effect.Effect<Info, NotFoundError>
readonly resolve: (input: AbsolutePath) => Effect.Effect<Resolved>
}

Expand Down Expand Up @@ -145,6 +155,33 @@ const layer = Layer.effect(
return rows.map(fromRow)
})

const update = Effect.fn("Project.update")(function* (projectID: ID, input: UpdateInput) {
const values = {
...(input.name === undefined ? {} : { name: input.name || null }),
...(input.icon?.url === undefined ? {} : { icon_url: input.icon.url || null }),
...(input.icon?.override === undefined ? {} : { icon_url_override: input.icon.override || null }),
...(input.icon?.color === undefined ? {} : { icon_color: input.icon.color || null }),
...(input.commands?.start === undefined
? {}
: { commands: input.commands.start ? { start: input.commands.start } : null }),
}
const changed = Object.keys(values).length > 0
const row = yield* (
changed
? db
.update(ProjectTable)
.set({ ...values, time_updated: Date.now() })
.where(eq(ProjectTable.id, projectID))
.returning()
.get()
: db.select().from(ProjectTable).where(eq(ProjectTable.id, projectID)).get()
).pipe(Effect.orDie)
if (!row) return yield* new NotFoundError({ projectID })
const info = fromRow(row)
if (changed) yield* bus.publish(ProjectSchema.Event.Updated, info)
return info
})

const cached = Effect.fnUntraced(function* (dir: string) {
return yield* fs.readFileString(path.join(dir, "opencode")).pipe(
Effect.map((value) => value.trim()),
Expand Down Expand Up @@ -258,7 +295,7 @@ const layer = Layer.effect(
return yield* persist({ id: ID.global, directory, canonical: directory, vcs: undefined })
})

return Service.of({ list, resolve })
return Service.of({ list, update, resolve })
}),
)

Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/project/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export type Current = typeof Current.Type
export const Info = Project.Info
export interface Info extends Schema.Schema.Type<typeof Info> {}

export const UpdateInput = Project.UpdateInput
export type UpdateInput = typeof UpdateInput.Type

export const Event = Project.Event

export const Vcs = Schema.Union([
Schema.Struct({
type: Schema.Literal("git"),
Expand Down
1 change: 1 addition & 0 deletions packages/core/test/effect/layer-node/node-build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ describe("node build", () => {
acquisitions++
return Project.Service.of({
list: () => Effect.succeed([]),
update: (projectID) => Effect.fail(new Project.NotFoundError({ projectID })),
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
})
}),
Expand Down
1 change: 1 addition & 0 deletions packages/core/test/lib/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const globalProjectLayer = Layer.succeed(
Project.Service,
Project.Service.of({
list: () => Effect.succeed([]),
update: (projectID) => Effect.fail(new Project.NotFoundError({ projectID })),
resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory, canonical: directory }),
}),
)
1 change: 1 addition & 0 deletions packages/core/test/location.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const projectLayer = Layer.succeed(
Project.Service,
Project.Service.of({
list: () => Effect.succeed([]),
update: (projectID) => Effect.fail(new Project.NotFoundError({ projectID })),
resolve: () =>
Effect.succeed({
id: Project.ID.make("project"),
Expand Down
Loading
Loading