Перейти к содержимому
GitHubXDiscord

Пайплайн уведомлений

Это версия флагманского сценария Triggery на Solid. Сначала прочитай рецепт на Reactфайл триггера идентичен, и проза там подробно разбирает сценарий. Ниже — код компонентов специфично под Solid.

Открыть в StackBlitz Открыть пример на GitHub
  • README.md обзорное описание
  • index.html точка входа Vite
  • Директорияsrc/
    • App.tsx продьюсеры + реакторы
    • main.tsx точка входа
    • Директорияtriggers/
      • index.ts правило — события, условия, действия, обработчик

Живёт в @triggery/core; никакой зависимости от фреймворка. Копируй как есть из React-рецепта.

src/triggers/message.trigger.ts
import { createTrigger } from '@triggery/core';

type Settings = { sound: boolean; notifications: boolean; dnd: boolean };
type Message  = { id: string; author: string; authorId: string; text: string; channelId: string };

export const messageTrigger = createTrigger<{
  events:     { 'new-message': Message };
  conditions: { settings: Settings; activeChannelId: string | null; currentUserId: string };
  actions:    {
    showToast:      { title: string; body: string };
    playSound:      'beep' | 'mention';
    incrementBadge: string;
  };
}>({
  id: 'message-received',
  events: ['new-message'],
  required: ['settings', 'currentUserId'],
  handler({ event, conditions, actions, check }) {
    const msg = event.payload;
    if (msg.channelId === conditions.activeChannelId) return;
    if (msg.authorId === conditions.currentUserId) return;

    actions.incrementBadge?.(msg.channelId);
    if (check.is('settings', s => s.notifications))    actions.showToast?.({ title: msg.author, body: msg.text });
    if (check.is('settings', s => s.sound && !s.dnd))  actions.debounce(800).playSound?.('beep');
  },
});
src/features/settings/SettingsPanel.tsx
import { useCondition } from '@triggery/solid';
import { createSignal } from 'solid-js';
import { messageTrigger } from '../../triggers/message.trigger';

export function SettingsPanel() {
  const [settings, setSettings] = createSignal({ sound: true, notifications: true, dnd: false });
  useCondition(messageTrigger, 'settings', settings);

  return (
    <fieldset>
      <legend>Notifications</legend>
      <label>
        <input
          type="checkbox"
          checked={settings().notifications}
          onChange={e => setSettings(s => ({ ...s, notifications: e.currentTarget.checked }))}
        />
        Show toasts
      </label>
    </fieldset>
  );
}
src/features/session/SessionProvider.tsx
import { useCondition } from '@triggery/solid';
import type { ParentProps } from 'solid-js';
import { messageTrigger } from '../../triggers/message.trigger';

export function SessionProvider(props: ParentProps<{ userId: string }>) {
  useCondition(messageTrigger, 'currentUserId', () => props.userId);
  return <>{props.children}</>;
}
src/features/chat/Chat.tsx
import { useEvent, useCondition } from '@triggery/solid';
import { messageTrigger } from '../../triggers/message.trigger';

export function Chat(props: { channelId: string | null }) {
  useCondition(messageTrigger, 'activeChannelId', () => props.channelId);
  const fireMessage = useEvent(messageTrigger, 'new-message');

  return (
    <button
      onClick={() =>
        fireMessage({
          id:        crypto.randomUUID(),
          author:    'Alice',
          authorId:  'u-alice',
          text:      'hi',
          channelId: 'c-lunch',
        })
      }
    >
      simulate inbound
    </button>
  );
}
src/features/notifications/NotificationLayer.tsx
import { useAction } from '@triggery/solid';
import { messageTrigger } from '../../triggers/message.trigger';
import { useBadgeStore } from '../../stores/badge';

export function NotificationLayer() {
  let audio: HTMLAudioElement | undefined;
  const increment = useBadgeStore(s => s.increment);

  useAction(messageTrigger, 'showToast', ({ title, body }) => {
    console.log('toast', title, body);
  });
  useAction(messageTrigger, 'playSound', () => {
    audio ??= new Audio('/beep.mp3');
    audio.play().catch(() => {});
  });
  useAction(messageTrigger, 'incrementBadge', channelId => increment(channelId));

  return null;
}
src/index.tsx
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')!,
);

Всё правило живёт в message.trigger.ts. При переключении с React на Solid поменялся только клей «хуки как побочные эффекты».