import { ApiRequestError } from "./errors"; import type { TeamPluginStateEntry, TeamView } from "./types"; type CloudApiRequest = (path: string, options?: RequestInit) => Promise; /** A write refused because the server holds a newer revision. */ export class TeamRevisionConflictError extends Error { constructor( message: string, public readonly currentRevision: number, public readonly current: unknown = null, ) { super(message); this.name = "TeamRevisionConflictError"; } } function ifMatch(revision: number | undefined): Record { return revision ? { "if-match": String(revision) } : {}; } export class CloudViewsApi { constructor(private readonly request: CloudApiRequest) {} async listTeamViews(teamId: string): Promise { const body = await this.request<{ items?: TeamView[] }>(`/teams/${encodeURIComponent(teamId)}/views`); return Array.isArray(body?.items) ? body.items : []; } async getTeamView(viewId: string): Promise { try { return await this.request(`/views/${encodeURIComponent(viewId)}`); } catch (error) { if (error instanceof ApiRequestError && error.status === 404) return null; throw error; } } async createTeamView(teamId: string, input: { name: string; spec: Record }): Promise { return this.request(`/teams/${encodeURIComponent(teamId)}/views`, { method: "POST", body: JSON.stringify(input), }); } async publishTeamViewRevision( viewId: string, input: { spec: Record; name?: string; expectedRevision: number }, ): Promise { try { return await this.request(`/views/${encodeURIComponent(viewId)}`, { method: "PUT", headers: ifMatch(input.expectedRevision), body: JSON.stringify({ spec: input.spec, ...(input.name ? { name: input.name } : {}) }), }); } catch (error) { if (error instanceof ApiRequestError && error.status === 412) { const current = await this.getTeamView(viewId).catch(() => null); throw new TeamRevisionConflictError(error.message, current?.revision ?? input.expectedRevision + 1, current); } throw error; } } async renameTeamView(viewId: string, name: string): Promise { return this.request(`/views/${encodeURIComponent(viewId)}`, { method: "PATCH", body: JSON.stringify({ name }) }); } async deleteTeamView(viewId: string): Promise { await this.request(`/views/${encodeURIComponent(viewId)}`, { method: "DELETE" }); } // Plugin state async listTeamPluginState(teamId: string, pluginId: string): Promise { const body = await this.request<{ items: TeamPluginStateEntry[] }>( `/teams/${encodeURIComponent(teamId)}/plugin-state/${encodeURIComponent(pluginId)}`, ); return body.items; } async getTeamPluginState(teamId: string, pluginId: string, key: string): Promise { try { return await this.request( `/teams/${encodeURIComponent(teamId)}/plugin-state/${encodeURIComponent(pluginId)}/${encodeURIComponent(key)}`, ); } catch (error) { if (error instanceof ApiRequestError && error.status === 404) return null; throw error; } } async putTeamPluginState( teamId: string, pluginId: string, key: string, value: unknown, expectedRevision?: number, ): Promise { try { return await this.request( `/teams/${encodeURIComponent(teamId)}/plugin-state/${encodeURIComponent(pluginId)}/${encodeURIComponent(key)}`, { method: "PUT", headers: ifMatch(expectedRevision), body: JSON.stringify({ value }) }, ); } catch (error) { if (error instanceof ApiRequestError && error.status === 412) { const current = await this.getTeamPluginState(teamId, pluginId, key).catch(() => null); throw new TeamRevisionConflictError(error.message, current?.revision ?? (expectedRevision ?? 0) + 1, current); } throw error; } } async deleteTeamPluginState(teamId: string, pluginId: string, key: string): Promise { try { await this.request( `/teams/${encodeURIComponent(teamId)}/plugin-state/${encodeURIComponent(pluginId)}/${encodeURIComponent(key)}`, { method: "DELETE" }, ); } catch (error) { if (error instanceof ApiRequestError && error.status === 404) return; throw error; } } }