Download client/api.ts from igorktech/pinned: direct link, hf CLI and curl.
- Browser
- Download file 2.83 kB
-
https://huggingface.co/spaces/igorktech/pinned/resolve/main/client/api.ts
- Command line
-
hf download hf://spaces/igorktech/pinned/client/api.ts
-
curl -L -o api.ts https://huggingface.co/spaces/igorktech/pinned/resolve/main/client/api.ts
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 }), | |
| } | |
| } | |