| 1 | /** |
| 2 | * The Files page's About, streamed in after the files: what the default |
| 3 | * branch says, stars and releases from repos; how many watch it from the |
| 4 | * inbox; packages published from it; how many tags it has. Each is its own promise, so a slow |
| 5 | * one holds up only its own section, and a failure stays in its section |
| 6 | * rather than failing the page: what the default branch says and the |
| 7 | * packages say they could not be loaded (lib/partial.ts), and the counts |
| 8 | * of watchers and tags, which only add to a line, are left out. |
| 9 | */ |
| 10 | import type { PackageSummary, RepoAbout, RepoPath, Viewer } from "@g1t/contracts"; |
| 11 | |
| 12 | import type { AboutData } from "../components/repo-about"; |
| 13 | import { type Failed, part } from "./partial"; |
| 14 | import { inbox, packages, repos } from "./services.server"; |
| 15 | |
| 16 | /** How long a section waits before it shows without its answer. */ |
| 17 | const WAIT_MS = 4_000; |
| 18 | |
| 19 | function within<T>(promise: Promise<T>): Promise<T | null> { |
| 20 | return Promise.race([promise.catch(() => null), new Promise<null>((resolve) => setTimeout(() => resolve(null), WAIT_MS))]); |
| 21 | } |
| 22 | |
| 23 | /** Like `within`, but one that fails or does not answer in time is `Failed`, for its section to say so. */ |
| 24 | function partWithin<T>(promise: Promise<T>, what: string): Promise<T | Failed> { |
| 25 | const late = new Promise<never>((_, reject) => setTimeout(() => reject(new Error(`no answer in ${WAIT_MS} ms`)), WAIT_MS)); |
| 26 | return part(Promise.race([promise, late]), what); |
| 27 | } |
| 28 | |
| 29 | export function aboutOf(path: RepoPath, viewer: Viewer): Promise<RepoAbout | null> { |
| 30 | return repos.about(path, viewer).then((found) => (found.ok ? found.value : null)); |
| 31 | } |
| 32 | |
| 33 | export function aboutFor(path: RepoPath, viewer: Viewer, repoId: string): AboutData { |
| 34 | return { |
| 35 | about: partWithin(aboutOf(path, viewer), "Releases, contributors and languages"), |
| 36 | watchers: within(inbox.watchers(repoId)), |
| 37 | packages: partWithin( |
| 38 | packages.list(path.namespace, viewer, { repoId }).then((found): PackageSummary[] | null => (found.ok ? found.value : null)), |
| 39 | "Packages", |
| 40 | ), |
| 41 | tags: within(repos.tags(path, viewer).then((found) => (found.ok ? found.value.length : null))), |
| 42 | }; |
| 43 | } |