Начало работы
За пять минут у тебя получится один файл со сценарием («пришло новое сообщение, у пользователя включены уведомления — показать тост») и три небольших компонента, которые к этому файлу подключаются. Ни один из компонентов не знает о других.
Пропустить установку — открыть пример счётчика в StackBlitz Пропустить установку — открыть пример счётчика в GitHubТребования
Заголовок раздела «Требования»- Node 20+ для инструментов разработки.
- Проект на React 18+/19, Solid 1.8+ или Vue 3.4+. (Если у тебя его пока нет, запусти
pnpm dlx @triggery/cli create my-app, чтобы развернуть минимальный Vite-проект — см.@triggery/cli.) - TypeScript рекомендуется, но не обязателен. Примеры на этом сайте предполагают TS.
1. Установка
Заголовок раздела «1. Установка»Понадобятся два пакета: @triggery/core (рантайм) и один биндинг под нужный фреймворк.
pnpm add @triggery/core @triggery/react npm install @triggery/core @triggery/react yarn add @triggery/core @triggery/react bun add @triggery/core @triggery/react Peer-зависимости: react >= 18.0.0, react-dom >= 18.0.0.
pnpm add @triggery/core @triggery/solid npm install @triggery/core @triggery/solid yarn add @triggery/core @triggery/solid bun add @triggery/core @triggery/solid Peer-зависимость: solid-js >= 1.8.0.
pnpm add @triggery/core @triggery/vue npm install @triggery/core @triggery/vue yarn add @triggery/core @triggery/vue bun add @triggery/core @triggery/vue Peer-зависимость: vue >= 3.4.0.
2. Оберни приложение в провайдер рантайма
Заголовок раздела «2. Оберни приложение в провайдер рантайма»Рантайм — то, что хранит реестр триггеров, условий и действий. В большинстве приложений создаётся один рантайм на корневом уровне.
import { createRuntime } from '@triggery/core';
import { TriggerRuntimeProvider } from '@triggery/react';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
const runtime = createRuntime();
createRoot(document.getElementById('root')!).render(
<StrictMode>
<TriggerRuntimeProvider runtime={runtime}>
<App />
</TriggerRuntimeProvider>
</StrictMode>,
);import { createRuntime } from '@triggery/core';
import { TriggerRuntimeProvider } from '@triggery/solid';
import { render } from 'solid-js/web';
import { App } from './App';
const runtime = createRuntime();
render(
() => (
<TriggerRuntimeProvider runtime={runtime}>
<App />
</TriggerRuntimeProvider>
),
document.getElementById('root')!,
);import { createRuntime } from '@triggery/core';
import { TriggerRuntimePlugin } from '@triggery/vue';
import { createApp } from 'vue';
import App from './App.vue';
const runtime = createRuntime();
createApp(App).use(TriggerRuntimePlugin, { runtime }).mount('#root');Провайдер можно вообще не ставить — тогда Triggery возьмёт общий рантайм, который создаётся по умолчанию при первом обращении. Явный провайдер нужен, чтобы изолировать рантаймы в тестах, микрофронтендах и мультиарендных приложениях.
3. Напиши первый триггер
Заголовок раздела «3. Напиши первый триггер»Принятый стиль — один триггер на файл, с суффиксом .trigger.ts. Файл читается сверху вниз как спецификация.
import { createTrigger } from '@triggery/core';
type Settings = { sound: boolean; notifications: boolean };
type Message = { author: string; text: string; channelId: string };
export const messageTrigger = createTrigger<{
events: { 'new-message': Message };
conditions: { settings: Settings; activeChannelId: string | null };
actions: { showToast: { title: string; body: string }; playSound: 'beep' };
}>({
id: 'message-received',
events: ['new-message'],
required: ['settings'],
handler({ event, conditions, actions, check }) {
// Skip if the user is already looking at this conversation.
if (conditions.activeChannelId === event.payload.channelId) return;
// Skip if notifications are off — `check.is` narrows the value.
if (!check.is('settings', s => s.notifications)) return;
// Debounce sound so a burst of messages plays one beep.
actions.debounce(800).playSound?.('beep');
actions.showToast?.({
title: event.payload.author,
body: event.payload.text,
});
},
});Один inline-обобщённый параметр описывает все порты триггера — три словаря с типами событий, условий и действий. Контекст обработчика ctx теперь строго типизирован: попробуй передать неверное имя события или несовместимый payload — TypeScript остановит на этапе компиляции.
4. Подключи компоненты
Заголовок раздела «4. Подключи компоненты»Triggery разделяет «кто что делает» на три роли компонента:
- Продьюсер — отправляет событие через
useEvent. - Провайдер — отдаёт снимок состояния через
useCondition, чтобы обработчик мог прочитать его лениво. - Реактор — регистрирует действие через
useAction.
В настоящем чат-приложении каждая из этих ролей живёт в своём компоненте — в той части фичи, которой она естественно принадлежит.
import { useCondition } from '@triggery/react';
import { useState } from 'react';
import { messageTrigger } from '../triggers/message.trigger';
export function SettingsPanel() {
const [settings, setSettings] = useState({ sound: true, notifications: true });
useCondition(messageTrigger, 'settings', () => settings, [settings]);
// …UI for editing settings
}import { useEvent, useCondition } from '@triggery/react';
import { messageTrigger } from '../triggers/message.trigger';
export function Chat({ channelId }: { channelId: string }) {
const fireMessage = useEvent(messageTrigger, 'new-message');
useCondition(messageTrigger, 'activeChannelId', () => channelId, [channelId]);
return (
<button type="button" onClick={() => fireMessage({ author: 'Alice', text: 'hi', channelId: 'b' })}>
send a fake message
</button>
);
}import { useAction } from '@triggery/react';
import { toast } from 'sonner';
import { messageTrigger } from '../triggers/message.trigger';
export function NotificationLayer() {
useAction(messageTrigger, 'showToast', payload => {
toast.success(payload.title, { description: payload.body });
});
useAction(messageTrigger, 'playSound', () => {
new Audio('/beep.mp3').play().catch(() => {});
});
return null;
}import { useCondition } from '@triggery/solid';
import { createSignal } from 'solid-js';
import { messageTrigger } from '../triggers/message.trigger';
export function SettingsPanel() {
const [settings] = createSignal({ sound: true, notifications: true });
useCondition(messageTrigger, 'settings', settings);
}import { useEvent, useCondition } from '@triggery/solid';
import { messageTrigger } from '../triggers/message.trigger';
export function Chat(props: { channelId: string }) {
const fireMessage = useEvent(messageTrigger, 'new-message');
useCondition(messageTrigger, 'activeChannelId', () => props.channelId);
return <button onClick={() => fireMessage({ author: 'Alice', text: 'hi', channelId: 'b' })}>send</button>;
}import { useAction } from '@triggery/solid';
import { messageTrigger } from '../triggers/message.trigger';
export function NotificationLayer() {
useAction(messageTrigger, 'showToast', payload => console.log('toast', payload));
useAction(messageTrigger, 'playSound', () => console.log('beep'));
return null;
}<script setup lang="ts">
import { useCondition } from '@triggery/vue';
import { ref } from 'vue';
import { messageTrigger } from '../triggers/message.trigger';
const settings = ref({ sound: true, notifications: true });
useCondition(messageTrigger, 'settings', () => settings.value);
</script><script setup lang="ts">
import { useEvent, useCondition } from '@triggery/vue';
import { defineProps } from 'vue';
import { messageTrigger } from '../triggers/message.trigger';
const props = defineProps<{ channelId: string }>();
const fireMessage = useEvent(messageTrigger, 'new-message');
useCondition(messageTrigger, 'activeChannelId', () => props.channelId);
</script>
<template>
<button @click="fireMessage({ author: 'Alice', text: 'hi', channelId: 'b' })">send</button>
</template><script setup lang="ts">
import { useAction } from '@triggery/vue';
import { messageTrigger } from '../triggers/message.trigger';
useAction(messageTrigger, 'showToast', payload => console.log('toast', payload));
useAction(messageTrigger, 'playSound', () => console.log('beep'));
</script>Размести три компонента где угодно в дереве под провайдером — они найдут друг друга через триггер, а не через контекст React/Solid/Vue.
5. Проверь
Заголовок раздела «5. Проверь»Нажми «send a fake message» в компоненте Chat. Слой уведомлений напишет в консоль или покажет тост. Выключи settings.notifications — и он замолчит. Весь сценарий целиком — это один файл, и каждый компонент может расти независимо.