Трансформери
Трансформер — це чиста функція серіалізації: вона бере рядок, який повернув репозиторій (з bigint id, об'єктами Date і внутрішніми полями), і перетворює його на публічний DTO, який можна безпечно віддати клієнту в JSON.
Навіщо потрібен цей шар
- Безпека даних. Рядок
usersмістить хеш пароля іsessionToken. Трансформер явно відкидає такі поля, тож вони не потраплять у відповідь навіть якщо сервіс поверне «весь користувач». Без цього шару легко випадково віддати клієнту зайве. - JSON-сумісність.
bigintне серіалізуєтьсяJSON.stringify, аDateтреба віддавати в єдиному форматі. Трансформер приводить id доstring, а дати — до ISO-рядка UTC (toISOString()). Схема відповіді описує вже цей формат — див. Перевірка відповідей. - Стабільний публічний контракт. Внутрішні назви колонок (
createdAt) можуть змінюватися, а клієнт очікуєcreated_at. Трансформер — одне місце, де визначається, як сутність виглядає зовні; похідні поля (emailVerified: booleanзамістьemailVerifiedAt) теж додаються тут. - Повторне використання. Один і той самий користувач повертається з логіну, реєстрації, OAuth — усі вони викликають
userTransformer.serialize, тож форма відповіді однакова. - Легко тестувати. Трансформер не має залежностей і побічних ефектів: вхідний рядок → вихідний об'єкт.
Як влаштовані
Трансформери в app/transformers/ — це об'єкти з методами serialize / serializeArray (у push-підписок ще serializeWithLogs / serializeArrayWithLogs, у логів push — serializeSummary). Є userTransformer, pushSubscriptionTransformer, pushNotificationLogTransformer і promptTransformer. Вони:
- приводять
bigintid доstring; - переводять
Dateу ISO-рядок UTC черезtoISOString()(формат2026-10-25T10:00:00.000Zне залежить від часового поясу процесу);userTransformerіpushSubscriptionTransformerще перейменовуютьcreatedAt/updatedAt→created_at/updated_at; - прибирають чутливі поля: user-transformer.ts відкидає
passwordіsessionToken(простір імен сесій у Redis, див. Автентифікація) і додає похіднеemailVerified: boolean.
serialize(user: UserRow): SerializedUser {
const { id, password: _password, sessionToken: _sessionToken, createdAt, updatedAt, emailVerifiedAt, ...rest } = user
return { ...rest, id: String(id), created_at: createdAt.toISOString(), /* ... */ }
}Тип результату (SerializedUser, SerializedPushSubscription) виводиться з типу рядка через Omit<...> & {...}, тож він живе в бекенді й не імпортується з packages/shared. Схеми відповідей у packages/shared/src/responses описані окремо (ArkType). Відповідність DTO і схеми перевіряє компілятор — defineRoute звіряє результат контролера зі схемою (Тип відповіді хендлера) — і перевірка відповіді під час виконання (Перевірка відповідей).
Де використовуються
Трансформери викликаються в сервісах: userTransformer — у auth-service.ts; pushSubscriptionTransformer — у push-subscription-service.ts і push-subscription-statistics-service.ts.
pushNotificationLogTransformer — у push-subscription-service.ts і всередині pushSubscriptionTransformer.serializeWithLogs; promptTransformer — у prompt-controller.ts (сервіс prompts кешує рядки БД).
Обхід: mainService.init збирає об'єкт користувача вручну (див. «Архітектура»); адмінські списки, knowledge base, support- та online-історія серіалізують рядки у своїх сервісах без окремого трансформера.
Тестування
Трансформери — чисті функції, тож їх тестують без моків: user-transformer.spec.ts перевіряє, що password і sessionToken не потрапляють у DTO. У тестах сервісів трансформери мокають через barrel:
vi.mock('#app/transformers/index.js', () => ({
pushSubscriptionTransformer: pushSubscriptionTransformerMock,
}))Суміжні шари
- Контролери — HTTP/WS-адаптери: приймають запит і віддають відповідь.
- Сервіси — бізнес-логіка, повертають
AppResult<T>. - Репозиторії — єдине місце, де пишеться SQL.
- Трансформери — перетворення рядків БД на публічні DTO.
Загальна схема — Архітектура, покроковий приклад — Додавання фічі.