EveryState + Vue 3
A complete task manager built with @everystate/vue. Every file shown below. No magic, no hidden code.
Install: npm i @everystate/core @everystate/vue vue
5 files, ~120 lines total
Store, business logic, 3 Vue components. That's the whole app.
Zero business logic in components
Components read (usePath) and publish (useIntent). Subscribers handle logic.
Testable without Vue
Every behavior is testable with store.set() + store.get(). No mount needed.
import { createEveryState } from '@everystate/core';
// ---- Store (created outside Vue) ----
export const store = createEveryState({
state: {
tasks: [],
taskCount: 0,
filter: 'all', // 'all' | 'active' | 'completed'
},
derived: {
tasks: { filtered: [] },
},
});
// ---- Business logic (pure subscribers, no Vue imports) ----
let nextId = 1;
store.subscribe('intent.addTask', (text) => {
const t = String(text || '').trim();
if (!t) return;
const tasks = store.get('state.tasks') || [];
const next = [...tasks, { id: nextId++, text: t, completed: false }];
store.set('state.tasks', next);
store.set('state.taskCount', next.length);
});
store.subscribe('intent.toggleTask', (id) => {
const tasks = store.get('state.tasks') || [];
store.set('state.tasks',
tasks.map(t => t.id === id ? { ...t, completed: !t.completed } : t)
);
});
store.subscribe('intent.deleteTask', (id) => {
const tasks = (store.get('state.tasks') || []).filter(t => t.id !== id);
store.set('state.tasks', tasks);
store.set('state.taskCount', tasks.length);
});
store.subscribe('intent.changeFilter', (filter) => {
store.set('state.filter', filter);
});
// ---- Derived state ----
function recomputeFiltered() {
const tasks = store.get('state.tasks') || [];
const filter = store.get('state.filter');
const filtered = tasks.filter(t => {
if (filter === 'active') return !t.completed;
if (filter === 'completed') return t.completed;
return true;
});
store.set('derived.tasks.filtered', filtered);
}
store.subscribe('state.tasks', recomputeFiltered);
store.subscribe('state.filter', recomputeFiltered);
recomputeFiltered();
<script setup>
import { provideStore } from '@everystate/vue';
import { store } from './store.js';
import TaskInput from './TaskInput.vue';
import TaskList from './TaskList.vue';
import FilterBar from './FilterBar.vue';
// Inject the store into all descendants
provideStore(store);
</script>
<template>
<div class="app">
<h1>Tasks</h1>
<TaskInput />
<FilterBar />
<TaskList />
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useIntent } from '@everystate/vue';
const text = ref('');
const addTask = useIntent('intent.addTask');
function submit() {
if (text.value.trim()) {
addTask(text.value);
text.value = '';
}
}
</script>
<template>
<div class="task-input">
<input
v-model="text"
placeholder="What needs to be done?"
@keydown.enter="submit"
/>
<button @click="submit">Add</button>
</div>
</template>
<script setup>
import { usePath, useIntent } from '@everystate/vue';
const filter = usePath('state.filter');
const count = usePath('state.taskCount');
const setFilter = useIntent('intent.changeFilter');
</script>
<template>
<div class="filter-bar">
<span>
{{ count }} task{{ count === 1 ? '' : 's' }}
</span>
<div class="filter-buttons">
<button
v-for="f in ['all', 'active', 'completed']"
:key="f"
:class="{ active: filter === f }"
@click="setFilter(f)"
>
{{ f }}
</button>
</div>
</div>
</template>
<script setup>
import { usePath, useIntent } from '@everystate/vue';
const items = usePath('derived.tasks.filtered');
const toggle = useIntent('intent.toggleTask');
const del = useIntent('intent.deleteTask');
</script>
<template>
<ul class="task-list" v-if="items && items.length">
<li v-for="task in items" :key="task.id" :class="{ done: task.completed }">
<input
type="checkbox"
:checked="task.completed"
@change="toggle(task.id)"
/>
<span class="task-text">{{ task.text }}</span>
<button class="delete-btn" @click="del(task.id)">×</button>
</li>
</ul>
<p class="empty" v-else>No tasks match the current filter.</p>
</template>
// No Vue imports. No mount(). No wrapper.find(). Just state.
import { store } from './store.js';
test('addTask creates a task and increments count', () => {
store.set('intent.addTask', 'Buy milk');
expect(store.get('state.taskCount')).toBe(1);
expect(store.get('state.tasks')[0].text).toBe('Buy milk');
});
test('toggleTask flips completed', () => {
const id = store.get('state.tasks')[0].id;
store.set('intent.toggleTask', id);
expect(store.get('state.tasks')[0].completed).toBe(true);
});
test('filter shows only active tasks', () => {
store.set('intent.addTask', 'Walk dog');
store.set('intent.changeFilter', 'active');
const filtered = store.get('derived.tasks.filtered');
expect(filtered.every(t => !t.completed)).toBe(true);
});
test('deleteTask removes task and decrements count', () => {
const before = store.get('state.taskCount');
const id = store.get('state.tasks')[0].id;
store.set('intent.deleteTask', id);
expect(store.get('state.taskCount')).toBe(before - 1);
});
The Entire Vue Adapter (~40 lines)
This is the full source of @everystate/vue. No hidden abstractions.
import { ref, computed, onMounted, onBeforeUnmount, provide, inject } from 'vue';
const STORE_KEY = Symbol('everystate');
// -- Dependency injection --
export function provideStore(store) {
provide(STORE_KEY, store);
}
export function useStore() {
const store = inject(STORE_KEY);
if (!store) {
throw new Error(
'useStore: no store found. Call provideStore(store) in an ancestor setup().'
);
}
return store;
}
// -- Read a single path --
export function usePath(path) {
const store = useStore();
const value = ref(store.get(path));
let unsub = null;
onMounted(() => {
unsub = store.subscribe(path, (val) => {
value.value = val;
});
});
onBeforeUnmount(() => { if (unsub) unsub(); });
return computed(() => value.value);
}
// -- Publish to a path --
export function useIntent(path) {
const store = useStore();
return (value) => store.set(path, value);
}
// -- Wildcard subscription --
export function useWildcard(wildcardPath) {
const store = useStore();
const parentPath = wildcardPath.endsWith('.*')
? wildcardPath.slice(0, -2)
: wildcardPath;
const value = ref(store.get(parentPath));
let unsub = null;
onMounted(() => {
unsub = store.subscribe(wildcardPath, () => {
value.value = store.get(parentPath);
});
});
onBeforeUnmount(() => { if (unsub) unsub(); });
return computed(() => value.value);
}
// -- Async with status tracking --
export function useAsync(path) {
const store = useStore();
const data = usePath(`${path}.data`);
const status = usePath(`${path}.status`);
const error = usePath(`${path}.error`);
const execute = (fetcher) => store.setAsync(path, fetcher);
const cancel = () => store.cancel(path);
return { data, status, error, execute, cancel };
}
Key Takeaways
Store lives outside Vue
Created once in store.js. Independent lifecycle. No defineStore, no reactive() wrapper.
Components are thin
usePath reads, useIntent writes. Components never compute or mutate state directly.
Logic is in subscribers
All business rules live in store.subscribe() handlers. Testable without mounting any component.
Vue reactivity still works
usePath returns a computed ref. Templates react normally. v-for, v-if, :class all work as expected.
Derived state is explicit
Filtered tasks are a subscriber that writes to derived.*. Components just read the result.
~40 lines adapter
The entire @everystate/vue package is shown above. ref + computed + provide/inject + lifecycle hooks.