@everystate/solid 1.0.1

Solid adapter for @everystate/core. Four functions - the entire API. ~30 lines.

See a complete task manager: Solid Example

Install

npm install @everystate/solid @everystate/core

Peers: solid-js >=1.6.0, @everystate/core >=1.0.5.

Quick Start

import { createEveryState } from '@everystate/core';
import { usePath, useIntent } from '@everystate/solid';

const store = createEveryState({ state: { count: 0 } });

store.subscribe('intent.increment', () => {
  store.set('state.count', store.get('state.count') + 1);
});

function Counter() {
  const count = usePath(store, 'state.count');
  const inc = useIntent(store, 'intent.increment');
  return <button onClick={() => inc(true)}>{count()}</button>;
}

usePath(store, path)

usePath<T>(store, path) → Accessor<T>

Read-only Solid accessor. Updates when store value changes. Fine-grained by default - only computations reading this accessor re-run.

const count = usePath(store, 'state.taskCount');
const items = usePath(store, 'derived.tasks.filtered');

useIntent(store, path)

useIntent(store, path) → (value) → any

Stable setter function. Publishes intent to the store.

const add = useIntent(store, 'intent.addTask');
const toggle = useIntent(store, 'intent.toggleTask');

useWildcard(store, path)

useWildcard<T>(store, wildcardPath) → Accessor<T>

Re-runs when any child of the path changes.

const user = useWildcard(store, 'state.user.*');

useAsync(store, path)

useAsync(store, path) → { data, status, error, execute, cancel }

Async lifecycle with auto-abort. Each field is a Solid accessor.

const { data, status, execute } = useAsync(store, 'users');
execute((signal) => fetch('/api/users', { signal }).then(r => r.json()));

Context Pattern (Optional)

Unlike React/Vue where the provider is essentially required, in Solid passing the store as a prop is perfectly idiomatic. But if you prefer DI:

import { createContext, useContext } from 'solid-js';

const StoreContext = createContext();

function App() {
  const store = createEveryState({ state: { count: 0 } });
  return (
    <StoreContext.Provider value={store}>
      <Counter />
    </StoreContext.Provider>
  );
}

function useStore() { return useContext(StoreContext); }

Three Namespaces

NamespacePurposeFunction
state.*Application stateusePath
derived.*Computed projectionsusePath
intent.*UI signalsuseIntent

Fine-Grained Updates

Solid has no virtual DOM. When usePath returns an accessor that changes, only the exact DOM expression reading it updates. No diffing, no reconciliation, no component re-renders. This is the most efficient bridge possible - EveryState fires per-path, Solid updates per-signal.

Testing Without Solid

test('add task increments count', () => {
  const store = createEveryState({ state: { tasks: [], taskCount: 0 } });
  store.subscribe('intent.addTask', (text) => {
    const tasks = store.get('state.tasks') || [];
    const next = [...tasks, { text, done: false }];
    store.set('state.tasks', next);
    store.set('state.taskCount', next.length);
  });
  store.set('intent.addTask', 'test');
  expect(store.get('state.taskCount')).toBe(1);
});

No createRoot. No renderToString. Just state in, state out.