Payload события у триггера типизирован на этапе компиляции, но runtime-данные — отправки форм, URL-параметры, фреймы WebSocket — приходят неструктурированными. Рекомендуемый паттерн Triggery: валидируй на границе продьюсера и запускай только то, что прошло проверку. Обработчик триггера тогда видит полностью типизированный, проверенный в рантайме payload. Этот рецепт показывает идиому и упаковывает её в маленький хелпер useValidatedEvent, чтобы каждый продьюсер в приложении был однострочником.
Маленькая обёртка над useEvent. Принимает валидатор Standard Schema — zod, valibot, arktype, любой реализующий спецификацию — и возвращает (input) => Result, где Result — либо { ok: true; data }, либо { ok: false; issues }. При успехе запускает событие с распарсенным значением.
src/lib/useValidatedEvent.ts
import type { EventKey, EventMap, Trigger, TriggerSchema } from '@triggery/core';import { useEvent } from '@triggery/react';import { useCallback } from 'react';// Tiny subset of the Standard Schema interface — enough for our purposes.type StandardSchema<Out> = { readonly '~standard': { readonly validate: (input: unknown) => | { value: Out; issues?: undefined } | { issues: readonly { message: string; path?: readonly (PropertyKey | { key: PropertyKey })[] }[] } | Promise<unknown>; };};export type ValidationResult<T> = | { ok: true; data: T } | { ok: false; issues: readonly { message: string; path?: readonly (PropertyKey | { key: PropertyKey })[] }[] };export function useValidatedEvent< S extends TriggerSchema, K extends EventKey<S>,>(trigger: Trigger<S>, eventName: K, schema: StandardSchema<EventMap<S>[K]>) { const fire = useEvent(trigger, eventName); return useCallback( (input: unknown): ValidationResult<EventMap<S>[K]> => { const out = schema['~standard'].validate(input); if (out instanceof Promise) { // For brevity: keep the helper synchronous. Most schemas (zod/valibot) // run sync unless you opt into async refinements. throw new Error('useValidatedEvent: async schemas are not supported'); } if ('issues' in out && out.issues) { return { ok: false, issues: out.issues }; } const data = (out as { value: EventMap<S>[K] }).value; // Strong assertion: zod's output is the trigger's event payload type. (fire as (payload: EventMap<S>[K]) => void)(data); return { ok: true, data }; }, [fire, schema], );}
Всё — меньше 30 строк, типобезопасно от и до. Продьюсер вызывает один раз, триггер видит только проверенные payload’ы.