Skip to content
GitHubXDiscord

@triggery/solid

SolidJS bindings for Triggery. Same hook shape as @triggery/react and @triggery/vue, native to Solid’s signals and onCleanup lifecycle. Zero runtime dependencies — a thin lifecycle layer over @triggery/core.

npm bundle

pnpm add @triggery/core @triggery/solid solid-js

Peer deps: solid-js >= 1.8.0.

ExportPurpose
useEventTyped event emitter, stable identity across the component lifetime.
useConditionRegister a pull-only condition getter. Reads signals naturally.
useActionRegister an action handler. Auto-cleans on onCleanup.
useInlineTriggerOne-off triggers declared inside a component.
<TriggerRuntimeProvider>Inject a custom runtime via Solid’s context API.
<TriggerScope>Scope condition/action registrations to a feature subtree.

Three small files. The trigger reads like a spec, components only know about their own port.

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

type Settings = { sound: boolean; notifications: boolean };

export const messageTrigger = createTrigger<{
  events: { 'new-message': { text: string; author: string } };
  conditions: { settings: Settings };
  actions: { showToast: { title: string; body: string } };
}>({
  id: 'message-received',
  events: ['new-message'],
  required: ['settings'],
  handler({ event, conditions, actions }) {
    if (!conditions.settings.notifications) return;
    actions.showToast?.({
      title: event.payload.author,
      body:  event.payload.text,
    });
  },
});
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')!,
);
src/App.tsx
import { createSignal, type Component } from 'solid-js';
import { useAction, useCondition, useEvent } from '@triggery/solid';
import { messageTrigger } from './triggers/message.trigger';

const SettingsProvider: Component = () => {
  const [settings] = createSignal({ sound: true, notifications: true });
  useCondition(messageTrigger, 'settings', () => settings());
  return null;
};

const Chat: Component = () => {
  const fire = useEvent(messageTrigger, 'new-message');
  return (
    <button onClick={() => fire({ text: 'hi', author: 'Alice' })}>
      send
    </button>
  );
};

const Toast: Component = () => {
  useAction(messageTrigger, 'showToast', ({ title, body }) =>
    console.log(`[${title}] ${body}`),
  );
  return null;
};

export const App: Component = () => (
  <>
    <SettingsProvider />
    <Chat />
    <Toast />
  </>
);

Click the button — Toast logs. Toggle settings.notifications to false — silent.

Solid components only run their setup once. No useCallback, no dependency arrays, no ref dance — just close over what you need. Condition getters that read signals automatically see the latest value at fire time because Solid signals are pure functions.

// Compare to React: no deps array, no useCallback, no memoization.
useCondition(trigger, 'settings', () => settings());

All composables auto-clean via onCleanup — they work both inside component setup and inside detached createRoot() scopes. No manual disposer plumbing.