Валидация (Standard Schema)
TypeScript проверяет, что твой код корректно использует схему. Он не может проверить, что WebSocket-фрейм, который ты получил в рантайме, реально соответствует заявленному типу payload. Для этого нужна runtime-валидация — парсинг нетипизированных данных через валидатор, который либо сужает к заявленной форме, либо бросает исключение.
Triggery не приносит свой валидатор; он опирается на Standard Schema — контракт, на который в 2024 году договорились zod, valibot, arktype, runtypes и остальные. Standard Schema — единственное, о чём библиотеке нужно знать: приноси любой валидатор, который уже используешь, валидируй на выбранной границе, остальная система остаётся чистой.
Две границы, которым нужна валидация
Заголовок раздела «Две границы, которым нужна валидация»У триггера три port-поверхности (events, conditions, actions). Из трёх обычно нужно валидировать только events:
- Events несут данные из внешнего мира: WebSocket-фрейм, fetch-ответ, postMessage. Продьюсер не знает, что payload корректен.
- Conditions — это значения, уже хранящиеся в типизированном состоянии (Zustand-стор, useState, ref). Геттер твой; недоверенной границы нет.
- Actions вызываются изнутри обработчика триггера с типизированными значениями, которые ты сам собрал. То же — недоверенной границы нет.
Так что валидация почти всегда — забота продьюсера: парси до того, как отправил.
Обработчик доверяет типам event.payload, потому что их обеспечил продьюсер. Downstream-код (обработчик, реакторы действий) никогда не перевалидирует — это была бы лишняя работа.
Используем Standard Schema ('~standard') — library-agnostic
Заголовок раздела «Используем Standard Schema ('~standard') — library-agnostic»Если не хочешь привязывать место валидации к одной библиотеке, используй контракт Standard Schema напрямую. Каждая поддерживающая библиотека выставляет свойство ~standard:
Дальше — любая библиотека:
Triggery не зависит ни от одной конкретной библиотеки. Твой выбор — твой выбор; контракт есть контракт.
Переиспользуемый хук продьюсера
Заголовок раздела «Переиспользуемый хук продьюсера»В React самый чистый паттерн — хук продьюсера, в котором валидатор лежит рядом с вызовом события:
Вызывай так же, как useEvent, только передай схему:
Та же идея работает для useEvent в Solid и Vue.
Декларативное API — validate:
Заголовок раздела «Декларативное API — validate:»Форма V1.1 живёт там, где живёт схема, — внутри createTrigger:
Что рантайм будет делать в V1.1:
- Events: на каждом
fire('new-message', payload)прогонять схему противpayloadдо планирования обработчика. Провал → skip в инспекторе сreason: 'validate-event: <issues>', обработчик не вызывается. - Conditions: когда обработчик запущен, валидировать возвращённое значение геттера каждого условия до того, как оно станет видно в
ctx.conditions. Провал → skip сreason: 'validate-condition: <name>'. Полезно для «доверяй типизированному state-контракту, перепроверяй в dev в рантайме». - Actions: валидировать вызовы
ctx.actions.foo?.(payload)до диспатча реакторам. Та же skip-семантика.
До V1.1 используй граничный паттерн выше. Ментальная модель идентична; меняется только место вызова.
Cost-модель — платишь только когда подключаешь
Заголовок раздела «Cost-модель — платишь только когда подключаешь»Валидация opt-in везде:
- Граничный паттерн V1 — в твоём коде. Рантайм ничего не валидирует на горячем пути.
- Карта
validate:в V1.1 — opt-in пер-порт. Невалидированные порты добавляют нулевые накладные расходы. - Сам контракт Standard Schema — один доступ к свойству (
schema['~standard'].validate) — никакого глобального реестра, никакой plugin-системы, никакой инициализации.
Конкретно: рантайм без validate:-выражений ни на одном триггере имеет ровно тот же путь диспатча, что и рантайм без поддержки валидации вообще. Быстрый путь — это путь без валидации.
Когда ты подключаешь, цена — один вызов валидатора на касание порта. Парсы Zod обычно — десятки микросекунд; valibot и arktype работают ближе к единицам микросекунд. Для высокочастотных событий (например, поток позиций курсора) валидируй первое событие и доверяй последующим или сэмплируй (валидируй 1 из 100). API validate: в V1.1 будет принимать функциональную форму именно для этого:
Заметка по conditions — защитная валидация
Заголовок раздела «Заметка по conditions — защитная валидация»Хотя conditions идут из твоего собственного состояния, есть причины валидировать их в dev:
- Миграция поменяла форму персистнутого состояния; старая форма всё ещё в
localStorageу каких-то пользователей. - Backend-ответ протекает в стор нетипизированным (
anyгде-то по цепочке). - Сторонняя библиотека, которую ты оборачиваешь, возвращает
unknown, и хочется рантайм-уверенности, что форма не изменилась.
Форма V1.1 это обрабатывает — однострочный validate.conditions.settings = settingsSchema, и рантайм проверяет на каждом чтении на момент срабатывания. Цена — один вызов валидатора на сработавшее событие на каждое условие, обычно пренебрежимо.
Сегодня эквивалент — обёртка вокруг геттера условия:
В production-сборках подмени обёртку на identity-функцию через build-флаг — тот же продьюсер, нулевая рантайм-стоимость.
Будущее направление — pattern matching и asserts
Заголовок раздела «Будущее направление — pattern matching и asserts»Roadmap упоминает ещё одно направление: asserts на уровне триггера. Сценарий вроде «после 'checkout:completed' действие с именем redirectTo должно сработать» — сейчас выражается чтением инспектора после теста — получит first-class-поле expect:. Та же форма Standard Schema, та же opt-in-модель стоимости. См. roadmap для таймлайна.
Нулевая зависимость от конкретного валидатора
Заголовок раздела «Нулевая зависимость от конкретного валидатора»Весь смысл сборки на Standard Schema в том, что Triggery никогда не импортирует zod, valibot, arktype или что-либо ещё. В твой бандл попадает ровно тот валидатор, который ты затащил, ничего лишнего. Переход с zod на valibot — это search-and-replace в твоём коде, не апгрейд Triggery. Принять валидатор, на который твоя команда стандартизируется через три года, — тоже search-and-replace.
Это и есть контракт: типы — работа TypeScript, runtime-инварианты — работа Standard Schema, оркестрация — работа Triggery. Три слоя; все остаются в своих рамках.