pinned / client /api.ts
igorktech's picture
Pinned demo: two recorded boards, agent off
6f91ce5 verified
Raw History Blame Contribute Delete
2.83 kB
import type { LinkKind } from '../shared/ids.ts'
import type { PaperStatus } from '../shared/paperCard.ts'
// The board service's REST API (T08) from the browser: JSON in and out, the member ID as the
// bearer (decision D5). Routes and shapes are in the root README.
export class ApiError extends Error {
status: number
code: string
constructor(status: number, code: string, message: string) {
super(message)
this.status = status
this.code = code
}
}
export interface Member {
memberId: string
displayName: string
lastSeenSeq: number
joinedAt: string
}
/** `GET /boards/:id`, the fields the browser reads. */
export interface Board {
id: string
title: string
question: string
context: string
/** A member id, or "demo:<board>" on a seeded demo board. */
createdBy?: string | null
seedCount: number
paperCount: number
/** The newest event's seq: what `seen` marks as read. */
lastSeq: number
agentActive: boolean
/** For the caller: events since they last looked ("Catch me up"). */
newCount?: number
}
export interface VoteResult {
status: PaperStatus
keepBy: string[]
discardBy: string[]
}
async function call<T>(method: string, path: string, memberId: string, body?: unknown): Promise<T> {
const res = await fetch(`/api/v1${path}`, {
method,
headers: { authorization: `Bearer ${memberId}`, ...(body === undefined ? {} : { 'content-type': 'application/json' }) },
body: body === undefined ? undefined : JSON.stringify(body),
})
if (res.status === 204) return undefined as T
const data = await res.json().catch(() => null)
if (!res.ok) throw new ApiError(res.status, data?.error?.code ?? 'error', data?.error?.message ?? `The board service answered ${res.status}`)
return data as T
}
/** One board's routes, as one member. */
export function boardApi(boardId: string, memberId: string) {
const board = `/boards/${encodeURIComponent(boardId)}`
return {
get: () => call<Board>('GET', board, memberId),
/** Everything up to `seq` counts as seen: Home's "Catch me up" count starts over. */
seen: (seq: number) => call<void>('POST', `${board}/seen`, memberId, { seq }),
/** Join the board; with a name, also set the name the others see. */
join: (displayName?: string) => call<Member>('PUT', `${board}/members/me`, memberId, { displayName }),
/** Keep, Discard, or (null) take the vote back; the card updates for everyone through sync. */
vote: (paperId: string, decision: 'keep' | 'discard' | null) =>
call<VoteResult>('PUT', `${board}/papers/${encodeURIComponent(paperId)}/vote`, memberId, { decision }),
/** A link a person deleted: get_board lists it in removedLinks and link_papers never draws it again (T14). */
removeLink: (from: string, to: string, kind: LinkKind) =>
call<void>('PUT', `${board}/removed-links`, memberId, { from, to, kind }),
}
}