perf(slug): point-lookup instead of full discovery #28

Merged
slaid098 merged 5 commits from perf/slug/point-lookup-single-repo into main 2026-08-07 19:28:51 +03:00
Owner

Что сделано

  • src/lib/readme-discovery.ts: вынесена общая per-repo логика (detail→readme→cover→parse) в buildDiscoveredRepo(); добавлена discoverSingleRepo(owner, repoName, token, fetchOptions) — точечный поиск одного репо через GET /repos/{owner}/{repoName} (1 запрос), 404/fork/private/slaid098-devnull. Цикл в discoverShowcaseRepos рефакторён вызывать buildDiscoveredRepo (без дублирования per-repo логики).
  • src/lib/showcase.ts: добавлена getShowcaseRepo(slug) — обёрнута в React cache() (дедуп per-request), вызывает discoverSingleRepo с теми же опциями кеширования (cache: "force-cache", revalidate: 3600, tags: ["showcase"]).
  • src/app/[locale]/[slug]/page.tsx: Page и generateMetadata переключены с getShowcaseRepos()+findRepo() на getShowcaseRepo(slug) (null → notFound() / return {}). generateStaticParams без изменений (использует getShowcaseRepos()).
  • src/app/[locale]/apps/page.tsx, middleware.ts, __404__/page.tsx — без изменений.
  • Обновлены существующие тесты, мокающие getShowcaseRepos+findRepo для Page (app-detail-features, slug-back-link, generate-metadata), на getShowcaseRepo.
  • Добавлены новые тесты: showcase-single-repo.test.ts (11 — nonexistent→null, fork/private/slaid098-dev→null, network error→null, valid→DiscoveredRepo, счётчик fetchей: 1 для 404, 3 для valid, NO list-вызова), slug-point-lookup.test.tsx (4 — Page для unknown slug вызывает notFound() БЕЗ getShowcaseRepos(), valid slug рендерится БЕЗ getShowcaseRepos(), generateStaticParams всё ещё использует getShowcaseRepos()).

Почему

Страницы одиночного репо (/[locale]/[slug]) и 404 на неизвестном slug делали 7 последовательных запросов к Forgejo API (1 list + 2×3 detail/readme/cover для всех кандидатов) на cold cache, ~1.5–2с. Для 404 страница сначала выполняла все 7 запросов, и только потом вызывала notFound(). Точечный поиск через GET /repos/{owner}/{slug} сокращает: 404 → 1 запрос, валидный slug → 3–4 запроса (detail+readme+cover для одного репо). Warm cache: 0 запросов (тот же tag showcase, инвалидируется webhook/cron). /apps список — без изменений (7 запросов, нужен полный список).

Watch out

  • findRepo оставлен в showcase.ts (тесты slug-static-params, app-detail-features, slug-back-link мокают его — совместимость). Спека: опциональное удаление, оставлен.
  • discoverSingleRepo возвращает null (не бросает) на network error — не отличим от 404 без проверки статуса; null выбран как более простая 404-логика.
  • generate-metadata.test.ts: mock для /repos/slaid098/opencode-config расширен полным объектом detail (раньше только { default_branch }) — discoverSingleRepo использует поля name/created_at/description/html_url из detail напрямую.
  • Кеш-опции идентичны getShowcaseReposrevalidateTag("showcase") (webhook/cron) инвалидирует и list, и single.

Pending

Closes #27

## Что сделано - `src/lib/readme-discovery.ts`: вынесена общая per-repo логика (detail→readme→cover→parse) в `buildDiscoveredRepo()`; добавлена `discoverSingleRepo(owner, repoName, token, fetchOptions)` — точечный поиск одного репо через `GET /repos/{owner}/{repoName}` (1 запрос), 404/fork/private/`slaid098-dev` → `null`. Цикл в `discoverShowcaseRepos` рефакторён вызывать `buildDiscoveredRepo` (без дублирования per-repo логики). - `src/lib/showcase.ts`: добавлена `getShowcaseRepo(slug)` — обёрнута в React `cache()` (дедуп per-request), вызывает `discoverSingleRepo` с теми же опциями кеширования (`cache: "force-cache"`, `revalidate: 3600`, tags: `["showcase"]`). - `src/app/[locale]/[slug]/page.tsx`: `Page` и `generateMetadata` переключены с `getShowcaseRepos()`+`findRepo()` на `getShowcaseRepo(slug)` (null → `notFound()` / `return {}`). `generateStaticParams` без изменений (использует `getShowcaseRepos()`). - `src/app/[locale]/apps/page.tsx`, `middleware.ts`, `__404__/page.tsx` — без изменений. - Обновлены существующие тесты, мокающие `getShowcaseRepos`+`findRepo` для Page (`app-detail-features`, `slug-back-link`, `generate-metadata`), на `getShowcaseRepo`. - Добавлены новые тесты: `showcase-single-repo.test.ts` (11 — nonexistent→null, fork/private/slaid098-dev→null, network error→null, valid→DiscoveredRepo, счётчик fetchей: 1 для 404, 3 для valid, NO list-вызова), `slug-point-lookup.test.tsx` (4 — Page для unknown slug вызывает `notFound()` БЕЗ `getShowcaseRepos()`, valid slug рендерится БЕЗ `getShowcaseRepos()`, `generateStaticParams` всё ещё использует `getShowcaseRepos()`). ## Почему Страницы одиночного репо (`/[locale]/[slug]`) и 404 на неизвестном slug делали 7 последовательных запросов к Forgejo API (1 list + 2×3 detail/readme/cover для всех кандидатов) на cold cache, ~1.5–2с. Для 404 страница сначала выполняла все 7 запросов, и только потом вызывала `notFound()`. Точечный поиск через `GET /repos/{owner}/{slug}` сокращает: 404 → 1 запрос, валидный slug → 3–4 запроса (detail+readme+cover для одного репо). Warm cache: 0 запросов (тот же tag `showcase`, инвалидируется webhook/cron). `/apps` список — без изменений (7 запросов, нужен полный список). ## Watch out - `findRepo` оставлен в `showcase.ts` (тесты `slug-static-params`, `app-detail-features`, `slug-back-link` мокают его — совместимость). Спека: опциональное удаление, оставлен. - `discoverSingleRepo` возвращает `null` (не бросает) на network error — не отличим от 404 без проверки статуса; `null` выбран как более простая 404-логика. - `generate-metadata.test.ts`: mock для `/repos/slaid098/opencode-config` расширен полным объектом detail (раньше только `{ default_branch }`) — `discoverSingleRepo` использует поля `name`/`created_at`/`description`/`html_url` из detail напрямую. - Кеш-опции идентичны `getShowcaseRepos` — `revalidateTag("showcase")` (webhook/cron) инвалидирует и list, и single. ## Pending — Closes #27
fix(ci): remove unused findRepo export flagged by knip
All checks were successful
CI / check (pull_request) Successful in 1m16s
055b92bbac
Author
Owner

Code Review Summary

PR реализует точечный lookup одного репо через GET /repos/{owner}/{slug} вместо full discovery (7 запросов → 1 для 404, 3 для валидного slug). Качество высокое: спецификация issue #27 выполнена полностью, per-repo логика вынесена в общий buildDiscoveredRepo (без дублирования), тесты покрывают все критерии приемки, backward-compat сохранён.

Positives

  • Спецификация выполнена на 100%: discoverSingleRepo (404/fork/private/slaid098-dev → null, те же cache options), getShowcaseRepo обёрнут в React cache(), Page/generateMetadata переключены, generateStaticParams и apps/page.tsx без изменений (подтверждено пустым diff).
  • Нет дублирования per-repo логики: buildDiscoveredRepo(owner, repo, headers, opts) — общий helper, вызывается из обоих путей (discoverSingleRepo и цикл в discoverShowcaseRepos). Раньше тело цикла (detail→readme→cover→parse, ~40 строк) дублировалось бы — теперь нет.
  • Тесты покрывают все acceptance criteria: showcase-single-repo.test.ts (11 тестов: nonexistent→null, fork/private/slaid098-dev→null, network error→null, valid→DiscoveredRepo, счётчик fetchей 1/3, NO list-вызова), slug-point-lookup.test.tsx (4 теста: Page unknown slug → notFound() БЕЗ getShowcaseRepos(), valid slug рендерится БЕЗ list, generateStaticParams regression — всё ещё list).
  • Cross-file clean: findRepo полностью удалён из кодовой базы (grep по src/ — 0 matches), все readers обновлены в этом PR. getShowcaseRepos readers (sitemap.ts, cron-refresh/route.ts, apps/page.tsx, generateStaticParams) не тронуты и остаются валидными.
  • Error handling: discoverSingleRepo — try/catch на fetch (network error → null), try/catch на json parse (→ null), !detailRes.ok → null. Документированное решение в PR body и issue spec: network error неотличим от 404 без проверки статуса, null выбран для простой 404-логики.
  • PR hygiene: title perf(slug): point-lookup instead of full discovery следует convention, body имеет все 4 заголовка осмысленно, ## Pending: — допустим, branch name descriptive, нет debug-кода/секретов.

Suggestions (info, not blocking)

  • src/lib/readme-discovery.ts:196-243 [style] buildDiscoveredRepo ~47 строк — превышает guideline 30 строк для TS из code-standards skill. Однако функция состоит из двух object-literal return'ов (одинаковая форма для readme-missing и parsed case), разбить без искусственной фрагментации сложно. Функция делает одну вещь (build DiscoveredRepo from RepoMeta) — оставлено как есть, приемлемо.
  • src/lib/readme-discovery.ts:291-316 [design] discoverShowcaseRepos цикл делает свой detail fetch (строки 293-298) вместо вызова discoverSingleRepo. Это намерерно и корректно: list-путь имеет другую error-обработку (makeErrorRepo + continue vs null), а общая логика — per-repo build (buildDiscoveredRepo), не detail fetch. Spec issue #27 явно допускал "или общую логику" — автор выбрал shared helper, что валидно.
  • PR body vs diff [docs] PR body говорит "findRepo оставлен в showcase.ts", но фактически findRepo удалён. Удаление — лучший выбор (нет dead code), расхождение косметическое.
  • src/tests/generate-metadata.test.ts:80-89 [test] Mock для /repos/slaid098/opencode-config расширен полным detail-объектом (раньше только { default_branch }). Необходимо: discoverSingleRepo читает name/created_at/description/html_url из detail напрямую (vs старый list-путь брал их из list item). Корректное обновление.

Verdict: APPROVE

## Code Review Summary PR реализует точечный lookup одного репо через `GET /repos/{owner}/{slug}` вместо full discovery (7 запросов → 1 для 404, 3 для валидного slug). Качество высокое: спецификация issue #27 выполнена полностью, per-repo логика вынесена в общий `buildDiscoveredRepo` (без дублирования), тесты покрывают все критерии приемки, backward-compat сохранён. ### Positives - **Спецификация выполнена на 100%**: `discoverSingleRepo` (404/fork/private/`slaid098-dev` → null, те же cache options), `getShowcaseRepo` обёрнут в React `cache()`, `Page`/`generateMetadata` переключены, `generateStaticParams` и `apps/page.tsx` без изменений (подтверждено пустым diff). - **Нет дублирования per-repo логики**: `buildDiscoveredRepo(owner, repo, headers, opts)` — общий helper, вызывается из обоих путей (`discoverSingleRepo` и цикл в `discoverShowcaseRepos`). Раньше тело цикла (detail→readme→cover→parse, ~40 строк) дублировалось бы — теперь нет. - **Тесты покрывают все acceptance criteria**: `showcase-single-repo.test.ts` (11 тестов: nonexistent→null, fork/private/slaid098-dev→null, network error→null, valid→DiscoveredRepo, счётчик fetchей 1/3, NO list-вызова), `slug-point-lookup.test.tsx` (4 теста: Page unknown slug → notFound() БЕЗ `getShowcaseRepos()`, valid slug рендерится БЕЗ list, `generateStaticParams` regression — всё ещё list). - **Cross-file clean**: `findRepo` полностью удалён из кодовой базы (grep по `src/` — 0 matches), все readers обновлены в этом PR. `getShowcaseRepos` readers (`sitemap.ts`, `cron-refresh/route.ts`, `apps/page.tsx`, `generateStaticParams`) не тронуты и остаются валидными. - **Error handling**: `discoverSingleRepo` — try/catch на fetch (network error → null), try/catch на json parse (→ null), `!detailRes.ok` → null. Документированное решение в PR body и issue spec: network error неотличим от 404 без проверки статуса, `null` выбран для простой 404-логики. - **PR hygiene**: title `perf(slug): point-lookup instead of full discovery` следует convention, body имеет все 4 заголовка осмысленно, `## Pending: —` допустим, branch name descriptive, нет debug-кода/секретов. ### Suggestions (info, not blocking) - **src/lib/readme-discovery.ts:196-243** [style] `buildDiscoveredRepo` ~47 строк — превышает guideline 30 строк для TS из code-standards skill. Однако функция состоит из двух object-literal return'ов (одинаковая форма для readme-missing и parsed case), разбить без искусственной фрагментации сложно. Функция делает одну вещь (build DiscoveredRepo from RepoMeta) — оставлено как есть, приемлемо. - **src/lib/readme-discovery.ts:291-316** [design] `discoverShowcaseRepos` цикл делает свой detail fetch (строки 293-298) вместо вызова `discoverSingleRepo`. Это намерерно и корректно: list-путь имеет другую error-обработку (`makeErrorRepo` + continue vs `null`), а общая логика — per-repo build (`buildDiscoveredRepo`), не detail fetch. Spec issue #27 явно допускал "или общую логику" — автор выбрал shared helper, что валидно. - **PR body vs diff** [docs] PR body говорит "`findRepo` оставлен в `showcase.ts`", но фактически `findRepo` удалён. Удаление — лучший выбор (нет dead code), расхождение косметическое. - **src/__tests__/generate-metadata.test.ts:80-89** [test] Mock для `/repos/slaid098/opencode-config` расширен полным detail-объектом (раньше только `{ default_branch }`). Необходимо: `discoverSingleRepo` читает `name`/`created_at`/`description`/`html_url` из detail напрямую (vs старый list-путь брал их из list item). Корректное обновление. ### Verdict: APPROVE
slaid098 deleted branch perf/slug/point-lookup-single-repo 2026-08-07 19:28:51 +03:00
Sign in to join this conversation.
No reviewers
No labels
bug
performance
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slaid098/slaid098-dev!28
No description provided.