{"id":35570950,"url":"https://github.com/shiftbloom-studio/symphony-state","last_synced_at":"2026-04-12T19:21:26.316Z","repository":{"id":332409209,"uuid":"1127751176","full_name":"shiftbloom-studio/symphony-state","owner":"shiftbloom-studio","description":"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.","archived":false,"fork":false,"pushed_at":"2026-01-11T21:54:47.000Z","size":1002,"stargazers_count":1,"open_issues_count":4,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-13T19:45:56.286Z","etag":null,"topics":["nextjs","orchestrator","react","state-management"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shiftbloom-studio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-04T14:25:22.000Z","updated_at":"2026-01-09T22:05:15.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/shiftbloom-studio/symphony-state","commit_stats":null,"previous_names":["shiftbloom-studio/symphony-state"],"tags_count":2,"template":false,"template_full_name":"shiftbloom-studio/npm-package-template","purl":"pkg:github/shiftbloom-studio/symphony-state","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shiftbloom-studio%2Fsymphony-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shiftbloom-studio%2Fsymphony-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shiftbloom-studio%2Fsymphony-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shiftbloom-studio%2Fsymphony-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shiftbloom-studio","download_url":"https://codeload.github.com/shiftbloom-studio/symphony-state/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shiftbloom-studio%2Fsymphony-state/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28529875,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T00:39:45.795Z","status":"online","status_checked_at":"2026-01-18T02:00:07.578Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nextjs","orchestrator","react","state-management"],"created_at":"2026-01-04T17:15:50.450Z","updated_at":"2026-04-12T19:21:26.298Z","avatar_url":"https://github.com/shiftbloom-studio.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Symphony State\n\n[![npm](https://img.shields.io/npm/v/@shiftbloom-studio/symphony-state)](https://www.npmjs.com/package/@shiftbloom-studio/symphony-state)\n[![CI](https://img.shields.io/github/actions/workflow/status/shiftbloom-studio/symphony-state/ci.yml)](https://github.com/shiftbloom-studio/symphony-state/actions/workflows/ci.yml)\n[![license](https://img.shields.io/npm/l/@shiftbloom-studio/symphony-state)](LICENSE)\n\n**Orchestrate multiple state sources without a monolithic global store.**\n\nSymphony 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.\n\n---\n\n## Why Symphony State?\n\nModern apps blend server state (SWR/TanStack Query), local UI state, URL params, and client caches. Those sources drift, and race conditions appear.\n\nSymphony State focuses on **coordination**:\n\n- **Orchestration, not ownership**: keep each state source independent.\n- **Deterministic updates**: staged commits resolve dependencies in a single wave.\n- **Smart reconciliation**: establish precedence between server, cache, and optimistic UI.\n- **Observability**: inspect which source is driving your UI at any time.\n- **Performance**: local updates notify only subscribers for touched sections.\n\n---\n\n## Install\n\n```bash\nnpm install @shiftbloom-studio/symphony-state\n```\n\n---\n\n## Quickstart (AtomAdapter + React)\n\n```tsx\nimport {\n  createConductor,\n  defineSection,\n  createAtomAdapter\n} from \"@shiftbloom-studio/symphony-state\";\nimport { SymphonyProvider, useSection } from \"@shiftbloom-studio/symphony-state/react\";\n\nconst auth = defineSection({\n  key: \"auth\",\n  source: createAtomAdapter({ userId: null as string | null })\n});\n\nconst conductor = createConductor({ sections: [auth] });\n\nfunction AuthPanel() {\n  const authSection = useSection\u003c{ userId: string | null }\u003e(\"auth\");\n  return (\n    \u003cbutton onClick={() =\u003e authSection.set({ userId: \"42\" })}\u003e\n      {authSection.value.userId ?? \"Login\"}\n    \u003c/button\u003e\n  );\n}\n\nexport function App() {\n  return (\n    \u003cSymphonyProvider conductor={conductor}\u003e\n      \u003cAuthPanel /\u003e\n    \u003c/SymphonyProvider\u003e\n  );\n}\n```\n\n---\n\n## The Conductor Pattern (multi-source orchestration)\n\nThe **Conductor** stitches independent sources together. Use the orchestrated adapter to define a \"symphony\" of sources that resolves into a single view.\n\n```ts\nimport {\n  createOrchestratedAdapter,\n  defineSection,\n  createAtomAdapter,\n  createExternalStoreAdapter\n} from \"@shiftbloom-studio/symphony-state\";\n\nconst serverCache = createExternalStoreAdapter(serverCacheStore);\nconst localDraft = createAtomAdapter({ title: \"\", body: \"\" });\n\nconst postSection = defineSection({\n  key: \"post\",\n  source: createOrchestratedAdapter({\n    instruments: [\n      { id: \"server\", source: serverCache, priority: 1, role: \"server\", staleAfterMs: 30_000 },\n      { id: \"draft\", source: localDraft, priority: 2, role: \"optimistic\" }\n    ],\n    writeTo: \"draft\",\n    optimistic: true\n  })\n});\n```\n\n**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.\n\n---\n\n## Smart Reconciliation\n\nWhen the server says **X** but the client says **Y**, Symphony State provides reconciliation hooks:\n\n- **Optimistic updates**: immediately update UI, then reconcile once server responds.\n- **Eventual consistency**: keep local drafts until authoritative data catches up.\n- **Custom policies**: write your own resolver to merge, prefer, or weight sources.\n\n```ts\nimport type { ReconcileContext } from \"@shiftbloom-studio/symphony-state\";\n\nconst reconcile = \u003cT extends { version: number }\u003e(ctx: ReconcileContext\u003cT\u003e) =\u003e {\n  const entries = Object.entries(ctx.values).map(([id, value]) =\u003e ({ id, value }));\n  const winner = entries.sort((a, b) =\u003e b.value.version - a.value.version)[0];\n  return {\n    value: winner.value,\n    sourceId: winner.id,\n    updatedAt: ctx.meta[winner.id].updatedAt\n  };\n};\n```\n\n\u003e **Note**: This example assumes all values have a `version` property. In production code, add runtime checks or type guards to ensure type safety.\n\n---\n\n## Derived Sections\n\n```ts\nimport { defineDerivedSection } from \"@shiftbloom-studio/symphony-state\";\n\nconst pricing = defineDerivedSection({\n  key: \"pricing\",\n  inputs: [\"cart\", \"auth\"],\n  compute: (cart, auth) =\u003e ({\n    total: cart.items.length * (auth.isPremium ? 0.8 : 1)\n  })\n});\n```\n\nDerived sections are read-only and recompute only when their inputs change.\n\n---\n\n## Transactions\n\n```ts\nconductor.transaction(() =\u003e {\n  conductor.getSection(\"auth\").set({ userId: \"42\" });\n  conductor.getSection(\"cart\").patch({ ownerId: \"42\" });\n}, \"login\");\n```\n\nAll updates are staged, resolved in dependency order, and committed atomically.\n\n---\n\n## Persistence\n\n```ts\nimport { createStorageSink } from \"@shiftbloom-studio/symphony-state\";\n\nconst auth = defineSection({\n  key: \"auth\",\n  source: createAtomAdapter({ userId: null }),\n  persist: createStorageSink({\n    key: \"symphony-auth\",\n    throttleMs: 200\n  })\n});\n```\n\n---\n\n## DevTools \u0026 Observability\n\nUse the built-in devtools panel or access orchestration snapshots programmatically.\n\n```tsx\nimport { SymphonyDevTools } from \"@shiftbloom-studio/symphony-state/devtools\";\n\n\u003cSymphonyDevTools maxTransactions={10} /\u003e;\n```\n\n```ts\n// Keep a reference to your orchestrated adapter when wiring the section:\nconst postAdapter = createOrchestratedAdapter({\n  instruments: [\n    { id: \"server\", source: serverCache, priority: 1, role: \"server\" },\n    { id: \"draft\", source: localDraft, priority: 2, role: \"optimistic\" }\n  ]\n});\n\nconst posts = defineSection({\n  key: \"posts\",\n  source: postAdapter\n});\n\n// Later, inspect the current orchestration snapshot:\nconst snapshot = postAdapter.getSnapshot();\n```\n\n`getSnapshot()` reports the active **driver** plus all instrument values, priorities, and staleness flags so you can see which source is in control.\n\n---\n\n## Next.js integration\n\n### App Router\n\n```tsx\n// app/layout.tsx\n\"use client\";\nimport { SymphonyProvider } from \"@shiftbloom-studio/symphony-state/react\";\nimport { conductor } from \"./symphony\";\n\nexport default function RootLayout({ children }) {\n  return \u003cSymphonyProvider conductor={conductor}\u003e{children}\u003c/SymphonyProvider\u003e;\n}\n```\n\n### Pages Router\n\n```tsx\n// pages/_app.tsx\nimport { SymphonyProvider } from \"@shiftbloom-studio/symphony-state/react\";\nimport { conductor } from \"../symphony\";\n\nexport default function App({ Component, pageProps }) {\n  return (\n    \u003cSymphonyProvider conductor={conductor}\u003e\n      \u003cComponent {...pageProps} /\u003e\n    \u003c/SymphonyProvider\u003e\n  );\n}\n```\n\n### Hydration helper\n\n```tsx\nimport { SymphonyScript } from \"@shiftbloom-studio/symphony-state/react\";\n\n\u003cSymphonyScript state={{ auth: { userId: \"42\" } }} /\u003e;\n```\n\n---\n\n## API Reference\n\n| API                                 | Description                                    |\n| ----------------------------------- | ---------------------------------------------- |\n| `createConductor(config)`           | Create a conductor instance.                   |\n| `defineSection(def)`                | Define a section backed by a source adapter.   |\n| `defineDerivedSection(def)`         | Define a derived, read-only section.           |\n| `createOrchestratedAdapter(config)` | Orchestrate multiple sources with precedence.  |\n| `createAtomAdapter(initial)`        | Built-in minimal store.                        |\n| `createExternalStoreAdapter(store)` | Wrap an external get/set/subscribe store.      |\n| `createUrlParamsAdapter(options)`   | Sync with URL search params.                   |\n| `createStorageSink(options)`        | Persist section values to storage.             |\n| `SymphonyProvider`                  | React context provider.                        |\n| `useSection(key)`                   | React hook for section read/write.             |\n| `useSelector(key, selector)`        | Selector hook with equality.                   |\n| `createSymphony(config)`            | Typed helper that wires a conductor and hooks. |\n| `SymphonyDevTools`                  | Optional devtools panel.                       |\n\n---\n\n## Design Principles\n\n- **Orchestration, not monolith**: state sources remain independent.\n- **Deterministic propagation**: dependency-ordered commit waves.\n- **Composable adapters**: plug in external stores without boilerplate.\n- **SSR-safe**: no unguarded `window` usage in core.\n\n---\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshiftbloom-studio%2Fsymphony-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshiftbloom-studio%2Fsymphony-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshiftbloom-studio%2Fsymphony-state/lists"}