| 1 | /** |
| 2 | * The inbox: what needs a person, or what they follow, as it happens. The |
| 3 | * events service keeps it and writes items as events arrive. Mirrors |
| 4 | * `crates/contracts/src/inbox.rs`, which says who is told of what. |
| 5 | */ |
| 6 | import type { ServiceBinding } from "./clients"; |
| 7 | import type { User } from "./identity"; |
| 8 | |
| 9 | /** |
| 10 | * How much an item matters: a failure, something a person must answer (an |
| 11 | * agent waiting on them), something that went well, or something to know. |
| 12 | */ |
| 13 | export type InboxSeverity = "error" | "warning" | "success" | "info"; |
| 14 | |
| 15 | export const INBOX_SEVERITIES: InboxSeverity[] = ["error", "warning", "success", "info"]; |
| 16 | |
| 17 | export type InboxSubjectKind = "issue" | "pull" | "run"; |
| 18 | |
| 19 | export type InboxItem = { |
| 20 | id: string; |
| 21 | /** Why they were told, such as `checks_failed`, `agent_asked` or `mentioned`. */ |
| 22 | reason: string; |
| 23 | severity: InboxSeverity; |
| 24 | /** One line: what happened, and where. */ |
| 25 | title: string; |
| 26 | /** One line: what it happened to, such as the pull request's title. */ |
| 27 | body: string; |
| 28 | /** `owner/name`. */ |
| 29 | repo: string | null; |
| 30 | workspace: string | null; |
| 31 | subject: InboxSubjectKind | null; |
| 32 | number: number | null; |
| 33 | /** A path on g1t.sh, such as `/acme/rocket/pull/12`. */ |
| 34 | url: string; |
| 35 | /** A username, or `g1t`. */ |
| 36 | actor: string | null; |
| 37 | /** RFC 3339. */ |
| 38 | createdAt: string; |
| 39 | readAt: string | null; |
| 40 | doneAt: string | null; |
| 41 | saved: boolean; |
| 42 | snoozedUntil: string | null; |
| 43 | }; |
| 44 | |
| 45 | /** The inbox itself (not done, not snoozed), what was saved, or what is done. */ |
| 46 | export type InboxView = "inbox" | "saved" | "done"; |
| 47 | |
| 48 | export type InboxQuery = { |
| 49 | view?: InboxView; |
| 50 | severity?: InboxSeverity | null; |
| 51 | unread?: boolean; |
| 52 | /** The `next` of the page before. */ |
| 53 | before?: string | null; |
| 54 | /** At most 100. */ |
| 55 | limit?: number; |
| 56 | }; |
| 57 | |
| 58 | export type InboxPage = { items: InboxItem[]; next: string | null }; |
| 59 | |
| 60 | /** Unread items in the inbox, by severity. */ |
| 61 | export type InboxCounts = { unread: number } & Record<InboxSeverity, number>; |
| 62 | |
| 63 | export type InboxMark = "read" | "unread" | "done" | "undone" | "save" | "unsave" | "snooze"; |
| 64 | |
| 65 | export type InboxMarkArgs = { |
| 66 | mark: InboxMark; |
| 67 | /** At most 100. */ |
| 68 | ids?: string[]; |
| 69 | /** Every item in the inbox, when `ids` is empty: Mark all read. */ |
| 70 | all?: boolean; |
| 71 | severity?: InboxSeverity | null; |
| 72 | /** For `snooze`: RFC 3339, later than now. */ |
| 73 | until?: string | null; |
| 74 | }; |
| 75 | |
| 76 | export interface InboxApi { |
| 77 | /** |
| 78 | * Newest first; in the inbox unfiltered, unread warnings first. Items about |
| 79 | * a repository the viewer can no longer read are dropped. |
| 80 | */ |
| 81 | list(viewer: User, query?: InboxQuery): Promise<InboxPage>; |
| 82 | counts(username: string): Promise<InboxCounts>; |
| 83 | /** Changes the person's own items. Returns how many changed. */ |
| 84 | mark(username: string, args: InboxMarkArgs): Promise<number>; |
| 85 | } |
| 86 | |
| 87 | /** The inbox, which the events service keeps. */ |
| 88 | export function inboxClient(events: ServiceBinding): InboxApi { |
| 89 | const call = async <T>(method: string, args: object): Promise<T> => { |
| 90 | const response = await events.fetch(`https://service/rpc/${method}`, { |
| 91 | method: "POST", |
| 92 | headers: { "content-type": "application/json" }, |
| 93 | body: JSON.stringify(args), |
| 94 | }); |
| 95 | if (!response.ok) throw new Error(`${method} failed with status ${response.status}`); |
| 96 | return (await response.json()) as T; |
| 97 | }; |
| 98 | return { |
| 99 | list: (viewer, query = {}) => call("inbox_list", { viewer, ...query }), |
| 100 | counts: (username) => call("inbox_counts", { username }), |
| 101 | mark: (username, args) => call("inbox_mark", { username, ...args }), |
| 102 | }; |
| 103 | } |