{"name":"@playlive/react-pipeline","dist-tags":{"latest":"0.5.5"},"versions":{"0.4.18":{"name":"@playlive/react-pipeline","version":"0.4.18","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.3.0","@playlive/tiltify-core":"^0.4.17","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.4.2","@playlive/fundraiser-data":"^0.5.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-zQCnu9q2UmbN+DJtgr2WXa2uhIntyNCvicJlOe28F04xIMoKWQNFnVuy0p3U3qsknYqmHZwAqsOH51wLQIATzQ==","shasum":"8242e89647dcaddc4fb7225858f9e7278e87caeb","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.18.tgz","shasum":"8242e89647dcaddc4fb7225858f9e7278e87caeb","integrity":"sha512-zQCnu9q2UmbN+DJtgr2WXa2uhIntyNCvicJlOe28F04xIMoKWQNFnVuy0p3U3qsknYqmHZwAqsOH51wLQIATzQ=="}},"0.1.0":{"name":"@playlive/react-pipeline","version":"0.1.0","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"}},"dependencies":{"@playlive/realtime-pipeline":"^0.1.1"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.1.1","@playlive/tiltify-core":"^0.1.1"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-57LS92X5Fc2ABJRFnxf5cn6mvpgSL8fImbXgZ6hHYKhN1rdCdeJEG8/xalnadCTs7PN1drDEKtipBhSRA/utnw==","shasum":"1ac47157e68a96cee6a4253e58d2f853e434dd33","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 21 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 21 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behaviour to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.1.0.tgz","shasum":"1ac47157e68a96cee6a4253e58d2f853e434dd33","integrity":"sha512-57LS92X5Fc2ABJRFnxf5cn6mvpgSL8fImbXgZ6hHYKhN1rdCdeJEG8/xalnadCTs7PN1drDEKtipBhSRA/utnw=="}},"0.1.1":{"name":"@playlive/react-pipeline","version":"0.1.1","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"}},"dependencies":{"@playlive/realtime-pipeline":"^0.1.1"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.1.1","@playlive/tiltify-core":"^0.1.1"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-6fLmVMUgI/nw0NeDIQ10NAFxu8mRPtv3reHSk245aPKN+WoNzWJ7MpsZk6FEqNKDsCzC7dYC234fEQJ9DEGIzg==","shasum":"6c35428a2df813fccb4bd94944a9fcec83c6321c","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 21 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 21 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behaviour to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.1.1.tgz","shasum":"6c35428a2df813fccb4bd94944a9fcec83c6321c","integrity":"sha512-6fLmVMUgI/nw0NeDIQ10NAFxu8mRPtv3reHSk245aPKN+WoNzWJ7MpsZk6FEqNKDsCzC7dYC234fEQJ9DEGIzg=="}},"0.2.0":{"name":"@playlive/react-pipeline","version":"0.2.0","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"}},"dependencies":{"@playlive/realtime-pipeline":"^0.2.0"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.0","@playlive/tiltify-core":"^0.1.1"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-LxLB/2VpdxqC4qJHSjcC9WhJ5fYnX3OLrVW8mTBONcq7ZsrlikHVnKwjcmJDJ9FSjXkuZ9sXtsfgrBtDbOcsbQ==","shasum":"ff702e63e9e35e16a3db68ac8298c7ccdfc744f6","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 21 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 21 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behaviour to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.2.0.tgz","shasum":"ff702e63e9e35e16a3db68ac8298c7ccdfc744f6","integrity":"sha512-LxLB/2VpdxqC4qJHSjcC9WhJ5fYnX3OLrVW8mTBONcq7ZsrlikHVnKwjcmJDJ9FSjXkuZ9sXtsfgrBtDbOcsbQ=="}},"0.2.1":{"name":"@playlive/react-pipeline","version":"0.2.1","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"}},"dependencies":{"@playlive/realtime-pipeline":"^0.2.1"},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.1","@playlive/tiltify-core":"^0.4.4"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-r3/Dj/zJ3/qhv716StdszSUVUAlhUFfixkUqwKH5snEfiVQzROr/PRqC8yrZTUF7Nvw3jcmXFe68JyboKOKgpw==","shasum":"f8195ce717c9488b2b751aed7578783027fc1efe","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 21 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 21 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.2.1.tgz","shasum":"f8195ce717c9488b2b751aed7578783027fc1efe","integrity":"sha512-r3/Dj/zJ3/qhv716StdszSUVUAlhUFfixkUqwKH5snEfiVQzROr/PRqC8yrZTUF7Nvw3jcmXFe68JyboKOKgpw=="}},"0.2.2":{"name":"@playlive/react-pipeline","version":"0.2.2","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.1","@playlive/tiltify-core":"^0.4.9"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-klIKTXXtLaUQLSh+tX0NamHcW+FrLWYmHw8uvJsMM9cWnop3l2VfPKQvHWjNxzHYi3a2K58+zH4qd3PGi/cwRQ==","shasum":"2b5f6c09b43be727f850ba580a72163ffd58a29e","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 21 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 21 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.2.2.tgz","shasum":"2b5f6c09b43be727f850ba580a72163ffd58a29e","integrity":"sha512-klIKTXXtLaUQLSh+tX0NamHcW+FrLWYmHw8uvJsMM9cWnop3l2VfPKQvHWjNxzHYi3a2K58+zH4qd3PGi/cwRQ=="}},"0.2.3":{"name":"@playlive/react-pipeline","version":"0.2.3","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.2","@playlive/tiltify-core":"^0.4.11"},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-t09ua6rbFX2yfeKnkYC1GIFuLRlPu9nZdal0VszgT0Fy9E29tpnC11zgo/L5pkxc0EydH4r88p3FSi4N4ZdyMQ==","shasum":"b1f7c151a0204d3ea18107387291f82eaabd74cb","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.2.3.tgz","shasum":"b1f7c151a0204d3ea18107387291f82eaabd74cb","integrity":"sha512-t09ua6rbFX2yfeKnkYC1GIFuLRlPu9nZdal0VszgT0Fy9E29tpnC11zgo/L5pkxc0EydH4r88p3FSi4N4ZdyMQ=="}},"0.3.0":{"name":"@playlive/react-pipeline","version":"0.3.0","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.2","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.2.0","@playlive/fundraiser-data":"^0.2.0"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-cxr1mZ/0zIX6qqlCa6Gs2xrziutIc2aJBiU/nl813a+4vMVU0CgCH5+9aeFln5haLeW8scc5DeD3P0hQRMwg1g==","shasum":"b7cb609ad51eb10a1e9ae5f134914401076db3cb","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.3.0.tgz","shasum":"b7cb609ad51eb10a1e9ae5f134914401076db3cb","integrity":"sha512-cxr1mZ/0zIX6qqlCa6Gs2xrziutIc2aJBiU/nl813a+4vMVU0CgCH5+9aeFln5haLeW8scc5DeD3P0hQRMwg1g=="}},"0.3.1":{"name":"@playlive/react-pipeline","version":"0.3.1","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.2","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.2.1","@playlive/fundraiser-data":"^0.2.0"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-H8iVYo1DyirklS3F4Ei3K1MbWANlVUQul2k238mWma1QDsw112XNQTmOwU/nGsi7S5SMVQUXvFEej56hAHT93g==","shasum":"4cc755a3880d7da0e0b819f9c93c68b4c98cc048","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.3.1.tgz","shasum":"4cc755a3880d7da0e0b819f9c93c68b4c98cc048","integrity":"sha512-H8iVYo1DyirklS3F4Ei3K1MbWANlVUQul2k238mWma1QDsw112XNQTmOwU/nGsi7S5SMVQUXvFEej56hAHT93g=="}},"0.3.2":{"name":"@playlive/react-pipeline","version":"0.3.2","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.3","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.2.2","@playlive/fundraiser-data":"^0.2.0"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-i36Eu72v8MA7VFAmgFB0IAAepN9aPGMpTvHCaPLi2Zd4LzqYx1uigtuWUAw2ViXVtkP7/nWyuoA9A3us5SqbaA==","shasum":"7303a49a46fc0bcde03fa8239a6dd99c800fedc4","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.3.2.tgz","shasum":"7303a49a46fc0bcde03fa8239a6dd99c800fedc4","integrity":"sha512-i36Eu72v8MA7VFAmgFB0IAAepN9aPGMpTvHCaPLi2Zd4LzqYx1uigtuWUAw2ViXVtkP7/nWyuoA9A3us5SqbaA=="}},"0.3.3":{"name":"@playlive/react-pipeline","version":"0.3.3","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.4","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.2.2","@playlive/fundraiser-data":"^0.2.0"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-76E0VJqU5czGJgjAebUKcQF1qcp08rHvzFqeTbWdKqZHCUYBnUEgKRk7VD0h3wWp/rvsTGL+RVLvfK+V3lBw7w==","shasum":"a4ae3b4ee2cd57434848d672fc9710bf278016d7","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.3.3.tgz","shasum":"a4ae3b4ee2cd57434848d672fc9710bf278016d7","integrity":"sha512-76E0VJqU5czGJgjAebUKcQF1qcp08rHvzFqeTbWdKqZHCUYBnUEgKRk7VD0h3wWp/rvsTGL+RVLvfK+V3lBw7w=="}},"0.3.4":{"name":"@playlive/react-pipeline","version":"0.3.4","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.5","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.2.6","@playlive/fundraiser-data":"^0.2.4"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-BzU0ha0IBzxOV4f/GKhPHEIGndIqPYOS96OS8iwm4dR+hKA4+koCpCkG9CNOUq+H8qDjpD2ULKSzAT4EZ/fP7Q==","shasum":"d63939ed6bb0d4b3c1bb54200436732f3ca4c337","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.3.4.tgz","shasum":"d63939ed6bb0d4b3c1bb54200436732f3ca4c337","integrity":"sha512-BzU0ha0IBzxOV4f/GKhPHEIGndIqPYOS96OS8iwm4dR+hKA4+koCpCkG9CNOUq+H8qDjpD2ULKSzAT4EZ/fP7Q=="}},"0.4.0":{"name":"@playlive/react-pipeline","version":"0.4.0","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.5","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.0","@playlive/fundraiser-data":"^0.3.0"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-EpQ080w18vrouR9S+sqsVnlRtr8hNBSmz77UEpvM2bdy/T4Lmsv1n25p9ZlIH2rAAOysHMRAei0Won0oUoVGsQ==","shasum":"3108e7dedff72cb68b8b32fa30a2a3c9db14f07e","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.0.tgz","shasum":"3108e7dedff72cb68b8b32fa30a2a3c9db14f07e","integrity":"sha512-EpQ080w18vrouR9S+sqsVnlRtr8hNBSmz77UEpvM2bdy/T4Lmsv1n25p9ZlIH2rAAOysHMRAei0Won0oUoVGsQ=="}},"0.4.1":{"name":"@playlive/react-pipeline","version":"0.4.1","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.5","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.1","@playlive/fundraiser-data":"^0.3.1"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-/AeADar/XB0WdrwYdS2ipe+BhXaoAH/KMBJPkbfWHG7rqVZ1hozGXpOjsx4NTy7ieW7zEpDvI6tKUSt7fknMRQ==","shasum":"cb5db5b5c0283c815a94abef9ae27e14deba0d01","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.1.tgz","shasum":"cb5db5b5c0283c815a94abef9ae27e14deba0d01","integrity":"sha512-/AeADar/XB0WdrwYdS2ipe+BhXaoAH/KMBJPkbfWHG7rqVZ1hozGXpOjsx4NTy7ieW7zEpDvI6tKUSt7fknMRQ=="}},"0.4.2":{"name":"@playlive/react-pipeline","version":"0.4.2","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.5","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.1","@playlive/fundraiser-data":"^0.3.1"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-qENL8FjXMgElTD2foC1HWOBoXpQGjrxUKgCP/A9HJzcVHlzJopCiluhA/Dorbzja8cEuY7o7yhJcYxUl4xEV1w==","shasum":"b65d073c1d6eb66d6b025d1de11da5a23e95d41f","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.2.tgz","shasum":"b65d073c1d6eb66d6b025d1de11da5a23e95d41f","integrity":"sha512-qENL8FjXMgElTD2foC1HWOBoXpQGjrxUKgCP/A9HJzcVHlzJopCiluhA/Dorbzja8cEuY7o7yhJcYxUl4xEV1w=="}},"0.4.3":{"name":"@playlive/react-pipeline","version":"0.4.3","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.5","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.2","@playlive/fundraiser-data":"^0.3.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-AzXUsyJQ8VZmhZ2VC02czsmNYWnyzQFxerGZx2LezCL3VtbbMoAT6rUKwi78MZpuFRKbkW/C1/i9/Qe2g3abaQ==","shasum":"89973ccd004c605fe2684adb5fb6a60651dbcea8","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.3.tgz","shasum":"89973ccd004c605fe2684adb5fb6a60651dbcea8","integrity":"sha512-AzXUsyJQ8VZmhZ2VC02czsmNYWnyzQFxerGZx2LezCL3VtbbMoAT6rUKwi78MZpuFRKbkW/C1/i9/Qe2g3abaQ=="}},"0.4.4":{"name":"@playlive/react-pipeline","version":"0.4.4","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.5","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.3","@playlive/fundraiser-data":"^0.3.4"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-korF+daxhIaUHFHFt5Z7pjmhrcJU5HWKXNUK3uqvt3Raq5LsE6TBd3pA0KszqvZ9/cwzYF4BIQCjO4RuO8JyUA==","shasum":"98ac1527d214f8a6d7fffff53e0d42e9ebb141cf","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.4.tgz","shasum":"98ac1527d214f8a6d7fffff53e0d42e9ebb141cf","integrity":"sha512-korF+daxhIaUHFHFt5Z7pjmhrcJU5HWKXNUK3uqvt3Raq5LsE6TBd3pA0KszqvZ9/cwzYF4BIQCjO4RuO8JyUA=="}},"0.4.5":{"name":"@playlive/react-pipeline","version":"0.4.5","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.6","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.1"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-65YM1MEmX9UZcYSd6WwmOzujJodDYLTDumrSNdyZvOzCFfFR+9UEMI7LEeFvnIv2osMI5HSxwhTbCl5UujgoKg==","shasum":"1115b6adce8c95b38a0b5e080300846f9f38a4e1","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.5.tgz","shasum":"1115b6adce8c95b38a0b5e080300846f9f38a4e1","integrity":"sha512-65YM1MEmX9UZcYSd6WwmOzujJodDYLTDumrSNdyZvOzCFfFR+9UEMI7LEeFvnIv2osMI5HSxwhTbCl5UujgoKg=="}},"0.4.6":{"name":"@playlive/react-pipeline","version":"0.4.6","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.6","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.1"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-jVflRSRqGGTCZ7FKIiEXEw3OBpP1dXYycPjV6uorzlV4UvJAbx9Sy1MyanCypwIe42io1KQoswcduJrN1WrCIA==","shasum":"51e08cc2582b4e2480ce5cb2c95752b102f14682","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.6.tgz","shasum":"51e08cc2582b4e2480ce5cb2c95752b102f14682","integrity":"sha512-jVflRSRqGGTCZ7FKIiEXEw3OBpP1dXYycPjV6uorzlV4UvJAbx9Sy1MyanCypwIe42io1KQoswcduJrN1WrCIA=="}},"0.4.7":{"name":"@playlive/react-pipeline","version":"0.4.7","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.7","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.1"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-pbJrTgoNbHn+O+uqsddJ1xbI55YoAT/w5bG2ibX1Ni2JKF0T6yj2rbMUiLmrWnLlQDs9/slRnBhgX+fMrxy5Rg==","shasum":"1fd9813ffba8e499894ac9fd2c3ee2a636f85dc3","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.7.tgz","shasum":"1fd9813ffba8e499894ac9fd2c3ee2a636f85dc3","integrity":"sha512-pbJrTgoNbHn+O+uqsddJ1xbI55YoAT/w5bG2ibX1Ni2JKF0T6yj2rbMUiLmrWnLlQDs9/slRnBhgX+fMrxy5Rg=="}},"0.4.8":{"name":"@playlive/react-pipeline","version":"0.4.8","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.7","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-AR/AV8TRf1K6UA0iQ6TvMdvMnHmW2AWc0ply0oraDhEi4ydWgE2N8GvpBkpUG7R24bwaVQ4MyG3/Mcq2QkVBVA==","shasum":"d243d56054ab95e37e5e442a787dd113b3c59314","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.8.tgz","shasum":"d243d56054ab95e37e5e442a787dd113b3c59314","integrity":"sha512-AR/AV8TRf1K6UA0iQ6TvMdvMnHmW2AWc0ply0oraDhEi4ydWgE2N8GvpBkpUG7R24bwaVQ4MyG3/Mcq2QkVBVA=="}},"0.4.9":{"name":"@playlive/react-pipeline","version":"0.4.9","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.7","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-dHR/z29BDxnameVYSi3sMV4rJewjxIrez633h3JaYIzdcGC7OdTuldnypPbBjm/BE5ZabZimcXs8lp91nWQEjA==","shasum":"a7915a00fd19e092ac695bc863407e91032e8fb0","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.9.tgz","shasum":"a7915a00fd19e092ac695bc863407e91032e8fb0","integrity":"sha512-dHR/z29BDxnameVYSi3sMV4rJewjxIrez633h3JaYIzdcGC7OdTuldnypPbBjm/BE5ZabZimcXs8lp91nWQEjA=="}},"0.4.10":{"name":"@playlive/react-pipeline","version":"0.4.10","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.8","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-wZY81K4MGJNFcVKnQhNofpL7Bj8TeZQE3nZ+j4+vsfMILGQQjol2et85cAmUzbdBOJ7BydyrKVRFfPDSbBOZmw==","shasum":"aa811bca8629a40c36c2d8c2fb144c21bb615148","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.10.tgz","shasum":"aa811bca8629a40c36c2d8c2fb144c21bb615148","integrity":"sha512-wZY81K4MGJNFcVKnQhNofpL7Bj8TeZQE3nZ+j4+vsfMILGQQjol2et85cAmUzbdBOJ7BydyrKVRFfPDSbBOZmw=="}},"0.4.11":{"name":"@playlive/react-pipeline","version":"0.4.11","description":"React bindings for @playlive/realtime-pipeline — three usage tiers (single selector / typed per-slice / kitchen-sink). Twitch-Extension safe; no react-use-websocket-lite.","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.8","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-exnWYyfUImfIkKJHkNT4bQ2gYC4ZUmoGiGJJHmDls3AruiR2rPIL9vYaLTWv0nNdxHA/UnV9CBH5hhR7XxQT4g==","shasum":"f206f2a8632ec3d9514434830407613ea234892b","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.11.tgz","shasum":"f206f2a8632ec3d9514434830407613ea234892b","integrity":"sha512-exnWYyfUImfIkKJHkNT4bQ2gYC4ZUmoGiGJJHmDls3AruiR2rPIL9vYaLTWv0nNdxHA/UnV9CBH5hhR7XxQT4g=="}},"0.4.13":{"name":"@playlive/react-pipeline","version":"0.4.13","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.8","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-WuLc7rPexvWpj2UhYwqDBOeogRPOb9o9V02uWzKjHIVXUP29j3iZDDo7/lFpU/hjuJZnzrt4PHrCHBPRRSL5ug==","shasum":"617b3e892d98a51962ffdaee8044491584849b77","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.13.tgz","shasum":"617b3e892d98a51962ffdaee8044491584849b77","integrity":"sha512-WuLc7rPexvWpj2UhYwqDBOeogRPOb9o9V02uWzKjHIVXUP29j3iZDDo7/lFpU/hjuJZnzrt4PHrCHBPRRSL5ug=="}},"0.4.14":{"name":"@playlive/react-pipeline","version":"0.4.14","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.8","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-PQKT8ubvPUgm8RZPwPIr4frD13qXEu5MxYrHo5wc24en3OSvT3Sz4kMI5ls8VOtms0YtWhluy8x13Z5yx8KswA==","shasum":"7b0ed186b2655aa8004633ae0cc448864aef346e","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.14.tgz","shasum":"7b0ed186b2655aa8004633ae0cc448864aef346e","integrity":"sha512-PQKT8ubvPUgm8RZPwPIr4frD13qXEu5MxYrHo5wc24en3OSvT3Sz4kMI5ls8VOtms0YtWhluy8x13Z5yx8KswA=="}},"0.4.15":{"name":"@playlive/react-pipeline","version":"0.4.15","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.8","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-1AMzoHbqGz5rN594drfANEzDccay4/m7OlKpCT92F0HGml/9cu9FaFPSK5lwdQNnXrHjo2P0sa1+FeuPSx6/tA==","shasum":"1bcf973729c975c81ffd987251a405b90cdc1cab","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.15.tgz","shasum":"1bcf973729c975c81ffd987251a405b90cdc1cab","integrity":"sha512-1AMzoHbqGz5rN594drfANEzDccay4/m7OlKpCT92F0HGml/9cu9FaFPSK5lwdQNnXrHjo2P0sa1+FeuPSx6/tA=="}},"0.4.16":{"name":"@playlive/react-pipeline","version":"0.4.16","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.8","@playlive/tiltify-core":"^0.4.11","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-DSCt6tRiWVXNlaO+oG4q79oJhrZSVVSU05SNCAlw+ZUuCCAbW4HFYFxHQ3bqIpkK4ShYQkFQqmxrd8hXMV18Xw==","shasum":"28d9d10f68239d3fae203387dcd4e01b700eee69","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.16.tgz","shasum":"28d9d10f68239d3fae203387dcd4e01b700eee69","integrity":"sha512-DSCt6tRiWVXNlaO+oG4q79oJhrZSVVSU05SNCAlw+ZUuCCAbW4HFYFxHQ3bqIpkK4ShYQkFQqmxrd8hXMV18Xw=="}},"0.4.17":{"name":"@playlive/react-pipeline","version":"0.4.17","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.2.10","@playlive/tiltify-core":"^0.4.13","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.3.4","@playlive/fundraiser-data":"^0.4.3"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-GcBkvxda4uiVT7agkchXyx0jv5fPDZDqDpMwwoEnjpENpB5Vc+IWYKoKrAZh3OA37gWTi6Up+QTk53a2915SZA==","shasum":"02a498e90bb0daf6cbe9810d45c60fa25faa4719","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nDrop-in replacement for `playlive-overlay-data-layer/src/websocket/*`\nwith **zero** dependency on `react-use-websocket-lite` — built directly\non Zustand v5's `useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                            | Surface                                                                         | Use when                                                              |\n| ---- | ---------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`         | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)            | Greenfield app; you want one selector primitive and nothing else.     |\n| 2    | `@playlive/react-pipeline`         | Tier 1 **plus** 22 typed per-slice hooks (`usePipelineDonations(id)`, …)        | You want stable, typed one-liners for the common reads.               |\n| 3    | `@playlive/react-pipeline/provider`| Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc.   | Porting an existing overlay-data-layer consumer with minimal churn.   |\n| —    | `@playlive/react-pipeline/legacy`  | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases     | Soft-deprecated shim for the legacy symbol names (one-release window).|\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add -d react react-dom zustand\n```\n\nPeer dependencies (jose-style — consumer brings their own):\n`react@^19`, `react-dom@^19`, `zustand@^5`,\n`@playlive/realtime-pipeline` (workspace), `@playlive/tiltify-core`\n(workspace, type-only — stripped at compile time).\n\nNothing else ships in the bundle.\n\n## Quick start — Tier 1\n\n```tsx\nimport { RealtimePipelineProvider, usePipelineCampaigns } from \"@playlive/react-pipeline\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://pipeline.example.com/socket\"\n      charityType=\"tiltify\"\n      autoConnect\n    >\n      <Overlay />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction Overlay() {\n  const campaigns = usePipelineCampaigns();\n  return <ul>{campaigns.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;\n}\n```\n\n`RealtimePipelineProvider` mounts a Zustand store + opens (or accepts)\na `PipelineConnection`. Children call any Tier 1 / Tier 2 hook to\nsubscribe.\n\n## Subpath exports\n\n| Subpath                              | Description                                                                       |\n| ------------------------------------ | --------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`           | Default barrel — Tier 1 + Tier 2 (provider + 21 typed slice hooks).               |\n| `@playlive/react-pipeline/provider`  | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, add-pipeline\\*. |\n| `@playlive/react-pipeline/fusion`    | Cross-package composition hooks (REST baseline + WS delta) — `useCurrentBlockRaised`, `useLiveSchedule`, `useCurrentScheduleItem`, `useLinkedTeamCampaign`, `useDonorSpotlight`, plus the WS-store-first / HTTP-fallback hybrid slice hooks (`useHybridRewards`, `useHybridTargets`, `useHybridMilestones`, `useHybridPolls`, `useHybridCause`, `useHybridFundraisingEvent`, `useHybridFundraisingEventSupportingCampaigns`, `useHybridAuctionHouses`) + the shared `useLoadingTimeout` primitive. |\n| `@playlive/react-pipeline/legacy`    | Soft-deprecated alias bundle (`useUDPStore`, `createUDPStore`, `UDPStore`, etc.). |\n\nEach subpath ships an ESM bundle, a Bun source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1)\nso stack traces stay readable in production.\n\n## API reference\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                            |\n| ------------------------------- | -------------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => JSX.Element`              |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                        |\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the\nthird `equalityFn` parameter on `useStore` to keep the package\nfree of the `use-sync-external-store` shim peer. For shallow / custom\nequality, wrap your selector with `useShallow` from\n`zustand/shallow` (zero-extra-dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nAll 22 hooks are stable wrappers over `usePipelineValue` with a typed\nselector + memo-stable empty-array fallbacks (no infinite-render\nchurn from fresh `[]` allocations — Decision §3 in CHANGELOG).\n\n| Hook                              | Returns                                                             |\n| --------------------------------- | ------------------------------------------------------------------- |\n| `usePipelineCampaigns()`          | `TiltifyCampaign[]`                                                 |\n| `usePipelineTeamCampaigns()`      | `TiltifyTeamCampaign[]`                                             |\n| `usePipelineFundraisingEvents()`  | `TiltifyFundraisingEvent[]`                                         |\n| `usePipelineCauses()`             | `TiltifyCause[]`                                                    |\n| `usePipelineDonations()`          | `Record<string, TiltifyDonationWithTestFlag[]>`                     |\n| `usePipelineDonations(id)`        | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if id unknown)    |\n| `usePipelineDonationSum(id, opts?)` | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` of the WS donation firehose. Pair with the REST `/schedules/campaigns/{id}/raised` `asOf` field to replace polling with a `baseline + WS-delta` view. |\n| `usePipelineDonationTrains(id, withStatus?)` | `DonationTrain[]`                                        |\n| `usePipelineSubathonTimer(id, withStatus?)`  | `SubathonTimer[]`                                        |\n| `usePipelineDonorSpotlight(id)`   | `ComputedSpotlight \\| null`                                         |\n| `usePipelinePolls(id)`            | `TiltifyPoll[]`                                                     |\n| `usePipelineRewards(id)`          | `TiltifyReward[]`                                                   |\n| `usePipelineTargets(id)`          | `TiltifyTarget[]`                                                   |\n| `usePipelineMilestones(id)`       | `TiltifyMilestone[]`                                                |\n| `usePipelineLeaderboardEntries(id)` | `TiltifyLeaderboardEntry[]`                                       |\n| `usePipelineAuctionHouses()`      | `AuctionHouse[]`                                                    |\n| `usePipelineTwitchChat()`         | `TwitchChatMessage[]`                                               |\n| `usePipelineConnectionState()`    | `PipelineConnectionState` (10 split selectors — no false re-renders)|\n| `usePipelineConnectionActions()`  | `PipelineConnectionActions` (stable `connect/disconnect/refresh/…`) |\n| `usePipelineSubscriptions()`      | `Record<string, TiltifyWebhookSubscription>`                        |\n| `usePipelineTiltifySettings()`    | `TiltifyWebSocketSettings`                                          |\n| `usePipelineLastRefreshResponse()`| `RefreshResponseMessage \\| null`                                    |\n| `usePipelineDonorSpotlightSettings()` | `Record<string, DonorSpotlightSettings>`                        |\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                              | Notes                                                                                  |\n| ----------------------------------- | -------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`       | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` + `debug`.|\n| `useUnifiedDataPipeline()`          | One-call kitchen-sink hook. Returns every slice + actions + legacy field aliases.      |\n| `useAddPipelineCampaignIDs(ids)`    | Effect hook — registers campaign ids with the store on mount.                          |\n| `useAddPipelineCampaigns(items)`    | Effect hook — registers full campaign records on mount.                                |\n| `useAddPipelineFundraisingEventIDs(ids)` | Effect hook — registers fundraising-event ids on mount.                           |\n| `useCommonContextHooks(…)`          | URL / mode / charity-type sync to the store. Same shape as the legacy hook.            |\n| `useIdentify()` / `useIdentify(…11 legacy args)` | Zero-arg returns the store's bound `identify` action. Positional arg form is preserved as an `@deprecated` overload — args are ignored, warns once. |\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity `usePipeline*` hooks lifted from\n`playlive-overlay-vite/src/hooks/pipeline/` so every overlay that\nwants the same WS-store-first / HTTP-fallback shape can drop the\nlocal copy. Every hook accepts a `mode: \"ws\" | \"http\"` param that\npicks between:\n\n- `\"ws\"` — read the Tier-2 store selector for the slice, latch a\n  local `hasLoadedX` boolean on the first slice arrival (or on the\n  server-side \"feature disabled\" signal), and fire\n  `updateTiltifySettings({ <sliceKey> })` whenever the local\n  `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST\n  hook (`useRewards`, `useTargets`, …) with the WS store slice\n  ignored.\n\nAll hybrid hooks share `useLoadingTimeout` — a 15 s safety-net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                     | Returns                                                                        |\n| -------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| `useHybridRewards(params)`                               | `{ currentRewards, setHasLoadedRewards, hasLoadedRewards, isLoadingRewards, isLoading, setRewardsEnabled, rewardsEnabled }` |\n| `useHybridTargets(params)`                               | `{ currentTargets, setHasLoadedTargets, hasLoadedTargets, isLoading, targetsEnabled, isLoadingTargets, setTargetsEnabled }` |\n| `useHybridMilestones(params)`                            | `{ isLoadingMilestones, currentMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls(params)`                                 | `{ isLoadingPolls, currentPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics (uses `getFirstCause`).         |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns(params)`   | `{ supportingCampaigns, isLoading, setSupportingCampaignsEnabled, supportingCampaignsEnabled, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useLoadingTimeout(params)`                              | `void` — safety-net timer primitive shared by every hook above.                |\n\nReturn shapes are preserved verbatim from the overlay so overlay\ncall-sites can eventually collapse to a re-export.\n\n### Fusion — baseline + WS delta hooks (`/fusion`)\n\n| Hook                                        | Returns                                                                     |\n| ------------------------------------------- | --------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline + WS delta. `opts.demoMode: true` short-circuits to `DemoProvider.getDemoScheduleBlockRaised` / `getDemoBlockRaised` fallback. |\n| `useLiveSchedule(params, opts?)`            | `{ schedule, isLoading, isError, refetch }` — WS-refetch on transition. `opts.demoMode: true` (+ optional `demoIntervalMs`) short-circuits to `DemoProvider.getDemoSchedule` / `buildDemoSchedule` fallback with a live tick timer.     |\n| `useCurrentScheduleItem(schedule, opts?)`   | `{ currentItem, nextItem }` — pure-client rollover projection.              |\n| `useLinkedTeamCampaign(params, opts?)`      | `{ linkedTeamCampaign, … }` — WS-store first, HTTP fallback.                |\n| `useDonorSpotlight(params, opts?)`          | `{ spotlight, isLoading, refetch }` — WS store preferred + REST snapshot.   |\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Identical behavior to the canonical exports —\nrename the import specifier once and the rest of your code base\nkeeps working unchanged:\n\n| Legacy name            | Canonical replacement                                          |\n| ---------------------- | -------------------------------------------------------------- |\n| `useUDPStore`          | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)       |\n| `useUDPStoreApi`       | (use a typed slice hook; or reach for the raw store API)       |\n| `createUDPStore`       | `createPipelineStore` from `@playlive/realtime-pipeline/store` |\n| `UDPStore` (type)      | `PipelineStore` from `@playlive/realtime-pipeline/store`       |\n| `UDPStoreApi` (type)   | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`    |\n| `CharityTypes`         | re-exported from `@playlive/realtime-pipeline/protocol`         |\n\nPlus every Tier 1+2+3 export under its canonical name.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot sed-friendly rewrite of every import specifier (no body\nedits needed for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) for\nthe full per-symbol mapping plus call-site walkthroughs.\n\n## Upstream spec\n\nNo external API surface — internal workspace package. All transport\nruns through `@playlive/realtime-pipeline/connection`.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nfetch. **It is empty.** This package opens WebSocket connections only\nto URLs the consumer passes to `<RealtimePipelineProvider url={…}>`;\nthose URLs must be added to your Extension submission's URL\ndisclosure list, not this one.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\nLive overlay apps consuming this provider land under `apps/*`\n(phase 10). Until then, the Tier 1 quick-start above is the minimum\nviable wiring.\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add new Tier-2\nhooks, mirror an existing slice hook in `src/hooks/index.ts` and\nadd a `it.each` entry in `tests/unit/index.test.ts`.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.17.tgz","shasum":"02a498e90bb0daf6cbe9810d45c60fa25faa4719","integrity":"sha512-GcBkvxda4uiVT7agkchXyx0jv5fPDZDqDpMwwoEnjpENpB5Vc+IWYKoKrAZh3OA37gWTi6Up+QTk53a2915SZA=="}},"0.4.19":{"name":"@playlive/react-pipeline","version":"0.4.19","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.3.1","@playlive/tiltify-core":"^0.4.18","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.4.3","@playlive/fundraiser-data":"^0.5.3"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-DyNpzI3qJ6CC8YVn6jbVN9GV/gGvJi2IKcSbipIZgiSfPScMdip3lpIqGZG5pqx9L10Sd3M0h9zsyPoouW9O5Q==","shasum":"ee39e04467399a832ca13454e13d85063f466f3f","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nSuccessor to `playlive-overlay-data-layer/src/websocket/*` with **zero**\ndependency on `react-use-websocket-lite` — built directly on Zustand v5's\n`useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                             | Surface                                                                       | Use when                                                               |\n| ---- | ----------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`          | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)          | Greenfield app; you want one selector primitive and nothing else.      |\n| 2    | `@playlive/react-pipeline`          | Tier 1 **plus** the typed per-slice hooks (`usePipelineDonations(id)`, …)     | You want stable, typed one-liners for the common reads.                |\n| 3    | `@playlive/react-pipeline/provider` | Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc. | Porting an existing overlay-data-layer consumer with minimal churn.    |\n| —    | `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (needs the optional TanStack peers)              | You want \"REST baseline + live WS delta\" without hand-rolling it.      |\n| —    | `@playlive/react-pipeline/legacy`   | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases   | Soft-deprecated shim for the legacy symbol names (one-release window). |\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add react react-dom zustand @playlive/realtime-pipeline @playlive/tiltify-core\n```\n\nPeer dependencies (jose-style — the consumer brings their own):\n\n| Peer                          | Range          | Required?                                 |\n| ----------------------------- | -------------- | ----------------------------------------- |\n| `react`                       | `^19.0.0`      | yes                                       |\n| `react-dom`                   | `^19.0.0`      | yes                                       |\n| `zustand`                     | `^5.0.0`       | yes                                       |\n| `@playlive/realtime-pipeline` | `workspace:*`  | yes — store, connection, protocol types    |\n| `@playlive/tiltify-core`      | `workspace:*`  | yes (type-only — stripped at compile time) |\n| `@tanstack/react-query`       | `^5.0.0`       | **optional** — only for `/fusion`          |\n| `@playlive/react-query`       | `workspace:*`  | **optional** — only for `/fusion`          |\n| `@playlive/fundraiser-data`   | `workspace:*`  | **optional** — only for `/fusion`          |\n\nThe three optional peers are declared `optional: true` in\n`peerDependenciesMeta`; you only need them if you import from\n[`@playlive/react-pipeline/fusion`](#fusion--rest-baseline--ws-delta-hooks-fusion).\n\nThis package has **no runtime `dependencies`** — nothing but your own\npeers ends up in the bundle.\n\n## Quick start\n\n```tsx\nimport {\n  RealtimePipelineProvider,\n  usePipelineCampaigns,\n  usePipelineConnectionState,\n  usePipelineDonations,\n} from \"@playlive/react-pipeline\";\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n      initialCampaignIDs={[CAMPAIGN_ID]}\n      initialOverlayName=\"donation-bar\"\n      initialTeamUserSlug=\"@playliver\"\n      initialOverlayPath=\"/overlays/donation-bar\"\n      autoConnect\n    >\n      <DonationBar campaignID={CAMPAIGN_ID} />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction DonationBar({ campaignID }: { campaignID: string }) {\n  const { connected, connecting, hasReceivedInitialData } = usePipelineConnectionState();\n  const campaigns = usePipelineCampaigns();\n  const donations = usePipelineDonations(campaignID);\n\n  if (connecting) return <p>Connecting…</p>;\n  if (!connected) return <p>Disconnected — reconnecting…</p>;\n  if (!hasReceivedInitialData) return <p>Waiting for first tick…</p>;\n\n  const campaign = campaigns.find((c) => c.id === campaignID);\n  const raised = Number(campaign?.total_amount_raised?.value ?? 0);\n  const goal = Number(campaign?.goal?.value ?? 0);\n\n  return (\n    <section>\n      <progress value={raised} max={goal || 1} />\n      <p>\n        ${raised.toFixed(2)} of ${goal.toFixed(2)}\n      </p>\n      <ul>\n        {donations.slice(0, 5).map((d) => (\n          <li key={d.id}>\n            {d.donor_name} — ${d.amount.value}\n          </li>\n        ))}\n      </ul>\n    </section>\n  );\n}\n```\n\n`RealtimePipelineProvider` builds one Zustand store per provider\ninstance (from the `initial*` props, which are read **once** at first\nconstruction) and opens a single `PipelineConnection`. `connection.destroy()`\nruns on unmount. Children subscribe with any Tier 1 / Tier 2 hook.\n\nEvery hook that reads the store throws when no provider is mounted\nabove it; the four `useOn*` observer hooks deliberately soft-fail\n(they no-op) so you can drop them into components that sometimes render\noutside a pipeline tree.\n\n## Subpath exports\n\n| Subpath                             | Description                                                                                             |\n| ----------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`          | Default barrel — Tier 1 + Tier 2 (provider, `usePipelineValue`, typed slice hooks, observers).           |\n| `@playlive/react-pipeline/provider` | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, `useAddPipeline*`, `useIdentify`, …    |\n| `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (`useCurrentBlockRaised`, `useLiveSchedule`, the `useHybrid*` family, …).    |\n| `@playlive/react-pipeline/legacy`   | Soft-deprecated alias bundle (`useUDPStore`, `useUDPStoreApi`, `createUDPStore`, `UDPStore`, …).         |\n\nEach subpath ships an ESM bundle, a `bun` source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified (PRD §6.1) so\nstack traces stay readable in production.\n\n## API reference\n\nRun `bun run docs:build` inside this package to emit the TypeDoc site at\n`dist/docs/`.\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                     |\n| ------------------------------- | ------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => ReactNode`          |\n| `RealtimePipelineProviderProps` | Type. `InitialProps` + `children`, `url?`, `autoConnect?` (default `true`), `webSocketCtor?`, `log?`, `store?`, `connection?`. |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                  |\n| `PACKAGE_NAME`                  | `\"@playlive/react-pipeline\"` — for runtime version-pinning.    |\n| `KNOWN_URLS`                    | `readonly string[]` — frozen, empty. See the disclosure section. |\n\n`RealtimePipelineProviderProps` extends\n[`InitialProps`](../realtime-pipeline/), so every `initial*` key\n(`initialCampaignIDs`, `initialTeamCampaignIDs`, `initialCauseIDs`,\n`initialFundraisingEventIDs`, `initialOverlayName`, `initialTeamUserSlug`,\n`initialOverlayPath`, `initialOverlayConfig`, `initialAdminApiKey`,\n`initialDonationTrainsEnabled`, …) is a valid prop. There is **no**\n`charityType` prop — set the charity type through the store's\n`setCurrentType` action or via `useCommonContextHooks` (Tier 3).\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the third\n`equalityFn` parameter on `useStore` to keep the package free of the\n`use-sync-external-store` shim peer. For shallow / custom equality, wrap\nyour selector with `useShallow` from `zustand/shallow` (zero extra dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nStable wrappers over `usePipelineValue` with a typed selector +\nmemo-stable empty-array/object fallbacks (no infinite-render churn from\nfresh `[]` allocations — Decision §3 in [CHANGELOG.md](./CHANGELOG.md)).\n\n| Hook                                          | Returns                                                     |\n| --------------------------------------------- | ----------------------------------------------------------- |\n| `usePipelineCampaigns()`                      | `TiltifyCampaign[]`                                          |\n| `usePipelineTeamCampaigns()`                  | `TiltifyTeamCampaign[]`                                      |\n| `usePipelineFundraisingEvents()`              | `TiltifyFundraisingEvent[]`                                  |\n| `usePipelineCauses()`                         | `TiltifyCause[]`                                             |\n| `usePipelineDonations()`                      | `Record<string, TiltifyDonationWithTestFlag[]>`               |\n| `usePipelineDonations(id)`                    | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if unknown) |\n| `usePipelineDonationTrains(id, withStatus?)`  | `DonationTrain[]` — `withStatus` is `\"ACTIVE\" \\| \"ENDED\" \\| \"ENDING\" \\| \"PENDING\"` |\n| `usePipelineSubathonTimer(id, withStatus?)`   | `SubathonTimer[]` — `withStatus` is `\"ACTIVE\" \\| \"INACTIVE\"`  |\n| `usePipelineDonorSpotlight(id)`               | `ComputedSpotlight \\| null`                                  |\n| `usePipelineDonorSpotlightSettings()`         | `Record<string, DonorSpotlightSettings>`                     |\n| `usePipelinePolls(id)`                        | `TiltifyPoll[]`                                              |\n| `usePipelinePollDeltas(pollID)`               | `PollOptionDelta[]` — server-computed \"+$X on option Y\" deltas |\n| `usePipelineRewards(id)`                      | `TiltifyReward[]`                                            |\n| `usePipelineTargets(id)`                      | `TiltifyTarget[]`                                            |\n| `usePipelineMilestones(id)`                   | `TiltifyMilestone[]`                                         |\n| `usePipelineLeaderboardEntries(id)`           | `TiltifyLeaderboardEntry[]`                                  |\n| `usePipelineLeaderboardExclusions(id)`        | `LeaderboardExclusion[]`                                     |\n| `usePipelineLeaderboardExclusionsMap()`       | `Record<string, LeaderboardExclusion[]>`                     |\n| `usePipelineSchedule(id)`                     | `ScheduleTransitionBlock[]` — full `updateSchedule` WS push  |\n| `usePipelineSchedules()`                      | `Record<string, ScheduleTransitionBlock[]>`                  |\n| `usePipelineLastScheduleTransition(id)`       | `ScheduleTransitionPayload \\| null` — latest block crossing  |\n| `usePipelineLastScheduleTransitions()`        | `Record<string, ScheduleTransitionPayload>`                  |\n| `usePipelineDonationSum(id, options?)`        | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` |\n| `usePipelineAuctionHouses()`                  | `AuctionHouse[]`                                             |\n| `usePipelineTwitchChat()`                     | `TwitchChatMessage[]`                                        |\n| `usePipelineConnectionState()`                | `PipelineConnectionState` (10 split selectors — no false re-renders) |\n| `usePipelineConnectionActions()`              | `PipelineConnectionActions` (stable `connect`/`disconnect`/…) |\n| `usePipelineSubscriptions()`                  | `Record<string, TiltifyWebhookSubscription>`                 |\n| `usePipelineTiltifySettings()`                | `TiltifyWebSocketSettings`                                   |\n| `usePipelineLastRefreshResponse()`            | `RefreshResponseMessage \\| null`                             |\n\nCompanion types exported alongside them: `PipelineConnectionState`\n(`connected`, `connecting`, `charityType`, `connectionType`, `wsURL`,\n`hasReceivedInitialData`, `lastIdentified`, `lastIdentityMessage`,\n`lastFirehoseMessage`, `lastMessage`), `PipelineConnectionActions`\n(`connect`, `disconnect`, `identify`, `refresh`, `reset`,\n`setAutoConnect`, `setAdminApiKey`), `PipelineDonationSumOptions`,\n`PipelineDonationSumByCurrency`, `PipelineDonationSumResult`.\n\n#### Change observers\n\nPush-style subscriptions that diff the store outside React's render\npath. Each stashes `handler` in a ref, subscribes exactly once, and\ntreats whatever is already on the store at mount as the baseline (so\nthey never fire for pre-existing data). All four **soft-fail** without\na provider.\n\n| Hook                                                | Handler payload                                        |\n| --------------------------------------------------- | ------------------------------------------------------ |\n| `useOnScheduleTransition(handler, options?)`        | `ScheduleTransitionPayload`                             |\n| `useOnScheduleUpdate(handler, options?)`            | `{ campaignID, schedule: ScheduleTransitionBlock[] }`   |\n| `useOnLeaderboardEntriesChange(handler, options?)`  | `{ campaignID, entries: TiltifyLeaderboardEntry[] }`    |\n| `useOnLeaderboardExclusionsChange(handler, options?)` | `{ campaignID, exclusions: LeaderboardExclusion[] }`  |\n\n`options` is `{ campaignIDs?: readonly string[] }` — omit it (or pass an\nempty list) to observe every campaign.\n\n```tsx\nuseOnLeaderboardEntriesChange(\n  ({ campaignID }) => {\n    queryClient.invalidateQueries({\n      queryKey: [\"leaderboard-with-exclusions\", \"tiltify\", campaignID],\n    });\n  },\n  { campaignIDs: [campaignID] },\n);\n```\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                                        | Notes                                                                                          |\n| --------------------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`                 | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` (default `true`) + `debug` (wires `console.debug` logging). |\n| `UnifiedDataPipelineProviderProps`            | Type. `Omit<RealtimePipelineProviderProps, \"children\"> & { children, reloadOnErrorReconnect?, debug? }`. |\n| `useUnifiedDataPipeline()`                    | One-call kitchen-sink hook → `UnifiedDataPipelineState`. Re-renders on **every** store change.  |\n| `UnifiedDataPipelineState`                    | Type. `PipelineStore` + `allRewards` / `allMilestones` / `allTargets` / `allPolls` / `allFundraisingEventSupportingCampaigns` / `tiltifySubscriptions` legacy aliases. |\n| `useAddPipelineCampaignIDs(params)`           | Layout-effect hook. `{ mode: \"ws\" \\| \"http\"; initialCampaign: { id }; isTeam: boolean; causeID? }` — no-op in `\"http\"` mode. |\n| `useAddPipelineCampaigns(params)`             | Layout-effect hook. `{ campaign?: TiltifyCampaign \\| null; teamCampaign?: TiltifyTeamCampaign \\| null }`. Idempotent by id. |\n| `useAddPipelineFundraisingEventIDs(params)`   | Layout-effect hook. `{ mode; initialCampaign: { id; fundraising_event_id: string \\| null } }`.   |\n| `useCommonContextHooks(params)`               | `{ url: string; mode: \"ws\" \\| \"http\"; charityType: CharityType }` — syncs type + URL and connects when `mode === \"ws\"`. |\n| `useIdentify()`                               | Returns the store-bound `identify` action. The 11-positional-arg legacy overload is kept as `@deprecated`; the args are ignored and a one-shot `console.warn` fires. |\n\n`RealtimePipelineProvider` / `RealtimePipelineProviderProps` are also\nre-exported from `/provider` so a Tier-3 consumer needs exactly one\nimport specifier.\n\n### Fusion — REST baseline + WS delta hooks (`/fusion`)\n\nThese fuse the WebSocket firehose with REST reads from\n[`@playlive/react-query`](../react-query/) /\n[`@playlive/fundraiser-data`](../fundraiser-data/). Importing this\nsubpath means you must also install the three optional peers.\n\n| Hook                                                   | Returns                                                                    |\n| ------------------------------------------------------ | -------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline `+` WS delta over `[start, end)`. `opts`: `{ enabled?, queryOptions?, demoMode? }`. |\n| `useLiveSchedule(params, opts?)`                       | `{ schedule, isLoading, isError, refetch }`. `params`: `{ charityType, campaignID, isTeam? }`; `opts`: `{ enabled?, queryOptions?, demoMode?, demoIntervalMs? }`. |\n| `useCurrentScheduleItem(schedule, opts?)`              | `{ currentItem, nextItem }` — pure client-side rollover projection with a self-scheduling boundary timer. |\n| `useLinkedTeamCampaign(params, opts?)`                 | `{ shouldFetchLinkedTeamCampaign, resolvedLinkedTeamCampaign, linkedTeamTotalAmount, linkedTeamGoalAmount, isLoadingLinkedTeamCampaign, debug }`. |\n| `useDonorSpotlight(params, opts?)`                     | `{ spotlight, isLoading, refetch }`. `params`: `{ campaignID, isTeam, live, mode }`; `opts`: `{ pollingIntervalMs?, enabled?, queryOptions? }`. |\n\n`useLiveSchedule` resolves in a strict fallback order: full\n`updateSchedule` WS push → `lastScheduleTransitions` synthesis (demo\nonly) → REST `useSchedule` → demo fixture.\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity hooks lifted from `playlive-overlay-vite/src/hooks/pipeline/`\nso every overlay can drop its local copy. Each takes a\n`mode: \"ws\" | \"http\"` that picks between:\n\n- `\"ws\"` — read the Tier-2 store selector, latch a local `hasLoadedX`\n  boolean on first arrival (or on the server's \"feature disabled\"\n  signal), and call `updateTiltifySettings({ <sliceKey> })` whenever the\n  local `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST hook\n  (`useRewards`, `useTargets`, …) and ignore the WS slice.\n\nAll of them share `useLoadingTimeout` — a 15 s safety net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                    | Params → Returns                                                              |\n| -------------------------------------------------------- | ----------------------------------------------------------------------------- |\n| `useHybridRewards({ campaignID, isTeam, mode, initialRewardsEnabled? })`   | `{ currentRewards, hasLoadedRewards, setHasLoadedRewards, isLoadingRewards, isLoading, rewardsEnabled, setRewardsEnabled }` |\n| `useHybridTargets({ campaignID, isTeam, mode })`         | `{ currentTargets, hasLoadedTargets, setHasLoadedTargets, isLoadingTargets, isLoading, targetsEnabled, setTargetsEnabled }` |\n| `useHybridMilestones({ campaignID, isTeam, mode })`      | `{ currentMilestones, isLoadingMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls({ campaignID, isTeam, mode })`           | `{ currentPolls, isLoadingPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics.                                |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns({ mode, initialFundraisingEvent, initialSupportingCampaignsEnabled? })` | `{ supportingCampaigns, isLoading, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns, supportingCampaignsEnabled, setSupportingCampaignsEnabled }` |\n| `useHybridDonationTrains({ campaignID, mode, initialDonationTrainsEnabled?, withStatus?, http?, auth? })` | `{ currentDonationTrains, hasLoadedDonationTrains, setHasLoadedDonationTrains, isLoadingDonationTrains, isLoading, donationTrainsEnabled, setDonationTrainsEnabled }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useHybridLeaderboardWithExclusions({ charityType, campaignID, isTeam?, timeType?, count?, startDate?, endDate?, enabled?, continuouslyUpdate? })` | `{ entries, isLoading }` — WS-driven invalidation over the REST leaderboard, with a `LEADERBOARD_SAFETY_POLL_MS` (60 s) backstop. |\n| `useLoadingTimeout({ mode, isLoading, onFinishedLoading, timeoutAmount? })` | `void` — the shared safety-net timer primitive.                    |\n\n`LEADERBOARD_SAFETY_POLL_MS` is exported alongside the hooks. Return\nshapes are preserved verbatim from the overlay so overlay call sites can\neventually collapse to a re-export.\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Behavior is identical to the canonical exports — rename\nthe import specifier once and the rest of your code base keeps working:\n\n| Legacy name          | Canonical replacement                                            |\n| -------------------- | ---------------------------------------------------------------- |\n| `useUDPStore`        | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)          |\n| `useUDPStoreApi`     | Use a typed slice hook, or reach for the raw store API            |\n| `createUDPStore`     | `createPipelineStore` from `@playlive/realtime-pipeline/store`    |\n| `UDPStore` (type)    | `PipelineStore` from `@playlive/realtime-pipeline/store`          |\n| `UDPStoreApi` (type) | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`       |\n| `CharityTypes`       | Re-exported from `@playlive/realtime-pipeline/protocol`           |\n\n`useUDPStore` / `useUDPStoreApi` throw with a\n`\"must be called inside <UnifiedDataPipelineProvider>\"` message when no\nprovider is mounted. The subpath also re-exports every Tier 1 + 2 + 3\nsymbol under its canonical name, plus the `PipelineStore` /\n`PipelineStoreApi` types.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot, sed-friendly rewrite of every import specifier (no body edits\nneeded for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure (PRD §10 — one-release window).\n\nTwo call-site changes are **not** covered by the sed:\n\n- `useIdentify(sendJsonMessage, campaignIDs, …)` — the positional args\n  are now ignored (the connection reads identify inputs off the store).\n  Drop them; the deprecated overload still compiles and warns once.\n- `useWebSocket` from `react-use-websocket-lite` — replaced by\n  `createPipelineConnection` in `@playlive/realtime-pipeline/connection`,\n  which `RealtimePipelineProvider` mounts for you. Use the\n  `/migrate:rusw` agent skill for the codemod.\n\nSee the phase-8 row in the root [MIGRATION.md](../../MIGRATION.md) and\n[`docs/overlay-data-layer-migration.md`](../../docs/overlay-data-layer-migration.md)\nfor the full per-symbol mapping.\n\n## Upstream spec\n\nNo external API surface — this package fetches nothing itself. Every\nbyte on the wire is framed by `@playlive/realtime-pipeline`; the message\ncatalogue lives in\n[`docs/realtime-protocol.md`](../../docs/realtime-protocol.md) and the\ntypes in [`@playlive/realtime-pipeline/protocol`](../realtime-pipeline/).\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nreach. **It is empty.** This package only opens the WebSocket URL the\nconsumer hands to `<RealtimePipelineProvider url={…}>` (or the store's\n`DEFAULT_WEBSOCKET_URL`, `wss://main.playlive.ws.api.experience.stjude.org`).\nDisclose that URL — plus the `KNOWN_URLS` of `@playlive/realtime-pipeline`\nand, if you import `/fusion`, `@playlive/fundraiser-data` — on your\nExtension submission.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\nSee [`docs/twitch-extension-checklist.md`](../../docs/twitch-extension-checklist.md).\n\n## Examples\n\n### Live schedule-block total without polling\n\nThe canonical fusion use case: fetch a REST baseline once, then add the\nWebSocket donation delta on top so the number ticks up on every donation\ninstead of every 30-second poll. Requires the optional peers plus a\n`<QueryClientProvider>` above the pipeline provider.\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\nimport { RealtimePipelineProvider } from \"@playlive/react-pipeline\";\nimport {\n  useCurrentBlockRaised,\n  useCurrentScheduleItem,\n  useLiveSchedule,\n} from \"@playlive/react-pipeline/fusion\";\nimport { makeQueryClient } from \"@playlive/react-query\";\nimport { QueryClientProvider } from \"@tanstack/react-query\";\n\nconfigure(getConfigForEnv(\"prod\", { tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL }));\nconst queryClient = makeQueryClient();\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nexport function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <RealtimePipelineProvider\n        url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n        initialCampaignIDs={[CAMPAIGN_ID]}\n        initialOverlayName=\"schedule-block\"\n        initialTeamUserSlug=\"@playliver\"\n      >\n        <BlockTotal campaignID={CAMPAIGN_ID} />\n      </RealtimePipelineProvider>\n    </QueryClientProvider>\n  );\n}\n\nfunction BlockTotal({ campaignID }: { campaignID: string }) {\n  const { schedule, isLoading: scheduleLoading, isError } = useLiveSchedule({\n    charityType: \"tiltify\",\n    campaignID,\n  });\n  const { currentItem, nextItem } = useCurrentScheduleItem(schedule);\n\n  const { raised, isLoading } = useCurrentBlockRaised(\n    campaignID,\n    currentItem?.starts_at,\n    currentItem?.ends_at,\n  );\n\n  if (scheduleLoading) return <p>Loading schedule…</p>;\n  if (isError) return <p>Schedule unavailable.</p>;\n  if (!currentItem) return <p>Up next: {nextItem?.name ?? \"nothing scheduled\"}</p>;\n\n  return (\n    <section>\n      <h2>{currentItem.name}</h2>\n      <p>{isLoading ? \"…\" : `$${raised.toFixed(2)} raised this block`}</p>\n    </section>\n  );\n}\n```\n\n### Raw baseline + delta with the Tier-2 primitive\n\nIf you already have the baseline (from a route loader, say), skip the\nfusion hook and reduce the firehose yourself:\n\n```tsx\nimport { usePipelineDonationSum } from \"@playlive/react-pipeline\";\n\nfunction BlockDelta({\n  campaignID,\n  baseline,\n}: {\n  campaignID: string;\n  // `GET /schedules/campaigns/{id}/raised`\n  baseline: { raised: number; currency: string; asOf: string };\n}) {\n  const delta = usePipelineDonationSum(campaignID, {\n    start: baseline.asOf,\n    end: \"2025-09-20T23:00:00.000Z\",\n    currency: baseline.currency,\n  });\n\n  const total = baseline.raised + (delta.raised ?? 0);\n  return (\n    <p>\n      ${total.toFixed(2)} ({delta.donationCount} live donations since baseline)\n    </p>\n  );\n}\n```\n\n`usePipelineDonationSum` matches `start <= completed_at < end`, dedupes\nby donation id (last write wins, so post-edit corrections propagate),\nand excludes `test: true` donations unless you pass\n`includeTest: true` — which keeps it consistent with the REST baseline.\n\n### Driving it locally\n\n[`dev/greenroom`](../../dev/greenroom/) boots an in-process pipeline\nserver you can point the provider at, then inject donations, schedule\ntransitions, and leaderboard updates over its `/_admin` knobs. The\npackage's own integration suite (`tests/integration/index.test.tsx`)\ndoes exactly that and is a good copy-paste starting point. See\n[`docs/greenroom-cookbook.md`](../../docs/greenroom-cookbook.md).\n\n## Contributing\n\nSee [CONTRIBUTING.md](../../CONTRIBUTING.md). To add a Tier-2 hook,\nmirror an existing slice hook in `src/hooks/index.ts` and add an\n`it.each` entry in `tests/unit/hooks.test.tsx`; the barrel-surface guard\nin `tests/unit/index.test.ts` also needs the new name. Fusion hooks live\nin `src/fusion/` with one file per slice.\n\n## License\n\nMIT — see [LICENSE](../../LICENSE). Distributed via Play Live\nCodeArtifact (PRD §6).\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.19.tgz","shasum":"ee39e04467399a832ca13454e13d85063f466f3f","integrity":"sha512-DyNpzI3qJ6CC8YVn6jbVN9GV/gGvJi2IKcSbipIZgiSfPScMdip3lpIqGZG5pqx9L10Sd3M0h9zsyPoouW9O5Q=="}},"0.4.20":{"name":"@playlive/react-pipeline","version":"0.4.20","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.3.2","@playlive/tiltify-core":"^0.4.19","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.4.4","@playlive/fundraiser-data":"^0.5.4"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-pp0cpzIiJf4pxAZ4jMddqMVS9+jo6Unpc9bpHkoLIshtZJWdVl4Diu5Vvga8Reyz4ZTmrzHd0ECNwV/oVZn86A==","shasum":"d576619a9d3ecdbb92e29ec76ec67404eb777eeb","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nSuccessor to `playlive-overlay-data-layer/src/websocket/*` with **zero**\ndependency on `react-use-websocket-lite` — built directly on Zustand v5's\n`useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                             | Surface                                                                       | Use when                                                               |\n| ---- | ----------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`          | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)          | Greenfield app; you want one selector primitive and nothing else.      |\n| 2    | `@playlive/react-pipeline`          | Tier 1 **plus** the typed per-slice hooks (`usePipelineDonations(id)`, …)     | You want stable, typed one-liners for the common reads.                |\n| 3    | `@playlive/react-pipeline/provider` | Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc. | Porting an existing overlay-data-layer consumer with minimal churn.    |\n| —    | `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (needs the optional TanStack peers)              | You want \"REST baseline + live WS delta\" without hand-rolling it.      |\n| —    | `@playlive/react-pipeline/legacy`   | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases   | Soft-deprecated shim for the legacy symbol names (one-release window). |\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add react react-dom zustand @playlive/realtime-pipeline @playlive/tiltify-core\n```\n\nPeer dependencies (jose-style — the consumer brings their own):\n\n| Peer                          | Range          | Required?                                 |\n| ----------------------------- | -------------- | ----------------------------------------- |\n| `react`                       | `^19.0.0`      | yes                                       |\n| `react-dom`                   | `^19.0.0`      | yes                                       |\n| `zustand`                     | `^5.0.0`       | yes                                       |\n| `@playlive/realtime-pipeline` | `workspace:*`  | yes — store, connection, protocol types    |\n| `@playlive/tiltify-core`      | `workspace:*`  | yes (type-only — stripped at compile time) |\n| `@tanstack/react-query`       | `^5.0.0`       | **optional** — only for `/fusion`          |\n| `@playlive/react-query`       | `workspace:*`  | **optional** — only for `/fusion`          |\n| `@playlive/fundraiser-data`   | `workspace:*`  | **optional** — only for `/fusion`          |\n\nThe three optional peers are declared `optional: true` in\n`peerDependenciesMeta`; you only need them if you import from\n[`@playlive/react-pipeline/fusion`](#fusion--rest-baseline--ws-delta-hooks-fusion).\n\nThis package has **no runtime `dependencies`** — nothing but your own\npeers ends up in the bundle.\n\n## Quick start\n\n```tsx\nimport {\n  RealtimePipelineProvider,\n  usePipelineCampaigns,\n  usePipelineConnectionState,\n  usePipelineDonations,\n} from \"@playlive/react-pipeline\";\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n      initialCampaignIDs={[CAMPAIGN_ID]}\n      initialOverlayName=\"donation-bar\"\n      initialTeamUserSlug=\"@playliver\"\n      initialOverlayPath=\"/overlays/donation-bar\"\n      autoConnect\n    >\n      <DonationBar campaignID={CAMPAIGN_ID} />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction DonationBar({ campaignID }: { campaignID: string }) {\n  const { connected, connecting, hasReceivedInitialData } = usePipelineConnectionState();\n  const campaigns = usePipelineCampaigns();\n  const donations = usePipelineDonations(campaignID);\n\n  if (connecting) return <p>Connecting…</p>;\n  if (!connected) return <p>Disconnected — reconnecting…</p>;\n  if (!hasReceivedInitialData) return <p>Waiting for first tick…</p>;\n\n  const campaign = campaigns.find((c) => c.id === campaignID);\n  const raised = Number(campaign?.total_amount_raised?.value ?? 0);\n  const goal = Number(campaign?.goal?.value ?? 0);\n\n  return (\n    <section>\n      <progress value={raised} max={goal || 1} />\n      <p>\n        ${raised.toFixed(2)} of ${goal.toFixed(2)}\n      </p>\n      <ul>\n        {donations.slice(0, 5).map((d) => (\n          <li key={d.id}>\n            {d.donor_name} — ${d.amount.value}\n          </li>\n        ))}\n      </ul>\n    </section>\n  );\n}\n```\n\n`RealtimePipelineProvider` builds one Zustand store per provider\ninstance (from the `initial*` props, which are read **once** at first\nconstruction) and opens a single `PipelineConnection`. `connection.destroy()`\nruns on unmount. Children subscribe with any Tier 1 / Tier 2 hook.\n\nEvery hook that reads the store throws when no provider is mounted\nabove it; the four `useOn*` observer hooks deliberately soft-fail\n(they no-op) so you can drop them into components that sometimes render\noutside a pipeline tree.\n\n## Subpath exports\n\n| Subpath                             | Description                                                                                             |\n| ----------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`          | Default barrel — Tier 1 + Tier 2 (provider, `usePipelineValue`, typed slice hooks, observers).           |\n| `@playlive/react-pipeline/provider` | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, `useAddPipeline*`, `useIdentify`, …    |\n| `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (`useCurrentBlockRaised`, `useLiveSchedule`, the `useHybrid*` family, …).    |\n| `@playlive/react-pipeline/legacy`   | Soft-deprecated alias bundle (`useUDPStore`, `useUDPStoreApi`, `createUDPStore`, `UDPStore`, …).         |\n\nEach subpath ships an ESM bundle, a `bun` source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified so stack\ntraces stay readable in production.\n\n## API reference\n\nFull generated API documentation:\n<https://packages.playlive.experience.stjude.org/p/@playlive/react-pipeline/docs/>\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                     |\n| ------------------------------- | ------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => ReactNode`          |\n| `RealtimePipelineProviderProps` | Type. `InitialProps` + `children`, `url?`, `autoConnect?` (default `true`), `webSocketCtor?`, `log?`, `store?`, `connection?`. |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                  |\n| `PACKAGE_NAME`                  | `\"@playlive/react-pipeline\"` — for runtime version-pinning.    |\n| `KNOWN_URLS`                    | `readonly string[]` — frozen, empty. See the disclosure section. |\n\n`RealtimePipelineProviderProps` extends\n[`InitialProps`](../realtime-pipeline/), so every `initial*` key\n(`initialCampaignIDs`, `initialTeamCampaignIDs`, `initialCauseIDs`,\n`initialFundraisingEventIDs`, `initialOverlayName`, `initialTeamUserSlug`,\n`initialOverlayPath`, `initialOverlayConfig`, `initialAdminApiKey`,\n`initialDonationTrainsEnabled`, …) is a valid prop. There is **no**\n`charityType` prop — set the charity type through the store's\n`setCurrentType` action or via `useCommonContextHooks` (Tier 3).\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the third\n`equalityFn` parameter on `useStore` to keep the package free of the\n`use-sync-external-store` shim peer. For shallow / custom equality, wrap\nyour selector with `useShallow` from `zustand/shallow` (zero extra dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nStable wrappers over `usePipelineValue` with a typed selector +\nmemo-stable empty-array/object fallbacks (no infinite-render churn from\nfresh `[]` allocations — Decision §3 in [CHANGELOG.md](./CHANGELOG.md)).\n\n| Hook                                          | Returns                                                     |\n| --------------------------------------------- | ----------------------------------------------------------- |\n| `usePipelineCampaigns()`                      | `TiltifyCampaign[]`                                          |\n| `usePipelineTeamCampaigns()`                  | `TiltifyTeamCampaign[]`                                      |\n| `usePipelineFundraisingEvents()`              | `TiltifyFundraisingEvent[]`                                  |\n| `usePipelineCauses()`                         | `TiltifyCause[]`                                             |\n| `usePipelineDonations()`                      | `Record<string, TiltifyDonationWithTestFlag[]>`               |\n| `usePipelineDonations(id)`                    | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if unknown) |\n| `usePipelineDonationTrains(id, withStatus?)`  | `DonationTrain[]` — `withStatus` is `\"ACTIVE\" \\| \"ENDED\" \\| \"ENDING\" \\| \"PENDING\"` |\n| `usePipelineSubathonTimer(id, withStatus?)`   | `SubathonTimer[]` — `withStatus` is `\"ACTIVE\" \\| \"INACTIVE\"`  |\n| `usePipelineDonorSpotlight(id)`               | `ComputedSpotlight \\| null`                                  |\n| `usePipelineDonorSpotlightSettings()`         | `Record<string, DonorSpotlightSettings>`                     |\n| `usePipelinePolls(id)`                        | `TiltifyPoll[]`                                              |\n| `usePipelinePollDeltas(pollID)`               | `PollOptionDelta[]` — server-computed \"+$X on option Y\" deltas |\n| `usePipelineRewards(id)`                      | `TiltifyReward[]`                                            |\n| `usePipelineTargets(id)`                      | `TiltifyTarget[]`                                            |\n| `usePipelineMilestones(id)`                   | `TiltifyMilestone[]`                                         |\n| `usePipelineLeaderboardEntries(id)`           | `TiltifyLeaderboardEntry[]`                                  |\n| `usePipelineLeaderboardExclusions(id)`        | `LeaderboardExclusion[]`                                     |\n| `usePipelineLeaderboardExclusionsMap()`       | `Record<string, LeaderboardExclusion[]>`                     |\n| `usePipelineSchedule(id)`                     | `ScheduleTransitionBlock[]` — full `updateSchedule` WS push  |\n| `usePipelineSchedules()`                      | `Record<string, ScheduleTransitionBlock[]>`                  |\n| `usePipelineLastScheduleTransition(id)`       | `ScheduleTransitionPayload \\| null` — latest block crossing  |\n| `usePipelineLastScheduleTransitions()`        | `Record<string, ScheduleTransitionPayload>`                  |\n| `usePipelineDonationSum(id, options?)`        | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` |\n| `usePipelineAuctionHouses()`                  | `AuctionHouse[]`                                             |\n| `usePipelineTwitchChat()`                     | `TwitchChatMessage[]`                                        |\n| `usePipelineConnectionState()`                | `PipelineConnectionState` (10 split selectors — no false re-renders) |\n| `usePipelineConnectionActions()`              | `PipelineConnectionActions` (stable `connect`/`disconnect`/…) |\n| `usePipelineSubscriptions()`                  | `Record<string, TiltifyWebhookSubscription>`                 |\n| `usePipelineTiltifySettings()`                | `TiltifyWebSocketSettings`                                   |\n| `usePipelineLastRefreshResponse()`            | `RefreshResponseMessage \\| null`                             |\n\nCompanion types exported alongside them: `PipelineConnectionState`\n(`connected`, `connecting`, `charityType`, `connectionType`, `wsURL`,\n`hasReceivedInitialData`, `lastIdentified`, `lastIdentityMessage`,\n`lastFirehoseMessage`, `lastMessage`), `PipelineConnectionActions`\n(`connect`, `disconnect`, `identify`, `refresh`, `reset`,\n`setAutoConnect`, `setAdminApiKey`), `PipelineDonationSumOptions`,\n`PipelineDonationSumByCurrency`, `PipelineDonationSumResult`.\n\n#### Change observers\n\nPush-style subscriptions that diff the store outside React's render\npath. Each stashes `handler` in a ref, subscribes exactly once, and\ntreats whatever is already on the store at mount as the baseline (so\nthey never fire for pre-existing data). All four **soft-fail** without\na provider.\n\n| Hook                                                | Handler payload                                        |\n| --------------------------------------------------- | ------------------------------------------------------ |\n| `useOnScheduleTransition(handler, options?)`        | `ScheduleTransitionPayload`                             |\n| `useOnScheduleUpdate(handler, options?)`            | `{ campaignID, schedule: ScheduleTransitionBlock[] }`   |\n| `useOnLeaderboardEntriesChange(handler, options?)`  | `{ campaignID, entries: TiltifyLeaderboardEntry[] }`    |\n| `useOnLeaderboardExclusionsChange(handler, options?)` | `{ campaignID, exclusions: LeaderboardExclusion[] }`  |\n\n`options` is `{ campaignIDs?: readonly string[] }` — omit it (or pass an\nempty list) to observe every campaign.\n\n```tsx\nuseOnLeaderboardEntriesChange(\n  ({ campaignID }) => {\n    queryClient.invalidateQueries({\n      queryKey: [\"leaderboard-with-exclusions\", \"tiltify\", campaignID],\n    });\n  },\n  { campaignIDs: [campaignID] },\n);\n```\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                                        | Notes                                                                                          |\n| --------------------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`                 | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` (default `true`) + `debug` (wires `console.debug` logging). |\n| `UnifiedDataPipelineProviderProps`            | Type. `Omit<RealtimePipelineProviderProps, \"children\"> & { children, reloadOnErrorReconnect?, debug? }`. |\n| `useUnifiedDataPipeline()`                    | One-call kitchen-sink hook → `UnifiedDataPipelineState`. Re-renders on **every** store change.  |\n| `UnifiedDataPipelineState`                    | Type. `PipelineStore` + `allRewards` / `allMilestones` / `allTargets` / `allPolls` / `allFundraisingEventSupportingCampaigns` / `tiltifySubscriptions` legacy aliases. |\n| `useAddPipelineCampaignIDs(params)`           | Layout-effect hook. `{ mode: \"ws\" \\| \"http\"; initialCampaign: { id }; isTeam: boolean; causeID? }` — no-op in `\"http\"` mode. |\n| `useAddPipelineCampaigns(params)`             | Layout-effect hook. `{ campaign?: TiltifyCampaign \\| null; teamCampaign?: TiltifyTeamCampaign \\| null }`. Idempotent by id. |\n| `useAddPipelineFundraisingEventIDs(params)`   | Layout-effect hook. `{ mode; initialCampaign: { id; fundraising_event_id: string \\| null } }`.   |\n| `useCommonContextHooks(params)`               | `{ url: string; mode: \"ws\" \\| \"http\"; charityType: CharityType }` — syncs type + URL and connects when `mode === \"ws\"`. |\n| `useIdentify()`                               | Returns the store-bound `identify` action. The 11-positional-arg legacy overload is kept as `@deprecated`; the args are ignored and a one-shot `console.warn` fires. |\n\n`RealtimePipelineProvider` / `RealtimePipelineProviderProps` are also\nre-exported from `/provider` so a Tier-3 consumer needs exactly one\nimport specifier.\n\n### Fusion — REST baseline + WS delta hooks (`/fusion`)\n\nThese fuse the WebSocket firehose with REST reads from\n[`@playlive/react-query`](../react-query/) /\n[`@playlive/fundraiser-data`](../fundraiser-data/). Importing this\nsubpath means you must also install the three optional peers.\n\n| Hook                                                   | Returns                                                                    |\n| ------------------------------------------------------ | -------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline `+` WS delta over `[start, end)`. `opts`: `{ enabled?, queryOptions?, demoMode? }`. |\n| `useLiveSchedule(params, opts?)`                       | `{ schedule, isLoading, isError, refetch }`. `params`: `{ charityType, campaignID, isTeam? }`; `opts`: `{ enabled?, queryOptions?, demoMode?, demoIntervalMs? }`. |\n| `useCurrentScheduleItem(schedule, opts?)`              | `{ currentItem, nextItem }` — pure client-side rollover projection with a self-scheduling boundary timer. |\n| `useLinkedTeamCampaign(params, opts?)`                 | `{ shouldFetchLinkedTeamCampaign, resolvedLinkedTeamCampaign, linkedTeamTotalAmount, linkedTeamGoalAmount, isLoadingLinkedTeamCampaign, debug }`. |\n| `useDonorSpotlight(params, opts?)`                     | `{ spotlight, isLoading, refetch }`. `params`: `{ campaignID, isTeam, live, mode }`; `opts`: `{ pollingIntervalMs?, enabled?, queryOptions? }`. |\n\n`useLiveSchedule` resolves in a strict fallback order: full\n`updateSchedule` WS push → `lastScheduleTransitions` synthesis (demo\nonly) → REST `useSchedule` → demo fixture.\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity hooks — the ones every Play Live overlay used to keep a\nlocal copy of — shipped here so they can be imported instead. Each takes a\n`mode: \"ws\" | \"http\"` that picks between:\n\n- `\"ws\"` — read the Tier-2 store selector, latch a local `hasLoadedX`\n  boolean on first arrival (or on the server's \"feature disabled\"\n  signal), and call `updateTiltifySettings({ <sliceKey> })` whenever the\n  local `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST hook\n  (`useRewards`, `useTargets`, …) and ignore the WS slice.\n\nAll of them share `useLoadingTimeout` — a 15 s safety net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                    | Params → Returns                                                              |\n| -------------------------------------------------------- | ----------------------------------------------------------------------------- |\n| `useHybridRewards({ campaignID, isTeam, mode, initialRewardsEnabled? })`   | `{ currentRewards, hasLoadedRewards, setHasLoadedRewards, isLoadingRewards, isLoading, rewardsEnabled, setRewardsEnabled }` |\n| `useHybridTargets({ campaignID, isTeam, mode })`         | `{ currentTargets, hasLoadedTargets, setHasLoadedTargets, isLoadingTargets, isLoading, targetsEnabled, setTargetsEnabled }` |\n| `useHybridMilestones({ campaignID, isTeam, mode })`      | `{ currentMilestones, isLoadingMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls({ campaignID, isTeam, mode })`           | `{ currentPolls, isLoadingPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics.                                |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns({ mode, initialFundraisingEvent, initialSupportingCampaignsEnabled? })` | `{ supportingCampaigns, isLoading, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns, supportingCampaignsEnabled, setSupportingCampaignsEnabled }` |\n| `useHybridDonationTrains({ campaignID, mode, initialDonationTrainsEnabled?, withStatus?, http?, auth? })` | `{ currentDonationTrains, hasLoadedDonationTrains, setHasLoadedDonationTrains, isLoadingDonationTrains, isLoading, donationTrainsEnabled, setDonationTrainsEnabled }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useHybridLeaderboardWithExclusions({ charityType, campaignID, isTeam?, timeType?, count?, startDate?, endDate?, enabled?, continuouslyUpdate? })` | `{ entries, isLoading }` — WS-driven invalidation over the REST leaderboard, with a `LEADERBOARD_SAFETY_POLL_MS` (60 s) backstop. |\n| `useLoadingTimeout({ mode, isLoading, onFinishedLoading, timeoutAmount? })` | `void` — the shared safety-net timer primitive.                    |\n\n`LEADERBOARD_SAFETY_POLL_MS` is exported alongside the hooks. Return\nshapes are preserved verbatim from the original overlay hooks, so\nexisting call sites can collapse to a re-export.\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Behavior is identical to the canonical exports — rename\nthe import specifier once and the rest of your code base keeps working:\n\n| Legacy name          | Canonical replacement                                            |\n| -------------------- | ---------------------------------------------------------------- |\n| `useUDPStore`        | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)          |\n| `useUDPStoreApi`     | Use a typed slice hook, or reach for the raw store API            |\n| `createUDPStore`     | `createPipelineStore` from `@playlive/realtime-pipeline/store`    |\n| `UDPStore` (type)    | `PipelineStore` from `@playlive/realtime-pipeline/store`          |\n| `UDPStoreApi` (type) | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`       |\n| `CharityTypes`       | Re-exported from `@playlive/realtime-pipeline/protocol`           |\n\n`useUDPStore` / `useUDPStoreApi` throw with a\n`\"must be called inside <UnifiedDataPipelineProvider>\"` message when no\nprovider is mounted. The subpath also re-exports every Tier 1 + 2 + 3\nsymbol under its canonical name, plus the `PipelineStore` /\n`PipelineStoreApi` types.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot, sed-friendly rewrite of every import specifier (no body edits\nneeded for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure — the aliases are kept for a\none-release window.\n\nTwo call-site changes are **not** covered by the sed:\n\n- `useIdentify(sendJsonMessage, campaignIDs, …)` — the positional args\n  are now ignored (the connection reads identify inputs off the store).\n  Drop them; the deprecated overload still compiles and warns once.\n- `useWebSocket` from `react-use-websocket-lite` — replaced by\n  `createPipelineConnection` in `@playlive/realtime-pipeline/connection`,\n  which `RealtimePipelineProvider` mounts for you.\n\nThe [Legacy aliases](#legacy-aliases-legacy) table above is the full\nper-symbol mapping.\n\n## Upstream spec\n\nNo external API surface — this package fetches nothing itself. It binds\nReact to [`@playlive/realtime-pipeline`](../realtime-pipeline/), which\nowns the WebSocket connection and frames every byte on the wire; the\nmessage catalogue and its types are exported from\n`@playlive/realtime-pipeline/protocol`. The `/fusion` hooks additionally\nread REST baselines through\n[`@playlive/react-query`](../react-query/) and\n[`@playlive/fundraiser-data`](../fundraiser-data/) — see those packages\nfor the endpoints and URL knobs involved.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nreach. **It is empty.** This package only opens the WebSocket URL the\nconsumer hands to `<RealtimePipelineProvider url={…}>` (or the store's\n`DEFAULT_WEBSOCKET_URL`, `wss://main.playlive.ws.api.experience.stjude.org`).\nDisclose that URL — plus the `KNOWN_URLS` of `@playlive/realtime-pipeline`\nand, if you import `/fusion`, `@playlive/fundraiser-data` — on your\nExtension submission.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\n## Examples\n\n### Live schedule-block total without polling\n\nThe canonical fusion use case: fetch a REST baseline once, then add the\nWebSocket donation delta on top so the number ticks up on every donation\ninstead of every 30-second poll. Requires the optional peers plus a\n`<QueryClientProvider>` above the pipeline provider.\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\nimport { RealtimePipelineProvider } from \"@playlive/react-pipeline\";\nimport {\n  useCurrentBlockRaised,\n  useCurrentScheduleItem,\n  useLiveSchedule,\n} from \"@playlive/react-pipeline/fusion\";\nimport { makeQueryClient } from \"@playlive/react-query\";\nimport { QueryClientProvider } from \"@tanstack/react-query\";\n\nconfigure(getConfigForEnv(\"prod\", { tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL }));\nconst queryClient = makeQueryClient();\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nexport function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <RealtimePipelineProvider\n        url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n        initialCampaignIDs={[CAMPAIGN_ID]}\n        initialOverlayName=\"schedule-block\"\n        initialTeamUserSlug=\"@playliver\"\n      >\n        <BlockTotal campaignID={CAMPAIGN_ID} />\n      </RealtimePipelineProvider>\n    </QueryClientProvider>\n  );\n}\n\nfunction BlockTotal({ campaignID }: { campaignID: string }) {\n  const { schedule, isLoading: scheduleLoading, isError } = useLiveSchedule({\n    charityType: \"tiltify\",\n    campaignID,\n  });\n  const { currentItem, nextItem } = useCurrentScheduleItem(schedule);\n\n  const { raised, isLoading } = useCurrentBlockRaised(\n    campaignID,\n    currentItem?.starts_at,\n    currentItem?.ends_at,\n  );\n\n  if (scheduleLoading) return <p>Loading schedule…</p>;\n  if (isError) return <p>Schedule unavailable.</p>;\n  if (!currentItem) return <p>Up next: {nextItem?.name ?? \"nothing scheduled\"}</p>;\n\n  return (\n    <section>\n      <h2>{currentItem.name}</h2>\n      <p>{isLoading ? \"…\" : `$${raised.toFixed(2)} raised this block`}</p>\n    </section>\n  );\n}\n```\n\n### Raw baseline + delta with the Tier-2 primitive\n\nIf you already have the baseline (from a route loader, say), skip the\nfusion hook and reduce the firehose yourself:\n\n```tsx\nimport { usePipelineDonationSum } from \"@playlive/react-pipeline\";\n\nfunction BlockDelta({\n  campaignID,\n  baseline,\n}: {\n  campaignID: string;\n  // `GET /schedules/campaigns/{id}/raised`\n  baseline: { raised: number; currency: string; asOf: string };\n}) {\n  const delta = usePipelineDonationSum(campaignID, {\n    start: baseline.asOf,\n    end: \"2025-09-20T23:00:00.000Z\",\n    currency: baseline.currency,\n  });\n\n  const total = baseline.raised + (delta.raised ?? 0);\n  return (\n    <p>\n      ${total.toFixed(2)} ({delta.donationCount} live donations since baseline)\n    </p>\n  );\n}\n```\n\n`usePipelineDonationSum` matches `start <= completed_at < end`, dedupes\nby donation id (last write wins, so post-edit corrections propagate),\nand excludes `test: true` donations unless you pass\n`includeTest: true` — which keeps it consistent with the REST baseline.\n\nBoth examples above are complete and runnable: point\n`<RealtimePipelineProvider url={…}>` at your pipeline WebSocket and the\nrest is copy-paste.\n\n## License\n\nMIT © St. Jude Children's Research Hospital\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.4.20.tgz","shasum":"d576619a9d3ecdbb92e29ec76ec67404eb777eeb","integrity":"sha512-pp0cpzIiJf4pxAZ4jMddqMVS9+jo6Unpc9bpHkoLIshtZJWdVl4Diu5Vvga8Reyz4ZTmrzHd0ECNwV/oVZn86A=="}},"0.5.0":{"name":"@playlive/react-pipeline","version":"0.5.0","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.4.0","@playlive/tiltify-core":"^0.4.19","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.4.4","@playlive/fundraiser-data":"^0.5.4"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-yJwY+vSwb143aQhUVo1UVIKhCNB1l+s3R21uZ5Tiy9N8HNgeKuPodn4q8+52qbFMKtjbUgOfAaWhhkp6/sClTg==","shasum":"0851431ddc3482bc3fc267ccffbf72fe728bd753","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nSuccessor to `playlive-overlay-data-layer/src/websocket/*` with **zero**\ndependency on `react-use-websocket-lite` — built directly on Zustand v5's\n`useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                             | Surface                                                                       | Use when                                                               |\n| ---- | ----------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`          | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)          | Greenfield app; you want one selector primitive and nothing else.      |\n| 2    | `@playlive/react-pipeline`          | Tier 1 **plus** the typed per-slice hooks (`usePipelineDonations(id)`, …)     | You want stable, typed one-liners for the common reads.                |\n| 3    | `@playlive/react-pipeline/provider` | Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc. | Porting an existing overlay-data-layer consumer with minimal churn.    |\n| —    | `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (needs the optional TanStack peers)              | You want \"REST baseline + live WS delta\" without hand-rolling it.      |\n| —    | `@playlive/react-pipeline/legacy`   | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases   | Soft-deprecated shim for the legacy symbol names (one-release window). |\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add react react-dom zustand @playlive/realtime-pipeline @playlive/tiltify-core\n```\n\nPeer dependencies (jose-style — the consumer brings their own):\n\n| Peer                          | Range          | Required?                                 |\n| ----------------------------- | -------------- | ----------------------------------------- |\n| `react`                       | `^19.0.0`      | yes                                       |\n| `react-dom`                   | `^19.0.0`      | yes                                       |\n| `zustand`                     | `^5.0.0`       | yes                                       |\n| `@playlive/realtime-pipeline` | `workspace:*`  | yes — store, connection, protocol types    |\n| `@playlive/tiltify-core`      | `workspace:*`  | yes (type-only — stripped at compile time) |\n| `@tanstack/react-query`       | `^5.0.0`       | **optional** — only for `/fusion`          |\n| `@playlive/react-query`       | `workspace:*`  | **optional** — only for `/fusion`          |\n| `@playlive/fundraiser-data`   | `workspace:*`  | **optional** — only for `/fusion`          |\n\nThe three optional peers are declared `optional: true` in\n`peerDependenciesMeta`; you only need them if you import from\n[`@playlive/react-pipeline/fusion`](#fusion--rest-baseline--ws-delta-hooks-fusion).\n\nThis package has **no runtime `dependencies`** — nothing but your own\npeers ends up in the bundle.\n\n## Quick start\n\n```tsx\nimport {\n  RealtimePipelineProvider,\n  usePipelineCampaigns,\n  usePipelineConnectionState,\n  usePipelineDonations,\n} from \"@playlive/react-pipeline\";\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n      initialCampaignIDs={[CAMPAIGN_ID]}\n      initialOverlayName=\"donation-bar\"\n      initialTeamUserSlug=\"@playliver\"\n      initialOverlayPath=\"/overlays/donation-bar\"\n      autoConnect\n    >\n      <DonationBar campaignID={CAMPAIGN_ID} />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction DonationBar({ campaignID }: { campaignID: string }) {\n  const { connected, connecting, hasReceivedInitialData } = usePipelineConnectionState();\n  const campaigns = usePipelineCampaigns();\n  const donations = usePipelineDonations(campaignID);\n\n  if (connecting) return <p>Connecting…</p>;\n  if (!connected) return <p>Disconnected — reconnecting…</p>;\n  if (!hasReceivedInitialData) return <p>Waiting for first tick…</p>;\n\n  const campaign = campaigns.find((c) => c.id === campaignID);\n  const raised = Number(campaign?.total_amount_raised?.value ?? 0);\n  const goal = Number(campaign?.goal?.value ?? 0);\n\n  return (\n    <section>\n      <progress value={raised} max={goal || 1} />\n      <p>\n        ${raised.toFixed(2)} of ${goal.toFixed(2)}\n      </p>\n      <ul>\n        {donations.slice(0, 5).map((d) => (\n          <li key={d.id}>\n            {d.donor_name} — ${d.amount.value}\n          </li>\n        ))}\n      </ul>\n    </section>\n  );\n}\n```\n\n`RealtimePipelineProvider` builds one Zustand store per provider\ninstance (from the `initial*` props, which are read **once** at first\nconstruction) and opens a single `PipelineConnection`. `connection.destroy()`\nruns on unmount. Children subscribe with any Tier 1 / Tier 2 hook.\n\nEvery hook that reads the store throws when no provider is mounted\nabove it; the four `useOn*` observer hooks deliberately soft-fail\n(they no-op) so you can drop them into components that sometimes render\noutside a pipeline tree.\n\n## Subpath exports\n\n| Subpath                             | Description                                                                                             |\n| ----------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`          | Default barrel — Tier 1 + Tier 2 (provider, `usePipelineValue`, typed slice hooks, observers).           |\n| `@playlive/react-pipeline/provider` | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, `useAddPipeline*`, `useIdentify`, …    |\n| `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (`useCurrentBlockRaised`, `useLiveSchedule`, the `useHybrid*` family, …).    |\n| `@playlive/react-pipeline/legacy`   | Soft-deprecated alias bundle (`useUDPStore`, `useUDPStoreApi`, `createUDPStore`, `UDPStore`, …).         |\n\nEach subpath ships an ESM bundle, a `bun` source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified so stack\ntraces stay readable in production.\n\n## API reference\n\nFull generated API documentation:\n<https://packages.playlive.experience.stjude.org/p/@playlive/react-pipeline/docs/>\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                     |\n| ------------------------------- | ------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => ReactNode`          |\n| `RealtimePipelineProviderProps` | Type. `InitialProps` + `children`, `url?`, `autoConnect?` (default `true`), `webSocketCtor?`, `log?`, `store?`, `connection?`. |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                  |\n| `PACKAGE_NAME`                  | `\"@playlive/react-pipeline\"` — for runtime version-pinning.    |\n| `KNOWN_URLS`                    | `readonly string[]` — frozen, empty. See the disclosure section. |\n\n`RealtimePipelineProviderProps` extends\n[`InitialProps`](../realtime-pipeline/), so every `initial*` key\n(`initialCampaignIDs`, `initialTeamCampaignIDs`, `initialCauseIDs`,\n`initialFundraisingEventIDs`, `initialOverlayName`, `initialTeamUserSlug`,\n`initialOverlayPath`, `initialOverlayConfig`, `initialAdminApiKey`,\n`initialDonationTrainsEnabled`, …) is a valid prop. There is **no**\n`charityType` prop — set the charity type through the store's\n`setCurrentType` action or via `useCommonContextHooks` (Tier 3).\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the third\n`equalityFn` parameter on `useStore` to keep the package free of the\n`use-sync-external-store` shim peer. For shallow / custom equality, wrap\nyour selector with `useShallow` from `zustand/shallow` (zero extra dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nStable wrappers over `usePipelineValue` with a typed selector +\nmemo-stable empty-array/object fallbacks (no infinite-render churn from\nfresh `[]` allocations — Decision §3 in [CHANGELOG.md](./CHANGELOG.md)).\n\n| Hook                                          | Returns                                                     |\n| --------------------------------------------- | ----------------------------------------------------------- |\n| `usePipelineCampaigns()`                      | `TiltifyCampaign[]`                                          |\n| `usePipelineTeamCampaigns()`                  | `TiltifyTeamCampaign[]`                                      |\n| `usePipelineFundraisingEvents()`              | `TiltifyFundraisingEvent[]`                                  |\n| `usePipelineCauses()`                         | `TiltifyCause[]`                                             |\n| `usePipelineDonations()`                      | `Record<string, TiltifyDonationWithTestFlag[]>`               |\n| `usePipelineDonations(id)`                    | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if unknown) |\n| `usePipelineDonationTrains(id, withStatus?)`  | `DonationTrain[]` — `withStatus` is `\"ACTIVE\" \\| \"ENDED\" \\| \"ENDING\" \\| \"PENDING\"` |\n| `usePipelineSubathonTimer(id, withStatus?)`   | `SubathonTimer[]` — `withStatus` is `\"ACTIVE\" \\| \"INACTIVE\"`  |\n| `usePipelineDonorSpotlight(id)`               | `ComputedSpotlight \\| null`                                  |\n| `usePipelineDonorSpotlightSettings()`         | `Record<string, DonorSpotlightSettings>`                     |\n| `usePipelinePolls(id)`                        | `TiltifyPoll[]`                                              |\n| `usePipelinePollDeltas(pollID)`               | `PollOptionDelta[]` — server-computed \"+$X on option Y\" deltas |\n| `usePipelineRewards(id)`                      | `TiltifyReward[]`                                            |\n| `usePipelineTargets(id)`                      | `TiltifyTarget[]`                                            |\n| `usePipelineMilestones(id)`                   | `TiltifyMilestone[]`                                         |\n| `usePipelineLeaderboardEntries(id)`           | `TiltifyLeaderboardEntry[]`                                  |\n| `usePipelineLeaderboardExclusions(id)`        | `LeaderboardExclusion[]`                                     |\n| `usePipelineLeaderboardExclusionsMap()`       | `Record<string, LeaderboardExclusion[]>`                     |\n| `usePipelineSchedule(id)`                     | `ScheduleTransitionBlock[]` — full `updateSchedule` WS push  |\n| `usePipelineSchedules()`                      | `Record<string, ScheduleTransitionBlock[]>`                  |\n| `usePipelineLastScheduleTransition(id)`       | `ScheduleTransitionPayload \\| null` — latest block crossing  |\n| `usePipelineLastScheduleTransitions()`        | `Record<string, ScheduleTransitionPayload>`                  |\n| `usePipelineDonationSum(id, options?)`        | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` |\n| `usePipelineAuctionHouses()`                  | `AuctionHouse[]`                                             |\n| `usePipelineTwitchChat()`                     | `TwitchChatMessage[]`                                        |\n| `usePipelineConnectionState()`                | `PipelineConnectionState` (10 split selectors — no false re-renders) |\n| `usePipelineConnectionActions()`              | `PipelineConnectionActions` (stable `connect`/`disconnect`/…) |\n| `usePipelineSubscriptions()`                  | `Record<string, TiltifyWebhookSubscription>`                 |\n| `usePipelineTiltifySettings()`                | `TiltifyWebSocketSettings`                                   |\n| `usePipelineLastRefreshResponse()`            | `RefreshResponseMessage \\| null`                             |\n\nCompanion types exported alongside them: `PipelineConnectionState`\n(`connected`, `connecting`, `charityType`, `connectionType`, `wsURL`,\n`hasReceivedInitialData`, `lastIdentified`, `lastIdentityMessage`,\n`lastFirehoseMessage`, `lastMessage`), `PipelineConnectionActions`\n(`connect`, `disconnect`, `identify`, `refresh`, `reset`,\n`setAutoConnect`, `setAdminApiKey`), `PipelineDonationSumOptions`,\n`PipelineDonationSumByCurrency`, `PipelineDonationSumResult`.\n\n#### Change observers\n\nPush-style subscriptions that diff the store outside React's render\npath. Each stashes `handler` in a ref, subscribes exactly once, and\ntreats whatever is already on the store at mount as the baseline (so\nthey never fire for pre-existing data). All four **soft-fail** without\na provider.\n\n| Hook                                                | Handler payload                                        |\n| --------------------------------------------------- | ------------------------------------------------------ |\n| `useOnScheduleTransition(handler, options?)`        | `ScheduleTransitionPayload`                             |\n| `useOnScheduleUpdate(handler, options?)`            | `{ campaignID, schedule: ScheduleTransitionBlock[] }`   |\n| `useOnLeaderboardEntriesChange(handler, options?)`  | `{ campaignID, entries: TiltifyLeaderboardEntry[] }`    |\n| `useOnLeaderboardExclusionsChange(handler, options?)` | `{ campaignID, exclusions: LeaderboardExclusion[] }`  |\n\n`options` is `{ campaignIDs?: readonly string[] }` — omit it (or pass an\nempty list) to observe every campaign.\n\n```tsx\nuseOnLeaderboardEntriesChange(\n  ({ campaignID }) => {\n    queryClient.invalidateQueries({\n      queryKey: [\"leaderboard-with-exclusions\", \"tiltify\", campaignID],\n    });\n  },\n  { campaignIDs: [campaignID] },\n);\n```\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                                        | Notes                                                                                          |\n| --------------------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`                 | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` (default `true`) + `debug` (wires `console.debug` logging). |\n| `UnifiedDataPipelineProviderProps`            | Type. `Omit<RealtimePipelineProviderProps, \"children\"> & { children, reloadOnErrorReconnect?, debug? }`. |\n| `useUnifiedDataPipeline()`                    | One-call kitchen-sink hook → `UnifiedDataPipelineState`. Re-renders on **every** store change.  |\n| `UnifiedDataPipelineState`                    | Type. `PipelineStore` + `allRewards` / `allMilestones` / `allTargets` / `allPolls` / `allFundraisingEventSupportingCampaigns` / `tiltifySubscriptions` legacy aliases. |\n| `useAddPipelineCampaignIDs(params)`           | Layout-effect hook. `{ mode: \"ws\" \\| \"http\"; initialCampaign: { id }; isTeam: boolean; causeID? }` — no-op in `\"http\"` mode. |\n| `useAddPipelineCampaigns(params)`             | Layout-effect hook. `{ campaign?: TiltifyCampaign \\| null; teamCampaign?: TiltifyTeamCampaign \\| null }`. Idempotent by id. |\n| `useAddPipelineFundraisingEventIDs(params)`   | Layout-effect hook. `{ mode; initialCampaign: { id; fundraising_event_id: string \\| null } }`.   |\n| `useCommonContextHooks(params)`               | `{ url: string; mode: \"ws\" \\| \"http\"; charityType: CharityType }` — syncs type + URL and connects when `mode === \"ws\"`. |\n| `useIdentify()`                               | Returns the store-bound `identify` action. The 11-positional-arg legacy overload is kept as `@deprecated`; the args are ignored and a one-shot `console.warn` fires. |\n\n`RealtimePipelineProvider` / `RealtimePipelineProviderProps` are also\nre-exported from `/provider` so a Tier-3 consumer needs exactly one\nimport specifier.\n\n### Fusion — REST baseline + WS delta hooks (`/fusion`)\n\nThese fuse the WebSocket firehose with REST reads from\n[`@playlive/react-query`](../react-query/) /\n[`@playlive/fundraiser-data`](../fundraiser-data/). Importing this\nsubpath means you must also install the three optional peers.\n\n| Hook                                                   | Returns                                                                    |\n| ------------------------------------------------------ | -------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline `+` WS delta over `[start, end)`. `opts`: `{ enabled?, queryOptions?, demoMode? }`. |\n| `useLiveSchedule(params, opts?)`                       | `{ schedule, isLoading, isError, refetch }`. `params`: `{ charityType, campaignID, isTeam? }`; `opts`: `{ enabled?, queryOptions?, demoMode?, demoIntervalMs? }`. |\n| `useCurrentScheduleItem(schedule, opts?)`              | `{ currentItem, nextItem }` — pure client-side rollover projection with a self-scheduling boundary timer. |\n| `useLinkedTeamCampaign(params, opts?)`                 | `{ shouldFetchLinkedTeamCampaign, resolvedLinkedTeamCampaign, linkedTeamTotalAmount, linkedTeamGoalAmount, isLoadingLinkedTeamCampaign, debug }`. |\n| `useDonorSpotlight(params, opts?)`                     | `{ spotlight, isLoading, refetch }`. `params`: `{ campaignID, isTeam, live, mode }`; `opts`: `{ pollingIntervalMs?, enabled?, queryOptions? }`. |\n\n`useLiveSchedule` resolves in a strict fallback order: full\n`updateSchedule` WS push → `lastScheduleTransitions` synthesis (demo\nonly) → REST `useSchedule` → demo fixture.\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity hooks — the ones every Play Live overlay used to keep a\nlocal copy of — shipped here so they can be imported instead. Each takes a\n`mode: \"ws\" | \"http\"` that picks between:\n\n- `\"ws\"` — read the Tier-2 store selector, latch a local `hasLoadedX`\n  boolean on first arrival (or on the server's \"feature disabled\"\n  signal), and call `updateTiltifySettings({ <sliceKey> })` whenever the\n  local `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST hook\n  (`useRewards`, `useTargets`, …) and ignore the WS slice.\n\nAll of them share `useLoadingTimeout` — a 15 s safety net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                    | Params → Returns                                                              |\n| -------------------------------------------------------- | ----------------------------------------------------------------------------- |\n| `useHybridRewards({ campaignID, isTeam, mode, initialRewardsEnabled? })`   | `{ currentRewards, hasLoadedRewards, setHasLoadedRewards, isLoadingRewards, isLoading, rewardsEnabled, setRewardsEnabled }` |\n| `useHybridTargets({ campaignID, isTeam, mode })`         | `{ currentTargets, hasLoadedTargets, setHasLoadedTargets, isLoadingTargets, isLoading, targetsEnabled, setTargetsEnabled }` |\n| `useHybridMilestones({ campaignID, isTeam, mode })`      | `{ currentMilestones, isLoadingMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls({ campaignID, isTeam, mode })`           | `{ currentPolls, isLoadingPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics.                                |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns({ mode, initialFundraisingEvent, initialSupportingCampaignsEnabled? })` | `{ supportingCampaigns, isLoading, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns, supportingCampaignsEnabled, setSupportingCampaignsEnabled }` |\n| `useHybridDonationTrains({ campaignID, mode, initialDonationTrainsEnabled?, withStatus?, http?, auth? })` | `{ currentDonationTrains, hasLoadedDonationTrains, setHasLoadedDonationTrains, isLoadingDonationTrains, isLoading, donationTrainsEnabled, setDonationTrainsEnabled }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useHybridLeaderboardWithExclusions({ charityType, campaignID, isTeam?, timeType?, count?, startDate?, endDate?, enabled?, continuouslyUpdate? })` | `{ entries, isLoading }` — WS-driven invalidation over the REST leaderboard, with a `LEADERBOARD_SAFETY_POLL_MS` (60 s) backstop. |\n| `useLoadingTimeout({ mode, isLoading, onFinishedLoading, timeoutAmount? })` | `void` — the shared safety-net timer primitive.                    |\n\n`LEADERBOARD_SAFETY_POLL_MS` is exported alongside the hooks. Return\nshapes are preserved verbatim from the original overlay hooks, so\nexisting call sites can collapse to a re-export.\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Behavior is identical to the canonical exports — rename\nthe import specifier once and the rest of your code base keeps working:\n\n| Legacy name          | Canonical replacement                                            |\n| -------------------- | ---------------------------------------------------------------- |\n| `useUDPStore`        | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)          |\n| `useUDPStoreApi`     | Use a typed slice hook, or reach for the raw store API            |\n| `createUDPStore`     | `createPipelineStore` from `@playlive/realtime-pipeline/store`    |\n| `UDPStore` (type)    | `PipelineStore` from `@playlive/realtime-pipeline/store`          |\n| `UDPStoreApi` (type) | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`       |\n| `CharityTypes`       | Re-exported from `@playlive/realtime-pipeline/protocol`           |\n\n`useUDPStore` / `useUDPStoreApi` throw with a\n`\"must be called inside <UnifiedDataPipelineProvider>\"` message when no\nprovider is mounted. The subpath also re-exports every Tier 1 + 2 + 3\nsymbol under its canonical name, plus the `PipelineStore` /\n`PipelineStoreApi` types.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot, sed-friendly rewrite of every import specifier (no body edits\nneeded for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure — the aliases are kept for a\none-release window.\n\nTwo call-site changes are **not** covered by the sed:\n\n- `useIdentify(sendJsonMessage, campaignIDs, …)` — the positional args\n  are now ignored (the connection reads identify inputs off the store).\n  Drop them; the deprecated overload still compiles and warns once.\n- `useWebSocket` from `react-use-websocket-lite` — replaced by\n  `createPipelineConnection` in `@playlive/realtime-pipeline/connection`,\n  which `RealtimePipelineProvider` mounts for you.\n\nThe [Legacy aliases](#legacy-aliases-legacy) table above is the full\nper-symbol mapping.\n\n## Upstream spec\n\nNo external API surface — this package fetches nothing itself. It binds\nReact to [`@playlive/realtime-pipeline`](../realtime-pipeline/), which\nowns the WebSocket connection and frames every byte on the wire; the\nmessage catalogue and its types are exported from\n`@playlive/realtime-pipeline/protocol`. The `/fusion` hooks additionally\nread REST baselines through\n[`@playlive/react-query`](../react-query/) and\n[`@playlive/fundraiser-data`](../fundraiser-data/) — see those packages\nfor the endpoints and URL knobs involved.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nreach. **It is empty.** This package only opens the WebSocket URL the\nconsumer hands to `<RealtimePipelineProvider url={…}>` (or the store's\n`DEFAULT_WEBSOCKET_URL`, `wss://main.playlive.ws.api.experience.stjude.org`).\nDisclose that URL — plus the `KNOWN_URLS` of `@playlive/realtime-pipeline`\nand, if you import `/fusion`, `@playlive/fundraiser-data` — on your\nExtension submission.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\n## Examples\n\n### Live schedule-block total without polling\n\nThe canonical fusion use case: fetch a REST baseline once, then add the\nWebSocket donation delta on top so the number ticks up on every donation\ninstead of every 30-second poll. Requires the optional peers plus a\n`<QueryClientProvider>` above the pipeline provider.\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\nimport { RealtimePipelineProvider } from \"@playlive/react-pipeline\";\nimport {\n  useCurrentBlockRaised,\n  useCurrentScheduleItem,\n  useLiveSchedule,\n} from \"@playlive/react-pipeline/fusion\";\nimport { makeQueryClient } from \"@playlive/react-query\";\nimport { QueryClientProvider } from \"@tanstack/react-query\";\n\nconfigure(getConfigForEnv(\"prod\", { tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL }));\nconst queryClient = makeQueryClient();\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nexport function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <RealtimePipelineProvider\n        url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n        initialCampaignIDs={[CAMPAIGN_ID]}\n        initialOverlayName=\"schedule-block\"\n        initialTeamUserSlug=\"@playliver\"\n      >\n        <BlockTotal campaignID={CAMPAIGN_ID} />\n      </RealtimePipelineProvider>\n    </QueryClientProvider>\n  );\n}\n\nfunction BlockTotal({ campaignID }: { campaignID: string }) {\n  const { schedule, isLoading: scheduleLoading, isError } = useLiveSchedule({\n    charityType: \"tiltify\",\n    campaignID,\n  });\n  const { currentItem, nextItem } = useCurrentScheduleItem(schedule);\n\n  const { raised, isLoading } = useCurrentBlockRaised(\n    campaignID,\n    currentItem?.starts_at,\n    currentItem?.ends_at,\n  );\n\n  if (scheduleLoading) return <p>Loading schedule…</p>;\n  if (isError) return <p>Schedule unavailable.</p>;\n  if (!currentItem) return <p>Up next: {nextItem?.name ?? \"nothing scheduled\"}</p>;\n\n  return (\n    <section>\n      <h2>{currentItem.name}</h2>\n      <p>{isLoading ? \"…\" : `$${raised.toFixed(2)} raised this block`}</p>\n    </section>\n  );\n}\n```\n\n### Raw baseline + delta with the Tier-2 primitive\n\nIf you already have the baseline (from a route loader, say), skip the\nfusion hook and reduce the firehose yourself:\n\n```tsx\nimport { usePipelineDonationSum } from \"@playlive/react-pipeline\";\n\nfunction BlockDelta({\n  campaignID,\n  baseline,\n}: {\n  campaignID: string;\n  // `GET /schedules/campaigns/{id}/raised`\n  baseline: { raised: number; currency: string; asOf: string };\n}) {\n  const delta = usePipelineDonationSum(campaignID, {\n    start: baseline.asOf,\n    end: \"2025-09-20T23:00:00.000Z\",\n    currency: baseline.currency,\n  });\n\n  const total = baseline.raised + (delta.raised ?? 0);\n  return (\n    <p>\n      ${total.toFixed(2)} ({delta.donationCount} live donations since baseline)\n    </p>\n  );\n}\n```\n\n`usePipelineDonationSum` matches `start <= completed_at < end`, dedupes\nby donation id (last write wins, so post-edit corrections propagate),\nand excludes `test: true` donations unless you pass\n`includeTest: true` — which keeps it consistent with the REST baseline.\n\nBoth examples above are complete and runnable: point\n`<RealtimePipelineProvider url={…}>` at your pipeline WebSocket and the\nrest is copy-paste.\n\n## License\n\nMIT © St. Jude Children's Research Hospital\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.5.0.tgz","shasum":"0851431ddc3482bc3fc267ccffbf72fe728bd753","integrity":"sha512-yJwY+vSwb143aQhUVo1UVIKhCNB1l+s3R21uZ5Tiy9N8HNgeKuPodn4q8+52qbFMKtjbUgOfAaWhhkp6/sClTg=="}},"0.5.1":{"name":"@playlive/react-pipeline","version":"0.5.1","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.4.0","@playlive/tiltify-core":"^0.4.19","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.4.5","@playlive/fundraiser-data":"^0.6.0"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-W6tEPI72uaMbikLpYppd52v88ftVqq1P9Tl7HPbDubddAiaq6rbDISoWyZfahQhXofr5qBOCzTmzCkkxpFPdOA==","shasum":"3d8646b670ff4ff8c7ed21fdccba2ac053118488","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nSuccessor to `playlive-overlay-data-layer/src/websocket/*` with **zero**\ndependency on `react-use-websocket-lite` — built directly on Zustand v5's\n`useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                             | Surface                                                                       | Use when                                                               |\n| ---- | ----------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`          | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)          | Greenfield app; you want one selector primitive and nothing else.      |\n| 2    | `@playlive/react-pipeline`          | Tier 1 **plus** the typed per-slice hooks (`usePipelineDonations(id)`, …)     | You want stable, typed one-liners for the common reads.                |\n| 3    | `@playlive/react-pipeline/provider` | Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc. | Porting an existing overlay-data-layer consumer with minimal churn.    |\n| —    | `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (needs the optional TanStack peers)              | You want \"REST baseline + live WS delta\" without hand-rolling it.      |\n| —    | `@playlive/react-pipeline/legacy`   | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases   | Soft-deprecated shim for the legacy symbol names (one-release window). |\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add react react-dom zustand @playlive/realtime-pipeline @playlive/tiltify-core\n```\n\nPeer dependencies (jose-style — the consumer brings their own):\n\n| Peer                          | Range          | Required?                                 |\n| ----------------------------- | -------------- | ----------------------------------------- |\n| `react`                       | `^19.0.0`      | yes                                       |\n| `react-dom`                   | `^19.0.0`      | yes                                       |\n| `zustand`                     | `^5.0.0`       | yes                                       |\n| `@playlive/realtime-pipeline` | `workspace:*`  | yes — store, connection, protocol types    |\n| `@playlive/tiltify-core`      | `workspace:*`  | yes (type-only — stripped at compile time) |\n| `@tanstack/react-query`       | `^5.0.0`       | **optional** — only for `/fusion`          |\n| `@playlive/react-query`       | `workspace:*`  | **optional** — only for `/fusion`          |\n| `@playlive/fundraiser-data`   | `workspace:*`  | **optional** — only for `/fusion`          |\n\nThe three optional peers are declared `optional: true` in\n`peerDependenciesMeta`; you only need them if you import from\n[`@playlive/react-pipeline/fusion`](#fusion--rest-baseline--ws-delta-hooks-fusion).\n\nThis package has **no runtime `dependencies`** — nothing but your own\npeers ends up in the bundle.\n\n## Quick start\n\n```tsx\nimport {\n  RealtimePipelineProvider,\n  usePipelineCampaigns,\n  usePipelineConnectionState,\n  usePipelineDonations,\n} from \"@playlive/react-pipeline\";\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n      initialCampaignIDs={[CAMPAIGN_ID]}\n      initialOverlayName=\"donation-bar\"\n      initialTeamUserSlug=\"@playliver\"\n      initialOverlayPath=\"/overlays/donation-bar\"\n      autoConnect\n    >\n      <DonationBar campaignID={CAMPAIGN_ID} />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction DonationBar({ campaignID }: { campaignID: string }) {\n  const { connected, connecting, hasReceivedInitialData } = usePipelineConnectionState();\n  const campaigns = usePipelineCampaigns();\n  const donations = usePipelineDonations(campaignID);\n\n  if (connecting) return <p>Connecting…</p>;\n  if (!connected) return <p>Disconnected — reconnecting…</p>;\n  if (!hasReceivedInitialData) return <p>Waiting for first tick…</p>;\n\n  const campaign = campaigns.find((c) => c.id === campaignID);\n  const raised = Number(campaign?.total_amount_raised?.value ?? 0);\n  const goal = Number(campaign?.goal?.value ?? 0);\n\n  return (\n    <section>\n      <progress value={raised} max={goal || 1} />\n      <p>\n        ${raised.toFixed(2)} of ${goal.toFixed(2)}\n      </p>\n      <ul>\n        {donations.slice(0, 5).map((d) => (\n          <li key={d.id}>\n            {d.donor_name} — ${d.amount.value}\n          </li>\n        ))}\n      </ul>\n    </section>\n  );\n}\n```\n\n`RealtimePipelineProvider` builds one Zustand store per provider\ninstance (from the `initial*` props, which are read **once** at first\nconstruction) and opens a single `PipelineConnection`. `connection.destroy()`\nruns on unmount. Children subscribe with any Tier 1 / Tier 2 hook.\n\nEvery hook that reads the store throws when no provider is mounted\nabove it; the four `useOn*` observer hooks deliberately soft-fail\n(they no-op) so you can drop them into components that sometimes render\noutside a pipeline tree.\n\n## Subpath exports\n\n| Subpath                             | Description                                                                                             |\n| ----------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`          | Default barrel — Tier 1 + Tier 2 (provider, `usePipelineValue`, typed slice hooks, observers).           |\n| `@playlive/react-pipeline/provider` | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, `useAddPipeline*`, `useIdentify`, …    |\n| `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (`useCurrentBlockRaised`, `useLiveSchedule`, the `useHybrid*` family, …).    |\n| `@playlive/react-pipeline/legacy`   | Soft-deprecated alias bundle (`useUDPStore`, `useUDPStoreApi`, `createUDPStore`, `UDPStore`, …).         |\n\nEach subpath ships an ESM bundle, a `bun` source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified so stack\ntraces stay readable in production.\n\n## API reference\n\nFull generated API documentation:\n<https://packages.playlive.experience.stjude.org/p/@playlive/react-pipeline/docs/>\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                     |\n| ------------------------------- | ------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => ReactNode`          |\n| `RealtimePipelineProviderProps` | Type. `InitialProps` + `children`, `url?`, `autoConnect?` (default `true`), `webSocketCtor?`, `log?`, `store?`, `connection?`. |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                  |\n| `PACKAGE_NAME`                  | `\"@playlive/react-pipeline\"` — for runtime version-pinning.    |\n| `KNOWN_URLS`                    | `readonly string[]` — frozen, empty. See the disclosure section. |\n\n`RealtimePipelineProviderProps` extends\n[`InitialProps`](../realtime-pipeline/), so every `initial*` key\n(`initialCampaignIDs`, `initialTeamCampaignIDs`, `initialCauseIDs`,\n`initialFundraisingEventIDs`, `initialOverlayName`, `initialTeamUserSlug`,\n`initialOverlayPath`, `initialOverlayConfig`, `initialAdminApiKey`,\n`initialDonationTrainsEnabled`, …) is a valid prop. There is **no**\n`charityType` prop — set the charity type through the store's\n`setCurrentType` action or via `useCommonContextHooks` (Tier 3).\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the third\n`equalityFn` parameter on `useStore` to keep the package free of the\n`use-sync-external-store` shim peer. For shallow / custom equality, wrap\nyour selector with `useShallow` from `zustand/shallow` (zero extra dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nStable wrappers over `usePipelineValue` with a typed selector +\nmemo-stable empty-array/object fallbacks (no infinite-render churn from\nfresh `[]` allocations — Decision §3 in [CHANGELOG.md](./CHANGELOG.md)).\n\n| Hook                                          | Returns                                                     |\n| --------------------------------------------- | ----------------------------------------------------------- |\n| `usePipelineCampaigns()`                      | `TiltifyCampaign[]`                                          |\n| `usePipelineTeamCampaigns()`                  | `TiltifyTeamCampaign[]`                                      |\n| `usePipelineFundraisingEvents()`              | `TiltifyFundraisingEvent[]`                                  |\n| `usePipelineCauses()`                         | `TiltifyCause[]`                                             |\n| `usePipelineDonations()`                      | `Record<string, TiltifyDonationWithTestFlag[]>`               |\n| `usePipelineDonations(id)`                    | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if unknown) |\n| `usePipelineDonationTrains(id, withStatus?)`  | `DonationTrain[]` — `withStatus` is `\"ACTIVE\" \\| \"ENDED\" \\| \"ENDING\" \\| \"PENDING\"` |\n| `usePipelineSubathonTimer(id, withStatus?)`   | `SubathonTimer[]` — `withStatus` is `\"ACTIVE\" \\| \"INACTIVE\"`  |\n| `usePipelineDonorSpotlight(id)`               | `ComputedSpotlight \\| null`                                  |\n| `usePipelineDonorSpotlightSettings()`         | `Record<string, DonorSpotlightSettings>`                     |\n| `usePipelinePolls(id)`                        | `TiltifyPoll[]`                                              |\n| `usePipelinePollDeltas(pollID)`               | `PollOptionDelta[]` — server-computed \"+$X on option Y\" deltas |\n| `usePipelineRewards(id)`                      | `TiltifyReward[]`                                            |\n| `usePipelineTargets(id)`                      | `TiltifyTarget[]`                                            |\n| `usePipelineMilestones(id)`                   | `TiltifyMilestone[]`                                         |\n| `usePipelineLeaderboardEntries(id)`           | `TiltifyLeaderboardEntry[]`                                  |\n| `usePipelineLeaderboardExclusions(id)`        | `LeaderboardExclusion[]`                                     |\n| `usePipelineLeaderboardExclusionsMap()`       | `Record<string, LeaderboardExclusion[]>`                     |\n| `usePipelineSchedule(id)`                     | `ScheduleTransitionBlock[]` — full `updateSchedule` WS push  |\n| `usePipelineSchedules()`                      | `Record<string, ScheduleTransitionBlock[]>`                  |\n| `usePipelineLastScheduleTransition(id)`       | `ScheduleTransitionPayload \\| null` — latest block crossing  |\n| `usePipelineLastScheduleTransitions()`        | `Record<string, ScheduleTransitionPayload>`                  |\n| `usePipelineDonationSum(id, options?)`        | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` |\n| `usePipelineAuctionHouses()`                  | `AuctionHouse[]`                                             |\n| `usePipelineTwitchChat()`                     | `TwitchChatMessage[]`                                        |\n| `usePipelineConnectionState()`                | `PipelineConnectionState` (10 split selectors — no false re-renders) |\n| `usePipelineConnectionActions()`              | `PipelineConnectionActions` (stable `connect`/`disconnect`/…) |\n| `usePipelineSubscriptions()`                  | `Record<string, TiltifyWebhookSubscription>`                 |\n| `usePipelineTiltifySettings()`                | `TiltifyWebSocketSettings`                                   |\n| `usePipelineLastRefreshResponse()`            | `RefreshResponseMessage \\| null`                             |\n\nCompanion types exported alongside them: `PipelineConnectionState`\n(`connected`, `connecting`, `charityType`, `connectionType`, `wsURL`,\n`hasReceivedInitialData`, `lastIdentified`, `lastIdentityMessage`,\n`lastFirehoseMessage`, `lastMessage`), `PipelineConnectionActions`\n(`connect`, `disconnect`, `identify`, `refresh`, `reset`,\n`setAutoConnect`, `setAdminApiKey`), `PipelineDonationSumOptions`,\n`PipelineDonationSumByCurrency`, `PipelineDonationSumResult`.\n\n#### Change observers\n\nPush-style subscriptions that diff the store outside React's render\npath. Each stashes `handler` in a ref, subscribes exactly once, and\ntreats whatever is already on the store at mount as the baseline (so\nthey never fire for pre-existing data). All four **soft-fail** without\na provider.\n\n| Hook                                                | Handler payload                                        |\n| --------------------------------------------------- | ------------------------------------------------------ |\n| `useOnScheduleTransition(handler, options?)`        | `ScheduleTransitionPayload`                             |\n| `useOnScheduleUpdate(handler, options?)`            | `{ campaignID, schedule: ScheduleTransitionBlock[] }`   |\n| `useOnLeaderboardEntriesChange(handler, options?)`  | `{ campaignID, entries: TiltifyLeaderboardEntry[] }`    |\n| `useOnLeaderboardExclusionsChange(handler, options?)` | `{ campaignID, exclusions: LeaderboardExclusion[] }`  |\n\n`options` is `{ campaignIDs?: readonly string[] }` — omit it (or pass an\nempty list) to observe every campaign.\n\n```tsx\nuseOnLeaderboardEntriesChange(\n  ({ campaignID }) => {\n    queryClient.invalidateQueries({\n      queryKey: [\"leaderboard-with-exclusions\", \"tiltify\", campaignID],\n    });\n  },\n  { campaignIDs: [campaignID] },\n);\n```\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                                        | Notes                                                                                          |\n| --------------------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`                 | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` (default `true`) + `debug` (wires `console.debug` logging). |\n| `UnifiedDataPipelineProviderProps`            | Type. `Omit<RealtimePipelineProviderProps, \"children\"> & { children, reloadOnErrorReconnect?, debug? }`. |\n| `useUnifiedDataPipeline()`                    | One-call kitchen-sink hook → `UnifiedDataPipelineState`. Re-renders on **every** store change.  |\n| `UnifiedDataPipelineState`                    | Type. `PipelineStore` + `allRewards` / `allMilestones` / `allTargets` / `allPolls` / `allFundraisingEventSupportingCampaigns` / `tiltifySubscriptions` legacy aliases. |\n| `useAddPipelineCampaignIDs(params)`           | Layout-effect hook. `{ mode: \"ws\" \\| \"http\"; initialCampaign: { id }; isTeam: boolean; causeID? }` — no-op in `\"http\"` mode. |\n| `useAddPipelineCampaigns(params)`             | Layout-effect hook. `{ campaign?: TiltifyCampaign \\| null; teamCampaign?: TiltifyTeamCampaign \\| null }`. Idempotent by id. |\n| `useAddPipelineFundraisingEventIDs(params)`   | Layout-effect hook. `{ mode; initialCampaign: { id; fundraising_event_id: string \\| null } }`.   |\n| `useCommonContextHooks(params)`               | `{ url: string; mode: \"ws\" \\| \"http\"; charityType: CharityType }` — syncs type + URL and connects when `mode === \"ws\"`. |\n| `useIdentify()`                               | Returns the store-bound `identify` action. The 11-positional-arg legacy overload is kept as `@deprecated`; the args are ignored and a one-shot `console.warn` fires. |\n\n`RealtimePipelineProvider` / `RealtimePipelineProviderProps` are also\nre-exported from `/provider` so a Tier-3 consumer needs exactly one\nimport specifier.\n\n### Fusion — REST baseline + WS delta hooks (`/fusion`)\n\nThese fuse the WebSocket firehose with REST reads from\n[`@playlive/react-query`](../react-query/) /\n[`@playlive/fundraiser-data`](../fundraiser-data/). Importing this\nsubpath means you must also install the three optional peers.\n\n| Hook                                                   | Returns                                                                    |\n| ------------------------------------------------------ | -------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline `+` WS delta over `[start, end)`. `opts`: `{ enabled?, queryOptions?, demoMode? }`. |\n| `useLiveSchedule(params, opts?)`                       | `{ schedule, isLoading, isError, refetch }`. `params`: `{ charityType, campaignID, isTeam? }`; `opts`: `{ enabled?, queryOptions?, demoMode?, demoIntervalMs? }`. |\n| `useCurrentScheduleItem(schedule, opts?)`              | `{ currentItem, nextItem }` — pure client-side rollover projection with a self-scheduling boundary timer. |\n| `useLinkedTeamCampaign(params, opts?)`                 | `{ shouldFetchLinkedTeamCampaign, resolvedLinkedTeamCampaign, linkedTeamTotalAmount, linkedTeamGoalAmount, isLoadingLinkedTeamCampaign, debug }`. |\n| `useDonorSpotlight(params, opts?)`                     | `{ spotlight, isLoading, refetch }`. `params`: `{ campaignID, isTeam, live, mode }`; `opts`: `{ pollingIntervalMs?, enabled?, queryOptions? }`. |\n\n`useLiveSchedule` resolves in a strict fallback order: full\n`updateSchedule` WS push → `lastScheduleTransitions` synthesis (demo\nonly) → REST `useSchedule` → demo fixture.\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity hooks — the ones every Play Live overlay used to keep a\nlocal copy of — shipped here so they can be imported instead. Each takes a\n`mode: \"ws\" | \"http\"` that picks between:\n\n- `\"ws\"` — read the Tier-2 store selector, latch a local `hasLoadedX`\n  boolean on first arrival (or on the server's \"feature disabled\"\n  signal), and call `updateTiltifySettings({ <sliceKey> })` whenever the\n  local `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST hook\n  (`useRewards`, `useTargets`, …) and ignore the WS slice.\n\nAll of them share `useLoadingTimeout` — a 15 s safety net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                    | Params → Returns                                                              |\n| -------------------------------------------------------- | ----------------------------------------------------------------------------- |\n| `useHybridRewards({ campaignID, isTeam, mode, initialRewardsEnabled? })`   | `{ currentRewards, hasLoadedRewards, setHasLoadedRewards, isLoadingRewards, isLoading, rewardsEnabled, setRewardsEnabled }` |\n| `useHybridTargets({ campaignID, isTeam, mode })`         | `{ currentTargets, hasLoadedTargets, setHasLoadedTargets, isLoadingTargets, isLoading, targetsEnabled, setTargetsEnabled }` |\n| `useHybridMilestones({ campaignID, isTeam, mode })`      | `{ currentMilestones, isLoadingMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls({ campaignID, isTeam, mode })`           | `{ currentPolls, isLoadingPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics.                                |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns({ mode, initialFundraisingEvent, initialSupportingCampaignsEnabled? })` | `{ supportingCampaigns, isLoading, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns, supportingCampaignsEnabled, setSupportingCampaignsEnabled }` |\n| `useHybridDonationTrains({ campaignID, mode, initialDonationTrainsEnabled?, withStatus?, http?, auth? })` | `{ currentDonationTrains, hasLoadedDonationTrains, setHasLoadedDonationTrains, isLoadingDonationTrains, isLoading, donationTrainsEnabled, setDonationTrainsEnabled }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useHybridLeaderboardWithExclusions({ charityType, campaignID, isTeam?, timeType?, count?, startDate?, endDate?, enabled?, continuouslyUpdate? })` | `{ entries, isLoading }` — WS-driven invalidation over the REST leaderboard, with a `LEADERBOARD_SAFETY_POLL_MS` (60 s) backstop. |\n| `useLoadingTimeout({ mode, isLoading, onFinishedLoading, timeoutAmount? })` | `void` — the shared safety-net timer primitive.                    |\n\n`LEADERBOARD_SAFETY_POLL_MS` is exported alongside the hooks. Return\nshapes are preserved verbatim from the original overlay hooks, so\nexisting call sites can collapse to a re-export.\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Behavior is identical to the canonical exports — rename\nthe import specifier once and the rest of your code base keeps working:\n\n| Legacy name          | Canonical replacement                                            |\n| -------------------- | ---------------------------------------------------------------- |\n| `useUDPStore`        | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)          |\n| `useUDPStoreApi`     | Use a typed slice hook, or reach for the raw store API            |\n| `createUDPStore`     | `createPipelineStore` from `@playlive/realtime-pipeline/store`    |\n| `UDPStore` (type)    | `PipelineStore` from `@playlive/realtime-pipeline/store`          |\n| `UDPStoreApi` (type) | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`       |\n| `CharityTypes`       | Re-exported from `@playlive/realtime-pipeline/protocol`           |\n\n`useUDPStore` / `useUDPStoreApi` throw with a\n`\"must be called inside <UnifiedDataPipelineProvider>\"` message when no\nprovider is mounted. The subpath also re-exports every Tier 1 + 2 + 3\nsymbol under its canonical name, plus the `PipelineStore` /\n`PipelineStoreApi` types.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot, sed-friendly rewrite of every import specifier (no body edits\nneeded for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure — the aliases are kept for a\none-release window.\n\nTwo call-site changes are **not** covered by the sed:\n\n- `useIdentify(sendJsonMessage, campaignIDs, …)` — the positional args\n  are now ignored (the connection reads identify inputs off the store).\n  Drop them; the deprecated overload still compiles and warns once.\n- `useWebSocket` from `react-use-websocket-lite` — replaced by\n  `createPipelineConnection` in `@playlive/realtime-pipeline/connection`,\n  which `RealtimePipelineProvider` mounts for you.\n\nThe [Legacy aliases](#legacy-aliases-legacy) table above is the full\nper-symbol mapping.\n\n## Upstream spec\n\nNo external API surface — this package fetches nothing itself. It binds\nReact to [`@playlive/realtime-pipeline`](../realtime-pipeline/), which\nowns the WebSocket connection and frames every byte on the wire; the\nmessage catalogue and its types are exported from\n`@playlive/realtime-pipeline/protocol`. The `/fusion` hooks additionally\nread REST baselines through\n[`@playlive/react-query`](../react-query/) and\n[`@playlive/fundraiser-data`](../fundraiser-data/) — see those packages\nfor the endpoints and URL knobs involved.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nreach. **It is empty.** This package only opens the WebSocket URL the\nconsumer hands to `<RealtimePipelineProvider url={…}>` (or the store's\n`DEFAULT_WEBSOCKET_URL`, `wss://main.playlive.ws.api.experience.stjude.org`).\nDisclose that URL — plus the `KNOWN_URLS` of `@playlive/realtime-pipeline`\nand, if you import `/fusion`, `@playlive/fundraiser-data` — on your\nExtension submission.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\n## Examples\n\n### Live schedule-block total without polling\n\nThe canonical fusion use case: fetch a REST baseline once, then add the\nWebSocket donation delta on top so the number ticks up on every donation\ninstead of every 30-second poll. Requires the optional peers plus a\n`<QueryClientProvider>` above the pipeline provider.\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\nimport { RealtimePipelineProvider } from \"@playlive/react-pipeline\";\nimport {\n  useCurrentBlockRaised,\n  useCurrentScheduleItem,\n  useLiveSchedule,\n} from \"@playlive/react-pipeline/fusion\";\nimport { makeQueryClient } from \"@playlive/react-query\";\nimport { QueryClientProvider } from \"@tanstack/react-query\";\n\nconfigure(getConfigForEnv(\"prod\", { tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL }));\nconst queryClient = makeQueryClient();\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nexport function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <RealtimePipelineProvider\n        url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n        initialCampaignIDs={[CAMPAIGN_ID]}\n        initialOverlayName=\"schedule-block\"\n        initialTeamUserSlug=\"@playliver\"\n      >\n        <BlockTotal campaignID={CAMPAIGN_ID} />\n      </RealtimePipelineProvider>\n    </QueryClientProvider>\n  );\n}\n\nfunction BlockTotal({ campaignID }: { campaignID: string }) {\n  const { schedule, isLoading: scheduleLoading, isError } = useLiveSchedule({\n    charityType: \"tiltify\",\n    campaignID,\n  });\n  const { currentItem, nextItem } = useCurrentScheduleItem(schedule);\n\n  const { raised, isLoading } = useCurrentBlockRaised(\n    campaignID,\n    currentItem?.starts_at,\n    currentItem?.ends_at,\n  );\n\n  if (scheduleLoading) return <p>Loading schedule…</p>;\n  if (isError) return <p>Schedule unavailable.</p>;\n  if (!currentItem) return <p>Up next: {nextItem?.name ?? \"nothing scheduled\"}</p>;\n\n  return (\n    <section>\n      <h2>{currentItem.name}</h2>\n      <p>{isLoading ? \"…\" : `$${raised.toFixed(2)} raised this block`}</p>\n    </section>\n  );\n}\n```\n\n### Raw baseline + delta with the Tier-2 primitive\n\nIf you already have the baseline (from a route loader, say), skip the\nfusion hook and reduce the firehose yourself:\n\n```tsx\nimport { usePipelineDonationSum } from \"@playlive/react-pipeline\";\n\nfunction BlockDelta({\n  campaignID,\n  baseline,\n}: {\n  campaignID: string;\n  // `GET /schedules/campaigns/{id}/raised`\n  baseline: { raised: number; currency: string; asOf: string };\n}) {\n  const delta = usePipelineDonationSum(campaignID, {\n    start: baseline.asOf,\n    end: \"2025-09-20T23:00:00.000Z\",\n    currency: baseline.currency,\n  });\n\n  const total = baseline.raised + (delta.raised ?? 0);\n  return (\n    <p>\n      ${total.toFixed(2)} ({delta.donationCount} live donations since baseline)\n    </p>\n  );\n}\n```\n\n`usePipelineDonationSum` matches `start <= completed_at < end`, dedupes\nby donation id (last write wins, so post-edit corrections propagate),\nand excludes `test: true` donations unless you pass\n`includeTest: true` — which keeps it consistent with the REST baseline.\n\nBoth examples above are complete and runnable: point\n`<RealtimePipelineProvider url={…}>` at your pipeline WebSocket and the\nrest is copy-paste.\n\n## License\n\nMIT © St. Jude Children's Research Hospital\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.5.1.tgz","shasum":"3d8646b670ff4ff8c7ed21fdccba2ac053118488","integrity":"sha512-W6tEPI72uaMbikLpYppd52v88ftVqq1P9Tl7HPbDubddAiaq6rbDISoWyZfahQhXofr5qBOCzTmzCkkxpFPdOA=="}},"0.5.2":{"name":"@playlive/react-pipeline","version":"0.5.2","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.4.0","@playlive/tiltify-core":"^0.4.19","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.4.6","@playlive/fundraiser-data":"^0.6.1"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-8Dq2FKVdadm51g2HJytRY3SoV5mUddHjqKQLd0rq2tPRDV4X8EPEqzbFeMgR9Jo622G1l6uu2Aq28v0jk1SdGw==","shasum":"55659d138f7bf979c20b742147c03a14844b75c3","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nSuccessor to `playlive-overlay-data-layer/src/websocket/*` with **zero**\ndependency on `react-use-websocket-lite` — built directly on Zustand v5's\n`useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                             | Surface                                                                       | Use when                                                               |\n| ---- | ----------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`          | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)          | Greenfield app; you want one selector primitive and nothing else.      |\n| 2    | `@playlive/react-pipeline`          | Tier 1 **plus** the typed per-slice hooks (`usePipelineDonations(id)`, …)     | You want stable, typed one-liners for the common reads.                |\n| 3    | `@playlive/react-pipeline/provider` | Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc. | Porting an existing overlay-data-layer consumer with minimal churn.    |\n| —    | `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (needs the optional TanStack peers)              | You want \"REST baseline + live WS delta\" without hand-rolling it.      |\n| —    | `@playlive/react-pipeline/legacy`   | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases   | Soft-deprecated shim for the legacy symbol names (one-release window). |\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add react react-dom zustand @playlive/realtime-pipeline @playlive/tiltify-core\n```\n\nPeer dependencies (jose-style — the consumer brings their own):\n\n| Peer                          | Range          | Required?                                 |\n| ----------------------------- | -------------- | ----------------------------------------- |\n| `react`                       | `^19.0.0`      | yes                                       |\n| `react-dom`                   | `^19.0.0`      | yes                                       |\n| `zustand`                     | `^5.0.0`       | yes                                       |\n| `@playlive/realtime-pipeline` | `workspace:*`  | yes — store, connection, protocol types    |\n| `@playlive/tiltify-core`      | `workspace:*`  | yes (type-only — stripped at compile time) |\n| `@tanstack/react-query`       | `^5.0.0`       | **optional** — only for `/fusion`          |\n| `@playlive/react-query`       | `workspace:*`  | **optional** — only for `/fusion`          |\n| `@playlive/fundraiser-data`   | `workspace:*`  | **optional** — only for `/fusion`          |\n\nThe three optional peers are declared `optional: true` in\n`peerDependenciesMeta`; you only need them if you import from\n[`@playlive/react-pipeline/fusion`](#fusion--rest-baseline--ws-delta-hooks-fusion).\n\nThis package has **no runtime `dependencies`** — nothing but your own\npeers ends up in the bundle.\n\n## Quick start\n\n```tsx\nimport {\n  RealtimePipelineProvider,\n  usePipelineCampaigns,\n  usePipelineConnectionState,\n  usePipelineDonations,\n} from \"@playlive/react-pipeline\";\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n      initialCampaignIDs={[CAMPAIGN_ID]}\n      initialOverlayName=\"donation-bar\"\n      initialTeamUserSlug=\"@playliver\"\n      initialOverlayPath=\"/overlays/donation-bar\"\n      autoConnect\n    >\n      <DonationBar campaignID={CAMPAIGN_ID} />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction DonationBar({ campaignID }: { campaignID: string }) {\n  const { connected, connecting, hasReceivedInitialData } = usePipelineConnectionState();\n  const campaigns = usePipelineCampaigns();\n  const donations = usePipelineDonations(campaignID);\n\n  if (connecting) return <p>Connecting…</p>;\n  if (!connected) return <p>Disconnected — reconnecting…</p>;\n  if (!hasReceivedInitialData) return <p>Waiting for first tick…</p>;\n\n  const campaign = campaigns.find((c) => c.id === campaignID);\n  const raised = Number(campaign?.total_amount_raised?.value ?? 0);\n  const goal = Number(campaign?.goal?.value ?? 0);\n\n  return (\n    <section>\n      <progress value={raised} max={goal || 1} />\n      <p>\n        ${raised.toFixed(2)} of ${goal.toFixed(2)}\n      </p>\n      <ul>\n        {donations.slice(0, 5).map((d) => (\n          <li key={d.id}>\n            {d.donor_name} — ${d.amount.value}\n          </li>\n        ))}\n      </ul>\n    </section>\n  );\n}\n```\n\n`RealtimePipelineProvider` builds one Zustand store per provider\ninstance (from the `initial*` props, which are read **once** at first\nconstruction) and opens a single `PipelineConnection`. `connection.destroy()`\nruns on unmount. Children subscribe with any Tier 1 / Tier 2 hook.\n\nEvery hook that reads the store throws when no provider is mounted\nabove it; the four `useOn*` observer hooks deliberately soft-fail\n(they no-op) so you can drop them into components that sometimes render\noutside a pipeline tree.\n\n## Subpath exports\n\n| Subpath                             | Description                                                                                             |\n| ----------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`          | Default barrel — Tier 1 + Tier 2 (provider, `usePipelineValue`, typed slice hooks, observers).           |\n| `@playlive/react-pipeline/provider` | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, `useAddPipeline*`, `useIdentify`, …    |\n| `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (`useCurrentBlockRaised`, `useLiveSchedule`, the `useHybrid*` family, …).    |\n| `@playlive/react-pipeline/legacy`   | Soft-deprecated alias bundle (`useUDPStore`, `useUDPStoreApi`, `createUDPStore`, `UDPStore`, …).         |\n\nEach subpath ships an ESM bundle, a `bun` source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified so stack\ntraces stay readable in production.\n\n## API reference\n\nFull generated API documentation:\n<https://packages.playlive.experience.stjude.org/p/@playlive/react-pipeline/docs/>\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                     |\n| ------------------------------- | ------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => ReactNode`          |\n| `RealtimePipelineProviderProps` | Type. `InitialProps` + `children`, `url?`, `autoConnect?` (default `true`), `webSocketCtor?`, `log?`, `store?`, `connection?`. |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                  |\n| `PACKAGE_NAME`                  | `\"@playlive/react-pipeline\"` — for runtime version-pinning.    |\n| `KNOWN_URLS`                    | `readonly string[]` — frozen, empty. See the disclosure section. |\n\n`RealtimePipelineProviderProps` extends\n[`InitialProps`](../realtime-pipeline/), so every `initial*` key\n(`initialCampaignIDs`, `initialTeamCampaignIDs`, `initialCauseIDs`,\n`initialFundraisingEventIDs`, `initialOverlayName`, `initialTeamUserSlug`,\n`initialOverlayPath`, `initialOverlayConfig`, `initialAdminApiKey`,\n`initialDonationTrainsEnabled`, …) is a valid prop. There is **no**\n`charityType` prop — set the charity type through the store's\n`setCurrentType` action or via `useCommonContextHooks` (Tier 3).\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the third\n`equalityFn` parameter on `useStore` to keep the package free of the\n`use-sync-external-store` shim peer. For shallow / custom equality, wrap\nyour selector with `useShallow` from `zustand/shallow` (zero extra dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nStable wrappers over `usePipelineValue` with a typed selector +\nmemo-stable empty-array/object fallbacks (no infinite-render churn from\nfresh `[]` allocations — Decision §3 in [CHANGELOG.md](./CHANGELOG.md)).\n\n| Hook                                          | Returns                                                     |\n| --------------------------------------------- | ----------------------------------------------------------- |\n| `usePipelineCampaigns()`                      | `TiltifyCampaign[]`                                          |\n| `usePipelineTeamCampaigns()`                  | `TiltifyTeamCampaign[]`                                      |\n| `usePipelineFundraisingEvents()`              | `TiltifyFundraisingEvent[]`                                  |\n| `usePipelineCauses()`                         | `TiltifyCause[]`                                             |\n| `usePipelineDonations()`                      | `Record<string, TiltifyDonationWithTestFlag[]>`               |\n| `usePipelineDonations(id)`                    | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if unknown) |\n| `usePipelineDonationTrains(id, withStatus?)`  | `DonationTrain[]` — `withStatus` is `\"ACTIVE\" \\| \"ENDED\" \\| \"ENDING\" \\| \"PENDING\"` |\n| `usePipelineSubathonTimer(id, withStatus?)`   | `SubathonTimer[]` — `withStatus` is `\"ACTIVE\" \\| \"INACTIVE\"`  |\n| `usePipelineDonorSpotlight(id)`               | `ComputedSpotlight \\| null`                                  |\n| `usePipelineDonorSpotlightSettings()`         | `Record<string, DonorSpotlightSettings>`                     |\n| `usePipelinePolls(id)`                        | `TiltifyPoll[]`                                              |\n| `usePipelinePollDeltas(pollID)`               | `PollOptionDelta[]` — server-computed \"+$X on option Y\" deltas |\n| `usePipelineRewards(id)`                      | `TiltifyReward[]`                                            |\n| `usePipelineTargets(id)`                      | `TiltifyTarget[]`                                            |\n| `usePipelineMilestones(id)`                   | `TiltifyMilestone[]`                                         |\n| `usePipelineLeaderboardEntries(id)`           | `TiltifyLeaderboardEntry[]`                                  |\n| `usePipelineLeaderboardExclusions(id)`        | `LeaderboardExclusion[]`                                     |\n| `usePipelineLeaderboardExclusionsMap()`       | `Record<string, LeaderboardExclusion[]>`                     |\n| `usePipelineSchedule(id)`                     | `ScheduleTransitionBlock[]` — full `updateSchedule` WS push  |\n| `usePipelineSchedules()`                      | `Record<string, ScheduleTransitionBlock[]>`                  |\n| `usePipelineLastScheduleTransition(id)`       | `ScheduleTransitionPayload \\| null` — latest block crossing  |\n| `usePipelineLastScheduleTransitions()`        | `Record<string, ScheduleTransitionPayload>`                  |\n| `usePipelineDonationSum(id, options?)`        | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` |\n| `usePipelineAuctionHouses()`                  | `AuctionHouse[]`                                             |\n| `usePipelineTwitchChat()`                     | `TwitchChatMessage[]`                                        |\n| `usePipelineConnectionState()`                | `PipelineConnectionState` (10 split selectors — no false re-renders) |\n| `usePipelineConnectionActions()`              | `PipelineConnectionActions` (stable `connect`/`disconnect`/…) |\n| `usePipelineSubscriptions()`                  | `Record<string, TiltifyWebhookSubscription>`                 |\n| `usePipelineTiltifySettings()`                | `TiltifyWebSocketSettings`                                   |\n| `usePipelineLastRefreshResponse()`            | `RefreshResponseMessage \\| null`                             |\n\nCompanion types exported alongside them: `PipelineConnectionState`\n(`connected`, `connecting`, `charityType`, `connectionType`, `wsURL`,\n`hasReceivedInitialData`, `lastIdentified`, `lastIdentityMessage`,\n`lastFirehoseMessage`, `lastMessage`), `PipelineConnectionActions`\n(`connect`, `disconnect`, `identify`, `refresh`, `reset`,\n`setAutoConnect`, `setAdminApiKey`), `PipelineDonationSumOptions`,\n`PipelineDonationSumByCurrency`, `PipelineDonationSumResult`.\n\n#### Change observers\n\nPush-style subscriptions that diff the store outside React's render\npath. Each stashes `handler` in a ref, subscribes exactly once, and\ntreats whatever is already on the store at mount as the baseline (so\nthey never fire for pre-existing data). All four **soft-fail** without\na provider.\n\n| Hook                                                | Handler payload                                        |\n| --------------------------------------------------- | ------------------------------------------------------ |\n| `useOnScheduleTransition(handler, options?)`        | `ScheduleTransitionPayload`                             |\n| `useOnScheduleUpdate(handler, options?)`            | `{ campaignID, schedule: ScheduleTransitionBlock[] }`   |\n| `useOnLeaderboardEntriesChange(handler, options?)`  | `{ campaignID, entries: TiltifyLeaderboardEntry[] }`    |\n| `useOnLeaderboardExclusionsChange(handler, options?)` | `{ campaignID, exclusions: LeaderboardExclusion[] }`  |\n\n`options` is `{ campaignIDs?: readonly string[] }` — omit it (or pass an\nempty list) to observe every campaign.\n\n```tsx\nuseOnLeaderboardEntriesChange(\n  ({ campaignID }) => {\n    queryClient.invalidateQueries({\n      queryKey: [\"leaderboard-with-exclusions\", \"tiltify\", campaignID],\n    });\n  },\n  { campaignIDs: [campaignID] },\n);\n```\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                                        | Notes                                                                                          |\n| --------------------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`                 | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` (default `true`) + `debug` (wires `console.debug` logging). |\n| `UnifiedDataPipelineProviderProps`            | Type. `Omit<RealtimePipelineProviderProps, \"children\"> & { children, reloadOnErrorReconnect?, debug? }`. |\n| `useUnifiedDataPipeline()`                    | One-call kitchen-sink hook → `UnifiedDataPipelineState`. Re-renders on **every** store change.  |\n| `UnifiedDataPipelineState`                    | Type. `PipelineStore` + `allRewards` / `allMilestones` / `allTargets` / `allPolls` / `allFundraisingEventSupportingCampaigns` / `tiltifySubscriptions` legacy aliases. |\n| `useAddPipelineCampaignIDs(params)`           | Layout-effect hook. `{ mode: \"ws\" \\| \"http\"; initialCampaign: { id }; isTeam: boolean; causeID? }` — no-op in `\"http\"` mode. |\n| `useAddPipelineCampaigns(params)`             | Layout-effect hook. `{ campaign?: TiltifyCampaign \\| null; teamCampaign?: TiltifyTeamCampaign \\| null }`. Idempotent by id. |\n| `useAddPipelineFundraisingEventIDs(params)`   | Layout-effect hook. `{ mode; initialCampaign: { id; fundraising_event_id: string \\| null } }`.   |\n| `useCommonContextHooks(params)`               | `{ url: string; mode: \"ws\" \\| \"http\"; charityType: CharityType }` — syncs type + URL and connects when `mode === \"ws\"`. |\n| `useIdentify()`                               | Returns the store-bound `identify` action. The 11-positional-arg legacy overload is kept as `@deprecated`; the args are ignored and a one-shot `console.warn` fires. |\n\n`RealtimePipelineProvider` / `RealtimePipelineProviderProps` are also\nre-exported from `/provider` so a Tier-3 consumer needs exactly one\nimport specifier.\n\n### Fusion — REST baseline + WS delta hooks (`/fusion`)\n\nThese fuse the WebSocket firehose with REST reads from\n[`@playlive/react-query`](../react-query/) /\n[`@playlive/fundraiser-data`](../fundraiser-data/). Importing this\nsubpath means you must also install the three optional peers.\n\n| Hook                                                   | Returns                                                                    |\n| ------------------------------------------------------ | -------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline `+` WS delta over `[start, end)`. `opts`: `{ enabled?, queryOptions?, demoMode? }`. |\n| `useLiveSchedule(params, opts?)`                       | `{ schedule, isLoading, isError, refetch }`. `params`: `{ charityType, campaignID, isTeam? }`; `opts`: `{ enabled?, queryOptions?, demoMode?, demoIntervalMs? }`. |\n| `useCurrentScheduleItem(schedule, opts?)`              | `{ currentItem, nextItem }` — pure client-side rollover projection with a self-scheduling boundary timer. |\n| `useLinkedTeamCampaign(params, opts?)`                 | `{ shouldFetchLinkedTeamCampaign, resolvedLinkedTeamCampaign, linkedTeamTotalAmount, linkedTeamGoalAmount, isLoadingLinkedTeamCampaign, debug }`. |\n| `useDonorSpotlight(params, opts?)`                     | `{ spotlight, isLoading, refetch }`. `params`: `{ campaignID, isTeam, live, mode }`; `opts`: `{ pollingIntervalMs?, enabled?, queryOptions? }`. |\n\n`useLiveSchedule` resolves in a strict fallback order: full\n`updateSchedule` WS push → `lastScheduleTransitions` synthesis (demo\nonly) → REST `useSchedule` → demo fixture.\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity hooks — the ones every Play Live overlay used to keep a\nlocal copy of — shipped here so they can be imported instead. Each takes a\n`mode: \"ws\" | \"http\"` that picks between:\n\n- `\"ws\"` — read the Tier-2 store selector, latch a local `hasLoadedX`\n  boolean on first arrival (or on the server's \"feature disabled\"\n  signal), and call `updateTiltifySettings({ <sliceKey> })` whenever the\n  local `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST hook\n  (`useRewards`, `useTargets`, …) and ignore the WS slice.\n\nAll of them share `useLoadingTimeout` — a 15 s safety net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                    | Params → Returns                                                              |\n| -------------------------------------------------------- | ----------------------------------------------------------------------------- |\n| `useHybridRewards({ campaignID, isTeam, mode, initialRewardsEnabled? })`   | `{ currentRewards, hasLoadedRewards, setHasLoadedRewards, isLoadingRewards, isLoading, rewardsEnabled, setRewardsEnabled }` |\n| `useHybridTargets({ campaignID, isTeam, mode })`         | `{ currentTargets, hasLoadedTargets, setHasLoadedTargets, isLoadingTargets, isLoading, targetsEnabled, setTargetsEnabled }` |\n| `useHybridMilestones({ campaignID, isTeam, mode })`      | `{ currentMilestones, isLoadingMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls({ campaignID, isTeam, mode })`           | `{ currentPolls, isLoadingPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics.                                |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns({ mode, initialFundraisingEvent, initialSupportingCampaignsEnabled? })` | `{ supportingCampaigns, isLoading, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns, supportingCampaignsEnabled, setSupportingCampaignsEnabled }` |\n| `useHybridDonationTrains({ campaignID, mode, initialDonationTrainsEnabled?, withStatus?, http?, auth? })` | `{ currentDonationTrains, hasLoadedDonationTrains, setHasLoadedDonationTrains, isLoadingDonationTrains, isLoading, donationTrainsEnabled, setDonationTrainsEnabled }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useHybridLeaderboardWithExclusions({ charityType, campaignID, isTeam?, timeType?, count?, startDate?, endDate?, enabled?, continuouslyUpdate? })` | `{ entries, isLoading }` — WS-driven invalidation over the REST leaderboard, with a `LEADERBOARD_SAFETY_POLL_MS` (60 s) backstop. |\n| `useLoadingTimeout({ mode, isLoading, onFinishedLoading, timeoutAmount? })` | `void` — the shared safety-net timer primitive.                    |\n\n`LEADERBOARD_SAFETY_POLL_MS` is exported alongside the hooks. Return\nshapes are preserved verbatim from the original overlay hooks, so\nexisting call sites can collapse to a re-export.\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Behavior is identical to the canonical exports — rename\nthe import specifier once and the rest of your code base keeps working:\n\n| Legacy name          | Canonical replacement                                            |\n| -------------------- | ---------------------------------------------------------------- |\n| `useUDPStore`        | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)          |\n| `useUDPStoreApi`     | Use a typed slice hook, or reach for the raw store API            |\n| `createUDPStore`     | `createPipelineStore` from `@playlive/realtime-pipeline/store`    |\n| `UDPStore` (type)    | `PipelineStore` from `@playlive/realtime-pipeline/store`          |\n| `UDPStoreApi` (type) | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`       |\n| `CharityTypes`       | Re-exported from `@playlive/realtime-pipeline/protocol`           |\n\n`useUDPStore` / `useUDPStoreApi` throw with a\n`\"must be called inside <UnifiedDataPipelineProvider>\"` message when no\nprovider is mounted. The subpath also re-exports every Tier 1 + 2 + 3\nsymbol under its canonical name, plus the `PipelineStore` /\n`PipelineStoreApi` types.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot, sed-friendly rewrite of every import specifier (no body edits\nneeded for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure — the aliases are kept for a\none-release window.\n\nTwo call-site changes are **not** covered by the sed:\n\n- `useIdentify(sendJsonMessage, campaignIDs, …)` — the positional args\n  are now ignored (the connection reads identify inputs off the store).\n  Drop them; the deprecated overload still compiles and warns once.\n- `useWebSocket` from `react-use-websocket-lite` — replaced by\n  `createPipelineConnection` in `@playlive/realtime-pipeline/connection`,\n  which `RealtimePipelineProvider` mounts for you.\n\nThe [Legacy aliases](#legacy-aliases-legacy) table above is the full\nper-symbol mapping.\n\n## Upstream spec\n\nNo external API surface — this package fetches nothing itself. It binds\nReact to [`@playlive/realtime-pipeline`](../realtime-pipeline/), which\nowns the WebSocket connection and frames every byte on the wire; the\nmessage catalogue and its types are exported from\n`@playlive/realtime-pipeline/protocol`. The `/fusion` hooks additionally\nread REST baselines through\n[`@playlive/react-query`](../react-query/) and\n[`@playlive/fundraiser-data`](../fundraiser-data/) — see those packages\nfor the endpoints and URL knobs involved.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nreach. **It is empty.** This package only opens the WebSocket URL the\nconsumer hands to `<RealtimePipelineProvider url={…}>` (or the store's\n`DEFAULT_WEBSOCKET_URL`, `wss://main.playlive.ws.api.experience.stjude.org`).\nDisclose that URL — plus the `KNOWN_URLS` of `@playlive/realtime-pipeline`\nand, if you import `/fusion`, `@playlive/fundraiser-data` — on your\nExtension submission.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\n## Examples\n\n### Live schedule-block total without polling\n\nThe canonical fusion use case: fetch a REST baseline once, then add the\nWebSocket donation delta on top so the number ticks up on every donation\ninstead of every 30-second poll. Requires the optional peers plus a\n`<QueryClientProvider>` above the pipeline provider.\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\nimport { RealtimePipelineProvider } from \"@playlive/react-pipeline\";\nimport {\n  useCurrentBlockRaised,\n  useCurrentScheduleItem,\n  useLiveSchedule,\n} from \"@playlive/react-pipeline/fusion\";\nimport { makeQueryClient } from \"@playlive/react-query\";\nimport { QueryClientProvider } from \"@tanstack/react-query\";\n\nconfigure(getConfigForEnv(\"prod\", { tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL }));\nconst queryClient = makeQueryClient();\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nexport function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <RealtimePipelineProvider\n        url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n        initialCampaignIDs={[CAMPAIGN_ID]}\n        initialOverlayName=\"schedule-block\"\n        initialTeamUserSlug=\"@playliver\"\n      >\n        <BlockTotal campaignID={CAMPAIGN_ID} />\n      </RealtimePipelineProvider>\n    </QueryClientProvider>\n  );\n}\n\nfunction BlockTotal({ campaignID }: { campaignID: string }) {\n  const { schedule, isLoading: scheduleLoading, isError } = useLiveSchedule({\n    charityType: \"tiltify\",\n    campaignID,\n  });\n  const { currentItem, nextItem } = useCurrentScheduleItem(schedule);\n\n  const { raised, isLoading } = useCurrentBlockRaised(\n    campaignID,\n    currentItem?.starts_at,\n    currentItem?.ends_at,\n  );\n\n  if (scheduleLoading) return <p>Loading schedule…</p>;\n  if (isError) return <p>Schedule unavailable.</p>;\n  if (!currentItem) return <p>Up next: {nextItem?.name ?? \"nothing scheduled\"}</p>;\n\n  return (\n    <section>\n      <h2>{currentItem.name}</h2>\n      <p>{isLoading ? \"…\" : `$${raised.toFixed(2)} raised this block`}</p>\n    </section>\n  );\n}\n```\n\n### Raw baseline + delta with the Tier-2 primitive\n\nIf you already have the baseline (from a route loader, say), skip the\nfusion hook and reduce the firehose yourself:\n\n```tsx\nimport { usePipelineDonationSum } from \"@playlive/react-pipeline\";\n\nfunction BlockDelta({\n  campaignID,\n  baseline,\n}: {\n  campaignID: string;\n  // `GET /schedules/campaigns/{id}/raised`\n  baseline: { raised: number; currency: string; asOf: string };\n}) {\n  const delta = usePipelineDonationSum(campaignID, {\n    start: baseline.asOf,\n    end: \"2025-09-20T23:00:00.000Z\",\n    currency: baseline.currency,\n  });\n\n  const total = baseline.raised + (delta.raised ?? 0);\n  return (\n    <p>\n      ${total.toFixed(2)} ({delta.donationCount} live donations since baseline)\n    </p>\n  );\n}\n```\n\n`usePipelineDonationSum` matches `start <= completed_at < end`, dedupes\nby donation id (last write wins, so post-edit corrections propagate),\nand excludes `test: true` donations unless you pass\n`includeTest: true` — which keeps it consistent with the REST baseline.\n\nBoth examples above are complete and runnable: point\n`<RealtimePipelineProvider url={…}>` at your pipeline WebSocket and the\nrest is copy-paste.\n\n## License\n\nMIT © St. Jude Children's Research Hospital\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.5.2.tgz","shasum":"55659d138f7bf979c20b742147c03a14844b75c3","integrity":"sha512-8Dq2FKVdadm51g2HJytRY3SoV5mUddHjqKQLd0rq2tPRDV4X8EPEqzbFeMgR9Jo622G1l6uu2Aq28v0jk1SdGw=="}},"0.5.3":{"name":"@playlive/react-pipeline","version":"0.5.3","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.4.0","@playlive/tiltify-core":"^0.4.19","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.4.6","@playlive/fundraiser-data":"^0.6.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-an2xJ1SNrSWalow7E+tQWPRUI02h79TP67TkBS6yNjAqozZK48uvNX9nPWkLYKLQ8ah8a/0aGzdJu/NUzatQvw==","shasum":"959f17208377ff8a9baf00644bd5700b7a2d9d03","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nSuccessor to `playlive-overlay-data-layer/src/websocket/*` with **zero**\ndependency on `react-use-websocket-lite` — built directly on Zustand v5's\n`useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                             | Surface                                                                       | Use when                                                               |\n| ---- | ----------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`          | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)          | Greenfield app; you want one selector primitive and nothing else.      |\n| 2    | `@playlive/react-pipeline`          | Tier 1 **plus** the typed per-slice hooks (`usePipelineDonations(id)`, …)     | You want stable, typed one-liners for the common reads.                |\n| 3    | `@playlive/react-pipeline/provider` | Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc. | Porting an existing overlay-data-layer consumer with minimal churn.    |\n| —    | `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (needs the optional TanStack peers)              | You want \"REST baseline + live WS delta\" without hand-rolling it.      |\n| —    | `@playlive/react-pipeline/legacy`   | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases   | Soft-deprecated shim for the legacy symbol names (one-release window). |\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add react react-dom zustand @playlive/realtime-pipeline @playlive/tiltify-core\n```\n\nPeer dependencies (jose-style — the consumer brings their own):\n\n| Peer                          | Range          | Required?                                 |\n| ----------------------------- | -------------- | ----------------------------------------- |\n| `react`                       | `^19.0.0`      | yes                                       |\n| `react-dom`                   | `^19.0.0`      | yes                                       |\n| `zustand`                     | `^5.0.0`       | yes                                       |\n| `@playlive/realtime-pipeline` | `workspace:*`  | yes — store, connection, protocol types    |\n| `@playlive/tiltify-core`      | `workspace:*`  | yes (type-only — stripped at compile time) |\n| `@tanstack/react-query`       | `^5.0.0`       | **optional** — only for `/fusion`          |\n| `@playlive/react-query`       | `workspace:*`  | **optional** — only for `/fusion`          |\n| `@playlive/fundraiser-data`   | `workspace:*`  | **optional** — only for `/fusion`          |\n\nThe three optional peers are declared `optional: true` in\n`peerDependenciesMeta`; you only need them if you import from\n[`@playlive/react-pipeline/fusion`](#fusion--rest-baseline--ws-delta-hooks-fusion).\n\nThis package has **no runtime `dependencies`** — nothing but your own\npeers ends up in the bundle.\n\n## Quick start\n\n```tsx\nimport {\n  RealtimePipelineProvider,\n  usePipelineCampaigns,\n  usePipelineConnectionState,\n  usePipelineDonations,\n} from \"@playlive/react-pipeline\";\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n      initialCampaignIDs={[CAMPAIGN_ID]}\n      initialOverlayName=\"donation-bar\"\n      initialTeamUserSlug=\"@playliver\"\n      initialOverlayPath=\"/overlays/donation-bar\"\n      autoConnect\n    >\n      <DonationBar campaignID={CAMPAIGN_ID} />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction DonationBar({ campaignID }: { campaignID: string }) {\n  const { connected, connecting, hasReceivedInitialData } = usePipelineConnectionState();\n  const campaigns = usePipelineCampaigns();\n  const donations = usePipelineDonations(campaignID);\n\n  if (connecting) return <p>Connecting…</p>;\n  if (!connected) return <p>Disconnected — reconnecting…</p>;\n  if (!hasReceivedInitialData) return <p>Waiting for first tick…</p>;\n\n  const campaign = campaigns.find((c) => c.id === campaignID);\n  const raised = Number(campaign?.total_amount_raised?.value ?? 0);\n  const goal = Number(campaign?.goal?.value ?? 0);\n\n  return (\n    <section>\n      <progress value={raised} max={goal || 1} />\n      <p>\n        ${raised.toFixed(2)} of ${goal.toFixed(2)}\n      </p>\n      <ul>\n        {donations.slice(0, 5).map((d) => (\n          <li key={d.id}>\n            {d.donor_name} — ${d.amount.value}\n          </li>\n        ))}\n      </ul>\n    </section>\n  );\n}\n```\n\n`RealtimePipelineProvider` builds one Zustand store per provider\ninstance (from the `initial*` props, which are read **once** at first\nconstruction) and opens a single `PipelineConnection`. `connection.destroy()`\nruns on unmount. Children subscribe with any Tier 1 / Tier 2 hook.\n\nEvery hook that reads the store throws when no provider is mounted\nabove it; the four `useOn*` observer hooks deliberately soft-fail\n(they no-op) so you can drop them into components that sometimes render\noutside a pipeline tree.\n\n## Subpath exports\n\n| Subpath                             | Description                                                                                             |\n| ----------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`          | Default barrel — Tier 1 + Tier 2 (provider, `usePipelineValue`, typed slice hooks, observers).           |\n| `@playlive/react-pipeline/provider` | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, `useAddPipeline*`, `useIdentify`, …    |\n| `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (`useCurrentBlockRaised`, `useLiveSchedule`, the `useHybrid*` family, …).    |\n| `@playlive/react-pipeline/legacy`   | Soft-deprecated alias bundle (`useUDPStore`, `useUDPStoreApi`, `createUDPStore`, `UDPStore`, …).         |\n\nEach subpath ships an ESM bundle, a `bun` source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified so stack\ntraces stay readable in production.\n\n## API reference\n\nFull generated API documentation:\n<https://packages.playlive.experience.stjude.org/p/@playlive/react-pipeline/docs/>\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                     |\n| ------------------------------- | ------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => ReactNode`          |\n| `RealtimePipelineProviderProps` | Type. `InitialProps` + `children`, `url?`, `autoConnect?` (default `true`), `webSocketCtor?`, `log?`, `store?`, `connection?`. |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                  |\n| `PACKAGE_NAME`                  | `\"@playlive/react-pipeline\"` — for runtime version-pinning.    |\n| `KNOWN_URLS`                    | `readonly string[]` — frozen, empty. See the disclosure section. |\n\n`RealtimePipelineProviderProps` extends\n[`InitialProps`](../realtime-pipeline/), so every `initial*` key\n(`initialCampaignIDs`, `initialTeamCampaignIDs`, `initialCauseIDs`,\n`initialFundraisingEventIDs`, `initialOverlayName`, `initialTeamUserSlug`,\n`initialOverlayPath`, `initialOverlayConfig`, `initialAdminApiKey`,\n`initialDonationTrainsEnabled`, …) is a valid prop. There is **no**\n`charityType` prop — set the charity type through the store's\n`setCurrentType` action or via `useCommonContextHooks` (Tier 3).\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the third\n`equalityFn` parameter on `useStore` to keep the package free of the\n`use-sync-external-store` shim peer. For shallow / custom equality, wrap\nyour selector with `useShallow` from `zustand/shallow` (zero extra dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nStable wrappers over `usePipelineValue` with a typed selector +\nmemo-stable empty-array/object fallbacks (no infinite-render churn from\nfresh `[]` allocations — Decision §3 in [CHANGELOG.md](./CHANGELOG.md)).\n\n| Hook                                          | Returns                                                     |\n| --------------------------------------------- | ----------------------------------------------------------- |\n| `usePipelineCampaigns()`                      | `TiltifyCampaign[]`                                          |\n| `usePipelineTeamCampaigns()`                  | `TiltifyTeamCampaign[]`                                      |\n| `usePipelineFundraisingEvents()`              | `TiltifyFundraisingEvent[]`                                  |\n| `usePipelineCauses()`                         | `TiltifyCause[]`                                             |\n| `usePipelineDonations()`                      | `Record<string, TiltifyDonationWithTestFlag[]>`               |\n| `usePipelineDonations(id)`                    | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if unknown) |\n| `usePipelineDonationTrains(id, withStatus?)`  | `DonationTrain[]` — `withStatus` is `\"ACTIVE\" \\| \"ENDED\" \\| \"ENDING\" \\| \"PENDING\"` |\n| `usePipelineSubathonTimer(id, withStatus?)`   | `SubathonTimer[]` — `withStatus` is `\"ACTIVE\" \\| \"INACTIVE\"`  |\n| `usePipelineDonorSpotlight(id)`               | `ComputedSpotlight \\| null`                                  |\n| `usePipelineDonorSpotlightSettings()`         | `Record<string, DonorSpotlightSettings>`                     |\n| `usePipelinePolls(id)`                        | `TiltifyPoll[]`                                              |\n| `usePipelinePollDeltas(pollID)`               | `PollOptionDelta[]` — server-computed \"+$X on option Y\" deltas |\n| `usePipelineRewards(id)`                      | `TiltifyReward[]`                                            |\n| `usePipelineTargets(id)`                      | `TiltifyTarget[]`                                            |\n| `usePipelineMilestones(id)`                   | `TiltifyMilestone[]`                                         |\n| `usePipelineLeaderboardEntries(id)`           | `TiltifyLeaderboardEntry[]`                                  |\n| `usePipelineLeaderboardExclusions(id)`        | `LeaderboardExclusion[]`                                     |\n| `usePipelineLeaderboardExclusionsMap()`       | `Record<string, LeaderboardExclusion[]>`                     |\n| `usePipelineSchedule(id)`                     | `ScheduleTransitionBlock[]` — full `updateSchedule` WS push  |\n| `usePipelineSchedules()`                      | `Record<string, ScheduleTransitionBlock[]>`                  |\n| `usePipelineLastScheduleTransition(id)`       | `ScheduleTransitionPayload \\| null` — latest block crossing  |\n| `usePipelineLastScheduleTransitions()`        | `Record<string, ScheduleTransitionPayload>`                  |\n| `usePipelineDonationSum(id, options?)`        | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` |\n| `usePipelineAuctionHouses()`                  | `AuctionHouse[]`                                             |\n| `usePipelineTwitchChat()`                     | `TwitchChatMessage[]`                                        |\n| `usePipelineConnectionState()`                | `PipelineConnectionState` (10 split selectors — no false re-renders) |\n| `usePipelineConnectionActions()`              | `PipelineConnectionActions` (stable `connect`/`disconnect`/…) |\n| `usePipelineSubscriptions()`                  | `Record<string, TiltifyWebhookSubscription>`                 |\n| `usePipelineTiltifySettings()`                | `TiltifyWebSocketSettings`                                   |\n| `usePipelineLastRefreshResponse()`            | `RefreshResponseMessage \\| null`                             |\n\nCompanion types exported alongside them: `PipelineConnectionState`\n(`connected`, `connecting`, `charityType`, `connectionType`, `wsURL`,\n`hasReceivedInitialData`, `lastIdentified`, `lastIdentityMessage`,\n`lastFirehoseMessage`, `lastMessage`), `PipelineConnectionActions`\n(`connect`, `disconnect`, `identify`, `refresh`, `reset`,\n`setAutoConnect`, `setAdminApiKey`), `PipelineDonationSumOptions`,\n`PipelineDonationSumByCurrency`, `PipelineDonationSumResult`.\n\n#### Change observers\n\nPush-style subscriptions that diff the store outside React's render\npath. Each stashes `handler` in a ref, subscribes exactly once, and\ntreats whatever is already on the store at mount as the baseline (so\nthey never fire for pre-existing data). All four **soft-fail** without\na provider.\n\n| Hook                                                | Handler payload                                        |\n| --------------------------------------------------- | ------------------------------------------------------ |\n| `useOnScheduleTransition(handler, options?)`        | `ScheduleTransitionPayload`                             |\n| `useOnScheduleUpdate(handler, options?)`            | `{ campaignID, schedule: ScheduleTransitionBlock[] }`   |\n| `useOnLeaderboardEntriesChange(handler, options?)`  | `{ campaignID, entries: TiltifyLeaderboardEntry[] }`    |\n| `useOnLeaderboardExclusionsChange(handler, options?)` | `{ campaignID, exclusions: LeaderboardExclusion[] }`  |\n\n`options` is `{ campaignIDs?: readonly string[] }` — omit it (or pass an\nempty list) to observe every campaign.\n\n```tsx\nuseOnLeaderboardEntriesChange(\n  ({ campaignID }) => {\n    queryClient.invalidateQueries({\n      queryKey: [\"leaderboard-with-exclusions\", \"tiltify\", campaignID],\n    });\n  },\n  { campaignIDs: [campaignID] },\n);\n```\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                                        | Notes                                                                                          |\n| --------------------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`                 | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` (default `true`) + `debug` (wires `console.debug` logging). |\n| `UnifiedDataPipelineProviderProps`            | Type. `Omit<RealtimePipelineProviderProps, \"children\"> & { children, reloadOnErrorReconnect?, debug? }`. |\n| `useUnifiedDataPipeline()`                    | One-call kitchen-sink hook → `UnifiedDataPipelineState`. Re-renders on **every** store change.  |\n| `UnifiedDataPipelineState`                    | Type. `PipelineStore` + `allRewards` / `allMilestones` / `allTargets` / `allPolls` / `allFundraisingEventSupportingCampaigns` / `tiltifySubscriptions` legacy aliases. |\n| `useAddPipelineCampaignIDs(params)`           | Layout-effect hook. `{ mode: \"ws\" \\| \"http\"; initialCampaign: { id }; isTeam: boolean; causeID? }` — no-op in `\"http\"` mode. |\n| `useAddPipelineCampaigns(params)`             | Layout-effect hook. `{ campaign?: TiltifyCampaign \\| null; teamCampaign?: TiltifyTeamCampaign \\| null }`. Idempotent by id. |\n| `useAddPipelineFundraisingEventIDs(params)`   | Layout-effect hook. `{ mode; initialCampaign: { id; fundraising_event_id: string \\| null } }`.   |\n| `useCommonContextHooks(params)`               | `{ url: string; mode: \"ws\" \\| \"http\"; charityType: CharityType }` — syncs type + URL and connects when `mode === \"ws\"`. |\n| `useIdentify()`                               | Returns the store-bound `identify` action. The 11-positional-arg legacy overload is kept as `@deprecated`; the args are ignored and a one-shot `console.warn` fires. |\n\n`RealtimePipelineProvider` / `RealtimePipelineProviderProps` are also\nre-exported from `/provider` so a Tier-3 consumer needs exactly one\nimport specifier.\n\n### Fusion — REST baseline + WS delta hooks (`/fusion`)\n\nThese fuse the WebSocket firehose with REST reads from\n[`@playlive/react-query`](../react-query/) /\n[`@playlive/fundraiser-data`](../fundraiser-data/). Importing this\nsubpath means you must also install the three optional peers.\n\n| Hook                                                   | Returns                                                                    |\n| ------------------------------------------------------ | -------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline `+` WS delta over `[start, end)`. `opts`: `{ enabled?, queryOptions?, demoMode?, monotonic?, onWindowChange? }`. |\n| `useLiveSchedule(params, opts?)`                       | `{ schedule, isLoading, isError, refetch }`. `params`: `{ charityType, campaignID, isTeam? }`; `opts`: `{ enabled?, queryOptions?, demoMode?, demoIntervalMs? }`. |\n| `useCurrentScheduleItem(schedule, opts?)`              | `{ currentItem, nextItem }` — pure client-side rollover projection with a self-scheduling boundary timer. |\n| `useLinkedTeamCampaign(params, opts?)`                 | `{ shouldFetchLinkedTeamCampaign, resolvedLinkedTeamCampaign, linkedTeamTotalAmount, linkedTeamGoalAmount, isLoadingLinkedTeamCampaign, debug }`. |\n| `useDonorSpotlight(params, opts?)`                     | `{ spotlight, isLoading, refetch }`. `params`: `{ campaignID, isTeam, live, mode }`; `opts`: `{ pollingIntervalMs?, enabled?, queryOptions? }`. |\n\n`useLiveSchedule` resolves in a strict fallback order: full\n`updateSchedule` WS push → `lastScheduleTransitions` synthesis (demo\nonly) → REST `useSchedule` → demo fixture.\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity hooks — the ones every Play Live overlay used to keep a\nlocal copy of — shipped here so they can be imported instead. Each takes a\n`mode: \"ws\" | \"http\"` that picks between:\n\n- `\"ws\"` — read the Tier-2 store selector, latch a local `hasLoadedX`\n  boolean on first arrival (or on the server's \"feature disabled\"\n  signal), and call `updateTiltifySettings({ <sliceKey> })` whenever the\n  local `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST hook\n  (`useRewards`, `useTargets`, …) and ignore the WS slice.\n\nAll of them share `useLoadingTimeout` — a 15 s safety net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                    | Params → Returns                                                              |\n| -------------------------------------------------------- | ----------------------------------------------------------------------------- |\n| `useHybridRewards({ campaignID, isTeam, mode, initialRewardsEnabled? })`   | `{ currentRewards, hasLoadedRewards, setHasLoadedRewards, isLoadingRewards, isLoading, rewardsEnabled, setRewardsEnabled }` |\n| `useHybridTargets({ campaignID, isTeam, mode })`         | `{ currentTargets, hasLoadedTargets, setHasLoadedTargets, isLoadingTargets, isLoading, targetsEnabled, setTargetsEnabled }` |\n| `useHybridMilestones({ campaignID, isTeam, mode })`      | `{ currentMilestones, isLoadingMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls({ campaignID, isTeam, mode })`           | `{ currentPolls, isLoadingPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics.                                |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns({ mode, initialFundraisingEvent, initialSupportingCampaignsEnabled? })` | `{ supportingCampaigns, isLoading, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns, supportingCampaignsEnabled, setSupportingCampaignsEnabled }` |\n| `useHybridDonationTrains({ campaignID, mode, initialDonationTrainsEnabled?, withStatus?, http?, auth? })` | `{ currentDonationTrains, hasLoadedDonationTrains, setHasLoadedDonationTrains, isLoadingDonationTrains, isLoading, donationTrainsEnabled, setDonationTrainsEnabled }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useHybridLeaderboardWithExclusions({ charityType, campaignID, isTeam?, timeType?, count?, startDate?, endDate?, enabled?, continuouslyUpdate? })` | `{ entries, isLoading }` — WS-driven invalidation over the REST leaderboard, with a `LEADERBOARD_SAFETY_POLL_MS` (60 s) backstop. |\n| `useLoadingTimeout({ mode, isLoading, onFinishedLoading, timeoutAmount? })` | `void` — the shared safety-net timer primitive.                    |\n\n`LEADERBOARD_SAFETY_POLL_MS` is exported alongside the hooks. Return\nshapes are preserved verbatim from the original overlay hooks, so\nexisting call sites can collapse to a re-export.\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Behavior is identical to the canonical exports — rename\nthe import specifier once and the rest of your code base keeps working:\n\n| Legacy name          | Canonical replacement                                            |\n| -------------------- | ---------------------------------------------------------------- |\n| `useUDPStore`        | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)          |\n| `useUDPStoreApi`     | Use a typed slice hook, or reach for the raw store API            |\n| `createUDPStore`     | `createPipelineStore` from `@playlive/realtime-pipeline/store`    |\n| `UDPStore` (type)    | `PipelineStore` from `@playlive/realtime-pipeline/store`          |\n| `UDPStoreApi` (type) | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`       |\n| `CharityTypes`       | Re-exported from `@playlive/realtime-pipeline/protocol`           |\n\n`useUDPStore` / `useUDPStoreApi` throw with a\n`\"must be called inside <UnifiedDataPipelineProvider>\"` message when no\nprovider is mounted. The subpath also re-exports every Tier 1 + 2 + 3\nsymbol under its canonical name, plus the `PipelineStore` /\n`PipelineStoreApi` types.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot, sed-friendly rewrite of every import specifier (no body edits\nneeded for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure — the aliases are kept for a\none-release window.\n\nTwo call-site changes are **not** covered by the sed:\n\n- `useIdentify(sendJsonMessage, campaignIDs, …)` — the positional args\n  are now ignored (the connection reads identify inputs off the store).\n  Drop them; the deprecated overload still compiles and warns once.\n- `useWebSocket` from `react-use-websocket-lite` — replaced by\n  `createPipelineConnection` in `@playlive/realtime-pipeline/connection`,\n  which `RealtimePipelineProvider` mounts for you.\n\nThe [Legacy aliases](#legacy-aliases-legacy) table above is the full\nper-symbol mapping.\n\n## Upstream spec\n\nNo external API surface — this package fetches nothing itself. It binds\nReact to [`@playlive/realtime-pipeline`](../realtime-pipeline/), which\nowns the WebSocket connection and frames every byte on the wire; the\nmessage catalogue and its types are exported from\n`@playlive/realtime-pipeline/protocol`. The `/fusion` hooks additionally\nread REST baselines through\n[`@playlive/react-query`](../react-query/) and\n[`@playlive/fundraiser-data`](../fundraiser-data/) — see those packages\nfor the endpoints and URL knobs involved.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nreach. **It is empty.** This package only opens the WebSocket URL the\nconsumer hands to `<RealtimePipelineProvider url={…}>` (or the store's\n`DEFAULT_WEBSOCKET_URL`, `wss://main.playlive.ws.api.experience.stjude.org`).\nDisclose that URL — plus the `KNOWN_URLS` of `@playlive/realtime-pipeline`\nand, if you import `/fusion`, `@playlive/fundraiser-data` — on your\nExtension submission.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\n## Examples\n\n### Live schedule-block total without polling\n\nThe canonical fusion use case: fetch a REST baseline once, then add the\nWebSocket donation delta on top so the number ticks up on every donation\ninstead of every 30-second poll. Requires the optional peers plus a\n`<QueryClientProvider>` above the pipeline provider.\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\nimport { RealtimePipelineProvider } from \"@playlive/react-pipeline\";\nimport {\n  useCurrentBlockRaised,\n  useCurrentScheduleItem,\n  useLiveSchedule,\n} from \"@playlive/react-pipeline/fusion\";\nimport { makeQueryClient } from \"@playlive/react-query\";\nimport { QueryClientProvider } from \"@tanstack/react-query\";\n\nconfigure(getConfigForEnv(\"prod\", { tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL }));\nconst queryClient = makeQueryClient();\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nexport function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <RealtimePipelineProvider\n        url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n        initialCampaignIDs={[CAMPAIGN_ID]}\n        initialOverlayName=\"schedule-block\"\n        initialTeamUserSlug=\"@playliver\"\n      >\n        <BlockTotal campaignID={CAMPAIGN_ID} />\n      </RealtimePipelineProvider>\n    </QueryClientProvider>\n  );\n}\n\nfunction BlockTotal({ campaignID }: { campaignID: string }) {\n  const { schedule, isLoading: scheduleLoading, isError } = useLiveSchedule({\n    charityType: \"tiltify\",\n    campaignID,\n  });\n  const { currentItem, nextItem } = useCurrentScheduleItem(schedule);\n\n  const { raised, isLoading } = useCurrentBlockRaised(\n    campaignID,\n    currentItem?.starts_at,\n    currentItem?.ends_at,\n  );\n\n  if (scheduleLoading) return <p>Loading schedule…</p>;\n  if (isError) return <p>Schedule unavailable.</p>;\n  if (!currentItem) return <p>Up next: {nextItem?.name ?? \"nothing scheduled\"}</p>;\n\n  return (\n    <section>\n      <h2>{currentItem.name}</h2>\n      <p>{isLoading ? \"…\" : `$${raised.toFixed(2)} raised this block`}</p>\n    </section>\n  );\n}\n```\n\n### Raw baseline + delta with the Tier-2 primitive\n\nIf you already have the baseline (from a route loader, say), skip the\nfusion hook and reduce the firehose yourself:\n\n```tsx\nimport { usePipelineDonationSum } from \"@playlive/react-pipeline\";\n\nfunction BlockDelta({\n  campaignID,\n  baseline,\n}: {\n  campaignID: string;\n  // `GET /schedules/campaigns/{id}/raised`\n  baseline: {\n    raised: number;\n    currency: string;\n    asOf: string;\n    donationCount: number;\n  };\n}) {\n  const delta = usePipelineDonationSum(campaignID, {\n    start: baseline.asOf,\n    end: \"2025-09-20T23:00:00.000Z\",\n    currency: baseline.currency,\n    // `asOf` is the ingest watermark — the newest donation actually\n    // inside `baseline.raised`. Exclude it or it gets counted twice.\n    // On an empty window `asOf` falls back to the window start, which\n    // is a bound rather than a donation, so keep that one inclusive.\n    startExclusive: baseline.donationCount > 0,\n  });\n\n  const total = baseline.raised + (delta.raised ?? 0);\n  return (\n    <p>\n      ${total.toFixed(2)} ({delta.donationCount} live donations since baseline)\n    </p>\n  );\n}\n```\n\n`usePipelineDonationSum` matches `start <= completed_at < end` (pass\n`startExclusive: true` for `start < completed_at < end`), dedupes\nby donation id (last write wins, so post-edit corrections propagate),\nand excludes `test: true` donations unless you pass\n`includeTest: true` — which keeps it consistent with the REST baseline.\n\n`useCurrentBlockRaised` handles the watermark seam for you, and adds two\nguards against an on-air total moving the wrong way:\n\n- **`monotonic`** (default `true`) — holds the high-water mark for as\n  long as the `(campaignID, start, end)` window is unchanged, so a\n  reconciliation artefact can't walk a live block total backwards. Resets\n  on every genuine window change, so block rollovers still start at zero.\n  Pass `false` for accounting views that must surface refunds as they land.\n- **`onWindowChange`** — fires when the block window is swapped under a\n  mounted hook, with the previous/next window and the figure that was on\n  screen immediately before the swap. Schedules can move with no user\n  action, which looks identical on stream to money appearing or\n  vanishing. Defaults to a `console.warn` breadcrumb.\n\nBoth examples above are complete and runnable: point\n`<RealtimePipelineProvider url={…}>` at your pipeline WebSocket and the\nrest is copy-paste.\n\n## License\n\nMIT © St. Jude Children's Research Hospital\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.5.3.tgz","shasum":"959f17208377ff8a9baf00644bd5700b7a2d9d03","integrity":"sha512-an2xJ1SNrSWalow7E+tQWPRUI02h79TP67TkBS6yNjAqozZK48uvNX9nPWkLYKLQ8ah8a/0aGzdJu/NUzatQvw=="}},"0.5.4":{"name":"@playlive/react-pipeline","version":"0.5.4","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.4.1","@playlive/tiltify-core":"^0.4.19","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.4.6","@playlive/fundraiser-data":"^0.6.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-hdAI2JpImWAupTGUejy+N3sAh+G5cKBaL6JohQAY36Oj7eY5c/4I1sWvbwXGtkrOaummi9MepwvjstCDWX2upg==","shasum":"dff8cd74a6d265f00acc3ed752c2a29862e83867","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nSuccessor to `playlive-overlay-data-layer/src/websocket/*` with **zero**\ndependency on `react-use-websocket-lite` — built directly on Zustand v5's\n`useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                             | Surface                                                                       | Use when                                                               |\n| ---- | ----------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`          | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)          | Greenfield app; you want one selector primitive and nothing else.      |\n| 2    | `@playlive/react-pipeline`          | Tier 1 **plus** the typed per-slice hooks (`usePipelineDonations(id)`, …)     | You want stable, typed one-liners for the common reads.                |\n| 3    | `@playlive/react-pipeline/provider` | Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc. | Porting an existing overlay-data-layer consumer with minimal churn.    |\n| —    | `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (needs the optional TanStack peers)              | You want \"REST baseline + live WS delta\" without hand-rolling it.      |\n| —    | `@playlive/react-pipeline/legacy`   | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases   | Soft-deprecated shim for the legacy symbol names (one-release window). |\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add react react-dom zustand @playlive/realtime-pipeline @playlive/tiltify-core\n```\n\nPeer dependencies (jose-style — the consumer brings their own):\n\n| Peer                          | Range          | Required?                                 |\n| ----------------------------- | -------------- | ----------------------------------------- |\n| `react`                       | `^19.0.0`      | yes                                       |\n| `react-dom`                   | `^19.0.0`      | yes                                       |\n| `zustand`                     | `^5.0.0`       | yes                                       |\n| `@playlive/realtime-pipeline` | `workspace:*`  | yes — store, connection, protocol types    |\n| `@playlive/tiltify-core`      | `workspace:*`  | yes (type-only — stripped at compile time) |\n| `@tanstack/react-query`       | `^5.0.0`       | **optional** — only for `/fusion`          |\n| `@playlive/react-query`       | `workspace:*`  | **optional** — only for `/fusion`          |\n| `@playlive/fundraiser-data`   | `workspace:*`  | **optional** — only for `/fusion`          |\n\nThe three optional peers are declared `optional: true` in\n`peerDependenciesMeta`; you only need them if you import from\n[`@playlive/react-pipeline/fusion`](#fusion--rest-baseline--ws-delta-hooks-fusion).\n\nThis package has **no runtime `dependencies`** — nothing but your own\npeers ends up in the bundle.\n\n## Quick start\n\n```tsx\nimport {\n  RealtimePipelineProvider,\n  usePipelineCampaigns,\n  usePipelineConnectionState,\n  usePipelineDonations,\n} from \"@playlive/react-pipeline\";\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n      initialCampaignIDs={[CAMPAIGN_ID]}\n      initialOverlayName=\"donation-bar\"\n      initialTeamUserSlug=\"@playliver\"\n      initialOverlayPath=\"/overlays/donation-bar\"\n      autoConnect\n    >\n      <DonationBar campaignID={CAMPAIGN_ID} />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction DonationBar({ campaignID }: { campaignID: string }) {\n  const { connected, connecting, hasReceivedInitialData } = usePipelineConnectionState();\n  const campaigns = usePipelineCampaigns();\n  const donations = usePipelineDonations(campaignID);\n\n  if (connecting) return <p>Connecting…</p>;\n  if (!connected) return <p>Disconnected — reconnecting…</p>;\n  if (!hasReceivedInitialData) return <p>Waiting for first tick…</p>;\n\n  const campaign = campaigns.find((c) => c.id === campaignID);\n  const raised = Number(campaign?.total_amount_raised?.value ?? 0);\n  const goal = Number(campaign?.goal?.value ?? 0);\n\n  return (\n    <section>\n      <progress value={raised} max={goal || 1} />\n      <p>\n        ${raised.toFixed(2)} of ${goal.toFixed(2)}\n      </p>\n      <ul>\n        {donations.slice(0, 5).map((d) => (\n          <li key={d.id}>\n            {d.donor_name} — ${d.amount.value}\n          </li>\n        ))}\n      </ul>\n    </section>\n  );\n}\n```\n\n`RealtimePipelineProvider` builds one Zustand store per provider\ninstance (from the `initial*` props, which are read **once** at first\nconstruction) and opens a single `PipelineConnection`. `connection.destroy()`\nruns on unmount. Children subscribe with any Tier 1 / Tier 2 hook.\n\nEvery hook that reads the store throws when no provider is mounted\nabove it; the four `useOn*` observer hooks deliberately soft-fail\n(they no-op) so you can drop them into components that sometimes render\noutside a pipeline tree.\n\n## Subpath exports\n\n| Subpath                             | Description                                                                                             |\n| ----------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`          | Default barrel — Tier 1 + Tier 2 (provider, `usePipelineValue`, typed slice hooks, observers).           |\n| `@playlive/react-pipeline/provider` | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, `useAddPipeline*`, `useIdentify`, …    |\n| `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (`useCurrentBlockRaised`, `useLiveSchedule`, the `useHybrid*` family, …).    |\n| `@playlive/react-pipeline/legacy`   | Soft-deprecated alias bundle (`useUDPStore`, `useUDPStoreApi`, `createUDPStore`, `UDPStore`, …).         |\n\nEach subpath ships an ESM bundle, a `bun` source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified so stack\ntraces stay readable in production.\n\n## API reference\n\nFull generated API documentation:\n<https://packages.playlive.experience.stjude.org/p/@playlive/react-pipeline/docs/>\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                     |\n| ------------------------------- | ------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => ReactNode`          |\n| `RealtimePipelineProviderProps` | Type. `InitialProps` + `children`, `url?`, `autoConnect?` (default `true`), `webSocketCtor?`, `log?`, `store?`, `connection?`. |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                  |\n| `PACKAGE_NAME`                  | `\"@playlive/react-pipeline\"` — for runtime version-pinning.    |\n| `KNOWN_URLS`                    | `readonly string[]` — frozen, empty. See the disclosure section. |\n\n`RealtimePipelineProviderProps` extends\n[`InitialProps`](../realtime-pipeline/), so every `initial*` key\n(`initialCampaignIDs`, `initialTeamCampaignIDs`, `initialCauseIDs`,\n`initialFundraisingEventIDs`, `initialOverlayName`, `initialTeamUserSlug`,\n`initialOverlayPath`, `initialOverlayConfig`, `initialAdminApiKey`,\n`initialDonationTrainsEnabled`, …) is a valid prop. There is **no**\n`charityType` prop — set the charity type through the store's\n`setCurrentType` action or via `useCommonContextHooks` (Tier 3).\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the third\n`equalityFn` parameter on `useStore` to keep the package free of the\n`use-sync-external-store` shim peer. For shallow / custom equality, wrap\nyour selector with `useShallow` from `zustand/shallow` (zero extra dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nStable wrappers over `usePipelineValue` with a typed selector +\nmemo-stable empty-array/object fallbacks (no infinite-render churn from\nfresh `[]` allocations — Decision §3 in [CHANGELOG.md](./CHANGELOG.md)).\n\n| Hook                                          | Returns                                                     |\n| --------------------------------------------- | ----------------------------------------------------------- |\n| `usePipelineCampaigns()`                      | `TiltifyCampaign[]`                                          |\n| `usePipelineTeamCampaigns()`                  | `TiltifyTeamCampaign[]`                                      |\n| `usePipelineFundraisingEvents()`              | `TiltifyFundraisingEvent[]`                                  |\n| `usePipelineCauses()`                         | `TiltifyCause[]`                                             |\n| `usePipelineDonations()`                      | `Record<string, TiltifyDonationWithTestFlag[]>`               |\n| `usePipelineDonations(id)`                    | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if unknown) |\n| `usePipelineDonationTrains(id, withStatus?)`  | `DonationTrain[]` — `withStatus` is `\"ACTIVE\" \\| \"ENDED\" \\| \"ENDING\" \\| \"PENDING\"` |\n| `usePipelineSubathonTimer(id, withStatus?)`   | `SubathonTimer[]` — `withStatus` is `\"ACTIVE\" \\| \"INACTIVE\"`  |\n| `usePipelineDonorSpotlight(id)`               | `ComputedSpotlight \\| null`                                  |\n| `usePipelineDonorSpotlightSettings()`         | `Record<string, DonorSpotlightSettings>`                     |\n| `usePipelinePolls(id)`                        | `TiltifyPoll[]`                                              |\n| `usePipelinePollDeltas(pollID)`               | `PollOptionDelta[]` — server-computed \"+$X on option Y\" deltas |\n| `usePipelineRewards(id)`                      | `TiltifyReward[]`                                            |\n| `usePipelineTargets(id)`                      | `TiltifyTarget[]`                                            |\n| `usePipelineMilestones(id)`                   | `TiltifyMilestone[]`                                         |\n| `usePipelineLeaderboardEntries(id)`           | `TiltifyLeaderboardEntry[]`                                  |\n| `usePipelineLeaderboardExclusions(id)`        | `LeaderboardExclusion[]`                                     |\n| `usePipelineLeaderboardExclusionsMap()`       | `Record<string, LeaderboardExclusion[]>`                     |\n| `usePipelineSchedule(id)`                     | `ScheduleTransitionBlock[]` — full `updateSchedule` WS push  |\n| `usePipelineSchedules()`                      | `Record<string, ScheduleTransitionBlock[]>`                  |\n| `usePipelineLastScheduleTransition(id)`       | `ScheduleTransitionPayload \\| null` — latest block crossing  |\n| `usePipelineLastScheduleTransitions()`        | `Record<string, ScheduleTransitionPayload>`                  |\n| `usePipelineDonationSum(id, options?)`        | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` |\n| `usePipelineAuctionHouses()`                  | `AuctionHouse[]`                                             |\n| `usePipelineTwitchChat()`                     | `TwitchChatMessage[]`                                        |\n| `usePipelineConnectionState()`                | `PipelineConnectionState` (10 split selectors — no false re-renders) |\n| `usePipelineConnectionActions()`              | `PipelineConnectionActions` (stable `connect`/`disconnect`/…) |\n| `usePipelineSubscriptions()`                  | `Record<string, TiltifyWebhookSubscription>`                 |\n| `usePipelineTiltifySettings()`                | `TiltifyWebSocketSettings`                                   |\n| `usePipelineLastRefreshResponse()`            | `RefreshResponseMessage \\| null`                             |\n\nCompanion types exported alongside them: `PipelineConnectionState`\n(`connected`, `connecting`, `charityType`, `connectionType`, `wsURL`,\n`hasReceivedInitialData`, `lastIdentified`, `lastIdentityMessage`,\n`lastFirehoseMessage`, `lastMessage`), `PipelineConnectionActions`\n(`connect`, `disconnect`, `identify`, `refresh`, `reset`,\n`setAutoConnect`, `setAdminApiKey`), `PipelineDonationSumOptions`,\n`PipelineDonationSumByCurrency`, `PipelineDonationSumResult`.\n\n#### Change observers\n\nPush-style subscriptions that diff the store outside React's render\npath. Each stashes `handler` in a ref, subscribes exactly once, and\ntreats whatever is already on the store at mount as the baseline (so\nthey never fire for pre-existing data). All four **soft-fail** without\na provider.\n\n| Hook                                                | Handler payload                                        |\n| --------------------------------------------------- | ------------------------------------------------------ |\n| `useOnScheduleTransition(handler, options?)`        | `ScheduleTransitionPayload`                             |\n| `useOnScheduleUpdate(handler, options?)`            | `{ campaignID, schedule: ScheduleTransitionBlock[] }`   |\n| `useOnLeaderboardEntriesChange(handler, options?)`  | `{ campaignID, entries: TiltifyLeaderboardEntry[] }`    |\n| `useOnLeaderboardExclusionsChange(handler, options?)` | `{ campaignID, exclusions: LeaderboardExclusion[] }`  |\n\n`options` is `{ campaignIDs?: readonly string[] }` — omit it (or pass an\nempty list) to observe every campaign.\n\n```tsx\nuseOnLeaderboardEntriesChange(\n  ({ campaignID }) => {\n    queryClient.invalidateQueries({\n      queryKey: [\"leaderboard-with-exclusions\", \"tiltify\", campaignID],\n    });\n  },\n  { campaignIDs: [campaignID] },\n);\n```\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                                        | Notes                                                                                          |\n| --------------------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`                 | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` (default `true`) + `debug` (wires `console.debug` logging). |\n| `UnifiedDataPipelineProviderProps`            | Type. `Omit<RealtimePipelineProviderProps, \"children\"> & { children, reloadOnErrorReconnect?, debug? }`. |\n| `useUnifiedDataPipeline()`                    | One-call kitchen-sink hook → `UnifiedDataPipelineState`. Re-renders on **every** store change.  |\n| `UnifiedDataPipelineState`                    | Type. `PipelineStore` + `allRewards` / `allMilestones` / `allTargets` / `allPolls` / `allFundraisingEventSupportingCampaigns` / `tiltifySubscriptions` legacy aliases. |\n| `useAddPipelineCampaignIDs(params)`           | Layout-effect hook. `{ mode: \"ws\" \\| \"http\"; initialCampaign: { id }; isTeam: boolean; causeID? }` — no-op in `\"http\"` mode. |\n| `useAddPipelineCampaigns(params)`             | Layout-effect hook. `{ campaign?: TiltifyCampaign \\| null; teamCampaign?: TiltifyTeamCampaign \\| null }`. Idempotent by id. |\n| `useAddPipelineFundraisingEventIDs(params)`   | Layout-effect hook. `{ mode; initialCampaign: { id; fundraising_event_id: string \\| null } }`.   |\n| `useCommonContextHooks(params)`               | `{ url: string; mode: \"ws\" \\| \"http\"; charityType: CharityType }` — syncs type + URL and connects when `mode === \"ws\"`. |\n| `useIdentify()`                               | Returns the store-bound `identify` action. The 11-positional-arg legacy overload is kept as `@deprecated`; the args are ignored and a one-shot `console.warn` fires. |\n\n`RealtimePipelineProvider` / `RealtimePipelineProviderProps` are also\nre-exported from `/provider` so a Tier-3 consumer needs exactly one\nimport specifier.\n\n### Fusion — REST baseline + WS delta hooks (`/fusion`)\n\nThese fuse the WebSocket firehose with REST reads from\n[`@playlive/react-query`](../react-query/) /\n[`@playlive/fundraiser-data`](../fundraiser-data/). Importing this\nsubpath means you must also install the three optional peers.\n\n| Hook                                                   | Returns                                                                    |\n| ------------------------------------------------------ | -------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline `+` WS delta over `[start, end)`. `opts`: `{ enabled?, queryOptions?, demoMode?, monotonic?, onWindowChange? }`. |\n| `useLiveSchedule(params, opts?)`                       | `{ schedule, isLoading, isError, refetch }`. `params`: `{ charityType, campaignID, isTeam? }`; `opts`: `{ enabled?, queryOptions?, demoMode?, demoIntervalMs? }`. |\n| `useCurrentScheduleItem(schedule, opts?)`              | `{ currentItem, nextItem }` — pure client-side rollover projection with a self-scheduling boundary timer. |\n| `useLinkedTeamCampaign(params, opts?)`                 | `{ shouldFetchLinkedTeamCampaign, resolvedLinkedTeamCampaign, linkedTeamTotalAmount, linkedTeamGoalAmount, isLoadingLinkedTeamCampaign, debug }`. |\n| `useDonorSpotlight(params, opts?)`                     | `{ spotlight, isLoading, refetch }`. `params`: `{ campaignID, isTeam, live, mode }`; `opts`: `{ pollingIntervalMs?, enabled?, queryOptions? }`. |\n\n`useLiveSchedule` resolves in a strict fallback order: full\n`updateSchedule` WS push → `lastScheduleTransitions` synthesis (demo\nonly) → REST `useSchedule` → demo fixture.\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity hooks — the ones every Play Live overlay used to keep a\nlocal copy of — shipped here so they can be imported instead. Each takes a\n`mode: \"ws\" | \"http\"` that picks between:\n\n- `\"ws\"` — read the Tier-2 store selector, latch a local `hasLoadedX`\n  boolean on first arrival (or on the server's \"feature disabled\"\n  signal), and call `updateTiltifySettings({ <sliceKey> })` whenever the\n  local `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST hook\n  (`useRewards`, `useTargets`, …) and ignore the WS slice.\n\nAll of them share `useLoadingTimeout` — a 15 s safety net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                    | Params → Returns                                                              |\n| -------------------------------------------------------- | ----------------------------------------------------------------------------- |\n| `useHybridRewards({ campaignID, isTeam, mode, initialRewardsEnabled? })`   | `{ currentRewards, hasLoadedRewards, setHasLoadedRewards, isLoadingRewards, isLoading, rewardsEnabled, setRewardsEnabled }` |\n| `useHybridTargets({ campaignID, isTeam, mode })`         | `{ currentTargets, hasLoadedTargets, setHasLoadedTargets, isLoadingTargets, isLoading, targetsEnabled, setTargetsEnabled }` |\n| `useHybridMilestones({ campaignID, isTeam, mode })`      | `{ currentMilestones, isLoadingMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls({ campaignID, isTeam, mode })`           | `{ currentPolls, isLoadingPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics.                                |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns({ mode, initialFundraisingEvent, initialSupportingCampaignsEnabled? })` | `{ supportingCampaigns, isLoading, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns, supportingCampaignsEnabled, setSupportingCampaignsEnabled }` |\n| `useHybridDonationTrains({ campaignID, mode, initialDonationTrainsEnabled?, withStatus?, http?, auth? })` | `{ currentDonationTrains, hasLoadedDonationTrains, setHasLoadedDonationTrains, isLoadingDonationTrains, isLoading, donationTrainsEnabled, setDonationTrainsEnabled }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useHybridLeaderboardWithExclusions({ charityType, campaignID, isTeam?, timeType?, count?, startDate?, endDate?, enabled?, continuouslyUpdate? })` | `{ entries, isLoading }` — WS-driven invalidation over the REST leaderboard, with a `LEADERBOARD_SAFETY_POLL_MS` (60 s) backstop. |\n| `useLoadingTimeout({ mode, isLoading, onFinishedLoading, timeoutAmount? })` | `void` — the shared safety-net timer primitive.                    |\n\n`LEADERBOARD_SAFETY_POLL_MS` is exported alongside the hooks. Return\nshapes are preserved verbatim from the original overlay hooks, so\nexisting call sites can collapse to a re-export.\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Behavior is identical to the canonical exports — rename\nthe import specifier once and the rest of your code base keeps working:\n\n| Legacy name          | Canonical replacement                                            |\n| -------------------- | ---------------------------------------------------------------- |\n| `useUDPStore`        | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)          |\n| `useUDPStoreApi`     | Use a typed slice hook, or reach for the raw store API            |\n| `createUDPStore`     | `createPipelineStore` from `@playlive/realtime-pipeline/store`    |\n| `UDPStore` (type)    | `PipelineStore` from `@playlive/realtime-pipeline/store`          |\n| `UDPStoreApi` (type) | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`       |\n| `CharityTypes`       | Re-exported from `@playlive/realtime-pipeline/protocol`           |\n\n`useUDPStore` / `useUDPStoreApi` throw with a\n`\"must be called inside <UnifiedDataPipelineProvider>\"` message when no\nprovider is mounted. The subpath also re-exports every Tier 1 + 2 + 3\nsymbol under its canonical name, plus the `PipelineStore` /\n`PipelineStoreApi` types.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot, sed-friendly rewrite of every import specifier (no body edits\nneeded for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure — the aliases are kept for a\none-release window.\n\nTwo call-site changes are **not** covered by the sed:\n\n- `useIdentify(sendJsonMessage, campaignIDs, …)` — the positional args\n  are now ignored (the connection reads identify inputs off the store).\n  Drop them; the deprecated overload still compiles and warns once.\n- `useWebSocket` from `react-use-websocket-lite` — replaced by\n  `createPipelineConnection` in `@playlive/realtime-pipeline/connection`,\n  which `RealtimePipelineProvider` mounts for you.\n\nThe [Legacy aliases](#legacy-aliases-legacy) table above is the full\nper-symbol mapping.\n\n## Upstream spec\n\nNo external API surface — this package fetches nothing itself. It binds\nReact to [`@playlive/realtime-pipeline`](../realtime-pipeline/), which\nowns the WebSocket connection and frames every byte on the wire; the\nmessage catalogue and its types are exported from\n`@playlive/realtime-pipeline/protocol`. The `/fusion` hooks additionally\nread REST baselines through\n[`@playlive/react-query`](../react-query/) and\n[`@playlive/fundraiser-data`](../fundraiser-data/) — see those packages\nfor the endpoints and URL knobs involved.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nreach. **It is empty.** This package only opens the WebSocket URL the\nconsumer hands to `<RealtimePipelineProvider url={…}>` (or the store's\n`DEFAULT_WEBSOCKET_URL`, `wss://main.playlive.ws.api.experience.stjude.org`).\nDisclose that URL — plus the `KNOWN_URLS` of `@playlive/realtime-pipeline`\nand, if you import `/fusion`, `@playlive/fundraiser-data` — on your\nExtension submission.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\n## Examples\n\n### Live schedule-block total without polling\n\nThe canonical fusion use case: fetch a REST baseline once, then add the\nWebSocket donation delta on top so the number ticks up on every donation\ninstead of every 30-second poll. Requires the optional peers plus a\n`<QueryClientProvider>` above the pipeline provider.\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\nimport { RealtimePipelineProvider } from \"@playlive/react-pipeline\";\nimport {\n  useCurrentBlockRaised,\n  useCurrentScheduleItem,\n  useLiveSchedule,\n} from \"@playlive/react-pipeline/fusion\";\nimport { makeQueryClient } from \"@playlive/react-query\";\nimport { QueryClientProvider } from \"@tanstack/react-query\";\n\nconfigure(getConfigForEnv(\"prod\", { tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL }));\nconst queryClient = makeQueryClient();\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nexport function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <RealtimePipelineProvider\n        url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n        initialCampaignIDs={[CAMPAIGN_ID]}\n        initialOverlayName=\"schedule-block\"\n        initialTeamUserSlug=\"@playliver\"\n      >\n        <BlockTotal campaignID={CAMPAIGN_ID} />\n      </RealtimePipelineProvider>\n    </QueryClientProvider>\n  );\n}\n\nfunction BlockTotal({ campaignID }: { campaignID: string }) {\n  const { schedule, isLoading: scheduleLoading, isError } = useLiveSchedule({\n    charityType: \"tiltify\",\n    campaignID,\n  });\n  const { currentItem, nextItem } = useCurrentScheduleItem(schedule);\n\n  const { raised, isLoading } = useCurrentBlockRaised(\n    campaignID,\n    currentItem?.starts_at,\n    currentItem?.ends_at,\n  );\n\n  if (scheduleLoading) return <p>Loading schedule…</p>;\n  if (isError) return <p>Schedule unavailable.</p>;\n  if (!currentItem) return <p>Up next: {nextItem?.name ?? \"nothing scheduled\"}</p>;\n\n  return (\n    <section>\n      <h2>{currentItem.name}</h2>\n      <p>{isLoading ? \"…\" : `$${raised.toFixed(2)} raised this block`}</p>\n    </section>\n  );\n}\n```\n\n### Raw baseline + delta with the Tier-2 primitive\n\nIf you already have the baseline (from a route loader, say), skip the\nfusion hook and reduce the firehose yourself:\n\n```tsx\nimport { usePipelineDonationSum } from \"@playlive/react-pipeline\";\n\nfunction BlockDelta({\n  campaignID,\n  baseline,\n}: {\n  campaignID: string;\n  // `GET /schedules/campaigns/{id}/raised`\n  baseline: {\n    raised: number;\n    currency: string;\n    asOf: string;\n    donationCount: number;\n  };\n}) {\n  const delta = usePipelineDonationSum(campaignID, {\n    start: baseline.asOf,\n    end: \"2025-09-20T23:00:00.000Z\",\n    currency: baseline.currency,\n    // `asOf` is the ingest watermark — the newest donation actually\n    // inside `baseline.raised`. Exclude it or it gets counted twice.\n    // On an empty window `asOf` falls back to the window start, which\n    // is a bound rather than a donation, so keep that one inclusive.\n    startExclusive: baseline.donationCount > 0,\n  });\n\n  const total = baseline.raised + (delta.raised ?? 0);\n  return (\n    <p>\n      ${total.toFixed(2)} ({delta.donationCount} live donations since baseline)\n    </p>\n  );\n}\n```\n\n`usePipelineDonationSum` matches `start <= completed_at < end` (pass\n`startExclusive: true` for `start < completed_at < end`), dedupes\nby donation id (last write wins, so post-edit corrections propagate),\nand excludes `test: true` donations unless you pass\n`includeTest: true` — which keeps it consistent with the REST baseline.\n\n`useCurrentBlockRaised` handles the watermark seam for you, and adds two\nguards against an on-air total moving the wrong way:\n\n- **`monotonic`** (default `true`) — holds the high-water mark for as\n  long as the `(campaignID, start, end)` window is unchanged, so a\n  reconciliation artefact can't walk a live block total backwards. Resets\n  on every genuine window change, so block rollovers still start at zero.\n  Pass `false` for accounting views that must surface refunds as they land.\n- **`onWindowChange`** — fires when the block window is swapped under a\n  mounted hook, with the previous/next window and the figure that was on\n  screen immediately before the swap. Schedules can move with no user\n  action, which looks identical on stream to money appearing or\n  vanishing. Defaults to a `console.warn` breadcrumb.\n\nBoth examples above are complete and runnable: point\n`<RealtimePipelineProvider url={…}>` at your pipeline WebSocket and the\nrest is copy-paste.\n\n## License\n\nMIT © St. Jude Children's Research Hospital\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.5.4.tgz","shasum":"dff8cd74a6d265f00acc3ed752c2a29862e83867","integrity":"sha512-hdAI2JpImWAupTGUejy+N3sAh+G5cKBaL6JohQAY36Oj7eY5c/4I1sWvbwXGtkrOaummi9MepwvjstCDWX2upg=="}},"0.5.5":{"name":"@playlive/react-pipeline","version":"0.5.5","description":"React bindings for @playlive/realtime-pipeline","type":"module","sideEffects":false,"main":"./index.js","types":"./index.d.ts","exports":{".":{"import":"./index.js","types":"./index.d.ts"},"./provider":{"import":"./provider/index.js","types":"./provider/index.d.ts"},"./legacy":{"import":"./legacy/index.js","types":"./legacy/index.d.ts"},"./fusion":{"import":"./fusion/index.js","types":"./fusion/index.d.ts"}},"dependencies":{},"peerDependencies":{"react":"^19.0.0","react-dom":"^19.0.0","zustand":"^5.0.0","@playlive/realtime-pipeline":"^0.4.2","@playlive/tiltify-core":"^0.4.19","@tanstack/react-query":"^5.0.0","@playlive/react-query":"^0.4.6","@playlive/fundraiser-data":"^0.6.2"},"peerDependenciesMeta":{"@tanstack/react-query":{"optional":true},"@playlive/react-query":{"optional":true},"@playlive/fundraiser-data":{"optional":true}},"playlive":{"target":"browser","frontendEligible":true,"coverageFloor":85},"publishConfig":{"access":"restricted","registry":"https://playlive-767397689694.d.codeartifact.us-east-1.amazonaws.com/npm/playlive/"},"integrity":"sha512-/s/MVXhwKdLCAts5gtenUQzL/UEuFt0FYA6fXg9f+NeS2PbR8FRX9kur65FHP3zc7FyNxTYixGTQtzksQ30ozw==","shasum":"555e8e4aaf03a82e041126b4796b9658d45b4ebf","readme":"# @playlive/react-pipeline\n\nReact bindings for [`@playlive/realtime-pipeline`](../realtime-pipeline/).\nSuccessor to `playlive-overlay-data-layer/src/websocket/*` with **zero**\ndependency on `react-use-websocket-lite` — built directly on Zustand v5's\n`useStore` + `useSyncExternalStore`.\n\n![Coverage](./coverage-badge.svg)\n\n## Three tiers — take only what you need\n\nPick the surface that matches how much of the legacy\n`UnifiedDataPipelineProvider` API you actually want. Each tier is\nstrictly larger than the last and lives at its own subpath so unused\ncode is tree-shaken out.\n\n| Tier | Subpath                             | Surface                                                                       | Use when                                                               |\n| ---- | ----------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------- |\n| 1    | `@playlive/react-pipeline`          | `RealtimePipelineProvider` + `usePipelineValue` (universal selector)          | Greenfield app; you want one selector primitive and nothing else.      |\n| 2    | `@playlive/react-pipeline`          | Tier 1 **plus** the typed per-slice hooks (`usePipelineDonations(id)`, …)     | You want stable, typed one-liners for the common reads.                |\n| 3    | `@playlive/react-pipeline/provider` | Tier 2 **plus** `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, etc. | Porting an existing overlay-data-layer consumer with minimal churn.    |\n| —    | `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (needs the optional TanStack peers)              | You want \"REST baseline + live WS delta\" without hand-rolling it.      |\n| —    | `@playlive/react-pipeline/legacy`   | Tier 3 **plus** `useUDPStore` / `useUDPStoreApi` / `createUDPStore` aliases   | Soft-deprecated shim for the legacy symbol names (one-release window). |\n\n## Install\n\n```bash\nbun add @playlive/react-pipeline\nbun add react react-dom zustand @playlive/realtime-pipeline @playlive/tiltify-core\n```\n\nPeer dependencies (jose-style — the consumer brings their own):\n\n| Peer                          | Range          | Required?                                 |\n| ----------------------------- | -------------- | ----------------------------------------- |\n| `react`                       | `^19.0.0`      | yes                                       |\n| `react-dom`                   | `^19.0.0`      | yes                                       |\n| `zustand`                     | `^5.0.0`       | yes                                       |\n| `@playlive/realtime-pipeline` | `workspace:*`  | yes — store, connection, protocol types    |\n| `@playlive/tiltify-core`      | `workspace:*`  | yes (type-only — stripped at compile time) |\n| `@tanstack/react-query`       | `^5.0.0`       | **optional** — only for `/fusion`          |\n| `@playlive/react-query`       | `workspace:*`  | **optional** — only for `/fusion`          |\n| `@playlive/fundraiser-data`   | `workspace:*`  | **optional** — only for `/fusion`          |\n\nThe three optional peers are declared `optional: true` in\n`peerDependenciesMeta`; you only need them if you import from\n[`@playlive/react-pipeline/fusion`](#fusion--rest-baseline--ws-delta-hooks-fusion).\n\nThis package has **no runtime `dependencies`** — nothing but your own\npeers ends up in the bundle.\n\n## Quick start\n\n```tsx\nimport {\n  RealtimePipelineProvider,\n  usePipelineCampaigns,\n  usePipelineConnectionState,\n  usePipelineDonations,\n} from \"@playlive/react-pipeline\";\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nfunction App() {\n  return (\n    <RealtimePipelineProvider\n      url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n      initialCampaignIDs={[CAMPAIGN_ID]}\n      initialOverlayName=\"donation-bar\"\n      initialTeamUserSlug=\"@playliver\"\n      initialOverlayPath=\"/overlays/donation-bar\"\n      autoConnect\n    >\n      <DonationBar campaignID={CAMPAIGN_ID} />\n    </RealtimePipelineProvider>\n  );\n}\n\nfunction DonationBar({ campaignID }: { campaignID: string }) {\n  const { connected, connecting, hasReceivedInitialData } = usePipelineConnectionState();\n  const campaigns = usePipelineCampaigns();\n  const donations = usePipelineDonations(campaignID);\n\n  if (connecting) return <p>Connecting…</p>;\n  if (!connected) return <p>Disconnected — reconnecting…</p>;\n  if (!hasReceivedInitialData) return <p>Waiting for first tick…</p>;\n\n  const campaign = campaigns.find((c) => c.id === campaignID);\n  const raised = Number(campaign?.total_amount_raised?.value ?? 0);\n  const goal = Number(campaign?.goal?.value ?? 0);\n\n  return (\n    <section>\n      <progress value={raised} max={goal || 1} />\n      <p>\n        ${raised.toFixed(2)} of ${goal.toFixed(2)}\n      </p>\n      <ul>\n        {donations.slice(0, 5).map((d) => (\n          <li key={d.id}>\n            {d.donor_name} — ${d.amount.value}\n          </li>\n        ))}\n      </ul>\n    </section>\n  );\n}\n```\n\n`RealtimePipelineProvider` builds one Zustand store per provider\ninstance (from the `initial*` props, which are read **once** at first\nconstruction) and opens a single `PipelineConnection`. `connection.destroy()`\nruns on unmount. Children subscribe with any Tier 1 / Tier 2 hook.\n\nEvery hook that reads the store throws when no provider is mounted\nabove it; the four `useOn*` observer hooks deliberately soft-fail\n(they no-op) so you can drop them into components that sometimes render\noutside a pipeline tree.\n\n## Subpath exports\n\n| Subpath                             | Description                                                                                             |\n| ----------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `@playlive/react-pipeline`          | Default barrel — Tier 1 + Tier 2 (provider, `usePipelineValue`, typed slice hooks, observers).           |\n| `@playlive/react-pipeline/provider` | Tier 3 — `UnifiedDataPipelineProvider`, `useUnifiedDataPipeline`, `useAddPipeline*`, `useIdentify`, …    |\n| `@playlive/react-pipeline/fusion`   | REST + WS composition hooks (`useCurrentBlockRaised`, `useLiveSchedule`, the `useHybrid*` family, …).    |\n| `@playlive/react-pipeline/legacy`   | Soft-deprecated alias bundle (`useUDPStore`, `useUDPStoreApi`, `createUDPStore`, `UDPStore`, …).         |\n\nEach subpath ships an ESM bundle, a `bun` source condition, and `.d.ts`\ndeclarations. Frontend-eligible bundles are not minified so stack\ntraces stay readable in production.\n\n## API reference\n\nFull generated API documentation:\n<https://packages.playlive.experience.stjude.org/p/@playlive/react-pipeline/docs/>\n\n### Tier 1 — universal primitive\n\n| Export                          | Signature                                                     |\n| ------------------------------- | ------------------------------------------------------------- |\n| `RealtimePipelineProvider`      | `(props: RealtimePipelineProviderProps) => ReactNode`          |\n| `RealtimePipelineProviderProps` | Type. `InitialProps` + `children`, `url?`, `autoConnect?` (default `true`), `webSocketCtor?`, `log?`, `store?`, `connection?`. |\n| `usePipelineValue`              | `<T>(selector: (s: PipelineStore) => T) => T`                  |\n| `PACKAGE_NAME`                  | `\"@playlive/react-pipeline\"` — for runtime version-pinning.    |\n| `KNOWN_URLS`                    | `readonly string[]` — frozen, empty. See the disclosure section. |\n\n`RealtimePipelineProviderProps` extends\n[`InitialProps`](../realtime-pipeline/), so every `initial*` key\n(`initialCampaignIDs`, `initialTeamCampaignIDs`, `initialCauseIDs`,\n`initialFundraisingEventIDs`, `initialOverlayName`, `initialTeamUserSlug`,\n`initialOverlayPath`, `initialOverlayConfig`, `initialAdminApiKey`,\n`initialDonationTrainsEnabled`, …) is a valid prop. There is **no**\n`charityType` prop — set the charity type through the store's\n`setCurrentType` action or via `useCommonContextHooks` (Tier 3).\n\n`usePipelineValue` is single-arg by design. Zustand v5 dropped the third\n`equalityFn` parameter on `useStore` to keep the package free of the\n`use-sync-external-store` shim peer. For shallow / custom equality, wrap\nyour selector with `useShallow` from `zustand/shallow` (zero extra dep):\n\n```tsx\nimport { useShallow } from \"zustand/shallow\";\nimport { usePipelineValue } from \"@playlive/react-pipeline\";\n\nconst { connected, connecting } = usePipelineValue(\n  useShallow((s) => ({ connected: s.connected, connecting: s.connecting })),\n);\n```\n\n### Tier 2 — typed per-slice hooks\n\nStable wrappers over `usePipelineValue` with a typed selector +\nmemo-stable empty-array/object fallbacks (no infinite-render churn from\nfresh `[]` allocations — Decision §3 in [CHANGELOG.md](./CHANGELOG.md)).\n\n| Hook                                          | Returns                                                     |\n| --------------------------------------------- | ----------------------------------------------------------- |\n| `usePipelineCampaigns()`                      | `TiltifyCampaign[]`                                          |\n| `usePipelineTeamCampaigns()`                  | `TiltifyTeamCampaign[]`                                      |\n| `usePipelineFundraisingEvents()`              | `TiltifyFundraisingEvent[]`                                  |\n| `usePipelineCauses()`                         | `TiltifyCause[]`                                             |\n| `usePipelineDonations()`                      | `Record<string, TiltifyDonationWithTestFlag[]>`               |\n| `usePipelineDonations(id)`                    | `TiltifyDonationWithTestFlag[]` (memo-stable `[]` if unknown) |\n| `usePipelineDonationTrains(id, withStatus?)`  | `DonationTrain[]` — `withStatus` is `\"ACTIVE\" \\| \"ENDED\" \\| \"ENDING\" \\| \"PENDING\"` |\n| `usePipelineSubathonTimer(id, withStatus?)`   | `SubathonTimer[]` — `withStatus` is `\"ACTIVE\" \\| \"INACTIVE\"`  |\n| `usePipelineDonorSpotlight(id)`               | `ComputedSpotlight \\| null`                                  |\n| `usePipelineDonorSpotlightSettings()`         | `Record<string, DonorSpotlightSettings>`                     |\n| `usePipelinePolls(id)`                        | `TiltifyPoll[]`                                              |\n| `usePipelinePollDeltas(pollID)`               | `PollOptionDelta[]` — server-computed \"+$X on option Y\" deltas |\n| `usePipelineRewards(id)`                      | `TiltifyReward[]`                                            |\n| `usePipelineTargets(id)`                      | `TiltifyTarget[]`                                            |\n| `usePipelineMilestones(id)`                   | `TiltifyMilestone[]`                                         |\n| `usePipelineLeaderboardEntries(id)`           | `TiltifyLeaderboardEntry[]`                                  |\n| `usePipelineLeaderboardExclusions(id)`        | `LeaderboardExclusion[]`                                     |\n| `usePipelineLeaderboardExclusionsMap()`       | `Record<string, LeaderboardExclusion[]>`                     |\n| `usePipelineSchedule(id)`                     | `ScheduleTransitionBlock[]` — full `updateSchedule` WS push  |\n| `usePipelineSchedules()`                      | `Record<string, ScheduleTransitionBlock[]>`                  |\n| `usePipelineLastScheduleTransition(id)`       | `ScheduleTransitionPayload \\| null` — latest block crossing  |\n| `usePipelineLastScheduleTransitions()`        | `Record<string, ScheduleTransitionPayload>`                  |\n| `usePipelineDonationSum(id, options?)`        | `PipelineDonationSumResult` — live per-currency sum over `[start, end)` |\n| `usePipelineAuctionHouses()`                  | `AuctionHouse[]`                                             |\n| `usePipelineTwitchChat()`                     | `TwitchChatMessage[]`                                        |\n| `usePipelineConnectionState()`                | `PipelineConnectionState` (10 split selectors — no false re-renders) |\n| `usePipelineConnectionActions()`              | `PipelineConnectionActions` (stable `connect`/`disconnect`/…) |\n| `usePipelineSubscriptions()`                  | `Record<string, TiltifyWebhookSubscription>`                 |\n| `usePipelineTiltifySettings()`                | `TiltifyWebSocketSettings`                                   |\n| `usePipelineLastRefreshResponse()`            | `RefreshResponseMessage \\| null`                             |\n\nCompanion types exported alongside them: `PipelineConnectionState`\n(`connected`, `connecting`, `charityType`, `connectionType`, `wsURL`,\n`hasReceivedInitialData`, `lastIdentified`, `lastIdentityMessage`,\n`lastFirehoseMessage`, `lastMessage`), `PipelineConnectionActions`\n(`connect`, `disconnect`, `identify`, `refresh`, `reset`,\n`setAutoConnect`, `setAdminApiKey`), `PipelineDonationSumOptions`,\n`PipelineDonationSumByCurrency`, `PipelineDonationSumResult`.\n\n#### Change observers\n\nPush-style subscriptions that diff the store outside React's render\npath. Each stashes `handler` in a ref, subscribes exactly once, and\ntreats whatever is already on the store at mount as the baseline (so\nthey never fire for pre-existing data). All four **soft-fail** without\na provider.\n\n| Hook                                                | Handler payload                                        |\n| --------------------------------------------------- | ------------------------------------------------------ |\n| `useOnScheduleTransition(handler, options?)`        | `ScheduleTransitionPayload`                             |\n| `useOnScheduleUpdate(handler, options?)`            | `{ campaignID, schedule: ScheduleTransitionBlock[] }`   |\n| `useOnLeaderboardEntriesChange(handler, options?)`  | `{ campaignID, entries: TiltifyLeaderboardEntry[] }`    |\n| `useOnLeaderboardExclusionsChange(handler, options?)` | `{ campaignID, exclusions: LeaderboardExclusion[] }`  |\n\n`options` is `{ campaignIDs?: readonly string[] }` — omit it (or pass an\nempty list) to observe every campaign.\n\n```tsx\nuseOnLeaderboardEntriesChange(\n  ({ campaignID }) => {\n    queryClient.invalidateQueries({\n      queryKey: [\"leaderboard-with-exclusions\", \"tiltify\", campaignID],\n    });\n  },\n  { campaignIDs: [campaignID] },\n);\n```\n\n### Tier 3 — kitchen-sink provider (`/provider`)\n\n| Export                                        | Notes                                                                                          |\n| --------------------------------------------- | ---------------------------------------------------------------------------------------------- |\n| `UnifiedDataPipelineProvider`                 | Superset of `RealtimePipelineProvider`; adds legacy `reloadOnErrorReconnect` (default `true`) + `debug` (wires `console.debug` logging). |\n| `UnifiedDataPipelineProviderProps`            | Type. `Omit<RealtimePipelineProviderProps, \"children\"> & { children, reloadOnErrorReconnect?, debug? }`. |\n| `useUnifiedDataPipeline()`                    | One-call kitchen-sink hook → `UnifiedDataPipelineState`. Re-renders on **every** store change.  |\n| `UnifiedDataPipelineState`                    | Type. `PipelineStore` + `allRewards` / `allMilestones` / `allTargets` / `allPolls` / `allFundraisingEventSupportingCampaigns` / `tiltifySubscriptions` legacy aliases. |\n| `useAddPipelineCampaignIDs(params)`           | Layout-effect hook. `{ mode: \"ws\" \\| \"http\"; initialCampaign: { id }; isTeam: boolean; causeID? }` — no-op in `\"http\"` mode. |\n| `useAddPipelineCampaigns(params)`             | Layout-effect hook. `{ campaign?: TiltifyCampaign \\| null; teamCampaign?: TiltifyTeamCampaign \\| null }`. Idempotent by id. |\n| `useAddPipelineFundraisingEventIDs(params)`   | Layout-effect hook. `{ mode; initialCampaign: { id; fundraising_event_id: string \\| null } }`.   |\n| `useCommonContextHooks(params)`               | `{ url: string; mode: \"ws\" \\| \"http\"; charityType: CharityType }` — syncs type + URL and connects when `mode === \"ws\"`. |\n| `useIdentify()`                               | Returns the store-bound `identify` action. The 11-positional-arg legacy overload is kept as `@deprecated`; the args are ignored and a one-shot `console.warn` fires. |\n\n`RealtimePipelineProvider` / `RealtimePipelineProviderProps` are also\nre-exported from `/provider` so a Tier-3 consumer needs exactly one\nimport specifier.\n\n### Fusion — REST baseline + WS delta hooks (`/fusion`)\n\nThese fuse the WebSocket firehose with REST reads from\n[`@playlive/react-query`](../react-query/) /\n[`@playlive/fundraiser-data`](../fundraiser-data/). Importing this\nsubpath means you must also install the three optional peers.\n\n| Hook                                                   | Returns                                                                    |\n| ------------------------------------------------------ | -------------------------------------------------------------------------- |\n| `useCurrentBlockRaised(campaignID, start, end, opts?)` | `{ raised, data, isLoading, isError, refetch }` — REST baseline `+` WS delta over `[start, end)`. `opts`: `{ enabled?, queryOptions?, demoMode?, monotonic?, onWindowChange? }`. |\n| `useLiveSchedule(params, opts?)`                       | `{ schedule, isLoading, isError, refetch }`. `params`: `{ charityType, campaignID, isTeam? }`; `opts`: `{ enabled?, queryOptions?, demoMode?, demoIntervalMs? }`. |\n| `useCurrentScheduleItem(schedule, opts?)`              | `{ currentItem, nextItem }` — pure client-side rollover projection with a self-scheduling boundary timer. |\n| `useLinkedTeamCampaign(params, opts?)`                 | `{ shouldFetchLinkedTeamCampaign, resolvedLinkedTeamCampaign, linkedTeamTotalAmount, linkedTeamGoalAmount, isLoadingLinkedTeamCampaign, debug }`. |\n| `useDonorSpotlight(params, opts?)`                     | `{ spotlight, isLoading, refetch }`. `params`: `{ campaignID, isTeam, live, mode }`; `opts`: `{ pollingIntervalMs?, enabled?, queryOptions? }`. |\n\n`useLiveSchedule` resolves in a strict fallback order: full\n`updateSchedule` WS push → `lastScheduleTransitions` synthesis (demo\nonly) → REST `useSchedule` → demo fixture.\n\n### Fusion — hybrid slice hooks (`/fusion`)\n\nOverlay-parity hooks — the ones every Play Live overlay used to keep a\nlocal copy of — shipped here so they can be imported instead. Each takes a\n`mode: \"ws\" | \"http\"` that picks between:\n\n- `\"ws\"` — read the Tier-2 store selector, latch a local `hasLoadedX`\n  boolean on first arrival (or on the server's \"feature disabled\"\n  signal), and call `updateTiltifySettings({ <sliceKey> })` whenever the\n  local `xEnabled` toggle flips so the pipeline dynamically un/subscribes.\n- `\"http\"` — delegate to the matching `@playlive/react-query` REST hook\n  (`useRewards`, `useTargets`, …) and ignore the WS slice.\n\nAll of them share `useLoadingTimeout` — a 15 s safety net that\nforce-completes the WS branch when the store never delivers.\n\n| Hook                                                    | Params → Returns                                                              |\n| -------------------------------------------------------- | ----------------------------------------------------------------------------- |\n| `useHybridRewards({ campaignID, isTeam, mode, initialRewardsEnabled? })`   | `{ currentRewards, hasLoadedRewards, setHasLoadedRewards, isLoadingRewards, isLoading, rewardsEnabled, setRewardsEnabled }` |\n| `useHybridTargets({ campaignID, isTeam, mode })`         | `{ currentTargets, hasLoadedTargets, setHasLoadedTargets, isLoadingTargets, isLoading, targetsEnabled, setTargetsEnabled }` |\n| `useHybridMilestones({ campaignID, isTeam, mode })`      | `{ currentMilestones, isLoadingMilestones, milestonesEnabled, setMilestonesEnabled }` |\n| `useHybridPolls({ campaignID, isTeam, mode })`           | `{ currentPolls, isLoadingPolls, pollsEnabled, setPollsEnabled }`              |\n| `useHybridCause({ mode, initialCampaign })`              | `{ cause, isLoading }` — single-slot semantics.                                |\n| `useHybridFundraisingEvent({ mode, initialCampaign })`   | `{ fundraisingEvent, isLoading }` — single-slot semantics.                     |\n| `useHybridFundraisingEventSupportingCampaigns({ mode, initialFundraisingEvent, initialSupportingCampaignsEnabled? })` | `{ supportingCampaigns, isLoading, isLoadingSupportingCampaigns, hasLoadedSupportingCampaigns, supportingCampaignsEnabled, setSupportingCampaignsEnabled }` |\n| `useHybridDonationTrains({ campaignID, mode, initialDonationTrainsEnabled?, withStatus?, http?, auth? })` | `{ currentDonationTrains, hasLoadedDonationTrains, setHasLoadedDonationTrains, isLoadingDonationTrains, isLoading, donationTrainsEnabled, setDonationTrainsEnabled }` |\n| `useHybridAuctionHouses({ mode, enabled, campaignID })`  | `{ auctionHouses, auctionHouseTotal, auctionHousesEnabled, setAuctionHousesEnabled }` — WS-only (no REST fallback yet). |\n| `useHybridLeaderboardWithExclusions({ charityType, campaignID, isTeam?, timeType?, count?, startDate?, endDate?, enabled?, continuouslyUpdate? })` | `{ entries, isLoading }` — WS-driven invalidation over the REST leaderboard, with a `LEADERBOARD_SAFETY_POLL_MS` (60 s) backstop. |\n| `useLoadingTimeout({ mode, isLoading, onFinishedLoading, timeoutAmount? })` | `void` — the shared safety-net timer primitive.                    |\n\n`LEADERBOARD_SAFETY_POLL_MS` is exported alongside the hooks. Return\nshapes are preserved verbatim from the original overlay hooks, so\nexisting call sites can collapse to a re-export.\n\n### Legacy aliases (`/legacy`)\n\nSoft-deprecated. Behavior is identical to the canonical exports — rename\nthe import specifier once and the rest of your code base keeps working:\n\n| Legacy name          | Canonical replacement                                            |\n| -------------------- | ---------------------------------------------------------------- |\n| `useUDPStore`        | `usePipelineValue` (Tier 1) / typed slice hooks (Tier 2)          |\n| `useUDPStoreApi`     | Use a typed slice hook, or reach for the raw store API            |\n| `createUDPStore`     | `createPipelineStore` from `@playlive/realtime-pipeline/store`    |\n| `UDPStore` (type)    | `PipelineStore` from `@playlive/realtime-pipeline/store`          |\n| `UDPStoreApi` (type) | `PipelineStoreApi` from `@playlive/realtime-pipeline/store`       |\n| `CharityTypes`       | Re-exported from `@playlive/realtime-pipeline/protocol`           |\n\n`useUDPStore` / `useUDPStoreApi` throw with a\n`\"must be called inside <UnifiedDataPipelineProvider>\"` message when no\nprovider is mounted. The subpath also re-exports every Tier 1 + 2 + 3\nsymbol under its canonical name, plus the `PipelineStore` /\n`PipelineStoreApi` types.\n\n## Migration from `playlive-overlay-data-layer`\n\nOne-shot, sed-friendly rewrite of every import specifier (no body edits\nneeded for the happy path):\n\n```bash\n# Tier 3 — kitchen-sink (drop-in replacement)\nrg -l '@playlive/overlay-data-layer/websocket' . \\\n  | xargs sed -i '' \\\n    -e 's|@playlive/overlay-data-layer/websocket|@playlive/react-pipeline/provider|g'\n```\n\nFor the soft-deprecated names (`useUDPStore`, `createUDPStore`, …)\nsubstitute `/legacy` instead of `/provider` to opt into the alias\nbundle, then migrate at your leisure — the aliases are kept for a\none-release window.\n\nTwo call-site changes are **not** covered by the sed:\n\n- `useIdentify(sendJsonMessage, campaignIDs, …)` — the positional args\n  are now ignored (the connection reads identify inputs off the store).\n  Drop them; the deprecated overload still compiles and warns once.\n- `useWebSocket` from `react-use-websocket-lite` — replaced by\n  `createPipelineConnection` in `@playlive/realtime-pipeline/connection`,\n  which `RealtimePipelineProvider` mounts for you.\n\nThe [Legacy aliases](#legacy-aliases-legacy) table above is the full\nper-symbol mapping.\n\n## Upstream spec\n\nNo external API surface — this package fetches nothing itself. It binds\nReact to [`@playlive/realtime-pipeline`](../realtime-pipeline/), which\nowns the WebSocket connection and frames every byte on the wire; the\nmessage catalogue and its types are exported from\n`@playlive/realtime-pipeline/protocol`. The `/fusion` hooks additionally\nread REST baselines through\n[`@playlive/react-query`](../react-query/) and\n[`@playlive/fundraiser-data`](../fundraiser-data/) — see those packages\nfor the endpoints and URL knobs involved.\n\n## Twitch Extension URL disclosure\n\n`KNOWN_URLS` enumerates every absolute URL or host this package can\nreach. **It is empty.** This package only opens the WebSocket URL the\nconsumer hands to `<RealtimePipelineProvider url={…}>` (or the store's\n`DEFAULT_WEBSOCKET_URL`, `wss://main.playlive.ws.api.experience.stjude.org`).\nDisclose that URL — plus the `KNOWN_URLS` of `@playlive/realtime-pipeline`\nand, if you import `/fusion`, `@playlive/fundraiser-data` — on your\nExtension submission.\n\n```ts\nimport { KNOWN_URLS } from \"@playlive/react-pipeline\";\nconsole.log(KNOWN_URLS); // []\n```\n\n## Examples\n\n### Live schedule-block total without polling\n\nThe canonical fusion use case: fetch a REST baseline once, then add the\nWebSocket donation delta on top so the number ticks up on every donation\ninstead of every 30-second poll. Requires the optional peers plus a\n`<QueryClientProvider>` above the pipeline provider.\n\n```tsx\nimport { configure } from \"@playlive/fundraiser-data/config\";\nimport { getConfigForEnv } from \"@playlive/fundraiser-data/environments\";\nimport { RealtimePipelineProvider } from \"@playlive/react-pipeline\";\nimport {\n  useCurrentBlockRaised,\n  useCurrentScheduleItem,\n  useLiveSchedule,\n} from \"@playlive/react-pipeline/fusion\";\nimport { makeQueryClient } from \"@playlive/react-query\";\nimport { QueryClientProvider } from \"@tanstack/react-query\";\n\nconfigure(getConfigForEnv(\"prod\", { tiltifyProxyUrl: import.meta.env.VITE_TILTIFY_PROXY_URL }));\nconst queryClient = makeQueryClient();\n\nconst CAMPAIGN_ID = \"5d4b0d3c-9f31-4a5c-8fd1-0a2b3c4d5e6f\";\n\nexport function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <RealtimePipelineProvider\n        url=\"wss://main.playlive.ws.api.experience.stjude.org\"\n        initialCampaignIDs={[CAMPAIGN_ID]}\n        initialOverlayName=\"schedule-block\"\n        initialTeamUserSlug=\"@playliver\"\n      >\n        <BlockTotal campaignID={CAMPAIGN_ID} />\n      </RealtimePipelineProvider>\n    </QueryClientProvider>\n  );\n}\n\nfunction BlockTotal({ campaignID }: { campaignID: string }) {\n  const { schedule, isLoading: scheduleLoading, isError } = useLiveSchedule({\n    charityType: \"tiltify\",\n    campaignID,\n  });\n  const { currentItem, nextItem } = useCurrentScheduleItem(schedule);\n\n  const { raised, isLoading } = useCurrentBlockRaised(\n    campaignID,\n    currentItem?.starts_at,\n    currentItem?.ends_at,\n  );\n\n  if (scheduleLoading) return <p>Loading schedule…</p>;\n  if (isError) return <p>Schedule unavailable.</p>;\n  if (!currentItem) return <p>Up next: {nextItem?.name ?? \"nothing scheduled\"}</p>;\n\n  return (\n    <section>\n      <h2>{currentItem.name}</h2>\n      <p>{isLoading ? \"…\" : `$${raised.toFixed(2)} raised this block`}</p>\n    </section>\n  );\n}\n```\n\n### Raw baseline + delta with the Tier-2 primitive\n\nIf you already have the baseline (from a route loader, say), skip the\nfusion hook and reduce the firehose yourself:\n\n```tsx\nimport { usePipelineDonationSum } from \"@playlive/react-pipeline\";\n\nfunction BlockDelta({\n  campaignID,\n  baseline,\n}: {\n  campaignID: string;\n  // `GET /schedules/campaigns/{id}/raised`\n  baseline: {\n    raised: number;\n    currency: string;\n    asOf: string;\n    donationCount: number;\n  };\n}) {\n  const delta = usePipelineDonationSum(campaignID, {\n    start: baseline.asOf,\n    end: \"2025-09-20T23:00:00.000Z\",\n    currency: baseline.currency,\n    // `asOf` is the ingest watermark — the newest donation actually\n    // inside `baseline.raised`. Exclude it or it gets counted twice.\n    // On an empty window `asOf` falls back to the window start, which\n    // is a bound rather than a donation, so keep that one inclusive.\n    startExclusive: baseline.donationCount > 0,\n  });\n\n  const total = baseline.raised + (delta.raised ?? 0);\n  return (\n    <p>\n      ${total.toFixed(2)} ({delta.donationCount} live donations since baseline)\n    </p>\n  );\n}\n```\n\n`usePipelineDonationSum` matches `start <= completed_at < end` (pass\n`startExclusive: true` for `start < completed_at < end`), dedupes\nby donation id (last write wins, so post-edit corrections propagate),\nand excludes `test: true` donations unless you pass\n`includeTest: true` — which keeps it consistent with the REST baseline.\n\n`useCurrentBlockRaised` handles the watermark seam for you, and adds two\nguards against an on-air total moving the wrong way:\n\n- **`monotonic`** (default `true`) — holds the high-water mark for as\n  long as the `(campaignID, start, end)` window is unchanged, so a\n  reconciliation artefact can't walk a live block total backwards. Resets\n  on every genuine window change, so block rollovers still start at zero.\n  Pass `false` for accounting views that must surface refunds as they land.\n- **`onWindowChange`** — fires when the block window is swapped under a\n  mounted hook, with the previous/next window and the figure that was on\n  screen immediately before the swap. Schedules can move with no user\n  action, which looks identical on stream to money appearing or\n  vanishing. Defaults to a `console.warn` breadcrumb.\n\nBoth examples above are complete and runnable: point\n`<RealtimePipelineProvider url={…}>` at your pipeline WebSocket and the\nrest is copy-paste.\n\n## License\n\nMIT © St. Jude Children's Research Hospital\n","readmeFilename":"README.md","dist":{"tarball":"https://packages.playlive.experience.stjude.org/@playlive/react-pipeline/-/react-pipeline-0.5.5.tgz","shasum":"555e8e4aaf03a82e041126b4796b9658d45b4ebf","integrity":"sha512-/s/MVXhwKdLCAts5gtenUQzL/UEuFt0FYA6fXg9f+NeS2PbR8FRX9kur65FHP3zc7FyNxTYixGTQtzksQ30ozw=="}}},"time":{"0.4.18":"2026-08-26T18:10:02.807Z","modified":"2026-09-21T20:24:28.802Z","0.1.0":"2026-08-26T18:15:13.047Z","0.1.1":"2026-08-26T18:15:13.685Z","0.2.0":"2026-08-26T18:15:14.243Z","0.2.1":"2026-08-26T18:15:14.728Z","0.2.2":"2026-08-26T18:15:15.260Z","0.2.3":"2026-08-26T18:15:15.936Z","0.3.0":"2026-08-26T18:15:16.443Z","0.3.1":"2026-08-26T18:15:16.982Z","0.3.2":"2026-08-26T18:15:17.569Z","0.3.3":"2026-08-26T18:15:18.058Z","0.3.4":"2026-08-26T18:15:18.614Z","0.4.0":"2026-08-26T18:15:19.134Z","0.4.1":"2026-08-26T18:15:19.597Z","0.4.2":"2026-08-26T18:15:20.088Z","0.4.3":"2026-08-26T18:15:20.648Z","0.4.4":"2026-08-26T18:15:21.230Z","0.4.5":"2026-08-26T18:15:21.962Z","0.4.6":"2026-08-26T18:15:22.657Z","0.4.7":"2026-08-26T18:15:23.155Z","0.4.8":"2026-08-26T18:15:23.697Z","0.4.9":"2026-08-26T18:15:24.384Z","0.4.10":"2026-08-26T18:15:25.111Z","0.4.11":"2026-08-26T18:15:25.636Z","0.4.13":"2026-08-26T18:15:26.286Z","0.4.14":"2026-08-26T18:15:26.998Z","0.4.15":"2026-08-26T18:15:27.632Z","0.4.16":"2026-08-26T18:15:28.419Z","0.4.17":"2026-08-26T18:15:29.056Z","0.4.19":"2026-08-26T19:46:21.270Z","0.4.20":"2026-08-26T20:10:10.405Z","0.5.0":"2026-09-15T16:34:50.814Z","0.5.1":"2026-09-15T16:34:51.347Z","0.5.2":"2026-09-15T16:34:51.892Z","0.5.3":"2026-09-15T16:34:52.424Z","0.5.4":"2026-09-15T16:34:53.018Z","0.5.5":"2026-09-21T20:24:28.802Z"}}