Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Commits show their accounts, and every name opens a card (repos 0016) | 1 | /** |
| 2 | * The one place commits get their people (lib/commit-people.ts): every | |
| 3 | * page that shows a commit's author asks here, once for all its commits. | |
| 4 | * One identity call per page for the addresses not already known, and | |
| 5 | * each answer kept a minute in the isolate, so a page of fifty commits by | |
| 6 | * three people asks about three addresses, and the next page asks about | |
| 7 | * none. A failure matches nobody: the names on the commits are shown. | |
| 8 | */ | |
| 9 | import type { Commit, EmailOwner } from "@g1t/contracts"; | |
| 10 | ||
| 11 | import { type ShownCommit, addressesToMatch, showCommit } from "./commit-people"; | |
| 12 | import { accounts } from "./services.server"; | |
| 13 | ||
| 14 | /** How long an answer is kept: an address can be confirmed or removed. */ | |
| 15 | const KEEP_MS = 60_000; | |
| 16 | /** Addresses kept at most; the oldest go first. */ | |
| 17 | const MAX_KEPT = 5_000; | |
| 18 | /** `email_owners` takes at most this many at once. */ | |
| 19 | const BATCH = 200; | |
| 20 | ||
| 21 | /** Each address's owner, or null for nobody, until `until`. */ | |
| 22 | const kept = new Map<string, { until: number; owner: EmailOwner | null }>(); | |
| 23 | ||
| 24 | function keep(email: string, owner: EmailOwner | null) { | |
| 25 | kept.delete(email); | |
| 26 | kept.set(email, { until: Date.now() + KEEP_MS, owner }); | |
| 27 | if (kept.size > MAX_KEPT) kept.delete(kept.keys().next().value as string); | |
| 28 | } | |
| 29 | ||
| 30 | /** The accounts behind these (lowercased) addresses; unknown ones are left out. */ | |
| 31 | export async function emailOwners(emails: string[]): Promise<Record<string, EmailOwner>> { | |
| 32 | const owners: Record<string, EmailOwner> = {}; | |
| 33 | const ask: string[] = []; | |
| 34 | const now = Date.now(); | |
| 35 | for (const email of new Set(emails)) { | |
| 36 | const hit = kept.get(email); | |
| 37 | if (hit && hit.until > now) { | |
| 38 | if (hit.owner) owners[email] = hit.owner; | |
| 39 | } else { | |
| 40 | ask.push(email); | |
| 41 | } | |
| 42 | } | |
| 43 | for (let start = 0; start < ask.length; start += BATCH) { | |
| 44 | const chunk = ask.slice(start, start + BATCH); | |
| 45 | const found = await accounts.emailOwners(chunk).catch(() => null); | |
| 46 | if (!found) continue; | |
| 47 | for (const email of chunk) { | |
| 48 | const owner = found[email] ?? null; | |
| 49 | keep(email, owner); | |
| 50 | if (owner) owners[email] = owner; | |
| 51 | } | |
| 52 | } | |
| 53 | return owners; | |
| 54 | } | |
| 55 | ||
| 56 | /** Commits with their people, in one identity call at most. */ | |
| 57 | export async function showCommits(commits: Commit[]): Promise<ShownCommit[]> { | |
| 58 | if (commits.length === 0) return []; | |
| 59 | const owners = await emailOwners(addressesToMatch(commits)); | |
| 60 | return commits.map((commit) => showCommit(commit, owners)); | |
| 61 | } | |
| 62 | ||
| 63 | /** One commit, or null, with its people. */ | |
| 64 | export async function showOneCommit(commit: Commit | null | undefined): Promise<ShownCommit | null> { | |
| 65 | return commit ? (await showCommits([commit]))[0]! : null; | |
| 66 | } |