Works Everywhere
Unlike most state management libraries, Valdres doesn't depend on any framework. The core valdres package is pure JavaScript — it works in Node.js, Deno, Bun, web workers, service workers, or any JavaScript runtime.
This is a fundamental design choice, not an afterthought. Your state logic lives in valdres, and framework bindings (valdres-react, valdres-vue, etc.) are thin adapters that subscribe to it.
Vanilla JavaScript
import { atom, selector, store } from "valdres"
const myStore = store()
const countAtom = atom(0)
const doubleSelector = selector(get => get(countAtom) * 2)
myStore.get(countAtom) // 0
myStore.get(doubleSelector) // 0
myStore.set(countAtom, 5)
myStore.get(doubleSelector) // 10
// Subscribe to changes
const unsub = myStore.sub(countAtom, value => {
console.log("count changed:", value)
})
myStore.set(countAtom, 10)
// logs: "count changed: 10"
unsub() // stop listening
Node.js / server-side
Valdres works on the server without any special configuration. This is useful for server-side rendering, API routes, or background jobs that share state definitions with the frontend.
// shared/state.ts — shared between client and server
import { atom, atomFamily } from "valdres"
export const sessionAtom = atom(null)
export const cacheAtom = atomFamily()
// server.ts
import { store } from "valdres"
import { sessionAtom, cacheAtom } from "./shared/state"
const serverStore = store()
// Pre-populate state on the server
serverStore.set(sessionAtom, { userId: "abc", role: "admin" })
serverStore.set(cacheAtom("config"), await loadConfig())
// Pass state to the client via serialization
const snapshot = {
session: serverStore.get(sessionAtom),
config: serverStore.get(cacheAtom("config")),
}
Web Workers
Since Valdres has no DOM dependency, it runs natively in web workers. Keep heavy computation off the main thread while sharing state definitions.
// worker.ts
import { atom, store } from "valdres"
const progressAtom = atom(0)
const workerStore = store()
self.onmessage = async (event) => {
const items = event.data
for (let i = 0; i < items.length; i++) {
await processItem(items[i])
workerStore.set(progressAtom, (i + 1) / items.length)
self.postMessage({ progress: workerStore.get(progressAtom) })
}
}
Testing
Testing with Valdres is straightforward — create a store, set state, assert. No providers, no mocking, no framework overhead.
import { atom, selector, store } from "valdres"
import { expect, test } from "bun:test"
const countAtom = atom(0)
const doubleSelector = selector(get => get(countAtom) * 2)
test("selector derives from atom", () => {
const testStore = store()
testStore.set(countAtom, 5)
expect(testStore.get(doubleSelector)).toBe(10)
})
test("transactions are atomic", () => {
const testStore = store()
const a = atom(100)
const b = atom(100)
const total = selector(get => get(a) + get(b))
testStore.txn(set => {
set(a, 50)
set(b, 150)
})
expect(testStore.get(total)).toBe(200)
})
Sharing state across frameworks
Because state lives in the store — not in components — you can share the same atoms across different frameworks in the same app. This is what powers the demo on our homepage.
// shared.ts — import this from React, Vue, Svelte, or anything else
import { atom, store } from "valdres"
export const appStore = store()
export const countAtom = atom(0)
export const themeAtom = atom("dark")
Each framework adapter just subscribes to the same store:
// In React
import { useValue } from "valdres-react"
import { countAtom } from "./shared"
const count = useValue(countAtom)
<!-- In Vue -->
<script setup>
import { useValue } from "valdres-vue"
import { countAtom } from "./shared"
const count = useValue(countAtom)
</script>
Why this matters
Most state libraries are tightly coupled to a specific framework's rendering model. This means:
- You can't test state logic without mounting components
- You can't share state between micro-frontends using different frameworks
- You can't use state in workers, CLI tools, or server code
- Migration between frameworks requires rewriting state management
Valdres avoids all of these problems by keeping state management framework-agnostic at its core.