textmachine/frontend/src/api/contract.ts

129 lines
4.7 KiB
TypeScript

// 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<Schemas['Book'], 'status' | 'reject_reason'> & {
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<Schemas['Run'], 'status' | 'paused_reason'> & {
status: BookStatus | null;
paused_reason: PausedReason | null;
};
export type Note = Omit<Schemas['Note'], 'severity'> & { severity: NoteSeverity | null };
export type Unit = Omit<Schemas['Unit'], 'state' | 'note'> & {
state: UnitState | null;
note?: Note | null;
};
export type BankTerm = Omit<Schemas['BankTerm'], 'kind' | 'status' | 'origin'> & {
kind: TermKind | null;
status: TermStatus | null;
origin: TermOrigin | null;
};
export type Usage = Omit<Schemas['Usage'], 'state'> & { state: UsageState | null };
export type Library = Omit<Schemas['Library'], 'books'> & { books: Book[] };
export type BookDetail = Omit<Schemas['BookDetail'], 'book' | 'run'> & {
book: Book;
run?: Run | null;
};
export type ChapterList = Schemas['ChapterList'];
export type UnitList = Omit<Schemas['UnitList'], 'units'> & { units: Unit[] };
export type NoteList = Omit<Schemas['NoteList'], 'notes'> & { notes: Note[] };
export type Bank = Omit<Schemas['Bank'], 'terms'> & { 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),
}),
};