https://github.com/shiftbloom-studio/symphony-state
An orchestrator that keeps multiple state sources (server state, client state, local states) neatly synchronized without everything ending up in a global monster store. Ideal for larger apps, where states often exist side by side in an uncoordinated manner today.
https://github.com/shiftbloom-studio/symphony-state
nextjs orchestrator react state-management
Last synced: 3 months ago
JSON representation
An orchestrator that keeps multiple state sources (server state, client state, local states) neatly synchronized without everything ending up in a global monster store. Ideal for larger apps, where states often exist side by side in an uncoordinated manner today.
- Host: GitHub
- URL: https://github.com/shiftbloom-studio/symphony-state
- Owner: shiftbloom-studio
- License: mit
- Created: 2026-01-04T14:25:22.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2026-01-11T21:54:47.000Z (6 months ago)
- Last Synced: 2026-01-13T19:45:56.286Z (6 months ago)
- Topics: nextjs, orchestrator, react, state-management
- Language: TypeScript
- Homepage:
- Size: 979 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README
# Symphony State
[](https://www.npmjs.com/package/@shiftbloom-studio/symphony-state)
[](https://github.com/shiftbloom-studio/symphony-state/actions/workflows/ci.yml)
[](LICENSE)
**Orchestrate multiple state sources without a monolithic global store.**
Symphony State is a lightweight orchestration layer that keeps server caches, UI state, and browser persistence in tempo. It does not replace your existing stores. Instead, it composes them into predictable, dependency-driven flows with atomic transactions, derived sections, and observable reconciliation.
---
## Why Symphony State?
Modern apps blend server state (SWR/TanStack Query), local UI state, URL params, and client caches. Those sources drift, and race conditions appear.
Symphony State focuses on **coordination**:
- **Orchestration, not ownership**: keep each state source independent.
- **Deterministic updates**: staged commits resolve dependencies in a single wave.
- **Smart reconciliation**: establish precedence between server, cache, and optimistic UI.
- **Observability**: inspect which source is driving your UI at any time.
- **Performance**: local updates notify only subscribers for touched sections.
---
## Install
```bash
npm install @shiftbloom-studio/symphony-state
```
---
## Quickstart (AtomAdapter + React)
```tsx
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 (
authSection.set({ userId: "42" })}>
{authSection.value.userId ?? "Login"}
);
}
export function App() {
return (
);
}
```
---
## The Conductor Pattern (multi-source orchestration)
The **Conductor** stitches independent sources together. Use the orchestrated adapter to define a "symphony" of sources that resolves into a single view.
```ts
import {
createOrchestratedAdapter,
defineSection,
createAtomAdapter,
createExternalStoreAdapter
} from "@shiftbloom-studio/symphony-state";
const serverCache = createExternalStoreAdapter(serverCacheStore);
const localDraft = createAtomAdapter({ title: "", body: "" });
const postSection = defineSection({
key: "post",
source: createOrchestratedAdapter({
instruments: [
{ id: "server", source: serverCache, priority: 1, role: "server", staleAfterMs: 30_000 },
{ id: "draft", source: localDraft, priority: 2, role: "optimistic" }
],
writeTo: "draft",
optimistic: true
})
});
```
**Precedence** is decided by `priority` and `updatedAt`, with staleness protection. By default, the highest priority, freshest instrument wins. You can override reconciliation with a custom `reconcile` function.
---
## Smart Reconciliation
When the server says **X** but the client says **Y**, Symphony State provides reconciliation hooks:
- **Optimistic updates**: immediately update UI, then reconcile once server responds.
- **Eventual consistency**: keep local drafts until authoritative data catches up.
- **Custom policies**: write your own resolver to merge, prefer, or weight sources.
```ts
import type { ReconcileContext } from "@shiftbloom-studio/symphony-state";
const reconcile = (ctx: ReconcileContext) => {
const entries = Object.entries(ctx.values).map(([id, value]) => ({ id, value }));
const winner = entries.sort((a, b) => b.value.version - a.value.version)[0];
return {
value: winner.value,
sourceId: winner.id,
updatedAt: ctx.meta[winner.id].updatedAt
};
};
```
> **Note**: This example assumes all values have a `version` property. In production code, add runtime checks or type guards to ensure type safety.
---
## Derived Sections
```ts
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.
---
## Transactions
```ts
conductor.transaction(() => {
conductor.getSection("auth").set({ userId: "42" });
conductor.getSection("cart").patch({ ownerId: "42" });
}, "login");
```
All updates are staged, resolved in dependency order, and committed atomically.
---
## Persistence
```ts
import { createStorageSink } from "@shiftbloom-studio/symphony-state";
const auth = defineSection({
key: "auth",
source: createAtomAdapter({ userId: null }),
persist: createStorageSink({
key: "symphony-auth",
throttleMs: 200
})
});
```
---
## DevTools & Observability
Use the built-in devtools panel or access orchestration snapshots programmatically.
```tsx
import { SymphonyDevTools } from "@shiftbloom-studio/symphony-state/devtools";
;
```
```ts
// Keep a reference to your orchestrated adapter when wiring the section:
const postAdapter = createOrchestratedAdapter({
instruments: [
{ id: "server", source: serverCache, priority: 1, role: "server" },
{ id: "draft", source: localDraft, priority: 2, role: "optimistic" }
]
});
const posts = defineSection({
key: "posts",
source: postAdapter
});
// Later, inspect the current orchestration snapshot:
const snapshot = postAdapter.getSnapshot();
```
`getSnapshot()` reports the active **driver** plus all instrument values, priorities, and staleness flags so you can see which source is in control.
---
## Next.js integration
### App Router
```tsx
// app/layout.tsx
"use client";
import { SymphonyProvider } from "@shiftbloom-studio/symphony-state/react";
import { conductor } from "./symphony";
export default function RootLayout({ children }) {
return {children};
}
```
### Pages Router
```tsx
// pages/_app.tsx
import { SymphonyProvider } from "@shiftbloom-studio/symphony-state/react";
import { conductor } from "../symphony";
export default function App({ Component, pageProps }) {
return (
);
}
```
### Hydration helper
```tsx
import { SymphonyScript } from "@shiftbloom-studio/symphony-state/react";
;
```
---
## 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. |
| `createOrchestratedAdapter(config)` | Orchestrate multiple sources with precedence. |
| `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 monolith**: state sources remain independent.
- **Deterministic propagation**: dependency-ordered commit waves.
- **Composable adapters**: plug in external stores without boilerplate.
- **SSR-safe**: no unguarded `window` usage in core.
---
## License
Apache-2.0