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.
Общий пакет 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" }
}
| Поле | Требование |
|---|---|
event | entry.create, entry.update, entry.delete, entry.publish или entry.unpublish |
model | Непустая строка; allowlist моделей пока нет |
entry | Объект |
entry.documentId | Необязательная строка |
entry.id | Необязательная строка или число |
Другие поля entry | Сохраняются благодаря .passthrough() |
Оба идентификатора необязательны: { entry: {} } проходит схему. Инвалидирование
всего редакционного кеша не требует ID. Отдельный учебный воркер по-прежнему
может получить неопределённый идентификатор; бизнес-задаче стоит ужесточить контракт. Верхний объект не помечен passthrough; используйте результат parse,
а не исходный JSON, чтобы явно следовать договорённости о сохраняемых полях.
Последовательность проверки webhook
- Наличие
STRAPI_WEBHOOK_SECRET. - Проверка заголовка
Authorization: Bearer <secret>черезtimingSafeEqual. - Чтение JSON и
safeParseобщей схемой. - Немедленное истечение тега
cmsи сброс кеша корневого layout. - Ответ
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.