Учебное руководство
Главы руководства
На этой странице

API, TypeScript и runtime-контракты

Оглавление · Далее: воркеры

tRPC: один router, два способа вызова

appRouter в apps/web/src/server/router.ts объявляет четыре read-only процедуры.

ПроцедураВходРезультат
news.listНетНовости, новые сначала
news.bySlug{ slug: string }Одна новость
events.listНетСобытия по возрастанию времени начала
events.bySlug{ slug: string }Одно событие

slugSchema требует строку длиной 1–120. Неизвестный slug даёт tRPC NOT_FOUND; пустой или неверно типизированный вход отвергается валидатором. Схема не запрещает пробелы или слэши — такие дополнительные требования пока не введены.

Серверный caller: src/server/api.ts вызывает appRouter.createCaller({}). Caller доступен серверным потребителям без HTTP-запроса к собственному серверу. Страницы используют CMS repository напрямую; router предоставляет тот же адаптер через tRPC. Невалидный вход даёт BAD_REQUEST, отказ CMS — INTERNAL_SERVER_ERROR.

HTTP: route handler подключает Fetch adapter на /api/trpc и экспортирует GET/POST. src/lib/trpc-client.ts создаёт браузерный клиент с httpBatchLink и относительным URL. Сейчас этот helper подготовлен, но страницы не используют его для загрузки списка.

Пример для нового клиентского компонента:

import { trpc } from "@/lib/trpc-client";
const items = await trpc.news.list.query();
const item = await trpc.news.bySlug.query({ slug: items[0].slug });

Пример HTTP-чтения:

curl --fail http://localhost:3000/api/trpc/news.list

Запрос одной новости с URL-encoded JSON:

curl --fail --get --data-urlencode 'input={"slug":"news-152"}' \
  http://localhost:3000/api/trpc/news.bySlug

Тип AppRouter экспортируется из серверного модуля и импортируется клиентом через import type: он нужен при компиляции, а не как runtime-зависимость браузера. Создание типизированного клиента само по себе не добавляет авторизацию, кэш запросов или доступ к стороннему REST API.

О серверных вызовах tRPC.

Общий пакет contracts

packages/contracts/src/index.ts экспортирует:

  • slugSchema — вход процедуры bySlug.
  • strapiWebhookSchema — вход webhook и задачи.
  • StrapiWebhook — TypeScript-тип, выведенный через z.infer.
  • strapiEventName — строку strapi:content-changed.
  • NewsItem/EventItem/SiteContent/Page/FAQ/Partner и Zod schemas Blocks/media/дат из content.ts.

Zod выполняет проверку значений в работающем процессе. z.infer позволяет не писать вручную второй интерфейс с теми же полями. Пакет публикует из dist JavaScript и .d.ts; исходники сами по себе не являются production-экспортом. После изменения contracts пересоберите пакет.

Контракт webhook

{
  "event": "entry.publish",
  "model": "news",
  "entry": { "documentId": "article-1" }
}
ПолеТребование
evententry.create, entry.update, entry.delete, entry.publish или entry.unpublish
modelНепустая строка; allowlist моделей пока нет
entryОбъект
entry.documentIdНеобязательная строка
entry.idНеобязательная строка или число
Другие поля entryСохраняются благодаря .passthrough()

Оба идентификатора необязательны: { entry: {} } проходит схему. Инвалидирование всего редакционного кеша не требует ID. Отдельный учебный воркер по-прежнему может получить неопределённый идентификатор; бизнес-задаче стоит ужесточить контракт. Верхний объект не помечен passthrough; используйте результат parse, а не исходный JSON, чтобы явно следовать договорённости о сохраняемых полях.

Последовательность проверки webhook

  1. Наличие STRAPI_WEBHOOK_SECRET.
  2. Проверка заголовка Authorization: Bearer <secret> через timingSafeEqual.
  3. Чтение JSON и safeParse общей схемой.
  4. Немедленное истечение тега cms и сброс кеша корневого layout.
  5. Ответ 200 {"revalidated":true}; страницы обновляются при следующем посещении.

Невалидный payload возвращает 400, неверный секрет — 401, отсутствие настройки или ошибка сброса кеша — 503. Hatchet token не требуется. Повторное уведомление безопасно повторяет инвалидирование. Собственной очереди доставки Strapi webhook нет; страховочная ревалидация раз в час ограничивает длительность устаревания.

Health endpoint

GET /api/health возвращает {"status":"ok"}. В нём нет запросов к PostgreSQL, Strapi, Hatchet или воркеру. Нельзя использовать его как доказательство готовности всей системы. Для цепочки событий проверяйте отдельно приём и выполнение.

Как расширять API

Добавьте Zod-схему входа, новую процедуру и осмысленную проверку результата. Рассчитывайте контекст и авторизацию до добавления операции изменения данных: нынешний createContext: () => ({}) пуст и не содержит пользователя.

Не помещайте серверные секреты в переменные NEXT_PUBLIC_*, UI props или результат процедуры. tRPC сохраняет удобство типов, но не исправляет утечку данных в коде.

Где смотреть код

Router, caller, HTTP adapter, webhook, contracts, браузерный клиент.

Готовность контента

GET /api/content-health читает опубликованный Site, news/events/pages/FAQ/partners через тот же repository. Успех — 200 с counts, отказ/невалидный CMS response — 503 с безопасным status unavailable. Данные записей и token не возвращаются. Пустые редакторские collections допустимы; отсутствие опубликованного Site — ошибка. Это дополняет /api/health (живой процесс), но не проверяет Hatchet/worker.