Notification pipeline
This is the Solid version of the flagship Triggery scenario. Read the React recipe first — the trigger file is identical, and the prose there explains the scenario in detail. Below is the Solid-specific component code.
Open in StackBlitz Open example on GitHubFile layout
Section titled “File layout”- README.md narrative overview
- index.html Vite entry
Directorysrc/
- App.tsx producers + reactors live here
- main.tsx bootstrap
Directorytriggers/
- index.ts the rule — events, conditions, actions, handler
The trigger (identical)
Section titled “The trigger (identical)”Lives in @triggery/core; no framework dependency. Copy as-is from the React recipe.
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');
},
});Providers (Solid signals)
Section titled “Providers (Solid signals)”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>
);
}Reactor
Section titled “Reactor”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;
}Wire the app
Section titled “Wire the app”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')!,
);The whole rule is in message.trigger.ts. Switching from React to Solid changed only the hooks-as-side-effects glue.
Related
Section titled “Related” React version (canonical) The full narrative, including comparison to useEffect.
Vue version Composition API equivalent.
Solid counter The smaller hello-world recipe.
@triggery/solid package Full hook reference.