@shiftbloom-studio/symphony-state
v0.1.0
Published
Orchestrate multiple state sources without a monolithic global store.
Downloads
172
Maintainers
Readme
Symphony State
Orchestrate multiple state sources without a monolithic global store.
Symphony State coordinates local state, React Context, external stores (Zustand/Redux), server state libraries (TanStack Query/SWR), URL params, and browser persistence without taking ownership of those sources. It focuses on deterministic, dependency-driven propagation and atomic transactions so you can keep cross-domain state consistent.
Why Symphony State?
- Mixed sources drift: local state, URL params, and external stores can diverge.
- Update storms: independent updates trigger multiple re-renders.
- Cross-domain dependencies: cart state depends on auth/pricing, etc.
Symphony State provides an orchestration layer that schedules updates in a single wave and keeps dependencies consistent.
Installation
npm install @shiftbloom-studio/symphony-stateQuickstart (AtomAdapter)
import {
createConductor,
defineSection,
createAtomAdapter
} from "@shiftbloom-studio/symphony-state";
import { SymphonyProvider, useSection } from "@shiftbloom-studio/symphony-state/react";
const auth = defineSection({
key: "auth",
source: createAtomAdapter({ userId: null as string | null })
});
const conductor = createConductor({ sections: [auth] });
function AuthPanel() {
const authSection = useSection<{ userId: string | null }>("auth");
return (
<button onClick={() => authSection.set({ userId: "42" })}>
{authSection.value.userId ?? "Login"}
</button>
);
}
export function App() {
return (
<SymphonyProvider conductor={conductor}>
<AuthPanel />
</SymphonyProvider>
);
}Quickstart (Zustand + Redux adapters)
import { createZustandAdapter } from "@shiftbloom-studio/symphony-state/adapters/zustand";
import { createReduxAdapter } from "@shiftbloom-studio/symphony-state/adapters/redux";
const zustandSection = defineSection({
key: "filters",
source: createZustandAdapter(zustandStore)
});
const reduxSection = defineSection({
key: "cart",
source: createReduxAdapter(reduxStore, {
select: (state) => state.cart,
update: (next) => ({ type: "cart/replace", payload: next }),
patch: (partial) => ({ type: "cart/patch", payload: partial })
})
});Transactions
conductor.transaction(() => {
conductor.getSection("auth").set({ userId: "42" });
conductor.getSection("cart").patch({ ownerId: "42" });
}, "login");All section updates are staged, resolved in dependency order, committed atomically, and only then are subscribers notified.
Derived sections
import { defineDerivedSection } from "@shiftbloom-studio/symphony-state";
const pricing = defineDerivedSection({
key: "pricing",
inputs: ["cart", "auth"],
compute: (cart, auth) => ({
total: cart.items.length * (auth.isPremium ? 0.8 : 1)
})
});Derived sections are read-only and recompute only when their inputs change.
Next.js patterns
App Router
// app/layout.tsx
"use client";
import { SymphonyProvider } from "@shiftbloom-studio/symphony-state/react";
import { conductor } from "./symphony";
export default function RootLayout({ children }) {
return <SymphonyProvider conductor={conductor}>{children}</SymphonyProvider>;
}Pages Router
// pages/_app.tsx
import { SymphonyProvider } from "@shiftbloom-studio/symphony-state/react";
import { conductor } from "../symphony";
export default function App({ Component, pageProps }) {
return (
<SymphonyProvider conductor={conductor}>
<Component {...pageProps} />
</SymphonyProvider>
);
}Hydration helper
import { SymphonyScript } from "@shiftbloom-studio/symphony-state/react";
<SymphonyScript state={{ auth: { userId: "42" } }} />;Persistence
import { createStorageSink } from "@shiftbloom-studio/symphony-state";
const auth = defineSection({
key: "auth",
source: createAtomAdapter({ userId: null }),
persist: createStorageSink({
key: "symphony-auth",
throttleMs: 200
})
});DevTools (optional)
import { SymphonyDevTools } from "@shiftbloom-studio/symphony-state/devtools";
<SymphonyDevTools maxTransactions={10} />;DevTools are dev-only, tree-shakeable, and unstyled apart from minimal CSS variables.
API Reference
| API | Description |
| ----------------------------------- | ---------------------------------------------- |
| createConductor(config) | Create a conductor instance. |
| defineSection(def) | Define a section backed by a source adapter. |
| defineDerivedSection(def) | Define a derived, read-only section. |
| createAtomAdapter(initial) | Built-in minimal store. |
| createExternalStoreAdapter(store) | Wrap an external get/set/subscribe store. |
| createUrlParamsAdapter(options) | Sync with URL search params. |
| createStorageSink(options) | Persist section values to storage. |
| SymphonyProvider | React context provider. |
| useSection(key) | React hook for section read/write. |
| useSelector(key, selector) | Selector hook with equality. |
| createSymphony(config) | Typed helper that wires a conductor and hooks. |
| SymphonyDevTools | Optional devtools panel. |
Design principles
- Orchestration, not ownership: Symphony State coordinates existing stores.
- Predictable updates: deterministic, dependency-ordered commit waves.
- Tree-shakeable: optional adapters and devtools ship as separate entrypoints.
- SSR-safe: no unguarded
windowusage in core.
License
MIT
