// The shapes the application sees, and the seam that produces them. // // Everything below is the GENERATED contract schema (`schema.ts`, made by openapi-typescript from // `docs/api-contract/openapi.yaml`) with one change: every field carrying a closed vocabulary is // widened to `… | null`, and the value is narrowed here, once, where it enters. A screen therefore // never indexes a map by a raw wire string — that was the silent crash of BACKLOG Ф-22. // // The application imports these names, not `schema.ts`: the only `Book` it knows is the narrowed // one. import { bookStatus, noteSeverity, pausedReason, rejectReason, termKind, termOrigin, termStatus, unitState, usageState, } from './vocabulary'; import type { components } from './schema'; type Schemas = components['schemas']; export type BookStatus = Schemas['BookStatus']; export type PausedReason = Schemas['PausedReason']; export type RejectReason = Schemas['RejectReason']; export type NoteSeverity = Schemas['NoteSeverity']; export type TermKind = Schemas['TermKind']; export type TermStatus = Schemas['TermStatus']; export type TermOrigin = Schemas['TermOrigin']; export type UnitState = Schemas['UnitState']; export type UsageState = Schemas['Usage']['state']; export type Id = Schemas['Id']; export type Revision = Schemas['Revision']; export type Progress = Schemas['Progress']; export type Counter = Schemas['Counter']; export type Chapter = Schemas['Chapter']; export type CeilingBounds = Schemas['CeilingBounds']; export type BankDecision = Schemas['BankDecision']; export type Problem = Schemas['Problem']; export type Book = Omit & { status: BookStatus | null; // Optional on the wire and narrowed to `null` when unknown: the contract says an absent reason // and one this build does not know are the same fact — "why is not known" — and are shown alike. reject_reason?: RejectReason | null; }; export type Run = Omit & { status: BookStatus | null; paused_reason: PausedReason | null; }; export type Note = Omit & { severity: NoteSeverity | null }; export type Unit = Omit & { state: UnitState | null; note?: Note | null; }; export type BankTerm = Omit & { kind: TermKind | null; status: TermStatus | null; origin: TermOrigin | null; }; export type Usage = Omit & { state: UsageState | null }; export type Library = Omit & { books: Book[] }; export type BookDetail = Omit & { book: Book; run?: Run | null; }; export type ChapterList = Schemas['ChapterList']; export type UnitList = Omit & { units: Unit[] }; export type NoteList = Omit & { notes: Note[] }; export type Bank = Omit & { terms: BankTerm[] }; export type RunOptions = Schemas['RunOptions']; /** Anything the platform answers to a list read: a revision, a cursor and rows. */ export interface Page { revision: Revision; next_cursor: Schemas['NextCursor']; } export const narrow = { book: (wire: Schemas['Book']): Book => ({ ...wire, status: bookStatus.read(wire.status), reject_reason: wire.reject_reason === undefined || wire.reject_reason === null ? wire.reject_reason : rejectReason.read(wire.reject_reason), }), run: (wire: Schemas['Run']): Run => ({ ...wire, status: bookStatus.read(wire.status), paused_reason: wire.paused_reason === null ? null : pausedReason.read(wire.paused_reason), }), note: (wire: Schemas['Note']): Note => ({ ...wire, severity: noteSeverity.read(wire.severity), }), unit: (wire: Schemas['Unit']): Unit => ({ ...wire, state: unitState.read(wire.state), note: wire.note ? narrow.note(wire.note) : wire.note, }), term: (wire: Schemas['BankTerm']): BankTerm => ({ ...wire, // A row legally carries no kind at all: a ruby candidate that is neither a name nor a place // gets none. "Undecided" and "unknown to this build" collapse into the same null on purpose — // both mean the screen shows the row and invents nothing. kind: wire.kind === null ? null : termKind.read(wire.kind), status: termStatus.read(wire.status), origin: termOrigin.read(wire.origin), }), usage: (wire: Schemas['Usage']): Usage => ({ ...wire, state: usageState.read(wire.state), paused_reason: wire.paused_reason === undefined || wire.paused_reason === null ? wire.paused_reason : pausedReason.read(wire.paused_reason), }), };